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 }