/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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.visualizers; import java.text.DecimalFormat; import org.apache.jmeter.samplers.SampleResult; /** * <p> * Running sample data container. Just instantiate a new instance of this * class, and then call {@link #addSample(SampleResult)} a few times, and pull * the stats out with whatever methods you prefer. * </p> * <p> * Please note that this class is not thread-safe. * The calling class is responsible for ensuring thread safety if required. * Versions prior to 2.3.2 appeared to be thread-safe but weren't as label and index were not final. * Also the caller needs to synchronize access in order to ensure that variables are consistent. * </p> * */ public class RunningSample { private final DecimalFormat rateFormatter = new DecimalFormat("#.0"); // $NON-NLS-1$ private final DecimalFormat errorFormatter = new DecimalFormat("#0.00%"); // $NON-NLS-1$ private long counter; private long runningSum; private long max; private long min; private long errorCount; private long firstTime; private long lastTime; private final String label; private final int index; /** * Use this constructor to create the initial instance * * @param label the label for this component * @param index the index of this component */ public RunningSample(String label, int index) { this.label = label; this.index = index; init(); } /** * Copy constructor to create a duplicate of existing instance (without the * disadvantages of clone() * * @param src existing RunningSample to be copied */ public RunningSample(RunningSample src) { this.counter = src.counter; this.errorCount = src.errorCount; this.firstTime = src.firstTime; this.index = src.index; this.label = src.label; this.lastTime = src.lastTime; this.max = src.max; this.min = src.min; this.runningSum = src.runningSum; } private void init() { // WARNING: called from ctor so must not be overridden (i.e. must be private or final) counter = 0L; runningSum = 0L; max = Long.MIN_VALUE; min = Long.MAX_VALUE; errorCount = 0L; firstTime = Long.MAX_VALUE; lastTime = 0L; } /** * Clear the counters (useful for differential stats) * */ public void clear() { init(); } /** * Get the elapsed time for the samples * * @return how long the samples took */ public long getElapsed() { if (lastTime == 0) { return 0;// No samples collected ... } return lastTime - firstTime; } /** * Returns the throughput associated to this sampler in requests per second. * May be slightly skewed because it takes the timestamps of the first and * last samples as the total time passed, and the test may actually have * started before that start time and ended after that end time. * * @return throughput associated with this sampler per second */ public double getRate() { if (counter == 0) { return 0.0; // Better behaviour when howLong=0 or lastTime=0 } long howLongRunning = lastTime - firstTime; if (howLongRunning == 0) { return Double.MAX_VALUE; } return (double) counter / howLongRunning * 1000.0; } /** * Returns the throughput associated to this sampler in requests per min. * May be slightly skewed because it takes the timestamps of the first and * last samples as the total time passed, and the test may actually have * started before that start time and ended after that end time. * * @return throughput associated with this sampler per minute */ public double getRatePerMin() { if (counter == 0) { return 0.0; // Better behaviour when howLong=0 or lastTime=0 } long howLongRunning = lastTime - firstTime; if (howLongRunning == 0) { return Double.MAX_VALUE; } return (double) counter / howLongRunning * 60000.0; } /** * Returns a String that represents the throughput associated for this * sampler, in units appropriate to its dimension: * <p> * The number is represented in requests/second or requests/minute or * requests/hour. * <p> * Examples: "34.2/sec" "0.1/sec" "43.0/hour" "15.9/min" * * @return a String representation of the rate the samples are being taken * at. */ public String getRateString() { double rate = getRate(); if (Double.compare(rate, Double.MAX_VALUE) == 0) { return "N/A"; } String unit = "sec"; if (rate < 1.0) { rate *= 60.0; unit = "min"; } if (rate < 1.0) { rate *= 60.0; unit = "hour"; } return rateFormatter.format(rate) + "/" + unit; } /** * @return the label for this component */ public String getLabel() { return label; } /** * @return the index of this component */ public int getIndex() { return index; } /** * Records a sample. * * @param res sample to record */ public void addSample(SampleResult res) { long aTimeInMillis = res.getTime(); counter+=res.getSampleCount(); errorCount += res.getErrorCount(); long startTime = res.getStartTime(); long endTime = res.getEndTime(); if (firstTime > startTime) { // this is our first sample, set the start time to current timestamp firstTime = startTime; } // Always update the end time if (lastTime < endTime) { lastTime = endTime; } runningSum += aTimeInMillis; if (aTimeInMillis > max) { max = aTimeInMillis; } if (aTimeInMillis < min) { min = aTimeInMillis; } } /** * Adds another RunningSample to this one. * Does not check if it has the same label and index. * * @param rs sample to add */ public void addSample(RunningSample rs) { this.counter += rs.counter; this.errorCount += rs.errorCount; this.runningSum += rs.runningSum; if (this.firstTime > rs.firstTime) { this.firstTime = rs.firstTime; } if (this.lastTime < rs.lastTime) { this.lastTime = rs.lastTime; } if (this.max < rs.max) { this.max = rs.max; } if (this.min > rs.min) { this.min = rs.min; } } /** * Returns the time in milliseconds of the quickest sample. * * @return the time in milliseconds of the quickest sample. */ public long getMin() { long rval = 0; if (min != Long.MAX_VALUE) { rval = min; } return rval; } /** * Returns the time in milliseconds of the slowest sample. * * @return the time in milliseconds of the slowest sample. */ public long getMax() { long rval = 0; if (max != Long.MIN_VALUE) { rval = max; } return rval; } /** * Returns the average time in milliseconds that samples ran in. * * @return the average time in milliseconds that samples ran in. */ public long getAverage() { if (counter == 0) { return 0; } return runningSum / counter; } /** * Returns the number of samples that have been recorded by this instance of * the RunningSample class. * * @return the number of samples that have been recorded by this instance of * the RunningSample class. */ public long getNumSamples() { return counter; } /** * Returns the raw double value of the percentage of samples with errors * that were recorded. (Between 0.0 and 1.0) If you want a nicer return * format, see {@link #getErrorPercentageString()}. * * @return the raw double value of the percentage of samples with errors * that were recorded. */ public double getErrorPercentage() { double rval = 0.0; if (counter == 0) { return rval; } rval = (double) errorCount / (double) counter; return rval; } /** * Returns a String which represents the percentage of sample errors that * have occurred. ("0.00%" through "100.00%") * * @return a String which represents the percentage of sample errors that * have occurred. */ public String getErrorPercentageString() { double myErrorPercentage = this.getErrorPercentage(); return errorFormatter.format(myErrorPercentage); } /** * For debugging purposes, mainly. */ @Override public String toString() { StringBuilder mySB = new StringBuilder(); mySB.append("Samples: " + this.getNumSamples() + " "); mySB.append("Avg: " + this.getAverage() + " "); mySB.append("Min: " + this.getMin() + " "); mySB.append("Max: " + this.getMax() + " "); mySB.append("Error Rate: " + this.getErrorPercentageString() + " "); mySB.append("Sample Rate: " + this.getRateString()); return mySB.toString(); } /** * @return errorCount */ public long getErrorCount() { return errorCount; } }