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 net.jcip.annotations.NotThreadSafe; 027 import org.jboss.dna.common.math.Duration; 028 import org.jboss.dna.common.math.DurationOperations; 029 030 /** 031 * Provides a mechanism to measure time in the same was as a physical stopwatch. 032 * @author Randall Hauch 033 */ 034 @NotThreadSafe 035 public class Stopwatch implements Comparable<Stopwatch> { 036 037 private long lastStarted; 038 private final SimpleStatistics<Duration> stats; 039 private final DetailedStatistics<Duration> detailedStats; 040 private String description; 041 042 public Stopwatch() { 043 this(true); 044 } 045 046 public Stopwatch( boolean detailedStats ) { 047 this(detailedStats, null); 048 } 049 050 public Stopwatch( boolean detailedStats, String description ) { 051 this.description = description != null ? description : ""; 052 this.detailedStats = detailedStats ? new DetailedStatistics<Duration>(new DurationOperations()) : null; 053 this.stats = detailedStats ? this.detailedStats : new SimpleStatistics<Duration>(new DurationOperations()); 054 reset(); 055 } 056 057 public String getDescription() { 058 return this.description; 059 } 060 061 /** 062 * Start the stopwatch and begin recording the statistics a new run. This method does nothing if the stopwatch is already 063 * {@link #isRunning() running} 064 * @see #isRunning() 065 */ 066 public void start() { 067 if (!this.isRunning()) { 068 this.lastStarted = System.nanoTime(); 069 } 070 } 071 072 /** 073 * Stop the stopwatch and record the statistics for the latest run. This method does nothing if the stopwatch is not currently 074 * {@link #isRunning() running} 075 * @see #isRunning() 076 */ 077 public void stop() { 078 if (this.isRunning()) { 079 long duration = System.nanoTime() - this.lastStarted; 080 this.lastStarted = 0l; 081 this.stats.add(new Duration(duration)); 082 } 083 } 084 085 /** 086 * Return the number of runs (complete starts and stops) this stopwatch has undergone. 087 * @return the number of runs. 088 * @see #isRunning() 089 */ 090 public int getCount() { 091 return this.stats.getCount(); 092 } 093 094 /** 095 * Return whether this stopwatch is currently running. 096 * @return true if running, or false if not 097 */ 098 public boolean isRunning() { 099 return this.lastStarted != 0; 100 } 101 102 /** 103 * Get the total duration that this stopwatch has recorded. 104 * @return the total duration, or an empty duration if this stopwatch has not been used since creation or being 105 * {@link #reset() reset} 106 */ 107 public Duration getTotalDuration() { 108 return this.stats.getTotal(); 109 } 110 111 /** 112 * Get the average duration that this stopwatch has recorded. 113 * @return the average duration, or an empty duration if this stopwatch has not been used since creation or being 114 * {@link #reset() reset} 115 */ 116 public Duration getAverageDuration() { 117 return this.stats.getMean(); 118 } 119 120 /** 121 * Get the median duration that this stopwatch has recorded. 122 * @return the median duration, or an empty duration if this stopwatch has not been used since creation or being 123 * {@link #reset() reset} 124 */ 125 public Duration getMedianDuration() { 126 return this.detailedStats != null ? this.detailedStats.getMedian() : new Duration(0l); 127 } 128 129 /** 130 * Get the minimum duration that this stopwatch has recorded. 131 * @return the total minimum, or an empty duration if this stopwatch has not been used since creation or being 132 * {@link #reset() reset} 133 */ 134 public Duration getMinimumDuration() { 135 return this.stats.getMinimum(); 136 } 137 138 /** 139 * Get the maximum duration that this stopwatch has recorded. 140 * @return the maximum duration, or an empty duration if this stopwatch has not been used since creation or being 141 * {@link #reset() reset} 142 */ 143 public Duration getMaximumDuration() { 144 return this.stats.getMaximum(); 145 } 146 147 /** 148 * Return this stopwatch's simple statistics. 149 * @return the statistics 150 * @see #getDetailedStatistics() 151 */ 152 public SimpleStatistics<Duration> getSimpleStatistics() { 153 return this.stats; 154 } 155 156 /** 157 * Return this stopwatch's detailed statistics, if they are being kept. 158 * @return the statistics 159 * @see #getSimpleStatistics() 160 */ 161 public DetailedStatistics<Duration> getDetailedStatistics() { 162 return this.detailedStats; 163 } 164 165 /** 166 * Return true if detailed statistics are being kept. 167 * @return true if {@link #getDetailedStatistics() detailed statistics} are being kept, or false if only 168 * {@link #getSimpleStatistics() simple statistics} are being kept. 169 */ 170 public boolean isDetailedStatistics() { 171 return this.detailedStats != null; 172 } 173 174 /** 175 * Return the histogram of this stopwatch's individual runs. Two different kinds of histograms can be created. The first kind 176 * is a histogram where all of the buckets are distributed normally and all have the same width. In this case, the 'numSigmas' 177 * should be set to 0. 178 * <p> 179 * <i>Note: if only {@link #getSimpleStatistics() simple statistics} are being kept, the resulting histogram is always empty. 180 * <p> 181 * The second kind of histogram is more useful when most of the data that is clustered near one value. This histogram is 182 * focused around the values that are up to 'numSigmas' above and below the {@link #getMedianDuration() median}, and all 183 * values outside of this range are placed in the first and last bucket. 184 * </p> 185 * @param numSigmas the number of standard deviations from the {@link #getMedianDuration() median}, or 0 if the buckets of 186 * the histogram should be evenly distributed 187 * @return the histogram 188 */ 189 public Histogram<Duration> getHistogram( int numSigmas ) { 190 return this.detailedStats != null ? this.detailedStats.getHistogram(numSigmas) : new Histogram<Duration>(this.stats.getMathOperations()); 191 } 192 193 /** 194 * Reset this stopwatch and clear all statistics. 195 */ 196 public void reset() { 197 this.lastStarted = 0l; 198 this.stats.reset(); 199 } 200 201 public int compareTo( Stopwatch that ) { 202 return this.getTotalDuration().compareTo(that.getTotalDuration()); 203 } 204 205 @Override 206 public String toString() { 207 StringBuilder sb = new StringBuilder(); 208 sb.append(this.getTotalDuration()); 209 if (this.stats.getCount() > 1) { 210 sb.append(" ("); 211 sb.append(this.stats.getCount()).append(" samples, avg="); 212 sb.append(this.getAverageDuration()); 213 sb.append("; median="); 214 sb.append(this.getMedianDuration()); 215 sb.append("; min="); 216 sb.append(this.getMinimumDuration()); 217 sb.append("; max="); 218 sb.append(this.getMaximumDuration()); 219 sb.append(")"); 220 } 221 return sb.toString(); 222 } 223 224 }