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.Serializable;
   22   import java.text.DecimalFormat;
   23   import java.util.Hashtable;
   24   import java.util.Map;
   25   
   26   import org.apache.jmeter.engine.event.LoopIterationEvent;
   27   import org.apache.jmeter.engine.util.NoThreadClone;
   28   import org.apache.jmeter.samplers.Remoteable;
   29   import org.apache.jmeter.samplers.SampleEvent;
   30   import org.apache.jmeter.samplers.SampleListener;
   31   import org.apache.jmeter.samplers.SampleResult;
   32   import org.apache.jmeter.testelement.AbstractTestElement;
   33   import org.apache.jmeter.testelement.TestListener;
   34   import org.apache.jmeter.util.JMeterUtils;
   35   import org.apache.jmeter.visualizers.RunningSample;
   36   import org.apache.jorphan.logging.LoggingManager;
   37   import org.apache.jorphan.util.JOrphanUtils;
   38   import org.apache.log.Logger;
   39   
   40   /**
   41    * Generate a summary of the test run so far to the log file and/or standard
   42    * output. Both running and differential totals are shown. Output is generated
   43    * every n seconds (default 3 minutes) on the appropriate time boundary, so that
   44    * multiple test runs on the same time will be synchronised.
   45    *
   46    * This is mainly intended for batch (non-GUI) runs
   47    *
   48    * Note that the RunningSample start and end times relate to the samples,
   49    * not the reporting interval.
   50    *
   51    * Since the first sample in a delta is likely to have started in the previous reporting interval,
   52    * this means that the delta interval is likely to be longer than the reporting interval.
   53    *
   54    * Also, the sum of the delta intervals will be larger than the overall elapsed time.
   55    * 
   56    * Data is accumulated according to the test element name.
   57    *
   58    */
   59   public class Summariser extends AbstractTestElement 
   60       implements Serializable, SampleListener, TestListener, NoThreadClone, Remoteable {
   61   
   62       /*
   63        * N.B. NoThreadClone is used to ensure that the testStarted() methods will share the same
   64        * instance as the sampleOccured() methods, so the testStarted() method can fetch the
   65        * Totals accumulator object for the samples to be stored in. 
   66        */
   67   
   68       private static final long serialVersionUID = 233L;
   69   
   70       private static final Logger log = LoggingManager.getLoggerForClass();
   71   
   72       /** interval between summaries (in seconds) default 3 minutes */
   73       private static final long INTERVAL = JMeterUtils.getPropDefault("summariser.interval", 3 * 60); //$NON-NLS-1$
   74   
   75       /** Write messages to log file ? */
   76       private static final boolean TOLOG = JMeterUtils.getPropDefault("summariser.log", true); //$NON-NLS-1$
   77   
   78       /** Write messages to System.out ? */
   79       private static final boolean TOOUT = JMeterUtils.getPropDefault("summariser.out", true); //$NON-NLS-1$
   80   
   81       /*
   82        * Ensure that a report is not skipped if we are slightly late in checking
   83        * the time.
   84        */
   85       private static final int INTERVAL_WINDOW = 5; // in seconds
   86   
   87       /*
   88        * This map allows summarisers with the same name to contribute to the same totals.
   89        */
   90       //@GuardedBy("accumulators")
   91       private static final Hashtable accumulators = new Hashtable();
   92       
   93       //@GuardedBy("accumulators")
   94       private static int instanceCount; // number of active tests
   95   
   96       /*
   97        * Cached copy of Totals for this instance.
   98        * The variables do not need to be synchronised,
   99        * as they are not shared between threads
  100        * However the contents do need to be synchronized.
  101        */
  102       //@GuardedBy("myTotals")
  103       private transient Totals myTotals = null;
  104   
  105       // Name of the accumulator. Set up by testStarted().
  106       private transient String myName;
  107   
  108       /*
  109        * Constructor is initially called once for each occurrence in the test plan.
  110        * For GUI, several more instances are created.
  111        * Then clear is called at start of test.
  112        * Called several times during test startup.
  113        * The name will not necessarily have been set at this point.
  114        */
  115       public Summariser() {
  116           super();
  117           synchronized (accumulators) {
  118               accumulators.clear();
  119               instanceCount=0;
  120           }
  121       }
  122   
  123       /**
  124        * Constructor for use during startup (intended for non-GUI use)
  125        *
  126        * @param name of summariser
  127        */
  128       public Summariser(String name) {
  129           this();
  130           setName(name);
  131       }
  132   
  133       /*
  134        * Contains the items needed to collect stats for a summariser
  135        *
  136        */
  137       private static class Totals {
  138   
  139           /** Time of last summary (to prevent double reporting) */
  140           private long last = 0;
  141   
  142           private final RunningSample delta = new RunningSample("DELTA",0);
  143   
  144           private final RunningSample total = new RunningSample("TOTAL",0);
  145   
  146           /**
  147            * Add the delta values to the total values and clear the delta
  148            */
  149           private void moveDelta() {
  150               total.addSample(delta);
  151               delta.clear();
  152           }
  153       }
  154   
  155       /**
  156        * Accumulates the sample in two SampleResult objects - one for running
  157        * totals, and the other for deltas.
  158        *
  159        * @see org.apache.jmeter.samplers.SampleListener#sampleOccurred(org.apache.jmeter.samplers.SampleEvent)
  160        */
  161       public void sampleOccurred(SampleEvent e) {
  162           SampleResult s = e.getResult();
  163   
  164           long now = System.currentTimeMillis() / 1000;// in seconds
  165   
  166           RunningSample myDelta = null;
  167           RunningSample myTotal = null;
  168           boolean reportNow = false;
  169   
  170           /*
  171            * Have we reached the reporting boundary?
  172            * Need to allow for a margin of error, otherwise can miss the slot.
  173            * Also need to check we've not hit the window already
  174            */
  175           synchronized (myTotals) {
  176               if (s != null) {
  177                   myTotals.delta.addSample(s);
  178               }
  179   
  180               if ((now > myTotals.last + INTERVAL_WINDOW) && (now % INTERVAL <= INTERVAL_WINDOW)) {
  181                   reportNow = true;
  182   
  183                   // copy the data to minimise the synch time
  184                   myDelta = new RunningSample(myTotals.delta);
  185                   myTotals.moveDelta();
  186                   myTotal = new RunningSample(myTotals.total);
  187   
  188                   myTotals.last = now; // stop double-reporting
  189               }
  190           }
  191           if (reportNow) {
  192               String str;
  193               str = format(myName, myDelta, "+");
  194               if (TOLOG) {
  195                   log.info(str);
  196               }
  197               if (TOOUT) {
  198                   System.out.println(str);
  199               }
  200   
  201               // Only if we have updated them
  202               if (myTotal != null && myDelta != null &&myTotal.getNumSamples() != myDelta.getNumSamples()) {
  203                   str = format(myName, myTotal, "=");
  204                   if (TOLOG) {
  205                       log.info(str);
  206                   }
  207                   if (TOOUT) {
  208                       System.out.println(str);
  209                   }
  210               }
  211           }
  212       }
  213   
  214       private static StringBuffer longToSb(StringBuffer sb, long l, int len) {
  215           sb.setLength(0);
  216           sb.append(l);
  217           return JOrphanUtils.rightAlign(sb, len);
  218       }
  219   
  220       private static final DecimalFormat dfDouble = new DecimalFormat("#0.0"); // $NON-NLS-1$
  221   
  222       private static StringBuffer doubleToSb(StringBuffer sb, double d, int len, int frac) {
  223           sb.setLength(0);
  224           dfDouble.setMinimumFractionDigits(frac);
  225           dfDouble.setMaximumFractionDigits(frac);
  226           sb.append(dfDouble.format(d));
  227           return JOrphanUtils.rightAlign(sb, len);
  228       }
  229   
  230       /**
  231        * @param myTotal
  232        * @param string
  233        * @return
  234        */
  235       private String format(String name, RunningSample s, String type) {
  236           StringBuffer tmp = new StringBuffer(20); // for intermediate use
  237           StringBuffer sb = new StringBuffer(100); // output line buffer
  238           sb.append(name);
  239           sb.append(" ");
  240           sb.append(type);
  241           sb.append(" ");
  242           sb.append(longToSb(tmp, s.getNumSamples(), 5));
  243           sb.append(" in ");
  244           long elapsed = s.getElapsed();
  245           sb.append(doubleToSb(tmp, elapsed / 1000.0, 5, 1));
  246           sb.append("s = ");
  247           if (elapsed > 0) {
  248               sb.append(doubleToSb(tmp, s.getRate(), 6, 1));
  249           } else {
  250               sb.append("******");// Rate is effectively infinite
  251           }
  252           sb.append("/s Avg: ");
  253           sb.append(longToSb(tmp, s.getAverage(), 5));
  254           sb.append(" Min: ");
  255           sb.append(longToSb(tmp, s.getMin(), 5));
  256           sb.append(" Max: ");
  257           sb.append(longToSb(tmp, s.getMax(), 5));
  258           sb.append(" Err: ");
  259           sb.append(longToSb(tmp, s.getErrorCount(), 5));
  260           sb.append(" (");
  261           sb.append(s.getErrorPercentageString());
  262           sb.append(")");
  263           return sb.toString();
  264       }
  265   
  266       /*
  267        * (non-Javadoc)
  268        *
  269        * @see org.apache.jmeter.samplers.SampleListener#sampleStarted(org.apache.jmeter.samplers.SampleEvent)
  270        */
  271       public void sampleStarted(SampleEvent e) {
  272           // not used
  273       }
  274   
  275       /*
  276        * (non-Javadoc)
  277        *
  278        * @see org.apache.jmeter.samplers.SampleListener#sampleStopped(org.apache.jmeter.samplers.SampleEvent)
  279        */
  280       public void sampleStopped(SampleEvent e) {
  281           // not used
  282       }
  283   
  284       /*
  285        * The testStarted/testEnded methods are called at the start and end of a test.
  286        * 
  287        * However, when a test is run on multiple nodes, there is no guarantee that all the
  288        * testStarted() methods will be called before all the threadStart() or sampleOccurred()
  289        * methods for other threads - nor that testEnded() will only be called after all
  290        * sampleOccurred() calls. The ordering is only guaranteed within a single test.
  291        * 
  292        */
  293       
  294       
  295       /*
  296        * (non-Javadoc)
  297        *
  298        * @see org.apache.jmeter.testelement.TestListener#testStarted()
  299        */
  300       public void testStarted() {
  301           testStarted("local");
  302       }
  303   
  304       /*
  305        * (non-Javadoc)
  306        *
  307        * @see org.apache.jmeter.testelement.TestListener#testEnded()
  308        */
  309       public void testEnded() {
  310           testEnded("local");
  311       }
  312   
  313       /*
  314        * (non-Javadoc)
  315        *
  316        * Called once for each Summariser in the test plan.
  317        * There may be more than one summariser with the same name,
  318        * however they will all be called before the test proper starts.
  319        * 
  320        * However, note that this applies to a single test only.
  321        * When running in client-server mode, testStarted() may be 
  322        * invoked after sampleOccurred().
  323        *
  324        * @see org.apache.jmeter.testelement.TestListener#testStarted(java.lang.String)
  325        */
  326       public void testStarted(String host) {
  327           synchronized (accumulators) {
  328               myName = getName();
  329               myTotals = (Totals) accumulators.get(myName);
  330               if (myTotals == null){
  331                   myTotals = new Totals();
  332                   accumulators.put(myName, myTotals);
  333               }
  334               instanceCount++;
  335           }
  336       }
  337   
  338       /*
  339        * (non-Javadoc)
  340        * Called from a different thread as testStarted() but using the same instance.
  341        * So synch is needed to fetch the accumulator, and the myName field will already be set up.
  342        * @see org.apache.jmeter.testelement.TestListener#testEnded(java.lang.String)
  343        */
  344       public void testEnded(String host) {
  345           Object[] totals = null;
  346           synchronized (accumulators) {
  347               instanceCount--;
  348               if (instanceCount <= 0){
  349                   totals = accumulators.entrySet().toArray();                
  350               }
  351           }
  352           if (totals == null) {// We're not done yet
  353               return;
  354           }
  355           for (int i=0; i<totals.length; i++) {
  356               Map.Entry me = (Map.Entry)totals[i];
  357               String str;
  358               String name = (String) me.getKey();
  359               Totals total = (Totals) me.getValue();
  360               // Only print final delta if there were some samples in the delta
  361               // and there has been at least one sample reported previously
  362               if (total.delta.getNumSamples() > 0 && total.total.getNumSamples() >  0) {
  363                   str = format(name, total.delta, "+");
  364                   if (TOLOG) {
  365                       log.info(str);
  366                   }
  367                   if (TOOUT) {
  368                       System.out.println(str);
  369                   }
  370               }
  371               total.moveDelta();
  372               str = format(name, total.total, "=");
  373               if (TOLOG) {
  374                   log.info(str);
  375               }
  376               if (TOOUT) {
  377                   System.out.println(str);
  378               }
  379           }
  380       }
  381   
  382       /*
  383        * (non-Javadoc)
  384        *
  385        * @see org.apache.jmeter.testelement.TestListener#testIterationStart(org.apache.jmeter.engine.event.LoopIterationEvent)
  386        */
  387       public void testIterationStart(LoopIterationEvent event) {
  388           // not used
  389       }
  390   }

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