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 }