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