001: /*
002: * Copyright (c) 2001-2007, Jean Tessier
003: * All rights reserved.
004: *
005: * Redistribution and use in source and binary forms, with or without
006: * modification, are permitted provided that the following conditions
007: * are met:
008: *
009: * * Redistributions of source code must retain the above copyright
010: * notice, this list of conditions and the following disclaimer.
011: *
012: * * Redistributions in binary form must reproduce the above copyright
013: * notice, this list of conditions and the following disclaimer in the
014: * documentation and/or other materials provided with the distribution.
015: *
016: * * Neither the name of Jean Tessier nor the names of his contributors
017: * may be used to endorse or promote products derived from this software
018: * without specific prior written permission.
019: *
020: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
021: * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
022: * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
023: * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR
024: * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
025: * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
026: * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
027: * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
028: * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
029: * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
030: * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
031: */
032:
033: package com.jeantessier.metrics;
034:
035: import java.io.*;
036: import java.text.*;
037: import java.util.*;
038:
039: import org.apache.log4j.*;
040:
041: /**
042: * <p>Computes the statistical properties of a given measurement across the
043: * submetrics of the measurement's context. Given a measurement name, it
044: * explores the tree of metrics rooted at the context and finds the numerical
045: * value of these named measurements in the tree. For these measurements, it
046: * computes:</p>
047: *
048: * <ul>
049: * <li>minimum value</li>
050: * <li>median value</li>
051: * <li>average value</li>
052: * <li>standard deviation</li>
053: * <li>maximum value</li>
054: * <li>sum</li>
055: * <li>number of data points</li>
056: * </ul>
057: *
058: * <p>This is the syntax for initializing this type of measurement:</p>
059: *
060: * <pre>
061: * <init>
062: * monitored measurement name [DISPOSE_x]
063: * [DISPOSE_x]
064: * </init>
065: * </pre>
066: *
067: * <p>If the monitored measurement is itself a statistical measurement, the
068: * disposition indicates how to deal with it, which of its values to use in
069: * this measurement's calculation. The default is {@link #DISPOSE_IGNORE},
070: * meaning it should skip statistical measurements look in further submetrics
071: * for raw values.</p>
072: *
073: * <p>The second disposition tells which internal value to return in calls to
074: * its {@link #compute} method, which will be used by clients that do not
075: * distinguish between StatisticalMeasurent and other Measurements. The
076: * default is {@link #DISPOSE_AVERAGE}.</p>
077: */
078: public class StatisticalMeasurement extends MeasurementBase {
079: private static final NumberFormat valueFormat = new DecimalFormat(
080: "#.##");
081:
082: /** Ignore StatisticalMeasurements and drill down to the next level */
083: public static final int DISPOSE_IGNORE = 0;
084:
085: /** Use Minimum() value on StatisticalMeasurements */
086: public static final int DISPOSE_MINIMUM = 1;
087:
088: /** Use Median() value on StatisticalMeasurements */
089: public static final int DISPOSE_MEDIAN = 2;
090:
091: /** Use Average() value on StatisticalMeasurements */
092: public static final int DISPOSE_AVERAGE = 3;
093:
094: /** Use StandardDeviation() value on StatisticalMeasurements */
095: public static final int DISPOSE_STANDARD_DEVIATION = 4;
096:
097: /** Use Maximum() value on StatisticalMeasurements */
098: public static final int DISPOSE_MAXIMUM = 5;
099:
100: /** Use Sum() value on StatisticalMeasurements */
101: public static final int DISPOSE_SUM = 6;
102:
103: /** Use NbDataPoints() value on StatisticalMeasurements */
104: public static final int DISPOSE_NB_DATA_POINTS = 7;
105:
106: public static String getDisposeLabel(int dispose) {
107: String result = "";
108:
109: switch (dispose) {
110: case DISPOSE_MINIMUM:
111: result = "minimum";
112: break;
113:
114: case DISPOSE_MEDIAN:
115: result = "median";
116: break;
117:
118: case DISPOSE_AVERAGE:
119: result = "average";
120: break;
121:
122: case DISPOSE_STANDARD_DEVIATION:
123: result = "standard deviation";
124: break;
125:
126: case DISPOSE_MAXIMUM:
127: result = "maximum";
128: break;
129:
130: case DISPOSE_SUM:
131: result = "sum";
132: break;
133:
134: case DISPOSE_NB_DATA_POINTS:
135: result = "number of data points";
136: break;
137:
138: case DISPOSE_IGNORE:
139: default:
140: break;
141: }
142:
143: return result;
144: }
145:
146: public static String getDisposeAbbreviation(int dispose) {
147: String result = "";
148:
149: switch (dispose) {
150: case DISPOSE_MINIMUM:
151: result = "min";
152: break;
153:
154: case DISPOSE_MEDIAN:
155: result = "med";
156: break;
157:
158: case DISPOSE_AVERAGE:
159: result = "avg";
160: break;
161:
162: case DISPOSE_STANDARD_DEVIATION:
163: result = "sdv";
164: break;
165:
166: case DISPOSE_MAXIMUM:
167: result = "max";
168: break;
169:
170: case DISPOSE_SUM:
171: result = "sum";
172: break;
173:
174: case DISPOSE_NB_DATA_POINTS:
175: result = "nb";
176: break;
177:
178: case DISPOSE_IGNORE:
179: default:
180: break;
181: }
182:
183: return result;
184: }
185:
186: private String monitoredMeasurement;
187: private int dispose;
188: private int selfDispose;
189:
190: private List<Double> data = new LinkedList<Double>();
191:
192: private double minimum = 0.0;
193: private double median = 0.0;
194: private double average = 0.0;
195: private double standardDeviation = 0.0;
196: private double maximum = 0.0;
197: private double sum = 0.0;
198: private int nbDataPoints = 0;
199:
200: private int nbSubmetrics = -1;
201:
202: public StatisticalMeasurement(MeasurementDescriptor descriptor,
203: Metrics context, String initText) {
204: super (descriptor, context, initText);
205:
206: try {
207: BufferedReader in = new BufferedReader(new StringReader(
208: initText));
209: monitoredMeasurement = in.readLine().trim();
210:
211: synchronized (perl()) {
212: if (perl().match("/(.*)\\s+(dispose_\\w+)$/i",
213: monitoredMeasurement)) {
214: monitoredMeasurement = perl().group(1);
215:
216: String disposeText = perl().group(2);
217:
218: if (disposeText.equalsIgnoreCase("DISPOSE_IGNORE")) {
219: dispose = DISPOSE_IGNORE;
220: } else if (disposeText
221: .equalsIgnoreCase("DISPOSE_MINIMUM")) {
222: dispose = DISPOSE_MINIMUM;
223: } else if (disposeText
224: .equalsIgnoreCase("DISPOSE_MEDIAN")) {
225: dispose = DISPOSE_MEDIAN;
226: } else if (disposeText
227: .equalsIgnoreCase("DISPOSE_AVERAGE")) {
228: dispose = DISPOSE_AVERAGE;
229: } else if (disposeText
230: .equalsIgnoreCase("DISPOSE_STANDARD_DEVIATION")) {
231: dispose = DISPOSE_STANDARD_DEVIATION;
232: } else if (disposeText
233: .equalsIgnoreCase("DISPOSE_MAXIMUM")) {
234: dispose = DISPOSE_MAXIMUM;
235: } else if (disposeText
236: .equalsIgnoreCase("DISPOSE_SUM")) {
237: dispose = DISPOSE_SUM;
238: } else if (disposeText
239: .equalsIgnoreCase("DISPOSE_NB_DATA_POINTS")) {
240: dispose = DISPOSE_NB_DATA_POINTS;
241: } else {
242: dispose = DISPOSE_IGNORE;
243: }
244: } else {
245: dispose = DISPOSE_IGNORE;
246: }
247: }
248:
249: String selfDisposeText = in.readLine();
250: if (selfDisposeText != null) {
251: selfDisposeText = selfDisposeText.trim();
252:
253: if (selfDisposeText.equalsIgnoreCase("DISPOSE_IGNORE")) {
254: selfDispose = DISPOSE_IGNORE;
255: } else if (selfDisposeText
256: .equalsIgnoreCase("DISPOSE_MINIMUM")) {
257: selfDispose = DISPOSE_MINIMUM;
258: } else if (selfDisposeText
259: .equalsIgnoreCase("DISPOSE_MEDIAN")) {
260: selfDispose = DISPOSE_MEDIAN;
261: } else if (selfDisposeText
262: .equalsIgnoreCase("DISPOSE_AVERAGE")) {
263: selfDispose = DISPOSE_AVERAGE;
264: } else if (selfDisposeText
265: .equalsIgnoreCase("DISPOSE_STANDARD_DEVIATION")) {
266: selfDispose = DISPOSE_STANDARD_DEVIATION;
267: } else if (selfDisposeText
268: .equalsIgnoreCase("DISPOSE_MAXIMUM")) {
269: selfDispose = DISPOSE_MAXIMUM;
270: } else if (selfDisposeText
271: .equalsIgnoreCase("DISPOSE_SUM")) {
272: selfDispose = DISPOSE_SUM;
273: } else if (selfDisposeText
274: .equalsIgnoreCase("DISPOSE_NB_DATA_POINTS")) {
275: selfDispose = DISPOSE_NB_DATA_POINTS;
276: } else {
277: selfDispose = DISPOSE_AVERAGE;
278: }
279: } else {
280: selfDispose = DISPOSE_AVERAGE;
281: }
282:
283: in.close();
284: } catch (Exception ex) {
285: Logger.getLogger(getClass()).debug(
286: "Cannot initialize with \"" + initText + "\"", ex);
287: monitoredMeasurement = null;
288: }
289: }
290:
291: public double getMinimum() {
292: collectData();
293: return minimum;
294: }
295:
296: public double getMedian() {
297: collectData();
298: return median;
299: }
300:
301: public double getAverage() {
302: collectData();
303: return average;
304: }
305:
306: /**
307: * Real standard deviation of the data set.
308: * This is NOT the estimator "s".
309: */
310: public double getStandardDeviation() {
311: collectData();
312: return standardDeviation;
313: }
314:
315: public double getMaximum() {
316: collectData();
317: return maximum;
318: }
319:
320: public double getSum() {
321: collectData();
322: return sum;
323: }
324:
325: public int getNbDataPoints() {
326: collectData();
327: return nbDataPoints;
328: }
329:
330: private void collectData() {
331: if (getContext().getSubMetrics().size() != nbSubmetrics) {
332: synchronized (this ) {
333: if (getContext().getSubMetrics().size() != nbSubmetrics) {
334: data = new LinkedList<Double>();
335: setEmpty(true);
336:
337: for (Metrics metrics : getContext().getSubMetrics()) {
338: visitMetrics(metrics);
339: }
340:
341: if (!data.isEmpty()) {
342: Collections.sort(data);
343:
344: minimum = data.get(0);
345: median = data.get(data.size() / 2);
346: maximum = data.get(data.size() - 1);
347: nbDataPoints = data.size();
348:
349: sum = 0.0;
350: for (Double number : data) {
351: sum += number;
352: }
353: } else {
354: minimum = Double.NaN;
355: median = Double.NaN;
356: maximum = Double.NaN;
357: nbDataPoints = 0;
358: sum = 0.0;
359: }
360:
361: average = sum / nbDataPoints;
362:
363: if (!data.isEmpty()) {
364: double temp = 0.0;
365:
366: for (Double number : data) {
367: temp += Math.pow(number - average, 2);
368: }
369:
370: standardDeviation = Math.sqrt(temp
371: / nbDataPoints);
372: } else {
373: standardDeviation = Double.NaN;
374: }
375:
376: nbSubmetrics = getContext().getSubMetrics().size();
377: }
378: }
379: }
380: }
381:
382: private void visitMetrics(Metrics metrics) {
383: Logger.getLogger(getClass()).debug(
384: "VisitMetrics: " + metrics.getName());
385:
386: Measurement measurement = metrics
387: .getMeasurement(monitoredMeasurement);
388:
389: Logger.getLogger(getClass()).debug(
390: "measurement for " + monitoredMeasurement + " is "
391: + measurement.getClass());
392:
393: if (measurement instanceof StatisticalMeasurement) {
394: StatisticalMeasurement stats = (StatisticalMeasurement) measurement;
395:
396: Logger.getLogger(getClass()).debug(
397: "dispose of StatisticalMeasurements is " + dispose);
398:
399: switch (dispose) {
400: case DISPOSE_MINIMUM:
401: Logger.getLogger(getClass()).debug(
402: "using Minimum(): " + stats.getMinimum());
403: data.add(stats.getMinimum());
404: break;
405:
406: case DISPOSE_MEDIAN:
407: Logger.getLogger(getClass()).debug(
408: "using Median(): " + stats.getMedian());
409: data.add(stats.getMedian());
410: break;
411:
412: case DISPOSE_AVERAGE:
413: Logger.getLogger(getClass()).debug(
414: "using Average(): " + stats.getAverage());
415: data.add(stats.getAverage());
416: break;
417:
418: case DISPOSE_STANDARD_DEVIATION:
419: Logger.getLogger(getClass()).debug(
420: "using StandardDeviation(): "
421: + stats.getStandardDeviation());
422: data.add(stats.getStandardDeviation());
423: break;
424:
425: case DISPOSE_MAXIMUM:
426: Logger.getLogger(getClass()).debug(
427: "using Maximum(): " + stats.getMaximum());
428: data.add(stats.getMaximum());
429: break;
430:
431: case DISPOSE_SUM:
432: Logger.getLogger(getClass()).debug(
433: "using Sum(): " + stats.getSum());
434: data.add(stats.getSum());
435: break;
436:
437: case DISPOSE_NB_DATA_POINTS:
438: Logger.getLogger(getClass()).debug(
439: "using NbDataPoints(): "
440: + stats.getNbDataPoints());
441: data.add((double) stats.getNbDataPoints());
442: break;
443:
444: case DISPOSE_IGNORE:
445: default:
446: Logger.getLogger(getClass()).debug(
447: "Skipping to next level ...");
448: for (Metrics subMetrics : metrics.getSubMetrics()) {
449: visitMetrics(subMetrics);
450: }
451: break;
452: }
453: } else if (measurement instanceof NullMeasurement) {
454: Logger.getLogger(getClass()).debug(
455: "Skipping to next level ...");
456: for (Metrics subMetrics : metrics.getSubMetrics()) {
457: visitMetrics(subMetrics);
458: }
459: } else {
460: Number value = measurement.getValue();
461:
462: Logger.getLogger(getClass()).debug(
463: monitoredMeasurement + " on " + metrics.getName()
464: + " is " + value);
465:
466: if (value != null) {
467: data.add(value.doubleValue());
468: }
469: }
470:
471: if (super .isEmpty()) {
472: setEmpty(measurement.isEmpty());
473: }
474: }
475:
476: public boolean isEmpty() {
477: collectData();
478:
479: return super .isEmpty();
480: }
481:
482: public void accept(MeasurementVisitor visitor) {
483: visitor.visitStatisticalMeasurement(this );
484: }
485:
486: protected double compute() {
487: double result = Double.NaN;
488:
489: switch (selfDispose) {
490: case DISPOSE_MINIMUM:
491: result = getMinimum();
492: break;
493:
494: case DISPOSE_MEDIAN:
495: result = getMedian();
496: break;
497:
498: case DISPOSE_AVERAGE:
499: result = getAverage();
500: break;
501:
502: case DISPOSE_STANDARD_DEVIATION:
503: result = getStandardDeviation();
504: break;
505:
506: case DISPOSE_MAXIMUM:
507: result = getMaximum();
508: break;
509:
510: case DISPOSE_SUM:
511: result = getSum();
512: break;
513:
514: case DISPOSE_NB_DATA_POINTS:
515: result = getNbDataPoints();
516: break;
517:
518: case DISPOSE_IGNORE:
519: default:
520: break;
521: }
522:
523: return result;
524: }
525:
526: public String toString() {
527: StringBuffer result = new StringBuffer();
528:
529: result.append("[").append(valueFormat.format(getMinimum()));
530: result.append(" ").append(valueFormat.format(getMedian()));
531: result.append("/").append(valueFormat.format(getAverage()));
532: result.append(" ").append(
533: valueFormat.format(getStandardDeviation()));
534: result.append(" ").append(valueFormat.format(getMaximum()));
535: result.append(" ").append(valueFormat.format(getSum()));
536: result.append(" (").append(
537: valueFormat.format(getNbDataPoints())).append(")]");
538:
539: return result.toString();
540: }
541: }
|