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 }