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