// $Header: /home/cvs/jakarta-jmeter/src/core/org/apache/jmeter/reporters/Summariser.java,v 1.5 2004/03/30 18:08:09 sebb Exp $
/*
* Copyright 2003-2004 The Apache Software Foundation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package org.apache.jmeter.reporters;
import java.io.Serializable;
import java.text.DecimalFormat;
import java.util.Hashtable;
import org.apache.jmeter.engine.event.LoopIterationEvent;
import org.apache.jmeter.samplers.Clearable;
import org.apache.jmeter.samplers.SampleEvent;
import org.apache.jmeter.samplers.SampleListener;
import org.apache.jmeter.samplers.SampleResult;
import org.apache.jmeter.testelement.AbstractTestElement;
import org.apache.jmeter.testelement.TestListener;
import org.apache.jmeter.util.JMeterUtils;
import org.apache.jmeter.visualizers.RunningSample;
import org.apache.jorphan.logging.LoggingManager;
import org.apache.jorphan.util.JOrphanUtils;
import org.apache.log.Logger;
/**
* Generate a summary of the test run so far to the log file and/or
* standard output. Both running and differential totals are shown.
* Output is generated every n seconds (default 3 minutes) on the appropriate
* time boundary, so that multiple test runs on the same time will be
* synchronised.
*
* This is mainly intended for batch (non-GUI) runs
*
* @version $Revision: 1.5 $ Last updated: $Date: 2004/03/30 18:08:09 $
*/
public class Summariser
extends AbstractTestElement
implements Serializable,
SampleListener,
TestListener,
Clearable
{
private static final Logger log = LoggingManager.getLoggerForClass();
/** interval between summaries (in seconds) default 3 minutes*/
private static final long INTERVAL =
JMeterUtils.getPropDefault("summariser.interval",3*60); //$NON-NLS-1$
/** Write messages to log file ?*/
private static final boolean TOLOG =
JMeterUtils.getPropDefault("summariser.log",true); //$NON-NLS-1$
/** Write messages to System.out ?*/
private static final boolean TOOUT =
JMeterUtils.getPropDefault("summariser.out",true); //$NON-NLS-1$
/**
* Summariser elements are cloned for each thread in each group;
* this Map is used to allow them to share the same statistics.
* The key is the Summariser name, so all Summarisers with the same name
* will use the same accumulators.
*/
private static Hashtable accumulators = new Hashtable();
/*
* Constructor is initially called once for each occurrence in the test plan
* For GUI, several more instances are created
* Then clear is called at start of test
* Called several times during test startup
* The name will not necessarily have been set at this point.
*/
public Summariser(){
super();
//log.debug(Thread.currentThread().getName());
//System.out.println(">> "+me+" "+this.getName()+" "+Thread.currentThread().getName());
}
/*
* Constructor for use during startup
* (intended for non-GUI use)
* @param name of summariser
*/
public Summariser(String name){
this();
setName(name);
}
/*
* This is called once for each occurrence in the test plan, before the start of the test.
* The super.clear() method clears the name (and all other properties),
* so it is called last.
*/
public void clear()
{
//System.out.println("-- "+me+this.getName()+" "+Thread.currentThread().getName());
myName = this.getName();
// Hashtable is synchronised, but there could be more than one Summariser
// with the same name, so we need to synch.
synchronized(accumulators){
Totals tots = (Totals) accumulators.get(myName);
if (tots != null){// This can be null (before first sample)
tots.clear();
} else {
//System.out.println("Creating totals for "+myName);
tots = new Totals();
accumulators.put(myName,tots);
}
}
super.clear();
}
/**
* Contains the items needed to collect stats for a summariser
*
* @version $revision$ Last updated: $date$
*/
private static class Totals{
/** Time of last summary (to prevent double reporting) */
private long last = 0;// set to -1 by TestEnded to prevent double reporting
private RunningSample delta = new RunningSample("DELTA",0);
private RunningSample total = new RunningSample("TOTAL",0);
private void clear(){
delta.clear();
total.clear();
last = 0;
}
/**
* Add the delta values to the total values and clear the delta
*/
private synchronized void moveDelta(){
total.addSample(delta);
delta.clear();
}
}
/**
* Cached copy of Totals for this instance
* These do not need to be synchronised, as they are not shared
* between threads
*/
transient private Totals myTotals = null;
transient private String myName;
/**
* Ensure that a report is not skipped if we are slightly late in checking
* the time.
*/
private static final int INTERVAL_WINDOW = 5; // in seconds
/**
* Accumulates the sample in two SampleResult objects
* - one for running totals, and the other for deltas
*
* @see org.apache.jmeter.samplers.SampleListener#sampleOccurred(org.apache.jmeter.samplers.SampleEvent)
*/
public void sampleOccurred(SampleEvent e) {
SampleResult s = e.getResult();
//System.out.println("SO "+me+this.getName()+" "+Thread.currentThread().getName()
//+" "+s.getSampleLabel());
if (myName == null) myName = getName();
if (myTotals == null) myTotals = (Totals) accumulators.get(myName);
if (s != null)
{
myTotals.delta.addSample(s);
}
long now = System.currentTimeMillis()/1000;// in seconds
RunningSample myDelta=null;
RunningSample myTotal=null;
boolean reportNow = false;
/* Have we reached the reporting boundary?
* Need to allow for a margin of error, otherwise can miss the slot
* Also need to check we've not hit the window already
*/
synchronized(myTotals){
if ((now > myTotals.last + INTERVAL_WINDOW) && (now % INTERVAL <= INTERVAL_WINDOW))
{
reportNow=true;
myDelta = new RunningSample(myTotals.delta);// copy the data to minimise ...
myTotals.moveDelta();
myTotal = new RunningSample(myTotals.total);// ... the synch time
myTotals.last = now;
}
}
if (reportNow){
String str;
str = format(myDelta,"+");
if (TOLOG) log.info(str);
if (TOOUT) System.out.println(str);
if (myTotal.getNumSamples() != myDelta.getNumSamples()) {// Only if we have updated them
str = format(myTotal,"=");
if (TOLOG) log.info(str);
if (TOOUT) System.out.println(str);
}
}
}
private static StringBuffer longToSb(StringBuffer sb,long l, int len){
sb.setLength(0);
sb.append(l);
return JOrphanUtils.rightAlign(sb,len);
}
private static DecimalFormat dfDouble = new DecimalFormat("#0.0");
private static StringBuffer doubleToSb(StringBuffer sb,double d, int len, int frac){
sb.setLength(0);
dfDouble.setMinimumFractionDigits(frac);
dfDouble.setMaximumFractionDigits(frac);
sb.append(dfDouble.format(d));
return JOrphanUtils.rightAlign(sb,len);
}
/**
* @param myTotal
* @param string
* @return
*/
private String format(RunningSample s, String type)
{
StringBuffer tmp = new StringBuffer(20); // for intermediate use
StringBuffer sb = new StringBuffer(100); // output line buffer
sb.append(myName);
sb.append(" ");
sb.append(type);
sb.append(" ");
sb.append(longToSb(tmp,s.getNumSamples(),5));
sb.append(" in ");
sb.append(longToSb(tmp,s.getElapsed()/1000,5));
sb.append("s = ");
sb.append(doubleToSb(tmp,s.getRate(),6,1));
sb.append("/s Avg: ");
sb.append(longToSb(tmp,s.getAverage(),5));
sb.append(" Min: ");
sb.append(longToSb(tmp,s.getMin(),5));
sb.append(" Max: ");
sb.append(longToSb(tmp,s.getMax(),5));
sb.append(" Err: ");
sb.append(longToSb(tmp,s.getErrorCount(),5));
sb.append(" (");
sb.append(doubleToSb(tmp,s.getErrorPercentage(),3,1));
sb.append("%)");
return sb.toString();
}
/* (non-Javadoc)
* @see org.apache.jmeter.samplers.SampleListener#sampleStarted(org.apache.jmeter.samplers.SampleEvent)
*/
public void sampleStarted(SampleEvent e)
{
// not used
}
/* (non-Javadoc)
* @see org.apache.jmeter.samplers.SampleListener#sampleStopped(org.apache.jmeter.samplers.SampleEvent)
*/
public void sampleStopped(SampleEvent e) {
// not used
}
/* (non-Javadoc)
* @see org.apache.jmeter.testelement.TestListener#testStarted()
*/
public void testStarted()
{
// not used
}
/* (non-Javadoc)
* @see org.apache.jmeter.testelement.TestListener#testEnded()
*/
public void testEnded()
{
testEnded("local");
}
/* (non-Javadoc)
* @see org.apache.jmeter.testelement.TestListener#testStarted(java.lang.String)
*/
public void testStarted(String host)
{
// not used
}
/* (non-Javadoc)
* Can be called more than once with the same name, so need to synch.
* However, there is no need to create copies, to shorten the synch zone,
* as timing is not critical at the end of the test.
*
* @see org.apache.jmeter.testelement.TestListener#testEnded(java.lang.String)
*/
public void testEnded(String host)
{
//System.out.println("TE "+me+this.getName()+" "+Thread.currentThread().getName());
synchronized(accumulators){
Totals t = (Totals) accumulators.get(myName);
if (t.last != -1){
String str;
if (t.total.getNumSamples() != 0){//Only print delta if different from total
str = format(t.delta,"+");
if (TOLOG) log.info(str);
if (TOOUT) System.out.println(str);
}
t.moveDelta();
str = format(t.total,"=");
if (TOLOG) log.info(str);
if (TOOUT) System.out.println(str);
t.last = -1;
}
}
}
/* (non-Javadoc)
* @see org.apache.jmeter.testelement.TestListener#testIterationStart(org.apache.jmeter.engine.event.LoopIterationEvent)
*/
public void testIterationStart(LoopIterationEvent event)
{
// not used
}
}