1 /*
2 * ModeShape (http://www.modeshape.org)
3 * See the COPYRIGHT.txt file distributed with this work for information
4 * regarding copyright ownership. Some portions may be licensed
5 * to Red Hat, Inc. under one or more contributor license agreements.
6 * See the AUTHORS.txt file in the distribution for a full listing of
7 * individual contributors.
8 *
9 * ModeShape is free software. Unless otherwise indicated, all code in ModeShape
10 * is licensed to you under the terms of the GNU Lesser General Public License as
11 * published by the Free Software Foundation; either version 2.1 of
12 * the License, or (at your option) any later version.
13 *
14 * ModeShape is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 * Lesser General Public License for more details.
18 *
19 * You should have received a copy of the GNU Lesser General Public
20 * License along with this software; if not, write to the Free
21 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
22 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
23 */
24 package org.modeshape.common.statistic;
25
26 import java.util.concurrent.locks.Lock;
27 import java.util.concurrent.locks.ReadWriteLock;
28 import java.util.concurrent.locks.ReentrantReadWriteLock;
29 import net.jcip.annotations.ThreadSafe;
30 import org.modeshape.common.math.MathOperations;
31 import org.modeshape.common.text.Inflector;
32 import org.modeshape.common.util.StringUtil;
33
34 /**
35 * Encapsulation of the statistics for a series of values to which new values are frequently added. The statistics include the
36 * {@link #getMinimum() minimum}, {@link #getMaximum() maximum}, {@link #getTotal() total (aggregate sum)}, and {@link #getMean()
37 * mean (average)}. See {@link DetailedStatistics} for a subclass that also calculates the {@link DetailedStatistics#getMedian()
38 * median}, {@link DetailedStatistics#getStandardDeviation() standard deviation} and the {@link DetailedStatistics#getHistogram()
39 * histogram} of the values.
40 * <p>
41 * This class is threadsafe.
42 * </p>
43 *
44 * @param <T> the number type used in these statistics
45 */
46 @ThreadSafe
47 public class SimpleStatistics<T extends Number> {
48
49 protected final MathOperations<T> math;
50 private int count = 0;
51 private T total;
52 private T maximum;
53 private T minimum;
54 private T mean;
55 private Double meanValue;
56 private final ReadWriteLock lock = new ReentrantReadWriteLock();
57
58 public SimpleStatistics( MathOperations<T> operations ) {
59 this.math = operations;
60 this.total = this.math.createZeroValue();
61 this.maximum = this.math.createZeroValue();
62 this.minimum = null;
63 this.mean = this.math.createZeroValue();
64 this.meanValue = 0.0d;
65 }
66
67 /**
68 * Add a new value to these statistics.
69 *
70 * @param value the new value
71 */
72 public void add( T value ) {
73 Lock lock = this.lock.writeLock();
74 try {
75 lock.lock();
76 doAddValue(value);
77 } finally {
78 lock.unlock();
79 }
80 }
81
82 /**
83 * A method that can be overridden by subclasses when {@link #add(Number) add} is called. This method is called within the
84 * write lock, and does real work. Therefore, subclasses should call this method when they overwrite it.
85 *
86 * @param value the value already added
87 */
88 protected void doAddValue( T value ) {
89 if (value == null) return;
90 // Modify the basic statistics ...
91 ++this.count;
92 this.total = math.add(this.total, value);
93 this.maximum = this.math.maximum(this.maximum, value);
94 this.minimum = this.math.minimum(this.minimum, value);
95 // Calculate the mean and standard deviation ...
96 int count = getCount();
97 if (count == 1) {
98 // M(1) = x(1)
99 this.meanValue = value.doubleValue();
100 this.mean = value;
101 } else {
102 double dValue = value.doubleValue();
103 double dCount = count;
104 // M(k) = M(k-1) + ( x(k) - M(k-1) ) / k
105 this.meanValue = this.meanValue + ((dValue - this.meanValue) / dCount);
106 this.mean = this.math.create(this.meanValue);
107 }
108 }
109
110 /**
111 * Get the aggregate sum of the values in the series.
112 *
113 * @return the total of the values, or 0.0 if the {@link #getCount() count} is 0
114 */
115 public T getTotal() {
116 Lock lock = this.lock.readLock();
117 lock.lock();
118 try {
119 return this.total;
120 } finally {
121 lock.unlock();
122 }
123 }
124
125 /**
126 * Get the maximum value in the series.
127 *
128 * @return the maximum value, or 0.0 if the {@link #getCount() count} is 0
129 */
130 public T getMaximum() {
131 Lock lock = this.lock.readLock();
132 lock.lock();
133 try {
134 return this.maximum;
135 } finally {
136 lock.unlock();
137 }
138 }
139
140 /**
141 * Get the minimum value in the series.
142 *
143 * @return the minimum value, or 0.0 if the {@link #getCount() count} is 0
144 */
145 public T getMinimum() {
146 Lock lock = this.lock.readLock();
147 lock.lock();
148 try {
149 return this.minimum != null ? this.minimum : (T)this.math.createZeroValue();
150 } finally {
151 lock.unlock();
152 }
153 }
154
155 /**
156 * Get the number of values that have been measured.
157 *
158 * @return the count
159 */
160 public int getCount() {
161 Lock lock = this.lock.readLock();
162 lock.lock();
163 try {
164 return this.count;
165 } finally {
166 lock.unlock();
167 }
168 }
169
170 /**
171 * Return the approximate mean (average) value represented as an instance of the operand type. Note that this may truncate if
172 * the operand type is not able to have the required precision. For the accurate mean, see {@link #getMeanValue() }.
173 *
174 * @return the mean (average), or 0.0 if the {@link #getCount() count} is 0
175 */
176 public T getMean() {
177 Lock lock = this.lock.readLock();
178 lock.lock();
179 try {
180 return this.mean;
181 } finally {
182 lock.unlock();
183 }
184 }
185
186 /**
187 * Return the mean (average) value.
188 *
189 * @return the mean (average), or 0.0 if the {@link #getCount() count} is 0
190 * @see #getMean()
191 */
192 public double getMeanValue() {
193 Lock lock = this.lock.readLock();
194 lock.lock();
195 try {
196 return this.meanValue;
197 } finally {
198 lock.unlock();
199 }
200 }
201
202 /**
203 * Reset the statistics in this object, and clear out any stored information.
204 */
205 public void reset() {
206 Lock lock = this.lock.writeLock();
207 lock.lock();
208 try {
209 doReset();
210 } finally {
211 lock.unlock();
212 }
213 }
214
215 public MathOperations<T> getMathOperations() {
216 return math;
217 }
218
219 protected ReadWriteLock getLock() {
220 return this.lock;
221 }
222
223 /**
224 * Method that can be overridden by subclasses when {@link #reset()} is called. This method is called while the object is
225 * locked for write and does work; therefore, the subclass should call this method.
226 */
227 protected void doReset() {
228 this.total = this.math.createZeroValue();
229 this.maximum = this.math.createZeroValue();
230 this.minimum = null;
231 this.mean = this.math.createZeroValue();
232 this.meanValue = 0.0d;
233 this.count = 0;
234 }
235
236 @Override
237 public String toString() {
238 int count = this.getCount();
239 String samples = Inflector.getInstance().pluralize("sample", count);
240 return StringUtil.createString("{0} {1}: min={2}; avg={3}; max={4}",
241 count,
242 samples,
243 this.minimum,
244 this.mean,
245 this.maximum);
246 }
247
248 }