Jmeter5配置元件插件开发JDBC Data Set Config

tech2024-06-18  65

最近基于jmeter做自动化测试框架,想从数据库直接读取编辑好的请求报文信息等,写了个基于数据库查询类似CSV DATA SET的组件直接上代码,需要引入JMeter核心包进行二次开发代码如下,

实现的效果如下

代码实现如下

JdbcDataSet类的实现

package org.apache.jmeter.config; import java.beans.BeanInfo; import java.beans.IntrospectionException; import java.beans.Introspector; import java.util.ResourceBundle; import org.apache.commons.dbcp2.BasicDataSource; import org.apache.commons.lang3.StringUtils; import org.apache.jmeter.engine.event.LoopIterationEvent; import org.apache.jmeter.engine.event.LoopIterationListener; import org.apache.jmeter.engine.util.NoConfigMerge; import org.apache.jmeter.gui.GUIMenuSortOrder; import org.apache.jmeter.services.JdbcServer; import org.apache.jmeter.testbeans.TestBean; import org.apache.jmeter.testbeans.gui.GenericTestBeanCustomizer; import org.apache.jmeter.testelement.property.JMeterProperty; import org.apache.jmeter.testelement.property.StringProperty; import org.apache.jmeter.threads.JMeterContext; import org.apache.jmeter.threads.JMeterVariables; import org.apache.jmeter.util.JMeterUtils; import org.apache.jorphan.util.JMeterStopThreadException; import org.apache.jorphan.util.JOrphanUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Read lines from a file and split int variables. * * The iterationStart() method is used to set up each set of values. * * By default, the same file is shared between all threads * (and other thread groups, if they use the same file name). * * The shareMode can be set to: * <ul> * <li>All threads - default, as described above</li> * <li>Current thread group</li> * <li>Current thread</li> * <li>Identifier - all threads sharing the same identifier</li> * </ul> * * The class uses the FileServer alias mechanism to provide the different share modes. * For all threads, the file alias is set to the file name. * Otherwise, a suffix is appended to the filename to make it unique within the required context. * For current thread group, the thread group identityHashcode is used; * for individual threads, the thread hashcode is used as the suffix. * Or the user can provide their own suffix, in which case the file is shared between all * threads with the same suffix. * */ @GUIMenuSortOrder(1) public class JdbcDataSet extends ConfigTestElement implements TestBean, LoopIterationListener, NoConfigMerge { private static final Logger log = LoggerFactory.getLogger(JdbcDataSet.class); private static final long serialVersionUID = 233L; private static final String EOFVALUE = // value to return at EOF JMeterUtils.getPropDefault("csvdataset.eofstring", "<EOF>"); //$NON-NLS-1$ //$NON-NLS-2$ private transient String filename; private transient String variableNames; private transient boolean recycle = true; private transient boolean stopThread; private transient String[] vars; private transient String alias; private transient String shareMode; private transient String driver; private transient String dbUrl; private transient String username; private transient String password; private transient BasicDataSource dataSource; private Object readResolve(){ recycle = true; return this; } /** * Override the setProperty method in order to convert * the original String shareMode property. * This used the locale-dependent display value, so caused * problems when the language was changed. * If the "shareMode" value matches a resource value then it is converted * into the resource key. * To reduce the need to look up resources, we only attempt to * convert values with spaces in them, as these are almost certainly * not variables (and they are definitely not resource keys). */ @Override public void setProperty(JMeterProperty property) { if (property instanceof StringProperty) { final String propName = property.getName(); if ("shareMode".equals(propName)) { // The original name of the property final String propValue = property.getStringValue(); if (propValue.contains(" ")){ // variables are unlikely to contain spaces, so most likely a translation try { final BeanInfo beanInfo = Introspector.getBeanInfo(this.getClass()); final ResourceBundle rb = (ResourceBundle) beanInfo.getBeanDescriptor().getValue(GenericTestBeanCustomizer.RESOURCE_BUNDLE); for(String resKey : JdbcDataSetBeanInfo.getShareTags()) { if (propValue.equals(rb.getString(resKey))) { if (log.isDebugEnabled()) { log.debug("Converted {}={} to {} using Locale: {}", propName, propValue, resKey, rb.getLocale()); } ((StringProperty) property).setValue(resKey); // reset the value super.setProperty(property); return; } } // This could perhaps be a variable name log.warn("Could not translate {}={} using Locale: {}", propName, propValue, rb.getLocale()); } catch (IntrospectionException e) { log.error("Could not find BeanInfo; cannot translate shareMode entries", e); } } } } super.setProperty(property); } @Override public void iterationStart(LoopIterationEvent iterEvent) { JdbcServer server = JdbcServer.getFileServer(); final JMeterContext context = getThreadContext(); if (vars == null) { String fileName = getFilename(); String mode = getShareMode(); int modeInt = JdbcDataSetBeanInfo.getShareModeAsInt(mode); switch(modeInt){ case JdbcDataSetBeanInfo.SHARE_ALL: alias = fileName; break; case JdbcDataSetBeanInfo.SHARE_GROUP: alias = fileName+"@"+System.identityHashCode(context.getThreadGroup()); break; case JdbcDataSetBeanInfo.SHARE_THREAD: alias = fileName+"@"+System.identityHashCode(context.getThread()); break; default: alias = fileName+"@"+mode; // user-specified key break; } final String names = getVariableNames(); if (StringUtils.isEmpty(names)) { vars=server.getVars(getDataSource(),fileName); if(vars.length==0)throw new IllegalArgumentException("Could not split DB filed from sqlcmd:" + fileName); //firstLineIsNames = true; } else { vars = JOrphanUtils.split(names, ","); // $NON-NLS-1$ } trimVarNames(vars); } // TODO: fetch this once as per vars above? JMeterVariables threadVars = context.getVariables(); String[] lineValues = {}; try { lineValues=server.readLineSql(getDataSource(),alias,recycle); for (int a = 0; a < vars.length && a < lineValues.length; a++) { threadVars.put(vars[a], lineValues[a]); } } catch (Exception e) { // treat the same as EOF log.error(e.toString()); } if (lineValues.length == 0) {// i.e. EOF if (getStopThread()) { throw new JMeterStopThreadException("End of DB:"+ getFilename()+" detected for JDBC DataSet:" +getName()+" configured with stopThread:"+ getStopThread()+", recycle:" + getRecycle()); } for (String var :vars) { threadVars.put(var, EOFVALUE); } } } /** * trim content of array varNames * @param varsNames */ private void trimVarNames(String[] varsNames) { for (int i = 0; i < varsNames.length; i++) { varsNames[i] = varsNames[i].trim(); } } /** * @return Returns the filename. */ public String getFilename() { return filename; } /** * @param filename * The filename to set. */ public void setFilename(String filename) { this.filename = filename; } /** * @return Returns the variableNames. */ public String getVariableNames() { return variableNames; } /** * @param variableNames * The variableNames to set. */ public void setVariableNames(String variableNames) { this.variableNames = variableNames; } public boolean getRecycle() { return recycle; } public void setRecycle(boolean recycle) { this.recycle = recycle; } public boolean getStopThread() { return stopThread; } public void setStopThread(boolean value) { this.stopThread = value; } public String getShareMode() { return shareMode; } public void setShareMode(String value) { this.shareMode = value; } public String getDriver() { return driver; } public void setDriver(String driver) { this.driver = driver; } public String getDbUrl() { return dbUrl; } public void setDbUrl(String dbUrl) { this.dbUrl = dbUrl; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public BasicDataSource getDataSource() { if (this.dataSource!=null) return this.dataSource; BasicDataSource dtSource = new BasicDataSource(); dtSource.setDriverClassName(getDriver()); dtSource.setUrl(getDbUrl()); if (getUsername().length() > 0){ dtSource.setUsername(getUsername()); dtSource.setPassword(getPassword()); } this.dataSource=dtSource; return dtSource; } }

JdbcDataSetBeanInfo类的实现

/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package org.apache.jmeter.config; import java.beans.PropertyDescriptor; import org.apache.jmeter.testbeans.BeanInfoSupport; import org.apache.jmeter.testbeans.gui.TypeEditor; import org.apache.jmeter.util.JMeterUtils; import org.apache.jorphan.util.JOrphanUtils; public class JdbcDataSetBeanInfo extends BeanInfoSupport { // These names must agree case-wise with the variable and property names private static final String FILENAME = "filename"; //$NON-NLS-1$ private static final String VARIABLE_NAMES = "variableNames"; //$NON-NLS-1$ private static final String RECYCLE = "recycle"; //$NON-NLS-1$ private static final String STOPTHREAD = "stopThread"; //$NON-NLS-1$ private static final String SHAREMODE = "shareMode"; //$NON-NLS-1$ // Access needed from CSVDataSet private static final String[] SHARE_TAGS = new String[3]; static final int SHARE_ALL = 0; static final int SHARE_GROUP = 1; static final int SHARE_THREAD = 2; // Store the resource keys static { SHARE_TAGS[SHARE_ALL] = "shareMode.all"; //$NON-NLS-1$ SHARE_TAGS[SHARE_GROUP] = "shareMode.group"; //$NON-NLS-1$ SHARE_TAGS[SHARE_THREAD] = "shareMode.thread"; //$NON-NLS-1$ } public JdbcDataSetBeanInfo() { super(JdbcDataSet.class); createPropertyGroup("database", new String[] { "dbUrl", "driver", "username", "password" }); createPropertyGroup("csv_data", //$NON-NLS-1$ new String[] { FILENAME, VARIABLE_NAMES, RECYCLE, STOPTHREAD, SHAREMODE }); PropertyDescriptor p = property(FILENAME,TypeEditor.TextAreaEditor); p.setValue(NOT_UNDEFINED, Boolean.TRUE); p.setValue(DEFAULT, ""); //$NON-NLS-1$ p.setValue(NOT_EXPRESSION, Boolean.TRUE); p = property(VARIABLE_NAMES); p.setValue(NOT_UNDEFINED, Boolean.TRUE); p.setValue(DEFAULT, ""); //$NON-NLS-1$ p.setValue(NOT_EXPRESSION, Boolean.TRUE); p = property(RECYCLE); p.setValue(NOT_UNDEFINED, Boolean.TRUE); p.setValue(DEFAULT, Boolean.TRUE); p = property(STOPTHREAD); p.setValue(NOT_UNDEFINED, Boolean.TRUE); p.setValue(DEFAULT, Boolean.FALSE); p = property(SHAREMODE, TypeEditor.ComboStringEditor); p.setValue(RESOURCE_BUNDLE, getBeanDescriptor().getValue(RESOURCE_BUNDLE)); p.setValue(NOT_UNDEFINED, Boolean.TRUE); p.setValue(DEFAULT, SHARE_TAGS[SHARE_ALL]); p.setValue(NOT_OTHER, Boolean.FALSE); p.setValue(NOT_EXPRESSION, Boolean.FALSE); p.setValue(TAGS, SHARE_TAGS); p = property("dbUrl"); p.setValue(NOT_UNDEFINED, Boolean.TRUE); p.setValue(DEFAULT, ""); p = property("driver", TypeEditor.ComboStringEditor); p.setValue(NOT_UNDEFINED, Boolean.TRUE); p.setValue(DEFAULT, ""); p.setValue(TAGS, getListJDBCDriverClass()); p = property("username"); p.setValue(NOT_UNDEFINED, Boolean.TRUE); p.setValue(DEFAULT, ""); p = property("password", TypeEditor.PasswordEditor); p.setValue(NOT_UNDEFINED, Boolean.TRUE); p.setValue(DEFAULT, ""); } public static int getShareModeAsInt(String mode) { if (mode == null || mode.length() == 0){ return SHARE_ALL; // default (e.g. if test plan does not have definition) } for (int i = 0; i < SHARE_TAGS.length; i++) { if (SHARE_TAGS[i].equals(mode)) { return i; } } return -1; } /** * @return array of String for possible sharing modes */ public static String[] getShareTags() { String[] copy = new String[SHARE_TAGS.length]; System.arraycopy(SHARE_TAGS, 0, copy, 0, SHARE_TAGS.length); return copy; } /** * Get the list of JDBC driver classname for the main databases * @return a String[] with the list of JDBC driver classname */ private String[] getListJDBCDriverClass() { return JOrphanUtils.split(JMeterUtils.getPropDefault("jdbc.config.jdbc.driver.class", ""), "|"); //$NON-NLS-1$ } }

JdbcServer的实现

/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations * under the License. * */ package org.apache.jmeter.services; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.Closeable; import java.io.EOFException; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.io.Reader; import java.io.Writer; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.ResultSetMetaData; import java.sql.SQLException; import java.util.ArrayList; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.concurrent.ThreadLocalRandom; import org.apache.commons.collections.ArrayStack; import org.apache.commons.dbcp2.BasicDataSource; import org.apache.jmeter.gui.JMeterFileFilter; import org.apache.jmeter.save.CSVSaveService; import org.apache.jmeter.threads.JMeterContext; import org.apache.jmeter.threads.JMeterContextService; import org.apache.jmeter.util.JMeterUtils; import org.apache.jorphan.util.JOrphanUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; //import com.mysql.cj.jdbc.result.ResultSetMetaData; //import com.mysql.jdbc.ResultSetMetaData; /** * This class provides thread-safe access to files, and to * provide some simplifying assumptions about where to find files and how to * name them. For instance, putting supporting files in the same directory as * the saved test plan file allows users to refer to the file with just it's * name - this FileServer class will find the file without a problem. * Eventually, I want all in-test file access to be done through here, with the * goal of packaging up entire test plans as a directory structure that can be * sent via rmi to remote servers (currently, one must make sure the remote * server has all support files in a relative-same location) and to package up * test plans to execute on unknown boxes that only have Java installed. */ public class JdbcServer { private static final Logger log = LoggerFactory.getLogger(JdbcServer.class); // public static final String url = "jdbc:mysql://172.16.206.82:3306/vbi_api?useSSL=FALSE&serverTimezone=UTC"; // public static final String name = "com.mysql.cj.jdbc.Driver"; // public static final String user = "vbi"; // public static final String password = "123456"; public java.sql.Connection conn = null; public PreparedStatement pst = null; public List<String> strList= new ArrayList<String>(); public List<LinkedHashMap<String, Object>> listdb = new ArrayList<LinkedHashMap<String, Object>>(); public int rownum=0; /** * The default base used for resolving relative files, i.e.<br/> * {@code System.getProperty("user.dir")} */ private static final String DEFAULT_BASE = System.getProperty("user.dir");// $NON-NLS-1$ /** Default base prefix: {@value} */ private static final String BASE_PREFIX_DEFAULT = "~/"; // $NON-NLS-1$ private static final String BASE_PREFIX = JMeterUtils.getPropDefault("jmeter.save.saveservice.base_prefix", // $NON-NLS-1$ BASE_PREFIX_DEFAULT); private File base; private final Map<String, FileEntry> files = new HashMap<>(); private static final JdbcServer server = new JdbcServer(); // volatile needed to ensure safe publication private volatile String scriptName; // Cannot be instantiated private JdbcServer() { base = new File(DEFAULT_BASE); log.info("Default base='{}'", DEFAULT_BASE); } public void close() { try { this.pst.close(); this.conn.close(); } catch (SQLException e) { e.printStackTrace(); } } //如果未设置变量名称,获取变量名 public synchronized String[] getVars(BasicDataSource ds,String sql) { if(!strList.isEmpty()) return strList.toArray(new String[strList.size()]); try { Class.forName(ds.getDriverClassName());//指定连接类型 conn = DriverManager.getConnection(ds.getUrl(),ds.getUsername(),ds.getPassword());//获取连接 sql=sql.replace(";","").replace(";","").concat(" limit 1"); pst = conn.prepareStatement(sql);//准备执行语句 } catch (Exception e) { e.printStackTrace(); log.error("数据库连接异常:"+ds.getUrl()); } try { ResultSet ret = server.pst.executeQuery();//执行语句,得到结果集 ResultSetMetaData md = (ResultSetMetaData) ret.getMetaData(); int columnCount = md.getColumnCount(); String[] vars=new String[columnCount]; ret.next(); for (int i = 1; i <= columnCount; i++) { strList.add(md.getColumnName(i)); vars[i-1]=md.getColumnName(i); } ret.close(); server.close();//关闭连接 return strList.toArray(new String[strList.size()]); } catch (SQLException e) { e.printStackTrace(); log.error("数据库sql执行异常:"+ds.getUrl()); return new String[]{}; } } List<String> threadsList=new ArrayList<String>(); public synchronized String[] readLineSql(BasicDataSource ds,String sql,boolean recycle) { int index=0; List<LinkedHashMap<String, Object>> line=getMysqlResult(ds,sql); JMeterContext context=JMeterContextService.getContext(); if(rownum>=line.size()) { rownum=0; if(!recycle) { if(!threadsList.contains(context.getThread().getThreadName())) threadsList.add(context.getThread().getThreadName()); return (new String[0]); } } if(!recycle&&rownum==0&&threadsList.size()>0) { if(threadsList.contains(context.getThread().getThreadName())) return (new String[0]); threadsList.add(context.getThread().getThreadName()); if(threadsList.size()>=JMeterContextService.getTotalThreads())//context.getThreadGroup().getNumThreads() threadsList.clear(); return new String[0]; } Map<String, Object> map=line.get(rownum); String[] values=new String[map.size()]; for (Map.Entry<String, Object> entry : map.entrySet()) { values[index]=(entry.getValue()==null||entry.getValue()=="") ?"":entry.getValue().toString(); index+=1; } rownum+=1; return values; } public synchronized List<LinkedHashMap<String, Object>> getMysqlResult(BasicDataSource ds,String sql) { //String sql = "select * from student";//SQL语句 if(!listdb.isEmpty()) return listdb; try { Class.forName(ds.getDriverClassName());//指定连接类型 conn = DriverManager.getConnection(ds.getUrl(),ds.getUsername(),ds.getPassword()); // Class.forName(name);//指定连接类型 // conn = DriverManager.getConnection(url,user,password);//获取连接 pst = conn.prepareStatement(sql);//准备执行语句 } catch (Exception e) { e.printStackTrace(); } try { ResultSet ret = server.pst.executeQuery();//执行语句,得到结果集 ResultSetMetaData md = (ResultSetMetaData) ret.getMetaData(); int columnCount = md.getColumnCount(); while (ret.next()) { LinkedHashMap<String, Object> rowData = new LinkedHashMap<String, Object>(); for (int i = 1; i <= columnCount; i++) { rowData.put(md.getColumnName(i), ret.getObject(i)); } listdb.add(rowData); // String uid = ret.getString(1); // String ufname = ret.getString(2); // String ulname = ret.getString(3); // String udate = ret.getString(4); // System.out.println(uid + "\t" + ufname + "\t" + ulname + "\t" + udate ); }//显示数据 //ret.first(); ret.close(); server.close();//关闭连接 return listdb; } catch (SQLException e) { e.printStackTrace(); log.error("数据库连接异常:"+ds.getUrl()); return listdb; } } /** * @return the singleton instance of the server. */ public static JdbcServer getFileServer() { return server; } /** * Resets the current base to DEFAULT_BASE. */ public synchronized void resetBase() { checkForOpenFiles(); base = new File(DEFAULT_BASE); log.info("Reset base to '{}'", base); } /** * Sets the current base directory for relative file names from the provided path. * If the path does not refer to an existing directory, then its parent is used. * Normally the provided path is a file, so using the parent directory is appropriate. * * @param basedir the path to set, or {@code null} if the GUI is being cleared * @throws IllegalStateException if files are still open */ public synchronized void setBasedir(String basedir) { checkForOpenFiles(); // TODO should this be called if basedir == null? if (basedir != null) { File newBase = new File(basedir); if (!newBase.isDirectory()) { newBase = newBase.getParentFile(); } base = newBase; log.info("Set new base='{}'", base); } } /** * Sets the current base directory for relative file names from the provided script file. * The parameter is assumed to be the path to a JMX file, so the base directory is derived * from its parent. * * @param scriptPath the path of the script file; must be not be {@code null} * @throws IllegalStateException if files are still open * @throws IllegalArgumentException if scriptPath parameter is null */ public synchronized void setBaseForScript(File scriptPath) { if (scriptPath == null){ throw new IllegalArgumentException("scriptPath must not be null"); } setScriptName(scriptPath.getName()); // getParentFile() may not work on relative paths setBase(scriptPath.getAbsoluteFile().getParentFile()); } /** * Sets the current base directory for relative file names. * * @param jmxBase the path of the script file base directory, cannot be null * @throws IllegalStateException if files are still open * @throws IllegalArgumentException if {@code basepath} is null */ public synchronized void setBase(File jmxBase) { if (jmxBase == null) { throw new IllegalArgumentException("jmxBase must not be null"); } checkForOpenFiles(); base = jmxBase; log.info("Set new base='{}'", base); } /** * Check if there are entries in use. * <p> * Caller must ensure that access to the files map is single-threaded as * there is a window between checking the files Map and clearing it. * * @throws IllegalStateException if there are any entries still in use */ private void checkForOpenFiles() throws IllegalStateException { if (filesOpen()) { // checks for entries in use throw new IllegalStateException("Files are still open, cannot change base directory"); } files.clear(); // tidy up any unused entries } public synchronized String getBaseDir() { return base.getAbsolutePath(); } public static String getDefaultBase(){ return DEFAULT_BASE; } /** * Calculates the relative path from DEFAULT_BASE to the current base, * which must be the same as or a child of the default. * * @return the relative path, or {@code "."} if the path cannot be determined */ public synchronized File getBaseDirRelative() { // Must first convert to absolute path names to ensure parents are available File parent = new File(DEFAULT_BASE).getAbsoluteFile(); File f = base.getAbsoluteFile(); ArrayStack l = new ArrayStack(); while (f != null) { if (f.equals(parent)){ if (l.isEmpty()){ break; } File rel = new File((String) l.pop()); while(!l.isEmpty()) { rel = new File(rel, (String) l.pop()); } return rel; } l.push(f.getName()); f = f.getParentFile(); } return new File("."); } /** * Creates an association between a filename and a File inputOutputObject, * and stores it for later use - unless it is already stored. * * @param filename - relative (to base) or absolute file name (must not be null) */ public void reserveFile(String filename) { reserveFile(filename,null); } /** * Creates an association between a filename and a File inputOutputObject, * and stores it for later use - unless it is already stored. * * @param filename - relative (to base) or absolute file name (must not be null) * @param charsetName - the character set encoding to use for the file (may be null) */ public void reserveFile(String filename, String charsetName) { reserveFile(filename, charsetName, filename, false); } /** * Creates an association between a filename and a File inputOutputObject, * and stores it for later use - unless it is already stored. * * @param filename - relative (to base) or absolute file name (must not be null) * @param charsetName - the character set encoding to use for the file (may be null) * @param alias - the name to be used to access the object (must not be null) */ public void reserveFile(String filename, String charsetName, String alias) { reserveFile(filename, charsetName, alias, false); } /** * Creates an association between a filename and a File inputOutputObject, * and stores it for later use - unless it is already stored. * * @param filename - relative (to base) or absolute file name (must not be null or empty) * @param charsetName - the character set encoding to use for the file (may be null) * @param alias - the name to be used to access the object (must not be null) * @param hasHeader true if the file has a header line describing the contents * @return the header line; may be null * @throws IllegalArgumentException if header could not be read or filename is null or empty */ public synchronized String reserveFile(String filename, String charsetName, String alias, boolean hasHeader) { if (filename == null || filename.isEmpty()){ throw new IllegalArgumentException("Filename must not be null or empty"); } if (alias == null){ throw new IllegalArgumentException("Alias must not be null"); } FileEntry fileEntry = files.get(alias); if (fileEntry == null) { fileEntry = new FileEntry(resolveFileFromPath(filename), null, charsetName); if (filename.equals(alias)){ log.info("Stored: {}", filename); } else { log.info("Stored: {} Alias: {}", filename, alias); } files.put(alias, fileEntry); if (hasHeader) { try { fileEntry.headerLine = readLine(alias, false); if (fileEntry.headerLine == null) { fileEntry.exception = new EOFException("File is empty: " + fileEntry.file); } } catch (IOException | IllegalArgumentException e) { fileEntry.exception = e; } } } if (hasHeader && fileEntry.headerLine == null) { throw new IllegalArgumentException("Could not read file header line for file " + filename, fileEntry.exception); } return fileEntry.headerLine; } /** * Resolves file name into {@link File} instance. * When filename is not absolute and not found from current working dir, * it tries to find it under current base directory * @param filename original file name * @return {@link File} instance */ private File resolveFileFromPath(String filename) { File f = new File(filename); if (f.isAbsolute() || f.exists()) { return f; } else { return new File(base, filename); } } /** * Get the next line of the named file, recycle by default. * * @param filename the filename or alias that was used to reserve the file * @return String containing the next line in the file * @throws IOException when reading of the file fails, or the file was not reserved properly */ public String readLine(String filename) throws IOException { return readLine(filename, true); } /** * Get the next line of the named file, first line is name to false * * @param filename the filename or alias that was used to reserve the file * @param recycle - should file be restarted at EOF? * @return String containing the next line in the file (null if EOF reached and not recycle) * @throws IOException when reading of the file fails, or the file was not reserved properly */ public String readLine(String filename, boolean recycle) throws IOException { return readLine(filename, recycle, false); } /** * Get the next line of the named file * * @param filename the filename or alias that was used to reserve the file * @param recycle - should file be restarted at EOF? * @param ignoreFirstLine - Ignore first line * @return String containing the next line in the file (null if EOF reached and not recycle) * @throws IOException when reading of the file fails, or the file was not reserved properly */ public synchronized String readLine(String filename, boolean recycle, boolean ignoreFirstLine) throws IOException { FileEntry fileEntry = files.get(filename); if (fileEntry != null) { if (fileEntry.inputOutputObject == null) { fileEntry.inputOutputObject = createBufferedReader(fileEntry); } else if (!(fileEntry.inputOutputObject instanceof Reader)) { throw new IOException("File " + filename + " already in use"); } BufferedReader reader = (BufferedReader) fileEntry.inputOutputObject; String line = reader.readLine(); if (line == null && recycle) { reader.close(); reader = createBufferedReader(fileEntry); fileEntry.inputOutputObject = reader; if (ignoreFirstLine) { // read first line and forget reader.readLine();//NOSONAR } line = reader.readLine(); } log.debug("Read:{}", line); return line; } throw new IOException("File never reserved: "+filename); } /** * * @param alias the file name or alias * @param recycle whether the file should be re-started on EOF * @param ignoreFirstLine whether the file contains a file header which will be ignored * @param delim the delimiter to use for parsing * @return the parsed line, will be empty if the file is at EOF * @throws IOException when reading of the aliased file fails, or the file was not reserved properly */ public synchronized String[] getParsedLine(String alias, boolean recycle, boolean ignoreFirstLine, char delim) throws IOException { BufferedReader reader = getReader(alias, recycle, ignoreFirstLine); return CSVSaveService.csvReadFile(reader, delim); } /** * Return BufferedReader handling close if EOF reached and recycle is true * and ignoring first line if ignoreFirstLine is true * * @param alias String alias * @param recycle Recycle at eof * @param ignoreFirstLine Ignore first line * @return {@link BufferedReader} */ private BufferedReader getReader(String alias, boolean recycle, boolean ignoreFirstLine) throws IOException { FileEntry fileEntry = files.get(alias); if (fileEntry != null) { BufferedReader reader; if (fileEntry.inputOutputObject == null) { reader = createBufferedReader(fileEntry); fileEntry.inputOutputObject = reader; if (ignoreFirstLine) { // read first line and forget reader.readLine(); //NOSONAR } } else if (!(fileEntry.inputOutputObject instanceof Reader)) { throw new IOException("File " + alias + " already in use"); } else { reader = (BufferedReader) fileEntry.inputOutputObject; if (recycle) { // need to check if we are at EOF already reader.mark(1); int peek = reader.read(); if (peek == -1) { // already at EOF reader.close(); reader = createBufferedReader(fileEntry); fileEntry.inputOutputObject = reader; if (ignoreFirstLine) { // read first line and forget reader.readLine(); //NOSONAR } } else { // OK, we still have some data, restore it reader.reset(); } } } return reader; } else { throw new IOException("File never reserved: "+alias); } } private BufferedReader createBufferedReader(FileEntry fileEntry) throws IOException { if (!fileEntry.file.canRead() || !fileEntry.file.isFile()) { throw new IllegalArgumentException("File "+ fileEntry.file.getName()+ " must exist and be readable"); } FileInputStream fis = new FileInputStream(fileEntry.file); InputStreamReader isr = null; // If file encoding is specified, read using that encoding, otherwise use default platform encoding String charsetName = fileEntry.charSetEncoding; if(!JOrphanUtils.isBlank(charsetName)) { isr = new InputStreamReader(fis, charsetName); } else { isr = new InputStreamReader(fis); } return new BufferedReader(isr); } public synchronized void write(String filename, String value) throws IOException { FileEntry fileEntry = files.get(filename); if (fileEntry != null) { if (fileEntry.inputOutputObject == null) { fileEntry.inputOutputObject = createBufferedWriter(fileEntry); } else if (!(fileEntry.inputOutputObject instanceof Writer)) { throw new IOException("File " + filename + " already in use"); } BufferedWriter writer = (BufferedWriter) fileEntry.inputOutputObject; log.debug("Write:{}", value); writer.write(value); } else { throw new IOException("File never reserved: "+filename); } } private BufferedWriter createBufferedWriter(FileEntry fileEntry) throws IOException { FileOutputStream fos = new FileOutputStream(fileEntry.file); OutputStreamWriter osw; // If file encoding is specified, write using that encoding, otherwise use default platform encoding String charsetName = fileEntry.charSetEncoding; if(!JOrphanUtils.isBlank(charsetName)) { osw = new OutputStreamWriter(fos, charsetName); } else { osw = new OutputStreamWriter(fos); } return new BufferedWriter(osw); } public synchronized void closeFiles() throws IOException { for (Map.Entry<String, FileEntry> me : files.entrySet()) { closeFile(me.getKey(),me.getValue() ); } files.clear(); } /** * @param name the name or alias of the file to be closed * @throws IOException when closing of the aliased file fails */ public synchronized void closeFile(String name) throws IOException { FileEntry fileEntry = files.get(name); closeFile(name, fileEntry); } private void closeFile(String name, FileEntry fileEntry) throws IOException { if (fileEntry != null && fileEntry.inputOutputObject != null) { log.info("Close: {}", name); fileEntry.inputOutputObject.close(); fileEntry.inputOutputObject = null; } } boolean filesOpen() { // package access for test code only return files.values().stream() .anyMatch(fileEntry -> fileEntry.inputOutputObject != null); } /** * Method will get a random file in a base directory * <p> * TODO hey, not sure this method belongs here. * FileServer is for thread safe File access relative to current test's base directory. * * @param basedir name of the directory in which the files can be found * @param extensions array of allowed extensions, if <code>null</code> is given, * any file be allowed * @return a random File from the <code>basedir</code> that matches one of * the extensions */ public File getRandomFile(String basedir, String[] extensions) { File input = null; if (basedir != null) { File src = new File(basedir); File[] lfiles = src.listFiles(new JMeterFileFilter(extensions)); if (lfiles != null) { // lfiles cannot be null as it has been checked before int count = lfiles.length; input = lfiles[ThreadLocalRandom.current().nextInt(count)]; } } return input; } /** * Get {@link File} instance for provided file path, * resolve file location relative to base dir or script dir when needed * * @param path original path to file, maybe relative * @return {@link File} instance */ public File getResolvedFile(String path) { reserveFile(path); return files.get(path).file; } private static class FileEntry{ private String headerLine; private Throwable exception; private final File file; private Closeable inputOutputObject; private final String charSetEncoding; FileEntry(File f, Closeable o, String e) { file = f; inputOutputObject = o; charSetEncoding = e; } } /** * Resolve a file name that may be relative to the base directory. If the * name begins with the value of the JMeter property * "jmeter.save.saveservice.base_prefix" - default "~/" - then the name is * assumed to be relative to the basename. * * @param relativeName * filename that should be checked for * <code>jmeter.save.saveservice.base_prefix</code> * @return the updated filename */ public static String resolveBaseRelativeName(String relativeName) { if (relativeName.startsWith(BASE_PREFIX)){ String newName = relativeName.substring(BASE_PREFIX.length()); return new File(getFileServer().getBaseDir(),newName).getAbsolutePath(); } return relativeName; } /** * @return JMX Script name * @since 2.6 */ public String getScriptName() { return scriptName; } /** * @param scriptName Script name * @since 2.6 */ public void setScriptName(String scriptName) { this.scriptName = scriptName; } }

JdbcDataSetResources.properties配置文件

# Licensed to the Apache Software Foundation (ASF) under one or more # contributor license agreements. See the NOTICE file distributed with # this work for additional information regarding copyright ownership. # The ASF licenses this file to You under the Apache License, Version 2.0 # (the "License"); you may not use this file except in compliance with # the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. displayName=JDBC Data Set Config csv_data.displayName=Configure the JDBC Data Source filename.displayName=SQL Query filename.shortDescription=Name of the file that holds the cvs data (relative or absolute filename) fileEncoding.displayName=File encoding fileEncoding.shortDescription=The character set encoding used in the file ignoreFirstLine.displayName=Ignore first line (only used if Variable Names is not empty) ignoreFirstLine.shortDescription=Ignore first line of CSV file, it will only be used used if Variable Names is not empty, if Variable Names is empty the first line must contain the headers. variableNames.displayName=Variable Names (comma-delimited) variableNames.shortDescription=List your variable names in order to match the order of columns in your csv data. Keep it empty to use the first line of the file for variable names. delimiter.displayName=Delimiter (use '\\t' for tab) delimiter.shortDescription=Enter the delimiter ('\\t' for tab) quotedData.displayName=Allow quoted data? quotedData.shortDescription=Allow CSV data values to be quoted? recycle.displayName=Recycle on EOF ? recycle.shortDescription=Should the file be re-read from the start on reaching EOF ? stopThread.displayName=Stop thread on EOF ? stopThread.shortDescription=Should the thread be stopped on reaching EOF (if Recycle is false) ? shareMode.displayName=Sharing mode shareMode.shortDescription=Select which threads share the same file pointer shareMode.all=All threads shareMode.group=Current thread group shareMode.thread=Current thread database.displayName=Database Connection Configuration driver.displayName=JDBC Driver class driver.shortDescription=Full package and class name of the JDBC driver to be used (Must be in JMeter's classpath) dbUrl.displayName=Database URL dbUrl.shortDescription=Full URL for the database, including jdbc protocol parts username.displayName=Username username.shortDescription=Username to use to connect to database password.displayName=Password password.shortDescription=Password used to connect to database

打包插件放于Jmeter的lib/ext目录下

下载链接: https://pan.baidu.com/s/1kZuYNtzjyvT3XnmSP-Mk4Q 提取码: uv87

最新回复(0)