001/* 002 * ModeShape (http://www.modeshape.org) 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package org.modeshape.common.statistic; 017 018import java.util.concurrent.locks.Lock; 019import java.util.concurrent.locks.ReadWriteLock; 020import java.util.concurrent.locks.ReentrantReadWriteLock; 021import org.modeshape.common.annotation.ThreadSafe; 022import org.modeshape.common.math.MathOperations; 023import org.modeshape.common.text.Inflector; 024import org.modeshape.common.util.StringUtil; 025 026/** 027 * Encapsulation of the statistics for a series of values to which new values are frequently added. The statistics include the 028 * {@link #getMinimum() minimum}, {@link #getMaximum() maximum}, {@link #getTotal() total (aggregate sum)}, and {@link #getMean() 029 * mean (average)}. See {@link DetailedStatistics} for a subclass that also calculates the {@link DetailedStatistics#getMedian() 030 * median}, {@link DetailedStatistics#getStandardDeviation() standard deviation} and the {@link DetailedStatistics#getHistogram() 031 * histogram} of the values. 032 * <p> 033 * This class is threadsafe. 034 * </p> 035 * 036 * @param <T> the number type used in these statistics 037 */ 038@ThreadSafe 039public class SimpleStatistics<T extends Number> { 040 041 protected final MathOperations<T> math; 042 private int count = 0; 043 private T total; 044 private T maximum; 045 private T minimum; 046 private T mean; 047 private Double meanValue; 048 private final ReadWriteLock lock = new ReentrantReadWriteLock(); 049 050 public SimpleStatistics( MathOperations<T> operations ) { 051 this.math = operations; 052 this.total = this.math.createZeroValue(); 053 this.maximum = this.math.createZeroValue(); 054 this.minimum = null; 055 this.mean = this.math.createZeroValue(); 056 this.meanValue = 0.0d; 057 } 058 059 /** 060 * Add a new value to these statistics. 061 * 062 * @param value the new value 063 */ 064 public void add( T value ) { 065 Lock lock = this.lock.writeLock(); 066 try { 067 lock.lock(); 068 doAddValue(value); 069 } finally { 070 lock.unlock(); 071 } 072 } 073 074 /** 075 * A method that can be overridden by subclasses when {@link #add(Number) add} is called. This method is called within the 076 * write lock, and does real work. Therefore, subclasses should call this method when they overwrite it. 077 * 078 * @param value the value already added 079 */ 080 protected void doAddValue( T value ) { 081 if (value == null) return; 082 // Modify the basic statistics ... 083 ++this.count; 084 this.total = math.add(this.total, value); 085 this.maximum = this.math.maximum(this.maximum, value); 086 this.minimum = this.math.minimum(this.minimum, value); 087 // Calculate the mean and standard deviation ... 088 int count = getCount(); 089 if (count == 1) { 090 // M(1) = x(1) 091 this.meanValue = value.doubleValue(); 092 this.mean = value; 093 } else { 094 double dValue = value.doubleValue(); 095 double dCount = count; 096 // M(k) = M(k-1) + ( x(k) - M(k-1) ) / k 097 this.meanValue = this.meanValue + ((dValue - this.meanValue) / dCount); 098 this.mean = this.math.create(this.meanValue); 099 } 100 } 101 102 /** 103 * Get the aggregate sum of the values in the series. 104 * 105 * @return the total of the values, or 0.0 if the {@link #getCount() count} is 0 106 */ 107 public T getTotal() { 108 Lock lock = this.lock.readLock(); 109 lock.lock(); 110 try { 111 return this.total; 112 } finally { 113 lock.unlock(); 114 } 115 } 116 117 /** 118 * Get the maximum value in the series. 119 * 120 * @return the maximum value, or 0.0 if the {@link #getCount() count} is 0 121 */ 122 public T getMaximum() { 123 Lock lock = this.lock.readLock(); 124 lock.lock(); 125 try { 126 return this.maximum; 127 } finally { 128 lock.unlock(); 129 } 130 } 131 132 /** 133 * Get the minimum value in the series. 134 * 135 * @return the minimum value, or 0.0 if the {@link #getCount() count} is 0 136 */ 137 public T getMinimum() { 138 Lock lock = this.lock.readLock(); 139 lock.lock(); 140 try { 141 return this.minimum != null ? this.minimum : (T)this.math.createZeroValue(); 142 } finally { 143 lock.unlock(); 144 } 145 } 146 147 /** 148 * Get the number of values that have been measured. 149 * 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 * 166 * @return the mean (average), or 0.0 if the {@link #getCount() count} is 0 167 */ 168 public T getMean() { 169 Lock lock = this.lock.readLock(); 170 lock.lock(); 171 try { 172 return this.mean; 173 } finally { 174 lock.unlock(); 175 } 176 } 177 178 /** 179 * Return the mean (average) value. 180 * 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}", 233 count, 234 samples, 235 this.minimum, 236 this.mean, 237 this.maximum); 238 } 239 240}