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 }