/* * Copyright (C) 2008 Digital Sundhed (SDSD) * * All source code and information supplied as part of chronos * is copyright to its contributers. * * The source code has been released under a dual license - meaning you can * use either licensed version of the library with your code. * * It is released under the Common Public License 1.0, a copy of which can * be found at the link below. * http://www.opensource.org/licenses/cpl.php * * It is released under the LGPL (GNU Lesser General Public License), either * version 2.1 of the License, or (at your option) any later version. A copy * of which can be found at the link below. * http://www.gnu.org/copyleft/lesser.html */ package org.codehaus.mojo.chronos.responsetime; import java.io.Serializable; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import org.apache.commons.math.stat.StatUtils; import org.codehaus.mojo.chronos.Utils; import org.jfree.data.time.Millisecond; import org.jfree.data.time.MovingAverage; import org.jfree.data.time.TimeSeries; import org.jfree.data.time.TimeSeriesDataItem; /** * Contains info from a jmeter jtl file. * * @author ksr@lakeside.dk */ public abstract class ResponsetimeSamples implements Serializable { private static final int PERCENTILE = 95; private static final long serialVersionUID = 4056724466498233661L; protected final List samples = new ArrayList(); private int succeeded; /** * add a (hopefully successful) sample. * * @param sample * a JMeterSample * @param sampleName */ public void add(ResponsetimeSample sample, String sampleName) { samples.add(sample); if(sample.isSuccess()) { succeeded++; } } /** * @return the number of samples */ public final int size() { return samples.size(); } /** * @return the successrate (in percentage) */ public final double getSuccessrate() { return 100 * ((double)getSucceeded()) / samples.size(); } /** * @return the number of failed samples */ public final int getFailed() { return samples.size() - getSucceeded(); } /** * @return the number of succeeded samples */ public final int getSucceeded() { return succeeded; } /** * @param responsetimedivider * TODO * @return the average responsetime of all samples */ public final double getAverage(int responsetimedivider) { return StatUtils.mean(extractResponsetimes(responsetimedivider)); } /** * @param responsetimedivider * TODO * @return the minimum responsetime of all samples */ public final double getMin(int responsetimedivider) { return StatUtils.min(extractResponsetimes(responsetimedivider)); } /** * @param responsetimedivider * TODO * @return the maximum responsetime of all samples */ public final double getMax(int responsetimedivider) { return StatUtils.max(extractResponsetimes(responsetimedivider)); } /** * @param responsetimedivider * TODO * @return the 95% fractile of all responsetimes */ public final double getPercentile95(int responsetimedivider) { return StatUtils.percentile(extractResponsetimes(responsetimedivider), PERCENTILE); }; /** * Note that the samples is ordered by timestamp, so the first one has the lowest timestamp. * * @return the first timestamp of all samples */ public final long getFirstTimestamp() { ResponsetimeSample responsetimeSample = (ResponsetimeSample)samples.get(0); return responsetimeSample.getTimestamp(); } /** * Extracts all responsetimes. * * @param responsetimedivider * @return the responsetimes as an array */ public final double[] extractResponsetimes(int responsetimedivider) { double[] responsetimes = new double[samples.size()]; int i = 0; for (Iterator it = samples.iterator(); it.hasNext();) { ResponsetimeSample sample = (ResponsetimeSample)it.next(); responsetimes[i++] = sample.getResponsetime() / responsetimedivider; } return responsetimes; } public final void appendResponsetimes(TimeSeries series, int responsetimeDivider) { long delta = getFirstTimestamp(); for (Iterator it = samples.iterator(); it.hasNext();) { ResponsetimeSample sample = (ResponsetimeSample)it.next(); Millisecond timestamp = Utils.createMS(sample.getTimestamp() - delta); double responseTime = sample.getResponsetime() / responsetimeDivider; series.addOrUpdate(timestamp, responseTime); } } public final void appendThreadCounts(TimeSeries series, long threadCountDuration) { if(samples.size() > 0) { long firstSerial = getFirstTimestamp(); Map activeThreads = new HashMap(); for (Iterator it = samples.iterator(); it.hasNext();) { ResponsetimeSample sample = (ResponsetimeSample)it.next(); int threadCount = activeThreads.size(); for (Iterator it2 = activeThreads.keySet().iterator(); it2.hasNext();) { String key = (String)it2.next(); if(sample.getTimestamp() > ((ResponsetimeSample)activeThreads.get(key)).getTimestamp() + threadCountDuration) { it2.remove(); } } activeThreads.put(sample.getThreadId(), sample); if(threadCount != activeThreads.size()) { series.addOrUpdate(Utils.createMS(sample.getTimestamp() - firstSerial), threadCount); series.addOrUpdate(Utils.createMS(sample.getTimestamp() - firstSerial + 1), activeThreads.size()); } } ResponsetimeSample sample = (ResponsetimeSample)samples.get(samples.size() - 1); series.addOrUpdate(Utils.createMS(sample.getTimestamp() - firstSerial + 1), activeThreads.size()); } } public final TimeSeries createMovingThroughput(String name, final int responsetimedivider) { TimeSeries series = new TimeSeries(name, Millisecond.class); if(samples.isEmpty()) { return series; } Collections.sort(samples, new Comparator() { public int compare(Object arg1, Object arg2) { ResponsetimeSample sample1 = (ResponsetimeSample)arg1; ResponsetimeSample sample2 = (ResponsetimeSample)arg2; long endtime1 = sample1.getTimestamp() + (long)sample1.getResponsetime() / responsetimedivider; long endtime2 = sample2.getTimestamp() + (long)sample2.getResponsetime() / responsetimedivider; return (int)(endtime1 - endtime2); } }); int periodLength = 1000; long rampUpTime = 0; int measurements = 0; final long firstAllowedTimestamp = getFirstTimestamp() + rampUpTime; long periodStart = firstAllowedTimestamp; long periodEnd = periodStart + periodLength; for (int i = 0; i < samples.size(); i++) { ResponsetimeSample sample = (ResponsetimeSample)samples.get(i); long sampleEndTime = sample.getTimestamp() + sample.getResponsetime() / responsetimedivider; if(sampleEndTime < periodStart) { continue; } if(sampleEndTime <= periodEnd) { measurements++; } else { if(measurements > 0) { series.addOrUpdate(Utils.createMS(periodEnd - firstAllowedTimestamp), measurements * responsetimedivider); } else { series.addOrUpdate(Utils.createMS(periodEnd - firstAllowedTimestamp), null); } measurements = 1; periodStart = periodEnd; periodEnd = periodStart + periodLength; } } return series; } /** * * @param responsetimedivider * TODO * @return the maximum of a moving average of the throughput */ public final double getMaxAverageThroughput(int averageduration, int responsetimedivider) { TimeSeries series = createMovingThroughput("", responsetimedivider); TimeSeries averageseries = MovingAverage.createMovingAverage(series, "", averageduration, 0); double max = 0; for (Iterator it = averageseries.getItems().iterator(); it.hasNext();) { TimeSeriesDataItem item = (TimeSeriesDataItem)it.next(); if(item.getValue() != null) { max = Math.max(max, item.getValue().doubleValue()); } } return max; } public final long getTotalTime() { ResponsetimeSample first = (ResponsetimeSample)samples.get(0); ResponsetimeSample last = (ResponsetimeSample)samples.get(samples.size() - 1); return last.getTimestamp() + last.getResponsetime() - first.getTimestamp(); } }