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 }