首先jmeter框架入口类: NewDriver类
public static void main(String[] args) {
String[] args1=new String[]{"-n","-t","E:\\svn\\QD_DependWL\\develop\\trunk\\java_server\\test_tool_server\\bin\\templates\\recording.jmx","-e","-l","test.jtl"};//,"-o","dashboard"};
//String[] args1=new String[]{"-t","E:\\svn\\QD_DependWL\\develop\\trunk\\java_server\\test_tool_server\\bin\\templates\\recording.jmx","-l","test.jtl"};//,"-o","dashboard"};
//String param = "{'Browser':'Mozilla FireFox','CharSetSupport':false,'Location':0,'Proctocol':'Http','Snapshot':true,'Url':'http://www.douban.com'}";
String param = "{\"Browser\":\"Mozilla FireFox\",\"CharSetSupport\":false,\"Location\":0,\"Proctocol\":\"Http\",\"Snapshot\":true,\"Url\":\"http://www.douban.com\"}";
String[] args2=new String[]{"-w",param,"-t","E:\\workspace\\java\\jiancy\\test_tool_server\\bin\\templates\\recording.jmx","-l","C:\\Users\\sstk\\Desktop\\test.jtl"};
Thread.currentThread().setContextClassLoader(loader);
if (System.getProperty("log4j.configuration") == null) {// $NON-NLS-1$ $NON-NLS-2$
File conf = new File(jmDir, "bin" + File.separator + "log4j.conf");// $NON-NLS-1$ $NON-NLS-2$
System.setProperty("log4j.configuration", "file:" + conf);
}
try {
//加载jmeter类
Class> initialClass = loader.loadClass("org.apache.jmeter.JMeter");// $NON-NLS-1$
//获取jmeter类实例
Object instance = initialClass.newInstance();
//获取start方法类实例
Method startup = initialClass.getMethod("start", new Class[] { new String[0].getClass() });// $NON-NLS-1$
//调用jmeter的start方法
startup.invoke(instance, new Object[] { args });
//startup.invoke(instance, new Object[] { args1 });
} catch(Throwable e){
e.printStackTrace();
System.err.println("JMeter home directory was detected as: "+jmDir);
}
}
Main JMeter class; processes options and starts the GUI, non-GUI or server as appropriate.
可以看出start函数主要用于根据命令行命令执行不同的操作
public void start(String[] args) {
//获得I/O的通道管理器
if(NioClient.getClientInstance().init(Constraint.IP,Constraint.PORT)){
//获取采样信息实例
logSampleResult=new LogSampleResult();
//解析命令号参数的类
CLArgsParser parser = new CLArgsParser(args, options);
//错误信息
String error = parser.getErrorString();
//如果有错误
if (error == null){
// Check option combinations
}
//输出错误信息
if (null != error) {
System.err.println("Error: " + error);
System.out.println("Usage");
System.out.println(CLUtil.describeOptions(options).toString());
// repeat the error so no need to scroll back past the usage to see it
System.out.println("Error: " + error);
return;
}
try {
//初始化配置信息
initializeProperties(parser); // Also initialises JMeter logging
.....;
//// Update classloader if necessary
updateClassLoader();
if (log.isDebugEnabled())
{
String jcp=System.getProperty("java.class.path");// $NON-NLS-1$
String[] bits = jcp.split(File.pathSeparator);
log.debug("ClassPath");
for(String bit : bits){
log.debug(bit);
}
}
// Set some (hopefully!) useful properties
long now=System.currentTimeMillis();
JMeterUtils.setProperty("START.MS",Long.toString(now));// $NON-NLS-1$
Date today=new Date(now); // so it agrees with above
// TODO perhaps should share code with __time() function for this...
JMeterUtils.setProperty("START.YMD",new SimpleDateFormat("yyyyMMdd").format(today));// $NON-NLS-1$ $NON-NLS-2$
JMeterUtils.setProperty("START.HMS",new SimpleDateFormat("HHmmss").format(today));// $NON-NLS-1$ $NON-NLS-2$
/**
*开始真正有用的了
*/
if (parser.getArgumentById(VERSION_OPT) != null) {
displayAsciiArt();
} else if (parser.getArgumentById(HELP_OPT) != null) {
displayAsciiArt();
System.out.println(JMeterUtils.getResourceFileAsText("org/apache/jmeter/help.txt"));// $NON-NLS-1$
} else if (parser.getArgumentById(OPTIONS_OPT) != null) {
displayAsciiArt();
System.out.println(CLUtil.describeOptions(options).toString());
} else if (parser.getArgumentById(SERVER_OPT) != null) {
// Start the server
try {
RemoteJMeterEngineImpl.startServer(JMeterUtils.getPropDefault("server_port", 0)); // $NON-NLS-1$
} catch (Exception ex) {
System.err.println("Server failed to start: "+ex);
log.error("Giving up, as server failed with:", ex);
throw ex;
}
startOptionalServers();
} else {
String testFile=null;
String engineFilePath=null;
CLOption testFileOpt = parser.getArgumentById(TESTFILE_OPT);
if (testFileOpt != null){
testFile = testFileOpt.getArgument();
if (USE_LAST_JMX.equals(testFile)) {
testFile = LoadRecentProject.getRecentFile(0);// most recent
}
}
CLOption engineFileOpt = parser.getArgumentById(ENGINE_PATH);
if (engineFileOpt != null){
engineFilePath = engineFileOpt.getArgument();
}
CLOption testReportOpt = parser.getArgumentById(REPORT_GENERATING_OPT);
if (testReportOpt != null) { // generate report from existing file
String reportFile = testReportOpt.getArgument();
extractAndSetReportOutputFolder(parser);
ReportGenerator generator = new ReportGenerator(reportFile, null);
generator.generate();
} else if (parser.getArgumentById(NONGUI_OPT) == null) { // not non-GUI => GUI
//在有用户界面下执行
startGui(testFile,parser);
startOptionalServers();
} else { // NON-GUI must be true
extractAndSetReportOutputFolder(parser);
CLOption rem = parser.getArgumentById(REMOTE_OPT_PARAM);
if (rem == null) {
rem = parser.getArgumentById(REMOTE_OPT);
}
CLOption jtl = parser.getArgumentById(LOGFILE_OPT);
String jtlFile = null;
if (jtl != null) {
jtlFile = processLAST(jtl.getArgument(), ".jtl"); // $NON-NLS-1$
}
CLOption reportAtEndOpt = parser.getArgumentById(REPORT_AT_END_OPT);
if(reportAtEndOpt != null) {
if(jtlFile == null) {
throw new IllegalUserActionException(
"Option -"+ ((char)REPORT_AT_END_OPT)+" requires -"+((char)LOGFILE_OPT )+ " option");
}
}
///无用户界面执行
startNonGui(testFile,engineFilePath, jtlFile, rem, reportAtEndOpt != null);
startOptionalServers();
}
}
} catch (IllegalUserActionException e) {
System.out.println("Incorrect Usage:"+e.getMessage());
System.out.println(CLUtil.describeOptions(options).toString());
} catch (Throwable e) {
log.fatalError("An error occurred: ",e);
System.out.println("An error occurred: " + e.getMessage());
System.exit(1); // TODO - could this be return?
}
}
上述代码主要功能函数为startgui和startnongui,其中startgui:
private void startGui(String testFile,CLArgsParser parser) {
CLOption weizhiOpt = parser.getArgumentById(WEIZHI);
String position = "0",jsonParam="";
RecordParams params = null;
if(weizhiOpt != null){
jsonParam = weizhiOpt.getArgument();
params = JSON.parseObject(jsonParam,RecordParams.class);
position = params.getLocation();
if(position==null||position==""){
position = "0";
}
if("setup".equalsIgnoreCase(position)){
position = "0";
}
if("event".equalsIgnoreCase(position)){
position = "1";
}
if("teardown".equalsIgnoreCase(position)){
position = "2";
}
}
/////////////////////////////////////////
String jMeterLaf = LookAndFeelCommand.getJMeterLaf();
try {
UIManager.setLookAndFeel(jMeterLaf);
} catch (Exception ex) {
log.warn("Could not set LAF to:"+jMeterLaf, ex);
}
PluginManager.install(this, true);
JMeterTreeModel treeModel = new JMeterTreeModel();
JMeterTreeListener treeLis = new JMeterTreeListener(treeModel);
final ActionRouter instance = ActionRouter.getInstance();
instance.populateCommandMap();
treeLis.setActionHandler(instance);
GuiPackage guiPack = GuiPackage.getInstance(treeLis, treeModel);
guiPack.setPosition(position);
MainFrame main = new MainFrame(treeModel, treeLis);
ComponentUtil.centerComponentInWindow(main, 80);
boolean visible = JMeterUtils.getProperty("jmeter.visible").equals("true")?true:false;
if(visible){
main.setVisible(true);//TODO 设置可见性,可见true
}else{
main.setVisible(false);//TODO 设置可见性,不可见false
}
instance.actionPerformed(new ActionEvent(main, 1, ActionNames.ADD_ALL));
if (testFile != null) {
try {
File f = new File(testFile);
log.info("Loading file: " + f);
FileServer.getFileServer().setBaseForScript(f);
HashTree tree = SaveService.loadTree(f);
GuiPackage.getInstance().setTestPlanFile(f.getAbsolutePath());
Load.insertLoadedTree(1, tree);
} catch (ConversionException e) {
log.error("Failure loading test file", e);
JMeterUtils.reportErrorToUser(SaveService.CEtoString(e));
} catch (Exception e) {
log.error("Failure loading test file", e);
JMeterUtils.reportErrorToUser(e.toString());
}
} else {
JTree jTree = GuiPackage.getInstance().getMainFrame().getTree();
TreePath path = jTree.getPathForRow(0);
jTree.setSelectionPath(path);
FocusRequester.requestFocus(jTree);
}
// TODO 启动录制
JMeterTreeModel jMeterTreeModel = GuiPackage.getInstance().getTreeModel();
List jmt = jMeterTreeModel.getNodesOfType(ProxyControl.class);
ProxyControlGui httpgui = (ProxyControlGui) GuiPackage.getInstance().getGui(jmt.get(0).getTestElement());
httpgui.startProxy();
// TODO 在这里打开浏览器
if(params.getBrowser()!=null){
// TODO 打开浏览器,在这之前应该设置代理,这里需要手动去设置
String url = params.getUrl();
try {
BrowserUtil.browse(url);
} catch (Exception e) {
e.printStackTrace();
}
}
// TODO 启动录制控制器
try {
new RecordBrowser("录制控制器",position);
} catch (HeadlessException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (UnsupportedLookAndFeelException e) {
e.printStackTrace();
}
}
startnongui
private void startNonGui(String testFile,String engineFilePath, String logFile, CLOption remoteStart, boolean generateReportDashboard)
throws IllegalUserActionException, ConfigurationException {
// add a system property so samplers can check to see if JMeter
// is running in NonGui mode
... ...
//设置场景
configureScene(testFile,engineFilePath,logFile,driver,remoteStart,remoteHostsString,generateReportDashboard);
}
进入configurescene
private void configureScene(String testFile,String engineFilePath,String logFile,JMeter driver,CLOption remoteStart,String remoteHostsString,boolean generateReportDashboard){
try {
runController=new RunController();
Scence scene=Utils.loadScence(testFile);
//COUNT_SCRIPT=scene.getScripts().getScript().size();
log.info("Script size "+COUNT_SCRIPT);
for(Script script : scene.getScripts().getScript()){
if(script.getRunflag().equals("1"))
{
COUNT_SCRIPT++;
//执行runnongui
driver.runNonGui(engineFilePath+script.getPath(),logFile , remoteStart != null, remoteHostsString, generateReportDashboard,scene,script,runController);
}
}
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
logSampleResult.logError("文件"+testFile+"不存在");
} catch (JAXBException e) {
// TODO Auto-generated catch block
e.printStackTrace();
logSampleResult.logError("文件"+testFile+"格式损坏,解析失败。");
}
}
runnongui:这个类滴代码是相当多啊,主要是跑脚本
private void runNonGui(String testFile, String logFile, boolean remoteStart, String remote_hosts_string, boolean generateReportDashboard,Scence scene,Script script,RunController runController) {
try {
//首先获得脚本文件
File f = new File(testFile);
if (!f.exists() || !f.isFile()) {
println("Could not open " + testFile);
logSampleResult.logError("文件"+testFile+"不存在");
System.exit(0);
}
// TODO 设置脚本文件
FileServer.getFileServer().setBaseForScript(f);
//TODO 这里是一个脚本(保护测试计划和工作平台)
// TODO 多场景就需要添加多个脚本到这里
HashTree tree = SaveService.loadTree(f);
@SuppressWarnings("deprecation") // Deliberate use of deprecated ctor
JMeterTreeModel treeModel = new JMeterTreeModel(new Object());// Create non-GUI version to avoid headless problems
JMeterTreeNode root = (JMeterTreeNode) treeModel.getRoot();
treeModel.addSubTree(tree, root);
// Hack to resolve ModuleControllers in non GUI mode
SearchByClass replaceableControllers =
new SearchByClass<>(ReplaceableController.class);
tree.traverse(replaceableControllers);
Collection replaceableControllersRes = replaceableControllers.getSearchResults();
for (ReplaceableController replaceableController : replaceableControllersRes) {
replaceableController.resolveReplacementSubTree(root);
}
// Remove the disabled items
// For GUI runs this is done in Start.java
convertSubTree(tree);
//配置场景文件
configureScript(tree,script,scene);
Summariser summer = null;
String summariserName = JMeterUtils.getPropDefault("summariser.name", "");//$NON-NLS-1$
if (summariserName.length() > 0) {
log.info("Creating summariser <" + summariserName + ">");
println("Creating summariser <" + summariserName + ">");
summer = new Summariser(summariserName);
}
ReportGenerator reportGenerator = null;
if (logFile != null) {
ResultCollector logger = new ResultCollector(summer);
logger.setFilename(logFile);
tree.add(tree.getArray()[0], logger);
if(generateReportDashboard) {
reportGenerator = new ReportGenerator(logFile, logger);
}
}
else {
// only add Summariser if it can not be shared with the ResultCollector
if (summer != null) {
tree.add(tree.getArray()[0], summer);
}
}
// Used for remote notification of threads start/stop,see BUG 54152
// Summariser uses this feature to compute correctly number of threads
// when NON GUI mode is used
tree.add(tree.getArray()[0], new RemoteThreadsListenerTestElement());
tree.add(tree.getArray()[0], new ListenToTest(parent, (remoteStart && remoteStop) ? engines : null, reportGenerator));
println("Created the tree successfully using "+testFile);
if (!remoteStart) {
//注意了,核心代码来了,实例化了一个engine来对付脚本,并调用了她的runtest函数,engine的本质是一个线程,在她的runrest中调用了自己
JMeterEngine engine;
if(null!=scene&&null!=script)
engine= new StandardJMeterEngine(script.getName(),scene.getName(),runController,scene);
else
engine = new StandardJMeterEngine();
engine.configure(tree);
long now=System.currentTimeMillis();
println("Starting the test @ "+new Date(now)+" ("+now+")");
engine.runTest();
engines.add(engine);
} else {
java.util.StringTokenizer st = new java.util.StringTokenizer(remote_hosts_string, ",");//$NON-NLS-1$
List hosts = new LinkedList<>();
while (st.hasMoreElements()) {
hosts.add((String) st.nextElement());
}
DistributedRunner distributedRunner=new DistributedRunner(this.remoteProps);
distributedRunner.setStdout(System.out);
distributedRunner.setStdErr(System.err);
distributedRunner.init(hosts, tree);
engines.addAll(distributedRunner.getEngines());
distributedRunner.start();
}
startUdpDdaemon(engines);
} catch (Exception e) {
System.out.println("Error in NonGUIDriver " + e.toString());
log.error("Error in NonGUIDriver", e);
}
}
好了,完了的jmeter类过去的,迎面走来的是更可恶的standardjmeterengine类
删掉冗余,只留核心:
public void run() {
log.info("Running the test!");
running = true;
/*
* Ensure that the sample variables are correctly initialised for each run.
* TODO is this the best way to do this? should it be done elsewhere ?
*/
SampleEvent.initSampleVariables();
JMeterContextService.startTest();
try {
PreCompiler compiler = new PreCompiler();
test.traverse(compiler);
} catch (RuntimeException e) {
log.error("Error occurred compiling the tree:",e);
JMeterUtils.reportErrorToUser("Error occurred compiling the tree: - see log file");
return; // no point continuing
}
/**
* Notification of test listeners needs to happen after function
* replacement, but before setting RunningVersion to true.
*/
SearchByClass testListeners = new SearchByClass<>(TestStateListener.class); // TL - S&E
test.traverse(testListeners);
// Merge in any additional test listeners
// currently only used by the function parser
testListeners.getSearchResults().addAll(testList);
testList.clear(); // no longer needed
test.traverse(new TurnElementsOn());
notifyTestListenersOfStart(testListeners);
List> testLevelElements = new LinkedList<>(test.list(test.getArray()[0]));
removeThreadGroups(testLevelElements);
SearchByClass setupSearcher = new SearchByClass<>(SetupThreadGroup.class);
SearchByClass searcher = new SearchByClass<>(AbstractThreadGroup.class);
SearchByClass postSearcher = new SearchByClass<>(PostThreadGroup.class);
test.traverse(setupSearcher);
test.traverse(searcher);
test.traverse(postSearcher);
TestCompiler.initialize();
// for each thread group, generate threads
// hand each thread the sampler controller
// and the listeners, and the timer
Iterator setupIter = setupSearcher.getSearchResults().iterator();
Iterator iter = searcher.getSearchResults().iterator();
Iterator postIter = postSearcher.getSearchResults().iterator();
ListenerNotifier notifier = new ListenerNotifier();
int groupCount = 0;
JMeterContextService.clearTotalThreads();
//遍历
if (setupIter.hasNext()) {
log.info("Starting setUp thread groups");
while (running && setupIter.hasNext()) {//for each setup thread group
AbstractThreadGroup group = setupIter.next();
groupCount++;
String groupName = group.getName();
log.info("Starting setUp ThreadGroup: " + groupCount + " : " + groupName);
startThreadGroup(group, groupCount, setupSearcher, testLevelElements, notifier);
if (serialized && setupIter.hasNext()) {
log.info("Waiting for setup thread group: "+groupName+" to finish before starting next setup group");
group.waitThreadsStopped();
}
}
log.info("Waiting for all setup thread groups to exit");
//wait for all Setup Threads To Exit
waitThreadsStopped();
log.info("All Setup Threads have ended");
groupCount=0;
JMeterContextService.clearTotalThreads();
}
groups.clear(); // The groups have all completed now
/*
* Here's where the test really starts. Run a Full GC now: it's no harm
* at all (just delays test start by a tiny amount) and hitting one too
* early in the test can impair results for short tests.
*/
JMeterUtils.helpGC();
JMeterContextService.getContext().setSamplingStarted(true);
boolean mainGroups = running; // still running at this point, i.e. setUp was not cancelled
while (running && iter.hasNext()) {// for each thread group
AbstractThreadGroup group = iter.next();
//ignore Setup and Post here. We could have filtered the searcher. but then
//future Thread Group objects wouldn't execute.
if (group instanceof SetupThreadGroup) {
continue;
}
if (group instanceof PostThreadGroup) {
continue;
}
groupCount++;
String groupName = group.getName();
log.info("Starting ThreadGroup: " + groupCount + " : " + groupName);
startThreadGroup(group, groupCount, searcher, testLevelElements, notifier);
if (serialized && iter.hasNext()) {
log.info("Waiting for thread group: "+groupName+" to finish before starting next group");
group.waitThreadsStopped();
}
} // end of thread groups
if (groupCount == 0){ // No TGs found
log.info("No enabled thread groups found");
} else {
if (running) {
log.info("All thread groups have been started");
} else {
log.info("Test stopped - no more thread groups will be started");
}
}
//wait for all Test Threads To Exit
waitThreadsStopped();
groups.clear(); // The groups have all completed now
if (postIter.hasNext()){
groupCount = 0;
JMeterContextService.clearTotalThreads();
log.info("Starting tearDown thread groups");
if (mainGroups && !running) { // i.e. shutdown/stopped during main thread groups
running = shutdown & tearDownOnShutdown; // re-enable for tearDown if necessary
}
while (running && postIter.hasNext()) {//for each setup thread group
AbstractThreadGroup group = postIter.next();
groupCount++;
String groupName = group.getName();
log.info("Starting tearDown ThreadGroup: " + groupCount + " : " + groupName);
startThreadGroup(group, groupCount, postSearcher, testLevelElements, notifier);
if (serialized && postIter.hasNext()) {
log.info("Waiting for post thread group: "+groupName+" to finish before starting next post group");
group.waitThreadsStopped();
}
}
waitThreadsStopped(); // wait for Post threads to stop
}
notifyTestListenersOfEnd(testListeners);
JMeterContextService.endTest();
if (JMeter.isNonGUI() && SYSTEM_EXIT_FORCED) {
log.info("Forced JVM shutdown requested at end of test");
System.exit(0);
}
}