• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2008 The Guava Authors
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.google.common.base;
18 
19 import static com.google.common.base.Preconditions.checkNotNull;
20 import static com.google.common.base.Preconditions.checkState;
21 import static java.util.concurrent.TimeUnit.MICROSECONDS;
22 import static java.util.concurrent.TimeUnit.MILLISECONDS;
23 import static java.util.concurrent.TimeUnit.NANOSECONDS;
24 import static java.util.concurrent.TimeUnit.SECONDS;
25 
26 import com.google.common.annotations.Beta;
27 import com.google.common.annotations.GwtCompatible;
28 import com.google.common.annotations.GwtIncompatible;
29 
30 import java.util.concurrent.TimeUnit;
31 
32 /**
33  * An object that measures elapsed time in nanoseconds. It is useful to measure
34  * elapsed time using this class instead of direct calls to {@link
35  * System#nanoTime} for a few reasons:
36  *
37  * <ul>
38  * <li>An alternate time source can be substituted, for testing or performance
39  *     reasons.
40  * <li>As documented by {@code nanoTime}, the value returned has no absolute
41  *     meaning, and can only be interpreted as relative to another timestamp
42  *     returned by {@code nanoTime} at a different time. {@code Stopwatch} is a
43  *     more effective abstraction because it exposes only these relative values,
44  *     not the absolute ones.
45  * </ul>
46  *
47  * <p>Basic usage:
48  * <pre>
49  *   Stopwatch stopwatch = new Stopwatch().{@link #start start}();
50  *   doSomething();
51  *   stopwatch.{@link #stop stop}(); // optional
52  *
53  *   long millis = stopwatch.{@link #elapsedMillis elapsedMillis}();
54  *
55  *   log.info("that took: " + stopwatch); // formatted string like "12.3 ms"
56  * </pre>
57  *
58  * <p>Stopwatch methods are not idempotent; it is an error to start or stop a
59  * stopwatch that is already in the desired state.
60  *
61  * <p>When testing code that uses this class, use the {@linkplain
62  * #Stopwatch(Ticker) alternate constructor} to supply a fake or mock ticker.
63  * <!-- TODO(kevinb): restore the "such as" --> This allows you to
64  * simulate any valid behavior of the stopwatch.
65  *
66  * <p><b>Note:</b> This class is not thread-safe.
67  *
68  * @author Kevin Bourrillion
69  * @since 10.0
70  */
71 @Beta
72 @GwtCompatible(emulated=true)
73 public final class Stopwatch {
74   private final Ticker ticker;
75   private boolean isRunning;
76   private long elapsedNanos;
77   private long startTick;
78 
79   /**
80    * Creates (but does not start) a new stopwatch using {@link System#nanoTime}
81    * as its time source.
82    */
Stopwatch()83   public Stopwatch() {
84     this(Ticker.systemTicker());
85   }
86 
87   /**
88    * Creates (but does not start) a new stopwatch, using the specified time
89    * source.
90    */
Stopwatch(Ticker ticker)91   public Stopwatch(Ticker ticker) {
92     this.ticker = checkNotNull(ticker);
93   }
94 
95   /**
96    * Returns {@code true} if {@link #start()} has been called on this stopwatch,
97    * and {@link #stop()} has not been called since the last call to {@code
98    * start()}.
99    */
isRunning()100   public boolean isRunning() {
101     return isRunning;
102   }
103 
104   /**
105    * Starts the stopwatch.
106    *
107    * @return this {@code Stopwatch} instance
108    * @throws IllegalStateException if the stopwatch is already running.
109    */
start()110   public Stopwatch start() {
111     checkState(!isRunning);
112     isRunning = true;
113     startTick = ticker.read();
114     return this;
115   }
116 
117   /**
118    * Stops the stopwatch. Future reads will return the fixed duration that had
119    * elapsed up to this point.
120    *
121    * @return this {@code Stopwatch} instance
122    * @throws IllegalStateException if the stopwatch is already stopped.
123    */
stop()124   public Stopwatch stop() {
125     long tick = ticker.read();
126     checkState(isRunning);
127     isRunning = false;
128     elapsedNanos += tick - startTick;
129     return this;
130   }
131 
132   /**
133    * Sets the elapsed time for this stopwatch to zero,
134    * and places it in a stopped state.
135    *
136    * @return this {@code Stopwatch} instance
137    */
reset()138   public Stopwatch reset() {
139     elapsedNanos = 0;
140     isRunning = false;
141     return this;
142   }
143 
elapsedNanos()144   private long elapsedNanos() {
145     return isRunning ? ticker.read() - startTick + elapsedNanos : elapsedNanos;
146   }
147 
148   /**
149    * Returns the current elapsed time shown on this stopwatch, expressed
150    * in the desired time unit, with any fraction rounded down.
151    *
152    * <p>Note that the overhead of measurement can be more than a microsecond, so
153    * it is generally not useful to specify {@link TimeUnit#NANOSECONDS}
154    * precision here.
155    */
elapsedTime(TimeUnit desiredUnit)156   public long elapsedTime(TimeUnit desiredUnit) {
157     return desiredUnit.convert(elapsedNanos(), NANOSECONDS);
158   }
159 
160   /**
161    * Returns the current elapsed time shown on this stopwatch, expressed
162    * in milliseconds, with any fraction rounded down. This is identical to
163    * {@code elapsedTime(TimeUnit.MILLISECONDS}.
164    */
elapsedMillis()165   public long elapsedMillis() {
166     return elapsedTime(MILLISECONDS);
167   }
168 
169   /**
170    * Returns a string representation of the current elapsed time; equivalent to
171    * {@code toString(4)} (four significant figures).
172    */
173   @GwtIncompatible("String.format()")
toString()174   @Override public String toString() {
175     return toString(4);
176   }
177 
178   /**
179    * Returns a string representation of the current elapsed time, choosing an
180    * appropriate unit and using the specified number of significant figures.
181    * For example, at the instant when {@code elapsedTime(NANOSECONDS)} would
182    * return {1234567}, {@code toString(4)} returns {@code "1.235 ms"}.
183    */
184   @GwtIncompatible("String.format()")
toString(int significantDigits)185   public String toString(int significantDigits) {
186     long nanos = elapsedNanos();
187 
188     TimeUnit unit = chooseUnit(nanos);
189     double value = (double) nanos / NANOSECONDS.convert(1, unit);
190 
191     // Too bad this functionality is not exposed as a regular method call
192     return String.format("%." + significantDigits + "g %s",
193         value, abbreviate(unit));
194   }
195 
chooseUnit(long nanos)196   private static TimeUnit chooseUnit(long nanos) {
197     if (SECONDS.convert(nanos, NANOSECONDS) > 0) {
198       return SECONDS;
199     }
200     if (MILLISECONDS.convert(nanos, NANOSECONDS) > 0) {
201       return MILLISECONDS;
202     }
203     if (MICROSECONDS.convert(nanos, NANOSECONDS) > 0) {
204       return MICROSECONDS;
205     }
206     return NANOSECONDS;
207   }
208 
abbreviate(TimeUnit unit)209   private static String abbreviate(TimeUnit unit) {
210     switch (unit) {
211       case NANOSECONDS:
212         return "ns";
213       case MICROSECONDS:
214         return "\u03bcs"; // μs
215       case MILLISECONDS:
216         return "ms";
217       case SECONDS:
218         return "s";
219       default:
220         throw new AssertionError();
221     }
222   }
223 }
224