Home » jakarta-jmeter-2.3.4_src » org.apache.jmeter.reporters » [javadoc | source]

    1   /*
    2    * Licensed to the Apache Software Foundation (ASF) under one or more
    3    * contributor license agreements.  See the NOTICE file distributed with
    4    * this work for additional information regarding copyright ownership.
    5    * The ASF licenses this file to You under the Apache License, Version 2.0
    6    * (the "License"); you may not use this file except in compliance with
    7    * the License.  You may obtain a copy of the License at
    8    *
    9    *   http://www.apache.org/licenses/LICENSE-2.0
   10    *
   11    * Unless required by applicable law or agreed to in writing, software
   12    * distributed under the License is distributed on an "AS IS" BASIS,
   13    * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   14    * See the License for the specific language governing permissions and
   15    * limitations under the License.
   16    *
   17    */
   18   
   19   package org.apache.jmeter.reporters;
   20   
   21   import java.io.BufferedInputStream;
   22   import java.io.BufferedOutputStream;
   23   import java.io.BufferedReader;
   24   import java.io.File;
   25   import java.io.FileInputStream;
   26   import java.io.FileNotFoundException;
   27   import java.io.FileOutputStream;
   28   import java.io.FileReader;
   29   import java.io.IOException;
   30   import java.io.OutputStreamWriter;
   31   import java.io.PrintWriter;
   32   import java.io.RandomAccessFile;
   33   import java.io.Serializable;
   34   import java.util.HashMap;
   35   import java.util.Iterator;
   36   import java.util.Map;
   37   
   38   import org.apache.avalon.framework.configuration.DefaultConfigurationSerializer;
   39   import org.apache.jmeter.engine.event.LoopIterationEvent;
   40   import org.apache.jmeter.engine.util.NoThreadClone;
   41   import org.apache.jmeter.gui.GuiPackage;
   42   import org.apache.jmeter.samplers.Clearable;
   43   import org.apache.jmeter.samplers.Remoteable;
   44   import org.apache.jmeter.samplers.SampleEvent;
   45   import org.apache.jmeter.samplers.SampleListener;
   46   import org.apache.jmeter.samplers.SampleResult;
   47   import org.apache.jmeter.samplers.SampleSaveConfiguration;
   48   import org.apache.jmeter.save.CSVSaveService;
   49   import org.apache.jmeter.save.OldSaveService;
   50   import org.apache.jmeter.save.SaveService;
   51   import org.apache.jmeter.testelement.TestElement;
   52   import org.apache.jmeter.testelement.TestListener;
   53   import org.apache.jmeter.testelement.property.BooleanProperty;
   54   import org.apache.jmeter.testelement.property.ObjectProperty;
   55   import org.apache.jmeter.visualizers.Visualizer;
   56   import org.apache.jorphan.logging.LoggingManager;
   57   import org.apache.jorphan.util.JMeterError;
   58   import org.apache.jorphan.util.JOrphanUtils;
   59   import org.apache.log.Logger;
   60   
   61   /**
   62    * This class handles all saving of samples.
   63    * The class must be thread-safe because it is shared between threads (NoThreadClone).
   64    */
   65   public class ResultCollector extends AbstractListenerElement implements SampleListener, Clearable, Serializable,
   66           TestListener, Remoteable, NoThreadClone {
   67   
   68       private static final Logger log = LoggingManager.getLoggerForClass();
   69   
   70       private static final long serialVersionUID = 233L;
   71   
   72       // This string is used to identify local test runs, so must not be a valid host name
   73       private static final String TEST_IS_LOCAL = "*local*"; // $NON-NLS-1$
   74   
   75       private static final String TESTRESULTS_START = "<testResults>"; // $NON-NLS-1$
   76   
   77       private static final String TESTRESULTS_START_V1_1_PREVER = "<testResults version=\"";  // $NON-NLS-1$
   78   
   79       private static final String TESTRESULTS_START_V1_1_POSTVER="\">"; // $NON-NLS-1$
   80   
   81       private static final String TESTRESULTS_END = "</testResults>"; // $NON-NLS-1$
   82   
   83       private static final String XML_HEADER = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"; // $NON-NLS-1$
   84   
   85       private static final int MIN_XML_FILE_LEN = XML_HEADER.length() + TESTRESULTS_START.length()
   86               + TESTRESULTS_END.length();
   87   
   88       public static final String FILENAME = "filename"; // $NON-NLS-1$
   89   
   90       private static final String SAVE_CONFIG = "saveConfig"; // $NON-NLS-1$
   91   
   92       private static final String ERROR_LOGGING = "ResultCollector.error_logging"; // $NON-NLS-1$
   93   
   94       private static final String SUCCESS_ONLY_LOGGING = "ResultCollector.success_only_logging"; // $NON-NLS-1$
   95       
   96       // Static variables
   97   
   98       // Lock used to guard static mutable variables
   99       private static final Object LOCK = new Object();
  100       
  101       //@GuardedBy("LOCK")
  102       private static final Map files = new HashMap(); // key=filename, entry=FileEntry
  103   
  104       /*
  105        * Keep track of the file writer and the configuration,
  106        * as the instance used to close them is not the same as the instance that creates
  107        * them. This means one cannot use the saved PrintWriter or use getSaveConfig()
  108        */
  109       private static class FileEntry{
  110           final PrintWriter pw;
  111           final SampleSaveConfiguration config;
  112           FileEntry(PrintWriter _pw, SampleSaveConfiguration _config){
  113               pw =_pw;
  114               config = _config;
  115           }
  116       }
  117   
  118       /**
  119        * The instance count is used to keep track of whether any tests are currently running.
  120        * It's not possible to use the constructor or threadStarted etc as tests may overlap
  121        * e.g. a remote test may be started,
  122        * and then a local test started whilst the remote test is still running. 
  123        */
  124       //@GuardedBy("LOCK")
  125       private static int instanceCount; // Keep track of how many instances are active
  126   
  127       // Instance variables
  128       
  129       private transient volatile DefaultConfigurationSerializer serializer;
  130   
  131       private transient volatile PrintWriter out;
  132   
  133       private volatile boolean inTest = false;
  134   
  135       private volatile boolean isStats = false;
  136   
  137       /**
  138        * No-arg constructor.
  139        */
  140       public ResultCollector() {
  141           setErrorLogging(false);
  142           setSuccessOnlyLogging(false);
  143           setProperty(new ObjectProperty(SAVE_CONFIG, new SampleSaveConfiguration()));
  144       }
  145   
  146       // Ensure that the sample save config is not shared between copied nodes
  147       // N.B. clone only seems to be used for client-server tests
  148       public Object clone(){
  149           ResultCollector clone = (ResultCollector) super.clone();
  150           clone.setSaveConfig((SampleSaveConfiguration)clone.getSaveConfig().clone());
  151           return clone;
  152       }
  153   
  154       private void setFilenameProperty(String f) {
  155           setProperty(FILENAME, f);
  156       }
  157   
  158       public String getFilename() {
  159           return getPropertyAsString(FILENAME);
  160       }
  161   
  162       public boolean isErrorLogging() {
  163           return getPropertyAsBoolean(ERROR_LOGGING);
  164       }
  165   
  166       public void setErrorLogging(boolean errorLogging) {
  167           setProperty(new BooleanProperty(ERROR_LOGGING, errorLogging));
  168       }
  169   
  170       public void setSuccessOnlyLogging(boolean value) {
  171           if (value) {
  172               setProperty(new BooleanProperty(SUCCESS_ONLY_LOGGING, true));
  173           } else {
  174               removeProperty(SUCCESS_ONLY_LOGGING);
  175           }
  176       }
  177   
  178       public boolean isSuccessOnlyLogging() {
  179           return getPropertyAsBoolean(SUCCESS_ONLY_LOGGING,false);
  180       }
  181   
  182       /**
  183        * Decides whether or not to a sample is wanted based on:<br/>
  184        * - errorOnly<br/>
  185        * - successOnly<br/>
  186        * - sample success<br/>
  187        * Should only be called for single samples.
  188        *
  189        * @param success is sample successful
  190        * @return whether to log/display the sample
  191        */
  192       public boolean isSampleWanted(boolean success){
  193           boolean errorOnly = isErrorLogging();
  194           boolean successOnly = isSuccessOnlyLogging();
  195           return isSampleWanted(success, errorOnly, successOnly);
  196       }
  197   
  198       /**
  199        * Decides whether or not to a sample is wanted based on: <br/>
  200        * - errorOnly <br/>
  201        * - successOnly <br/>
  202        * - sample success <br/>
  203        * This version is intended to be called by code that loops over many samples;
  204        * it is cheaper than fetching the settings each time.
  205        * @param success status of sample
  206        * @param errorOnly if errors only wanted
  207        * @param successOnly if success only wanted
  208        * @return whether to log/display the sample
  209        */
  210       public static boolean isSampleWanted(boolean success, boolean errorOnly,
  211               boolean successOnly) {
  212           return (!errorOnly && !successOnly) ||
  213                  (success && successOnly) ||
  214                  (!success && errorOnly);
  215           // successOnly and errorOnly cannot both be set
  216       }
  217       /**
  218        * Sets the filename attribute of the ResultCollector object.
  219        *
  220        * @param f
  221        *            the new filename value
  222        */
  223       public void setFilename(String f) {
  224           if (inTest) {
  225               return;
  226           }
  227           setFilenameProperty(f);
  228       }
  229   
  230       public void testEnded(String host) {
  231           synchronized(LOCK){
  232               instanceCount--;
  233               if (instanceCount <= 0) {
  234                   finalizeFileOutput();
  235                   inTest = false;
  236               }            
  237           }
  238       }
  239   
  240       public synchronized void testStarted(String host) {
  241           synchronized(LOCK){
  242               instanceCount++;
  243               try {
  244                   initializeFileOutput();
  245                   if (getVisualizer() != null) {
  246                       this.isStats = getVisualizer().isStats();
  247                   }
  248               } catch (Exception e) {
  249                   log.error("", e);
  250               }
  251           }
  252           inTest = true;
  253       }
  254   
  255       public void testEnded() {
  256           testEnded(TEST_IS_LOCAL);
  257       }
  258   
  259       public void testStarted() {
  260           testStarted(TEST_IS_LOCAL);
  261       }
  262   
  263       /**
  264        * Loads an existing sample data (JTL) file.
  265        * This can be one of:
  266        * - XStream format
  267        * - Avalon format
  268        * - CSV format
  269        *
  270        */
  271       public void loadExistingFile() {
  272           final Visualizer visualizer = getVisualizer();
  273           if (visualizer == null) {
  274               return; // No point reading the file if there's no visualiser
  275           }
  276           boolean parsedOK = false;
  277           String filename = getFilename();
  278           File file = new File(filename);
  279           if (file.exists()) {
  280               BufferedReader dataReader = null;
  281               BufferedInputStream bufferedInputStream = null;
  282               try {
  283                   dataReader = new BufferedReader(new FileReader(file));
  284                   // Get the first line, and see if it is XML
  285                   String line = dataReader.readLine();
  286                   dataReader.close();
  287                   dataReader = null;
  288                   if (line == null) {
  289                       log.warn(filename+" is empty");
  290                   } else {
  291                       if (!line.startsWith("<?xml ")){// No, must be CSV //$NON-NLS-1$
  292                           CSVSaveService.processSamples(filename, visualizer, this);
  293                           parsedOK = true;
  294                       } else { // We are processing XML
  295                           try { // Assume XStream
  296                               bufferedInputStream = new BufferedInputStream(new FileInputStream(file));
  297                               SaveService.loadTestResults(bufferedInputStream, 
  298                                       new ResultCollectorHelper(this, visualizer));
  299                               parsedOK = true;
  300                           } catch (Exception e) {
  301                               log.info("Failed to load "+filename+" using XStream, trying old XML format. Error was: "+e);
  302                               try {
  303                                   OldSaveService.processSamples(filename, visualizer, this);
  304                                   parsedOK = true;
  305                               } catch (Exception e1) {
  306                                   log.warn("Error parsing Avalon XML. " + e1.getLocalizedMessage());
  307                               }
  308                           }
  309                       }
  310                   }
  311               } catch (IOException e) {
  312                   log.warn("Problem reading JTL file: "+file);
  313               } catch (JMeterError e){
  314                   log.warn("Problem reading JTL file: "+file);
  315               } catch (RuntimeException e){ // e.g. NullPointerException
  316                   log.warn("Problem reading JTL file: "+file,e);
  317               } catch (OutOfMemoryError e) {
  318                   log.warn("Problem reading JTL file: "+file,e);
  319               } finally {
  320                   JOrphanUtils.closeQuietly(dataReader);
  321                   JOrphanUtils.closeQuietly(bufferedInputStream);
  322                   if (!parsedOK) {
  323                       GuiPackage.showErrorMessage(
  324                                   "Error loading results file - see log file",
  325                                   "Result file loader");
  326                   }
  327               }
  328           } else {
  329               GuiPackage.showErrorMessage(
  330                       "Error loading results file - could not open file",
  331                       "Result file loader");
  332           }
  333       }
  334   
  335       private static void writeFileStart(PrintWriter writer, SampleSaveConfiguration saveConfig) {
  336           if (saveConfig.saveAsXml()) {
  337               writer.print(XML_HEADER);
  338               // Write the EOL separately so we generate LF line ends on Unix and Windows
  339               writer.print("\n"); // $NON-NLS-1$
  340               String pi=saveConfig.getXmlPi();
  341               if (pi.length() > 0) {
  342                   writer.println(pi);
  343               }
  344               // Can't do it as a static initialisation, because SaveService
  345               // is being constructed when this is called
  346               writer.print(TESTRESULTS_START_V1_1_PREVER);
  347               writer.print(SaveService.getVERSION());
  348               writer.print(TESTRESULTS_START_V1_1_POSTVER);
  349               // Write the EOL separately so we generate LF line ends on Unix and Windows
  350               writer.print("\n"); // $NON-NLS-1$
  351           } else if (saveConfig.saveFieldNames()) {
  352               writer.println(CSVSaveService.printableFieldNamesToString(saveConfig));
  353           }
  354       }
  355   
  356       private static void writeFileEnd(PrintWriter pw, SampleSaveConfiguration saveConfig) {
  357           if (saveConfig.saveAsXml()) {
  358               pw.print("\n"); // $NON-NLS-1$
  359               pw.print(TESTRESULTS_END);
  360               pw.print("\n");// Added in version 1.1 // $NON-NLS-1$
  361           }
  362       }
  363   
  364       private static PrintWriter getFileWriter(String filename, SampleSaveConfiguration saveConfig)
  365               throws IOException {
  366           if (filename == null || filename.length() == 0) {
  367               return null;
  368           }
  369           FileEntry fe = (FileEntry) files.get(filename);
  370           PrintWriter writer = null;
  371           boolean trimmed = true;
  372   
  373           if (fe == null) {
  374               if (saveConfig.saveAsXml()) {
  375                   trimmed = trimLastLine(filename);
  376               } else {
  377                   trimmed = new File(filename).exists();
  378               }
  379               // Find the name of the directory containing the file
  380               // and create it - if there is one
  381               File pdir = new File(filename).getParentFile();
  382               if (pdir != null) {
  383                   pdir.mkdirs();// returns false if directory already exists, so need to check again
  384                   if (!pdir.exists()){
  385                       log.warn("Error creating directories for "+pdir.toString());
  386                   }
  387               }
  388               writer = new PrintWriter(new OutputStreamWriter(new BufferedOutputStream(new FileOutputStream(filename,
  389                       trimmed)), SaveService.getFileEncoding("UTF-8")), true); // $NON-NLS-1$
  390               log.debug("Opened file: "+filename);
  391               files.put(filename, new FileEntry(writer, saveConfig));
  392           } else {
  393               writer = fe.pw;
  394           }
  395           if (!trimmed) {
  396               writeFileStart(writer, saveConfig);
  397           }
  398           return writer;
  399       }
  400   
  401       // returns false if the file did not contain the terminator
  402       private static boolean trimLastLine(String filename) {
  403           RandomAccessFile raf = null;
  404           try {
  405               raf = new RandomAccessFile(filename, "rw"); // $NON-NLS-1$
  406               long len = raf.length();
  407               if (len < MIN_XML_FILE_LEN) {
  408                   return false;
  409               }
  410               raf.seek(len - TESTRESULTS_END.length() - 10);// TODO: may not work on all OSes?
  411               String line;
  412               long pos = raf.getFilePointer();
  413               int end = 0;
  414               while ((line = raf.readLine()) != null)// reads to end of line OR end of file
  415               {
  416                   end = line.indexOf(TESTRESULTS_END);
  417                   if (end >= 0) // found the string
  418                   {
  419                       break;
  420                   }
  421                   pos = raf.getFilePointer();
  422               }
  423               if (line == null) {
  424                   log.warn("Unexpected EOF trying to find XML end marker in " + filename);
  425                   raf.close();
  426                   return false;
  427               }
  428               raf.setLength(pos + end);// Truncate the file
  429               raf.close();
  430               raf = null;
  431           } catch (FileNotFoundException e) {
  432               return false;
  433           } catch (IOException e) {
  434               log.warn("Error trying to find XML terminator " + e.toString());
  435               return false;
  436           } finally {
  437               try {
  438                   if (raf != null) {
  439                       raf.close();
  440                   }
  441               } catch (IOException e1) {
  442                   log.info("Could not close " + filename + " " + e1.getLocalizedMessage());
  443               }
  444           }
  445           return true;
  446       }
  447   
  448       public void sampleStarted(SampleEvent e) {
  449       }
  450   
  451       public void sampleStopped(SampleEvent e) {
  452       }
  453   
  454       /**
  455        * When a test result is received, display it and save it.
  456        *
  457        * @param event
  458        *            the sample event that was received
  459        */
  460       public void sampleOccurred(SampleEvent event) {
  461           SampleResult result = event.getResult();
  462   
  463           if (isSampleWanted(result.isSuccessful())) {
  464               sendToVisualizer(result);
  465               if (out != null && !isResultMarked(result) && !this.isStats) {
  466                   SampleSaveConfiguration config = getSaveConfig();
  467                   result.setSaveConfig(config);
  468                   try {
  469                       if (config.saveAsXml()) {
  470                           if (SaveService.isSaveTestLogFormat20()) {
  471                               if (serializer == null) {
  472                                   serializer = new DefaultConfigurationSerializer();
  473                               }
  474                               out.write(OldSaveService.getSerializedSampleResult(result, serializer, config));
  475                           } else { // !LogFormat20
  476                               SaveService.saveSampleResult(event, out);
  477                           }
  478                       } else { // !saveAsXml
  479                           String savee = CSVSaveService.resultToDelimitedString(event);
  480                           out.println(savee);
  481                       }
  482                   } catch (Exception err) {
  483                       log.error("Error trying to record a sample", err); // should throw exception back to caller
  484                   }
  485               }
  486           }
  487       }
  488   
  489       protected final void sendToVisualizer(SampleResult r) {
  490           if (getVisualizer() != null) {
  491               getVisualizer().add(r);
  492           }
  493       }
  494   
  495       /**
  496        * recordStats is used to save statistics generated by visualizers
  497        *
  498        * @param e
  499        * @throws Exception
  500        */
  501       // Used by: MonitorHealthVisualizer.add(SampleResult res)
  502       public void recordStats(TestElement e) throws Exception {
  503           if (out != null) {
  504               SaveService.saveTestElement(e, out);
  505           }
  506       }
  507   
  508       /**
  509        * Checks if the sample result is marked or not, and marks it
  510        * @param res - the sample result to check
  511        * @return <code>true</code> if the result was marked
  512        */
  513       private boolean isResultMarked(SampleResult res) {
  514           String filename = getFilename();
  515           return res.markFile(filename);
  516       }
  517   
  518       private void initializeFileOutput() throws IOException {
  519   
  520           String filename = getFilename();
  521           if (filename != null) {
  522               if (out == null) {
  523                   try {
  524                       out = getFileWriter(filename, getSaveConfig());
  525                   } catch (FileNotFoundException e) {
  526                       out = null;
  527                   }
  528               }
  529           }
  530       }
  531   
  532       private void finalizeFileOutput() {
  533           Iterator it = files.entrySet().iterator();
  534           while(it.hasNext()){
  535               Map.Entry me = (Map.Entry) it.next();
  536               log.debug("Closing: "+me.getKey());
  537               FileEntry fe = (FileEntry) me.getValue();
  538               writeFileEnd(fe.pw, fe.config);
  539               fe.pw.close();
  540               if (fe.pw.checkError()){
  541                   log.warn("Problem detected during use of "+me.getKey());
  542               }
  543           }
  544           files.clear();
  545       }
  546   
  547       /*
  548        * (non-Javadoc)
  549        *
  550        * @see TestListener#testIterationStart(LoopIterationEvent)
  551        */
  552       public void testIterationStart(LoopIterationEvent event) {
  553       }
  554   
  555       /**
  556        * @return Returns the saveConfig.
  557        */
  558       public SampleSaveConfiguration getSaveConfig() {
  559           try {
  560               return (SampleSaveConfiguration) getProperty(SAVE_CONFIG).getObjectValue();
  561           } catch (ClassCastException e) {
  562               setSaveConfig(new SampleSaveConfiguration());
  563               return getSaveConfig();
  564           }
  565       }
  566   
  567       /**
  568        * @param saveConfig
  569        *            The saveConfig to set.
  570        */
  571       public void setSaveConfig(SampleSaveConfiguration saveConfig) {
  572           getProperty(SAVE_CONFIG).setObjectValue(saveConfig);
  573       }
  574   
  575       // This is required so that
  576       // @see org.apache.jmeter.gui.tree.JMeterTreeModel.getNodesOfType()
  577       // can find the Clearable nodes - the userObject has to implement the interface.
  578       public void clearData() {
  579       }
  580   
  581   }

Home » jakarta-jmeter-2.3.4_src » org.apache.jmeter.reporters » [javadoc | source]