001: /*
002: * All content copyright (c) 2003-2006 Terracotta, Inc., except as may otherwise be noted in a separate copyright notice. All rights reserved.
003: */
004: package com.tc.management.stats;
005:
006: import java.io.Serializable;
007: import java.text.MessageFormat;
008:
009: public final class AggregateInteger implements Serializable {
010:
011: /**
012: * @param {0} name
013: * @param {1} n
014: * @param {2} sum
015: * @param {3} minimum
016: * @param {4} maximum
017: * @param {5} average
018: */
019: private static final String TO_STRING_FORMAT = "<{0}(integer): [samples/{1}], [sum/{2}], [minimum/{3}], [maximum/{4}], [average/{5}]>";
020:
021: private static final class Sample {
022:
023: final int sample;
024: final long timestamp;
025:
026: Sample(final int sample) {
027: this .sample = sample;
028: timestamp = System.currentTimeMillis();
029: }
030:
031: }
032:
033: private final String name;
034:
035: // Even though it is unnecessary to make ints volatile, we say so here to indicate that readers of these variables do
036: // not necessarily need synchronization; if they are a little behind in reading values it's ok so long as they are not
037: // corrupted (hence making them volatile), only the writers need actual synchronization
038: private volatile int n;
039: private volatile int sum;
040: private volatile int minimum;
041: private volatile int maximum;
042:
043: // This used to be a variable-length linked list based on time rather than sample count, but the performance was
044: // miserable so now it's a fixed size circular buffer :(
045: private final Sample[] sampleHistory;
046: private int nextHistoryPosition;
047: private final Sample[] sampleHistorySnapshot;
048:
049: /**
050: * Creates a new aggregate integer statistic without maintaining a history of samples.
051: *
052: * @param name the name of this statistic
053: */
054: public AggregateInteger(final String name) {
055: this (name, 0);
056: }
057:
058: /**
059: * Creates a new aggregate integer statistic, maintaining a rolling history of samples for the last
060: * {@link historyLengthInSamples} samples. Sample rates are extrapolated based on how many samples are maintained.
061: */
062: public AggregateInteger(final String name,
063: final int historyLengthInSamples) {
064: this .name = name;
065: sampleHistory = historyLengthInSamples > 0 ? new Sample[historyLengthInSamples]
066: : null;
067: sampleHistorySnapshot = historyLengthInSamples > 0 ? new Sample[historyLengthInSamples]
068: : null;
069: nextHistoryPosition = 0;
070: reset();
071: }
072:
073: public synchronized void addSample(final int sample) {
074: if (sample < minimum)
075: minimum = sample;
076: if (sample > maximum)
077: maximum = sample;
078: ++n;
079: sum += sample;
080: // If we are keeping track of history, add our sample to the tail of the list and trim the front so it's within our
081: // timing boundary
082: if (sampleHistory != null) {
083: sampleHistory[nextHistoryPosition++] = new Sample(sample);
084: nextHistoryPosition %= sampleHistory.length;
085: }
086: }
087:
088: /**
089: * Resets this statistic, all counters/averages/etc. go to 0; any history is cleared as well.
090: */
091: public synchronized void reset() {
092: n = sum = 0;
093: minimum = Integer.MAX_VALUE;
094: maximum = Integer.MIN_VALUE;
095: if (sampleHistory != null) {
096: for (int pos = 0; pos < sampleHistory.length; ++pos) {
097: sampleHistory[pos] = null;
098: }
099: nextHistoryPosition = 0;
100: }
101: }
102:
103: public String getName() {
104: return name;
105: }
106:
107: /**
108: * @return the maximum value of all samples
109: */
110: public int getMaximum() {
111: return maximum;
112: }
113:
114: /**
115: * @return the minimum value of all samples
116: */
117: public int getMinimum() {
118: return minimum;
119: }
120:
121: /**
122: * @return the number of samples (so far)
123: */
124: public int getN() {
125: return n;
126: }
127:
128: /**
129: * @return the running sum of samples (so far)
130: */
131: public int getSum() {
132: return sum;
133: }
134:
135: /**
136: * @return the running average of the samples (so far)
137: */
138: public double getAverage() {
139: return n > 0 ? ((double) sum / (double) n) : 0.0;
140: }
141:
142: /**
143: * Returns an average rate at which samples were added, if you want this rate per second then pass in <strong>1000</strong>,
144: * if you want it per minute then pass in <strong>1000 * 60</strong>, etc. This rate is extrapolated from the entire
145: * available history as defined in the constructor. For finer and more accurate rates, the history length should be
146: * lengthened.
147: *
148: * @return the rate at which samples were added per {@link periodInMillis}, averaged over the (rolling) history
149: * length, or -1 if history is not being kept
150: */
151: public int getSampleRate(final long periodInMillis) {
152: // XXX:
153: // NOTE:
154: // IMPORTANT:
155: // If you mess with this method, please run the AggregateIntegerTest manually and un-disable the
156: // testGetSampleRate() method there. It does not pass reliably because it is timing dependent, but it should
157: // be run manually if this method is modified.
158: final int sampleRate;
159: if (sampleHistorySnapshot != null) {
160: // We synchronize on and use our history snapshot (thus keeping with our fixed-memory requirements) for each
161: // calculation
162: synchronized (sampleHistorySnapshot) {
163: final int snapshotPosition;
164: final int localN;
165: synchronized (this ) {
166: for (int pos = 0; pos < sampleHistory.length; ++pos) {
167: sampleHistorySnapshot[pos] = sampleHistory[pos];
168: }
169: snapshotPosition = nextHistoryPosition;
170: localN = n;
171: }
172: if (localN > 0) {
173: // Now with our snapshot data we need to extrapolate our rate information
174: final Sample oldestSample;
175: final int existingSampleCount;
176: if (localN > sampleHistorySnapshot.length) {
177: oldestSample = sampleHistorySnapshot[snapshotPosition];
178: existingSampleCount = sampleHistorySnapshot.length;
179: } else {
180: oldestSample = sampleHistorySnapshot[0];
181: existingSampleCount = localN;
182: }
183: final double elapsedSampleTimeInMillis = System
184: .currentTimeMillis()
185: - oldestSample.timestamp;
186: if (elapsedSampleTimeInMillis > 0) {
187: sampleRate = (int) ((periodInMillis / elapsedSampleTimeInMillis) * existingSampleCount);
188: } else {
189: sampleRate = 0;
190: }
191: } else {
192: sampleRate = 0;
193: }
194: }
195: } else {
196: sampleRate = -1;
197: }
198: return sampleRate;
199: }
200:
201: public String toString() {
202: return MessageFormat.format(TO_STRING_FORMAT, new Object[] {
203: name, new Integer(n), new Integer(sum),
204: new Integer(minimum), new Integer(maximum),
205: new Integer(sum / n) });
206: }
207:
208: }
|