001 /*
002 * JBoss, Home of Professional Open Source.
003 * Copyright 2008, Red Hat Middleware LLC, and individual contributors
004 * as indicated by the @author tags. See the copyright.txt file in the
005 * distribution for a full listing of individual contributors.
006 *
007 * This is free software; you can redistribute it and/or modify it
008 * under the terms of the GNU Lesser General Public License as
009 * published by the Free Software Foundation; either version 2.1 of
010 * the License, or (at your option) any later version.
011 *
012 * This software is distributed in the hope that it will be useful,
013 * but WITHOUT ANY WARRANTY; without even the implied warranty of
014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015 * Lesser General Public License for more details.
016 *
017 * You should have received a copy of the GNU Lesser General Public
018 * License along with this software; if not, write to the Free
019 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
020 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
021 */
022 package org.jboss.dna.common.stats;
023
024 import java.util.concurrent.locks.Lock;
025 import java.util.concurrent.locks.ReadWriteLock;
026 import java.util.concurrent.locks.ReentrantReadWriteLock;
027 import net.jcip.annotations.ThreadSafe;
028 import org.jboss.dna.common.math.MathOperations;
029 import org.jboss.dna.common.text.Inflector;
030 import org.jboss.dna.common.util.StringUtil;
031
032 /**
033 * Encapsulation of the statistics for a series of values to which new values are frequently added. The statistics include the
034 * {@link #getMinimum() minimum}, {@link #getMaximum() maximum}, {@link #getTotal() total (aggregate sum)}, and
035 * {@link #getMean() mean (average)}. See {@link DetailedStatistics} for a subclass that also calculates the
036 * {@link DetailedStatistics#getMedian() median}, {@link DetailedStatistics#getStandardDeviation() standard deviation} and the
037 * {@link DetailedStatistics#getHistogram() histogram} of the values.
038 * <p>
039 * This class is threadsafe.
040 * </p>
041 * @param <T> the number type used in these statistics
042 * @author Randall Hauch
043 */
044 @ThreadSafe
045 public class SimpleStatistics<T extends Number> {
046
047 protected final MathOperations<T> math;
048 private int count = 0;
049 private T total;
050 private T maximum;
051 private T minimum;
052 private T mean;
053 private Double meanValue;
054 private final ReadWriteLock lock = new ReentrantReadWriteLock();
055
056 public SimpleStatistics( MathOperations<T> operations ) {
057 this.math = operations;
058 this.total = this.math.createZeroValue();
059 this.maximum = this.math.createZeroValue();
060 this.minimum = null;
061 this.mean = this.math.createZeroValue();
062 this.meanValue = 0.0d;
063 }
064
065 /**
066 * Add a new value to these statistics.
067 * @param value the new value
068 */
069 public void add( T value ) {
070 Lock lock = this.lock.writeLock();
071 try {
072 lock.lock();
073 doAddValue(value);
074 } finally {
075 lock.unlock();
076 }
077 }
078
079 /**
080 * A method that can be overridden by subclasses when {@link #add(Number) add} is called. This method is called within the
081 * write lock, and does real work. Therefore, subclasses should call this method when they overwrite it.
082 * @param value the value already added
083 */
084 protected void doAddValue( T value ) {
085 if (value == null) return;
086 // Modify the basic statistics ...
087 ++this.count;
088 this.total = math.add(this.total, value);
089 this.maximum = this.math.maximum(this.maximum, value);
090 this.minimum = this.math.minimum(this.minimum, value);
091 // Calculate the mean and standard deviation ...
092 int count = getCount();
093 if (count == 1) {
094 // M(1) = x(1)
095 this.meanValue = value.doubleValue();
096 this.mean = value;
097 } else {
098 double dValue = value.doubleValue();
099 double dCount = count;
100 // M(k) = M(k-1) + ( x(k) - M(k-1) ) / k
101 this.meanValue = this.meanValue + ((dValue - this.meanValue) / dCount);
102 this.mean = this.math.create(this.meanValue);
103 }
104 }
105
106 /**
107 * Get the aggregate sum of the values in the series.
108 * @return the total of the values, or 0.0 if the {@link #getCount() count} is 0
109 */
110 public T getTotal() {
111 Lock lock = this.lock.readLock();
112 lock.lock();
113 try {
114 return this.total;
115 } finally {
116 lock.unlock();
117 }
118 }
119
120 /**
121 * Get the maximum value in the series.
122 * @return the maximum value, or 0.0 if the {@link #getCount() count} is 0
123 */
124 public T getMaximum() {
125 Lock lock = this.lock.readLock();
126 lock.lock();
127 try {
128 return this.maximum;
129 } finally {
130 lock.unlock();
131 }
132 }
133
134 /**
135 * Get the minimum value in the series.
136 * @return the minimum value, or 0.0 if the {@link #getCount() count} is 0
137 */
138 public T getMinimum() {
139 Lock lock = this.lock.readLock();
140 lock.lock();
141 try {
142 return this.minimum != null ? this.minimum : (T)this.math.createZeroValue();
143 } finally {
144 lock.unlock();
145 }
146 }
147
148 /**
149 * Get the number of values that have been measured.
150 * @return the count
151 */
152 public int getCount() {
153 Lock lock = this.lock.readLock();
154 lock.lock();
155 try {
156 return this.count;
157 } finally {
158 lock.unlock();
159 }
160 }
161
162 /**
163 * Return the approximate mean (average) value represented as an instance of the operand type. Note that this may truncate if
164 * the operand type is not able to have the required precision. For the accurate mean, see {@link #getMeanValue() }.
165 * @return the mean (average), or 0.0 if the {@link #getCount() count} is 0
166 */
167 public T getMean() {
168 Lock lock = this.lock.readLock();
169 lock.lock();
170 try {
171 return this.mean;
172 } finally {
173 lock.unlock();
174 }
175 }
176
177 /**
178 * Return the mean (average) value.
179 * @return the mean (average), or 0.0 if the {@link #getCount() count} is 0
180 * @see #getMean()
181 */
182 public double getMeanValue() {
183 Lock lock = this.lock.readLock();
184 lock.lock();
185 try {
186 return this.meanValue;
187 } finally {
188 lock.unlock();
189 }
190 }
191
192 /**
193 * Reset the statistics in this object, and clear out any stored information.
194 */
195 public void reset() {
196 Lock lock = this.lock.writeLock();
197 lock.lock();
198 try {
199 doReset();
200 } finally {
201 lock.unlock();
202 }
203 }
204
205 public MathOperations<T> getMathOperations() {
206 return math;
207 }
208
209 protected ReadWriteLock getLock() {
210 return this.lock;
211 }
212
213 /**
214 * Method that can be overridden by subclasses when {@link #reset()} is called. This method is called while the object is
215 * locked for write and does work; therefore, the subclass should call this method.
216 */
217 protected void doReset() {
218 this.total = this.math.createZeroValue();
219 this.maximum = this.math.createZeroValue();
220 this.minimum = null;
221 this.mean = this.math.createZeroValue();
222 this.meanValue = 0.0d;
223 this.count = 0;
224 }
225
226 @Override
227 public String toString() {
228 int count = this.getCount();
229 String samples = Inflector.getInstance().pluralize("sample", count);
230 return StringUtil.createString("{0} {1}: min={2}; avg={3}; max={4}", count, samples, this.minimum, this.mean, this.maximum);
231 }
232
233 }