Jmeter测试框架学习总结之代码分析

首先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);
        }
    }

现在进入的jmeter类的star方法,jmeter类:

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);
        }
    }





你可能感兴趣的:(测试,J2SE)