001: //$Header$
002: /*
003: * Licensed to the Apache Software Foundation (ASF) under one or more
004: * contributor license agreements. See the NOTICE file distributed with
005: * this work for additional information regarding copyright ownership.
006: * The ASF licenses this file to You under the Apache License, Version 2.0
007: * (the "License"); you may not use this file except in compliance with
008: * the License. You may obtain a copy of the License at
009: *
010: * http://www.apache.org/licenses/LICENSE-2.0
011: *
012: * Unless required by applicable law or agreed to in writing, software
013: * distributed under the License is distributed on an "AS IS" BASIS,
014: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015: * See the License for the specific language governing permissions and
016: * limitations under the License.
017: *
018: */
019: package org.apache.jmeter;
020:
021: import java.awt.event.ActionEvent;
022: import java.io.File;
023: import java.io.FileInputStream;
024: import java.io.FileNotFoundException;
025: import java.io.IOException;
026: import java.util.Enumeration;
027: import java.util.Iterator;
028: import java.util.LinkedList;
029: import java.util.List;
030: import java.util.Properties;
031:
032: import org.apache.commons.cli.avalon.CLArgsParser;
033: import org.apache.commons.cli.avalon.CLOption;
034: import org.apache.commons.cli.avalon.CLOptionDescriptor;
035: import org.apache.commons.cli.avalon.CLUtil;
036: import org.apache.jmeter.config.gui.AbstractConfigGui;
037: import org.apache.jmeter.control.gui.ReportGui;
038: import org.apache.jmeter.engine.event.LoopIterationEvent;
039: import org.apache.jmeter.gui.ReportGuiPackage;
040: import org.apache.jmeter.plugin.JMeterPlugin;
041: import org.apache.jmeter.plugin.PluginManager;
042: import org.apache.jmeter.samplers.Remoteable;
043: import org.apache.jmeter.save.SaveService;
044: import org.apache.jmeter.services.FileServer;
045: import org.apache.jmeter.testelement.TestElement;
046: import org.apache.jmeter.testelement.TestListener;
047: import org.apache.jmeter.testelement.ReportPlan;
048: import org.apache.jmeter.report.gui.ReportPageGui;
049: import org.apache.jmeter.report.gui.action.ReportLoad;
050: import org.apache.jmeter.report.gui.action.ReportActionRouter;
051: import org.apache.jmeter.report.gui.action.ReportCheckDirty;
052: import org.apache.jmeter.report.gui.tree.ReportTreeListener;
053: import org.apache.jmeter.report.gui.tree.ReportTreeModel;
054: import org.apache.jmeter.report.writers.gui.HTMLReportWriterGui;
055: import org.apache.jmeter.reporters.ResultCollector;
056: import org.apache.jmeter.reporters.Summariser;
057: import org.apache.jmeter.util.JMeterUtils;
058: import org.apache.jmeter.visualizers.gui.AbstractVisualizer;
059: import org.apache.jorphan.collections.HashTree;
060: import org.apache.jorphan.gui.ComponentUtil;
061: import org.apache.jorphan.logging.LoggingManager;
062: import org.apache.jorphan.util.JOrphanUtils;
063: import org.apache.log.Logger;
064:
065: /**
066: * @author pete
067: *
068: * JMeterReport is the main class for the reporting component. For now,
069: * the plan is to make the reporting component a separate GUI, which
070: * can run in GUI or console mode. The purpose of the GUI is to design
071: * reports, which can then be run. One of the primary goals of the
072: * reporting component is to make it so the reports can be run in an
073: * automated process.
074: * The report GUI is different than the main JMeter GUI in several ways.
075: * <ul>
076: * <li> the gui is not multi-threaded</li>
077: * <li> the gui uses different components</li>
078: * <li> the gui is focused on designing reports from the jtl logs
079: * generated during a test run</li>
080: * </ul>
081: * The class follows the same design as JMeter.java. This should keep
082: * things consistent and make it easier to maintain.
083: */
084: public class JMeterReport implements JMeterPlugin {
085:
086: transient private static Logger log = LoggingManager
087: .getLoggerForClass();
088:
089: private static final int PROPFILE_OPT = 'p';
090:
091: private static final int PROPFILE2_OPT = 'q'; // Bug 33920 - additional
092: // prop files
093:
094: private static final int TESTFILE_OPT = 't';
095:
096: private static final int LOGFILE_OPT = 'l';
097:
098: private static final int NONGUI_OPT = 'n';
099:
100: private static final int HELP_OPT = 'h';
101:
102: private static final int VERSION_OPT = 'v';
103:
104: private static final int SERVER_OPT = 's';
105:
106: private static final int JMETER_PROPERTY = 'J';
107:
108: private static final int SYSTEM_PROPERTY = 'D';
109:
110: private static final int LOGLEVEL = 'L';
111:
112: private static final int REMOTE_OPT = 'r';
113:
114: private static final int JMETER_HOME_OPT = 'd';
115:
116: private JMeterReport parent;
117:
118: private static final CLOptionDescriptor[] options = new CLOptionDescriptor[] {
119: new CLOptionDescriptor("help",
120: CLOptionDescriptor.ARGUMENT_DISALLOWED, HELP_OPT,
121: "print usage information and exit"),
122: new CLOptionDescriptor("version",
123: CLOptionDescriptor.ARGUMENT_DISALLOWED,
124: VERSION_OPT,
125: "print the version information and exit"),
126: new CLOptionDescriptor("propfile",
127: CLOptionDescriptor.ARGUMENT_REQUIRED, PROPFILE_OPT,
128: "the jmeter property file to use"),
129: new CLOptionDescriptor("addprop",
130: CLOptionDescriptor.ARGUMENT_REQUIRED
131: | CLOptionDescriptor.DUPLICATES_ALLOWED, // Bug 33920 -
132: // allow
133: // multiple
134: // props
135: PROPFILE2_OPT, "additional property file(s)"),
136: new CLOptionDescriptor("testfile",
137: CLOptionDescriptor.ARGUMENT_REQUIRED, TESTFILE_OPT,
138: "the jmeter test(.jmx) file to run"),
139: new CLOptionDescriptor("logfile",
140: CLOptionDescriptor.ARGUMENT_REQUIRED, LOGFILE_OPT,
141: "the file to log samples to"),
142: new CLOptionDescriptor("nongui",
143: CLOptionDescriptor.ARGUMENT_DISALLOWED, NONGUI_OPT,
144: "run JMeter in nongui mode"),
145: new CLOptionDescriptor("server",
146: CLOptionDescriptor.ARGUMENT_DISALLOWED, SERVER_OPT,
147: "run the JMeter server"),
148: new CLOptionDescriptor("jmeterproperty",
149: CLOptionDescriptor.DUPLICATES_ALLOWED
150: | CLOptionDescriptor.ARGUMENTS_REQUIRED_2,
151: JMETER_PROPERTY,
152: "Define additional JMeter properties"),
153: new CLOptionDescriptor("systemproperty",
154: CLOptionDescriptor.DUPLICATES_ALLOWED
155: | CLOptionDescriptor.ARGUMENTS_REQUIRED_2,
156: SYSTEM_PROPERTY,
157: "Define additional JMeter properties"),
158: new CLOptionDescriptor("loglevel",
159: CLOptionDescriptor.DUPLICATES_ALLOWED
160: | CLOptionDescriptor.ARGUMENTS_REQUIRED_2,
161: LOGLEVEL,
162: "Define loglevel: [category=]level e.g. jorphan=INFO or "
163: + "jmeter.util=DEBUG"),
164: new CLOptionDescriptor("runremote",
165: CLOptionDescriptor.ARGUMENT_DISALLOWED, REMOTE_OPT,
166: "Start remote servers from non-gui mode"),
167: new CLOptionDescriptor("homedir",
168: CLOptionDescriptor.ARGUMENT_REQUIRED,
169: JMETER_HOME_OPT, "the jmeter home directory to use"), };
170:
171: transient boolean testEnded = false;
172:
173: /**
174: *
175: */
176: public JMeterReport() {
177: super ();
178: }
179:
180: /**
181: * The default icons for the report GUI.
182: */
183: private static final String[][] DEFAULT_ICONS = {
184: { AbstractVisualizer.class.getName(),
185: "org/apache/jmeter/images/meter.png" },
186: { AbstractConfigGui.class.getName(),
187: "org/apache/jmeter/images/testtubes.png" },
188: { HTMLReportWriterGui.class.getName(),
189: "org/apache/jmeter/images/new/pencil.png" },
190: { ReportPageGui.class.getName(),
191: "org/apache/jmeter/images/new/scroll.png" },
192: { ReportGui.class.getName(),
193: "org/apache/jmeter/images/new/book.png" } };
194:
195: /* (non-Javadoc)
196: * @see org.apache.jmeter.plugin.JMeterPlugin#getIconMappings()
197: */
198: public String[][] getIconMappings() {
199: String iconProp = JMeterUtils.getPropDefault("jmeter.icons",
200: "org/apache/jmeter/images/icon.properties");
201: Properties p = JMeterUtils.loadProperties(iconProp);
202: if (p == null) {
203: log.info(iconProp + " not found - using default icon set");
204: return DEFAULT_ICONS;
205: }
206: log.info("Loaded icon properties from " + iconProp);
207: String[][] iconlist = new String[p.size()][3];
208: Enumeration pe = p.keys();
209: int i = 0;
210: while (pe.hasMoreElements()) {
211: String key = (String) pe.nextElement();
212: String icons[] = JOrphanUtils
213: .split(p.getProperty(key), " ");
214: iconlist[i][0] = key;
215: iconlist[i][1] = icons[0];
216: if (icons.length > 1)
217: iconlist[i][2] = icons[1];
218: i++;
219: }
220: return iconlist;
221: }
222:
223: /* (non-Javadoc)
224: * @see org.apache.jmeter.plugin.JMeterPlugin#getResourceBundles()
225: */
226: public String[][] getResourceBundles() {
227: return new String[0][];
228: }
229:
230: public void startNonGui(CLOption testFile, CLOption logFile) {
231: System.setProperty("JMeter.NonGui", "true");
232: JMeterReport driver = new JMeterReport();
233: driver.parent = this ;
234: PluginManager.install(this , false);
235: }
236:
237: public void startGui(CLOption testFile) {
238: PluginManager.install(this , true);
239: ReportTreeModel treeModel = new ReportTreeModel();
240: ReportTreeListener treeLis = new ReportTreeListener(treeModel);
241: treeLis.setActionHandler(ReportActionRouter.getInstance());
242: ReportGuiPackage.getInstance(treeLis, treeModel);
243: org.apache.jmeter.gui.ReportMainFrame main = new org.apache.jmeter.gui.ReportMainFrame(
244: ReportActionRouter.getInstance(), treeModel, treeLis);
245: ComponentUtil.centerComponentInWindow(main, 80);
246: main.show();
247:
248: ReportActionRouter.getInstance().actionPerformed(
249: new ActionEvent(main, 1, ReportCheckDirty.ADD_ALL));
250: if (testFile != null) {
251: FileInputStream reader = null;
252: try {
253: File f = new File(testFile.getArgument());
254: log.info("Loading file: " + f);
255: reader = new FileInputStream(f);
256: HashTree tree = SaveService.loadTree(reader);
257:
258: ReportGuiPackage.getInstance().setReportPlanFile(
259: f.getAbsolutePath());
260:
261: new ReportLoad().insertLoadedTree(1, tree);
262: } catch (Exception e) {
263: log.error("Failure loading test file", e);
264: JMeterUtils.reportErrorToUser(e.toString());
265: } finally {
266: JOrphanUtils.closeQuietly(reader);
267: }
268: }
269: }
270:
271: private void run(String testFile, String logFile,
272: boolean remoteStart) {
273: FileInputStream reader = null;
274: try {
275: File f = new File(testFile);
276: if (!f.exists() || !f.isFile()) {
277: System.out.println("Could not open " + testFile);
278: return;
279: }
280: FileServer.getFileServer().setBasedir(f.getAbsolutePath());
281:
282: reader = new FileInputStream(f);
283: log.info("Loading file: " + f);
284:
285: HashTree tree = SaveService.loadTree(reader);
286:
287: // Remove the disabled items
288: // For GUI runs this is done in Start.java
289: convertSubTree(tree);
290:
291: if (logFile != null) {
292: ResultCollector logger = new ResultCollector();
293: logger.setFilename(logFile);
294: tree.add(tree.getArray()[0], logger);
295: }
296: String summariserName = JMeterUtils.getPropDefault(
297: "summariser.name", "");//$NON-NLS-1$
298: if (summariserName.length() > 0) {
299: log
300: .info("Creating summariser <" + summariserName
301: + ">");
302: System.out.println("Creating summariser <"
303: + summariserName + ">");
304: Summariser summer = new Summariser(summariserName);
305: tree.add(tree.getArray()[0], summer);
306: }
307: tree.add(tree.getArray()[0], new ListenToTest(parent));
308: System.out.println("Created the tree successfully");
309: /**
310: JMeterEngine engine = null;
311: if (!remoteStart) {
312: engine = new StandardJMeterEngine();
313: engine.configure(tree);
314: System.out.println("Starting the test");
315: engine.runTest();
316: } else {
317: String remote_hosts_string = JMeterUtils.getPropDefault(
318: "remote_hosts", "127.0.0.1");
319: java.util.StringTokenizer st = new java.util.StringTokenizer(
320: remote_hosts_string, ",");
321: List engines = new LinkedList();
322: while (st.hasMoreElements()) {
323: String el = (String) st.nextElement();
324: System.out.println("Configuring remote engine for " + el);
325: // engines.add(doRemoteInit(el.trim(), tree));
326: }
327: System.out.println("Starting remote engines");
328: Iterator iter = engines.iterator();
329: while (iter.hasNext()) {
330: engine = (JMeterEngine) iter.next();
331: engine.runTest();
332: }
333: System.out.println("Remote engines have been started");
334: }
335: **/
336: } catch (Exception e) {
337: System.out.println("Error in NonGUIDriver " + e.toString());
338: log.error("", e);
339: } finally {
340: JOrphanUtils.closeQuietly(reader);
341: }
342: }
343:
344: /**
345: *
346: * @param args
347: */
348: public void start(String[] args) {
349: CLArgsParser parser = new CLArgsParser(args, options);
350: if (null != parser.getErrorString()) {
351: System.err.println("Error: " + parser.getErrorString());
352: System.out.println("Usage");
353: System.out.println(CLUtil.describeOptions(options)
354: .toString());
355: return;
356: }
357: try {
358: initializeProperties(parser);
359: log.info("Version " + JMeterUtils.getJMeterVersion());
360: log.info("java.version="
361: + System.getProperty("java.version"));
362: log.info(JMeterUtils.getJMeterCopyright());
363: if (parser.getArgumentById(VERSION_OPT) != null) {
364: System.out.println(JMeterUtils.getJMeterCopyright());
365: System.out.println("Version "
366: + JMeterUtils.getJMeterVersion());
367: } else if (parser.getArgumentById(HELP_OPT) != null) {
368: System.out
369: .println(JMeterUtils
370: .getResourceFileAsText("org/apache/jmeter/help.txt"));
371: } else if (parser.getArgumentById(NONGUI_OPT) == null) {
372: startGui(parser.getArgumentById(TESTFILE_OPT));
373: } else {
374: startNonGui(parser.getArgumentById(TESTFILE_OPT),
375: parser.getArgumentById(LOGFILE_OPT));
376: }
377: } catch (Exception e) {
378: e.printStackTrace();
379: System.out.println("An error occurred: " + e.getMessage());
380: System.exit(-1);
381: }
382: }
383:
384: private void initializeProperties(CLArgsParser parser) {
385: if (parser.getArgumentById(PROPFILE_OPT) != null) {
386: JMeterUtils.getProperties(parser.getArgumentById(
387: PROPFILE_OPT).getArgument());
388: } else {
389: JMeterUtils.getProperties(NewDriver.getJMeterDir()
390: + File.separator + "bin" + File.separator
391: + "jmeter.properties");
392: }
393:
394: // Bug 33845 - allow direct override of Home dir
395: if (parser.getArgumentById(JMETER_HOME_OPT) == null) {
396: JMeterUtils.setJMeterHome(NewDriver.getJMeterDir());
397: } else {
398: JMeterUtils.setJMeterHome(parser.getArgumentById(
399: JMETER_HOME_OPT).getArgument());
400: }
401:
402: // Process command line property definitions (can occur multiple times)
403:
404: Properties jmeterProps = JMeterUtils.getJMeterProperties();
405: List clOptions = parser.getArguments();
406: int size = clOptions.size();
407:
408: for (int i = 0; i < size; i++) {
409: CLOption option = (CLOption) clOptions.get(i);
410: String name = option.getArgument(0);
411: String value = option.getArgument(1);
412:
413: switch (option.getDescriptor().getId()) {
414: case PROPFILE2_OPT: // Bug 33920 - allow multiple props
415: File f = new File(name);
416: try {
417: jmeterProps.load(new FileInputStream(f));
418: } catch (FileNotFoundException e) {
419: log.warn("Can't find additional property file: "
420: + name, e);
421: } catch (IOException e) {
422: log.warn("Error loading additional property file: "
423: + name, e);
424: }
425: break;
426: case SYSTEM_PROPERTY:
427: if (value.length() > 0) { // Set it
428: log.info("Setting System property: " + name + "="
429: + value);
430: System.getProperties().setProperty(name, value);
431: } else { // Reset it
432: log.warn("Removing System property: " + name);
433: System.getProperties().remove(name);
434: }
435: break;
436: case JMETER_PROPERTY:
437: if (value.length() > 0) { // Set it
438: log.info("Setting JMeter property: " + name + "="
439: + value);
440: jmeterProps.setProperty(name, value);
441: } else { // Reset it
442: log.warn("Removing JMeter property: " + name);
443: jmeterProps.remove(name);
444: }
445: break;
446: case LOGLEVEL:
447: if (value.length() > 0) { // Set category
448: log.info("LogLevel: " + name + "=" + value);
449: LoggingManager.setPriority(value, name);
450: } else { // Set root level
451: log.warn("LogLevel: " + name);
452: LoggingManager.setPriority(name);
453: }
454: break;
455: }
456: }
457:
458: }
459:
460: /**
461: * Code copied from AbstractAction.java and modified to suit TestElements
462: *
463: * @param tree
464: */
465: private void convertSubTree(HashTree tree) {// TODO check build dependencies
466: Iterator iter = new LinkedList(tree.list()).iterator();
467: while (iter.hasNext()) {
468: TestElement item = (TestElement) iter.next();
469: if (item.isEnabled()) {
470: // This is done for GUI runs in JMeterTreeModel.addSubTree()
471: if (item instanceof ReportPlan) {
472: ReportPlan tp = (ReportPlan) item;
473: }
474: // TODO handle ReplaceableControllers
475: // if (item instanceof ReplaceableController)
476: // {
477: // System.out.println("Replaceable "+item.getClass().getName());
478: // HashTree subTree = tree.getTree(item);
479: //
480: // if (subTree != null)
481: // {
482: // ReplaceableController rc =
483: // (ReplaceableController) item;//.createTestElement();
484: // rc.replace(subTree);
485: // convertSubTree(subTree);
486: // tree.replace(item, rc.getReplacement());
487: // }
488: // }
489: // else
490: {
491: // System.out.println("NonReplaceable
492: // "+item.getClass().getName());
493: convertSubTree(tree.getTree(item));
494: // TestElement testElement = item.createTestElement();
495: // tree.replace(item, testElement);
496: }
497: } else {
498: // System.out.println("Disabled "+item.getClass().getName());
499: tree.remove(item);
500: }
501: }
502: }
503:
504: /**
505: * Listen to test and exit program after test completes, after a 5 second
506: * delay to give listeners a chance to close out their files.
507: */
508: private static class ListenToTest implements TestListener,
509: Runnable, Remoteable {
510: int started = 0;
511:
512: private JMeterReport _parent;
513:
514: private ListenToTest(JMeterReport parent) {
515: _parent = parent;
516: }
517:
518: public synchronized void testEnded(String host) {
519: started--;
520: log.info("Remote host " + host + " finished");
521: if (started == 0) {
522: testEnded();
523: }
524: }
525:
526: public void testEnded() {
527: Thread stopSoon = new Thread(this );
528: stopSoon.start();
529: }
530:
531: public synchronized void testStarted(String host) {
532: started++;
533: log.info("Started remote host: " + host);
534: }
535:
536: public void testStarted() {
537: log.info(JMeterUtils.getResString("running_test"));
538: }
539:
540: /**
541: * This is a hack to allow listeners a chance to close their files. Must
542: * implement a queue for sample responses tied to the engine, and the
543: * engine won't deliver testEnded signal till all sample responses have
544: * been delivered. Should also improve performance of remote JMeter
545: * testing.
546: */
547: public void run() {
548: System.out.println("Tidying up ...");
549: try {
550: Thread.sleep(5000);
551: } catch (InterruptedException e) {
552: // ignored
553: }
554: System.out.println("... end of run");
555: _parent.testEnded = true;
556: }
557:
558: /**
559: * @see TestListener#testIterationStart(LoopIterationEvent)
560: */
561: public void testIterationStart(LoopIterationEvent event) {
562: // ignored
563: }
564: }
565:
566: }
|