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 net.jcip.annotations.NotThreadSafe; 27 import org.modeshape.common.math.Duration; 28 import org.modeshape.common.math.DurationOperations; 29 30 /** 31 * Provides a mechanism to measure time in the same way as a physical stopwatch. 32 */ 33 @NotThreadSafe 34 public class Stopwatch implements Comparable<Stopwatch> { 35 36 private long lastStarted; 37 private final SimpleStatistics<Duration> stats; 38 private final DetailedStatistics<Duration> detailedStats; 39 private String description; 40 41 public Stopwatch() { 42 this(true); 43 } 44 45 public Stopwatch( boolean detailedStats ) { 46 this(detailedStats, null); 47 } 48 49 public Stopwatch( boolean detailedStats, 50 String description ) { 51 this.description = description != null ? description : ""; 52 this.detailedStats = detailedStats ? new DetailedStatistics<Duration>(new DurationOperations()) : null; 53 this.stats = detailedStats ? this.detailedStats : new SimpleStatistics<Duration>(new DurationOperations()); 54 reset(); 55 } 56 57 public String getDescription() { 58 return this.description; 59 } 60 61 /** 62 * Start the stopwatch and begin recording the statistics a new run. This method does nothing if the stopwatch is already 63 * {@link #isRunning() running} 64 * 65 * @see #isRunning() 66 */ 67 public void start() { 68 if (!this.isRunning()) { 69 this.lastStarted = System.nanoTime(); 70 } 71 } 72 73 /** 74 * Stop the stopwatch and record the statistics for the latest run. This method does nothing if the stopwatch is not currently 75 * {@link #isRunning() running} 76 * 77 * @see #isRunning() 78 */ 79 public void stop() { 80 if (this.isRunning()) { 81 long duration = System.nanoTime() - this.lastStarted; 82 this.lastStarted = 0l; 83 this.stats.add(new Duration(duration)); 84 } 85 } 86 87 /** 88 * Return the number of runs (complete starts and stops) this stopwatch has undergone. 89 * 90 * @return the number of runs. 91 * @see #isRunning() 92 */ 93 public int getCount() { 94 return this.stats.getCount(); 95 } 96 97 /** 98 * Return whether this stopwatch is currently running. 99 * 100 * @return true if running, or false if not 101 */ 102 public boolean isRunning() { 103 return this.lastStarted != 0; 104 } 105 106 /** 107 * Get the total duration that this stopwatch has recorded. 108 * 109 * @return the total duration, or an empty duration if this stopwatch has not been used since creation or being 110 * {@link #reset() reset} 111 */ 112 public Duration getTotalDuration() { 113 return this.stats.getTotal(); 114 } 115 116 /** 117 * Get the average duration that this stopwatch has recorded. 118 * 119 * @return the average duration, or an empty duration if this stopwatch has not been used since creation or being 120 * {@link #reset() reset} 121 */ 122 public Duration getAverageDuration() { 123 return this.stats.getMean(); 124 } 125 126 /** 127 * Get the median duration that this stopwatch has recorded. 128 * 129 * @return the median duration, or an empty duration if this stopwatch has not been used since creation or being 130 * {@link #reset() reset} 131 */ 132 public Duration getMedianDuration() { 133 return this.detailedStats != null ? this.detailedStats.getMedian() : new Duration(0l); 134 } 135 136 /** 137 * Get the minimum duration that this stopwatch has recorded. 138 * 139 * @return the total minimum, or an empty duration if this stopwatch has not been used since creation or being 140 * {@link #reset() reset} 141 */ 142 public Duration getMinimumDuration() { 143 return this.stats.getMinimum(); 144 } 145 146 /** 147 * Get the maximum duration that this stopwatch has recorded. 148 * 149 * @return the maximum duration, or an empty duration if this stopwatch has not been used since creation or being 150 * {@link #reset() reset} 151 */ 152 public Duration getMaximumDuration() { 153 return this.stats.getMaximum(); 154 } 155 156 /** 157 * Return this stopwatch's simple statistics. 158 * 159 * @return the statistics 160 * @see #getDetailedStatistics() 161 */ 162 public SimpleStatistics<Duration> getSimpleStatistics() { 163 return this.stats; 164 } 165 166 /** 167 * Return this stopwatch's detailed statistics, if they are being kept. 168 * 169 * @return the statistics 170 * @see #getSimpleStatistics() 171 */ 172 public DetailedStatistics<Duration> getDetailedStatistics() { 173 return this.detailedStats; 174 } 175 176 /** 177 * Return true if detailed statistics are being kept. 178 * 179 * @return true if {@link #getDetailedStatistics() detailed statistics} are being kept, or false if only 180 * {@link #getSimpleStatistics() simple statistics} are being kept. 181 */ 182 public boolean isDetailedStatistics() { 183 return this.detailedStats != null; 184 } 185 186 /** 187 * Return the histogram of this stopwatch's individual runs. Two different kinds of histograms can be created. The first kind 188 * is a histogram where all of the buckets are distributed normally and all have the same width. In this case, the 'numSigmas' 189 * should be set to 0. 190 * <p> 191 * <i>Note: if only {@link #getSimpleStatistics() simple statistics} are being kept, the resulting histogram is always empty. 192 * <p> 193 * The second kind of histogram is more useful when most of the data that is clustered near one value. This histogram is 194 * focused around the values that are up to 'numSigmas' above and below the {@link #getMedianDuration() median}, and all 195 * values outside of this range are placed in the first and last bucket. 196 * </p> 197 * 198 * @param numSigmas the number of standard deviations from the {@link #getMedianDuration() median}, or 0 if the buckets of the 199 * histogram should be evenly distributed 200 * @return the histogram 201 */ 202 public Histogram<Duration> getHistogram( int numSigmas ) { 203 return this.detailedStats != null ? this.detailedStats.getHistogram(numSigmas) : new Histogram<Duration>( 204 this.stats.getMathOperations()); 205 } 206 207 /** 208 * Reset this stopwatch and clear all statistics. 209 */ 210 public void reset() { 211 this.lastStarted = 0l; 212 this.stats.reset(); 213 } 214 215 public int compareTo( Stopwatch that ) { 216 return this.getTotalDuration().compareTo(that.getTotalDuration()); 217 } 218 219 @Override 220 public String toString() { 221 StringBuilder sb = new StringBuilder(); 222 sb.append(this.getTotalDuration()); 223 if (this.stats.getCount() > 1) { 224 sb.append(" ("); 225 sb.append(this.stats.getCount()).append(" samples, avg="); 226 sb.append(this.getAverageDuration()); 227 sb.append("; median="); 228 sb.append(this.getMedianDuration()); 229 sb.append("; min="); 230 sb.append(this.getMinimumDuration()); 231 sb.append("; max="); 232 sb.append(this.getMaximumDuration()); 233 sb.append(")"); 234 } 235 return sb.toString(); 236 } 237 238 }