001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: *
017: */
018:
019: package org.apache.jmeter.visualizers;
020:
021: import java.text.DecimalFormat;
022:
023: import org.apache.jmeter.samplers.SampleResult;
024:
025: /**
026: * Aggegate sample data container. Just instantiate a new instance of this
027: * class, and then call {@link #addSample(SampleResult)} a few times, and pull
028: * the stats out with whatever methods you prefer.
029: *
030: * @author James Boutcher
031: */
032: public class RunningSample {
033:
034: private static DecimalFormat rateFormatter = new DecimalFormat(
035: "#.0");
036:
037: private static DecimalFormat errorFormatter = new DecimalFormat(
038: "#0.00%");
039:
040: // The counts all need to be volatile - or else the get() methods need to be synchronised.
041:
042: private volatile long counter;
043:
044: private volatile long runningSum;
045:
046: private volatile long max, min;
047:
048: private volatile long errorCount;
049:
050: private volatile long firstTime;
051:
052: private volatile long lastTime;
053:
054: private String label;
055:
056: private int index;
057:
058: private RunningSample() {// Don't (can't) use this...
059: }
060:
061: /**
062: * Use this constructor to create the initial instance
063: */
064: public RunningSample(String label, int index) {
065: this .label = label;
066: this .index = index;
067: init();
068: }
069:
070: /**
071: * Copy constructor to create a duplicate of existing instance (without the
072: * disadvantages of clone()
073: *
074: * @param src existing RunningSample to be copied
075: */
076: public RunningSample(RunningSample src) {
077: this .counter = src.counter;
078: this .errorCount = src.errorCount;
079: this .firstTime = src.firstTime;
080: this .index = src.index;
081: this .label = src.label;
082: this .lastTime = src.lastTime;
083: this .max = src.max;
084: this .min = src.min;
085: this .runningSum = src.runningSum;
086: }
087:
088: private void init() {
089: counter = 0L;
090: runningSum = 0L;
091: max = Long.MIN_VALUE;
092: min = Long.MAX_VALUE;
093: errorCount = 0L;
094: firstTime = Long.MAX_VALUE;
095: lastTime = 0L;
096: }
097:
098: /**
099: * Clear the counters (useful for differential stats)
100: *
101: */
102: public synchronized void clear() {
103: init();
104: }
105:
106: /**
107: * Get the elapsed time for the samples
108: *
109: * @return how long the samples took
110: */
111: public long getElapsed() {
112: if (lastTime == 0)
113: return 0;// No samples collected ...
114: return lastTime - firstTime;
115: }
116:
117: /**
118: * Returns the throughput associated to this sampler in requests per second.
119: * May be slightly skewed because it takes the timestamps of the first and
120: * last samples as the total time passed, and the test may actually have
121: * started before that start time and ended after that end time.
122: */
123: public double getRate() {
124: if (counter == 0)
125: return 0.0; // Better behaviour when howLong=0 or lastTime=0
126:
127: long howLongRunning = lastTime - firstTime;
128:
129: if (howLongRunning == 0) {
130: return Double.MAX_VALUE;
131: }
132:
133: return (double) counter / howLongRunning * 1000.0;
134: }
135:
136: /**
137: * Returns the throughput associated to this sampler in requests per min.
138: * May be slightly skewed because it takes the timestamps of the first and
139: * last samples as the total time passed, and the test may actually have
140: * started before that start time and ended after that end time.
141: */
142: public double getRatePerMin() {
143: if (counter == 0)
144: return 0.0; // Better behaviour when howLong=0 or lastTime=0
145:
146: long howLongRunning = lastTime - firstTime;
147:
148: if (howLongRunning == 0) {
149: return Double.MAX_VALUE;
150: }
151: return (double) counter / howLongRunning * 60000.0;
152: }
153:
154: /**
155: * Returns a String that represents the throughput associated for this
156: * sampler, in units appropriate to its dimension:
157: * <p>
158: * The number is represented in requests/second or requests/minute or
159: * requests/hour.
160: * <p>
161: * Examples: "34.2/sec" "0.1/sec" "43.0/hour" "15.9/min"
162: *
163: * @return a String representation of the rate the samples are being taken
164: * at.
165: */
166: public String getRateString() {
167: double rate = getRate();
168:
169: if (rate == Double.MAX_VALUE) {
170: return "N/A";
171: }
172:
173: String unit = "sec";
174:
175: if (rate < 1.0) {
176: rate *= 60.0;
177: unit = "min";
178: }
179: if (rate < 1.0) {
180: rate *= 60.0;
181: unit = "hour";
182: }
183:
184: String rval = rateFormatter.format(rate) + "/" + unit;
185:
186: return (rval);
187: }
188:
189: public String getLabel() {
190: return label;
191: }
192:
193: public int getIndex() {
194: return index;
195: }
196:
197: /**
198: * Records a sample.
199: *
200: */
201: public synchronized void addSample(SampleResult res) {
202: long aTimeInMillis = res.getTime();
203: boolean aSuccessFlag = res.isSuccessful();
204:
205: counter++;
206: long startTime = res.getStartTime();
207: long endTime = res.getEndTime();
208:
209: if (firstTime > startTime) {
210: // this is our first sample, set the start time to current timestamp
211: firstTime = startTime;
212: }
213:
214: // Always update the end time
215: if (lastTime < endTime) {
216: lastTime = endTime;
217: }
218: runningSum += aTimeInMillis;
219:
220: if (aTimeInMillis > max) {
221: max = aTimeInMillis;
222: }
223:
224: if (aTimeInMillis < min) {
225: min = aTimeInMillis;
226: }
227:
228: if (!aSuccessFlag) {
229: errorCount++;
230: }
231: }
232:
233: /**
234: * Adds another RunningSample to this one Does not check if it has the same
235: * label and index
236: */
237: public synchronized void addSample(RunningSample rs) {
238: this .counter += rs.counter;
239: this .errorCount += rs.errorCount;
240: this .runningSum += rs.runningSum;
241: if (this .firstTime > rs.firstTime)
242: this .firstTime = rs.firstTime;
243: if (this .lastTime < rs.lastTime)
244: this .lastTime = rs.lastTime;
245: if (this .max < rs.max)
246: this .max = rs.max;
247: if (this .min > rs.min)
248: this .min = rs.min;
249: }
250:
251: /**
252: * Returns the time in milliseconds of the quickest sample.
253: *
254: * @return the time in milliseconds of the quickest sample.
255: */
256: public long getMin() {
257: long rval = 0;
258:
259: if (min != Long.MAX_VALUE) {
260: rval = min;
261: }
262: return (rval);
263: }
264:
265: /**
266: * Returns the time in milliseconds of the slowest sample.
267: *
268: * @return the time in milliseconds of the slowest sample.
269: */
270: public long getMax() {
271: long rval = 0;
272:
273: if (max != Long.MIN_VALUE) {
274: rval = max;
275: }
276: return (rval);
277: }
278:
279: /**
280: * Returns the average time in milliseconds that samples ran in.
281: *
282: * @return the average time in milliseconds that samples ran in.
283: */
284: public long getAverage() {
285: if (counter == 0) {
286: return (0);
287: }
288: return (runningSum / counter);
289: }
290:
291: /**
292: * Returns the number of samples that have been recorded by this instance of
293: * the RunningSample class.
294: *
295: * @return the number of samples that have been recorded by this instance of
296: * the RunningSample class.
297: */
298: public long getNumSamples() {
299: return (counter);
300: }
301:
302: /**
303: * Returns the raw double value of the percentage of samples with errors
304: * that were recorded. (Between 0.0 and 1.0) If you want a nicer return
305: * format, see {@link #getErrorPercentageString()}.
306: *
307: * @return the raw double value of the percentage of samples with errors
308: * that were recorded.
309: */
310: public double getErrorPercentage() {
311: double rval = 0.0;
312:
313: if (counter == 0) {
314: return (rval);
315: }
316: rval = (double) errorCount / (double) counter;
317: return (rval);
318: }
319:
320: /**
321: * Returns a String which represents the percentage of sample errors that
322: * have occurred. ("0.00%" through "100.00%")
323: *
324: * @return a String which represents the percentage of sample errors that
325: * have occurred.
326: */
327: public String getErrorPercentageString() {
328: double myErrorPercentage = this .getErrorPercentage();
329:
330: return (errorFormatter.format(myErrorPercentage));
331: }
332:
333: /**
334: * For debugging purposes, mainly.
335: */
336: public String toString() {
337: StringBuffer mySB = new StringBuffer();
338:
339: mySB.append("Samples: " + this .getNumSamples() + " ");
340: mySB.append("Avg: " + this .getAverage() + " ");
341: mySB.append("Min: " + this .getMin() + " ");
342: mySB.append("Max: " + this .getMax() + " ");
343: mySB.append("Error Rate: " + this .getErrorPercentageString()
344: + " ");
345: mySB.append("Sample Rate: " + this .getRateString());
346: return (mySB.toString());
347: }
348:
349: /**
350: * @return errorCount
351: */
352: public long getErrorCount() {
353: return errorCount;
354: }
355:
356: }
|