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