View Javadoc
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.DAYS;
22  import static java.util.concurrent.TimeUnit.HOURS;
23  import static java.util.concurrent.TimeUnit.MICROSECONDS;
24  import static java.util.concurrent.TimeUnit.MILLISECONDS;
25  import static java.util.concurrent.TimeUnit.MINUTES;
26  import static java.util.concurrent.TimeUnit.NANOSECONDS;
27  import static java.util.concurrent.TimeUnit.SECONDS;
28  
29  import com.google.common.annotations.Beta;
30  import com.google.common.annotations.GwtCompatible;
31  import com.google.common.annotations.GwtIncompatible;
32  
33  import java.util.concurrent.TimeUnit;
34  
35  /**
36   * An object that measures elapsed time in nanoseconds. It is useful to measure
37   * elapsed time using this class instead of direct calls to {@link
38   * System#nanoTime} for a few reasons:
39   *
40   * <ul>
41   * <li>An alternate time source can be substituted, for testing or performance
42   *     reasons.
43   * <li>As documented by {@code nanoTime}, the value returned has no absolute
44   *     meaning, and can only be interpreted as relative to another timestamp
45   *     returned by {@code nanoTime} at a different time. {@code Stopwatch} is a
46   *     more effective abstraction because it exposes only these relative values,
47   *     not the absolute ones.
48   * </ul>
49   *
50   * <p>Basic usage:
51   * <pre>
52   *   Stopwatch stopwatch = Stopwatch.{@link #createStarted createStarted}();
53   *   doSomething();
54   *   stopwatch.{@link #stop stop}(); // optional
55   *
56   *   long millis = stopwatch.elapsed(MILLISECONDS);
57   *
58   *   log.info("time: " + stopwatch); // formatted string like "12.3 ms"</pre>
59   *
60   * <p>Stopwatch methods are not idempotent; it is an error to start or stop a
61   * stopwatch that is already in the desired state.
62   *
63   * <p>When testing code that uses this class, use
64   * {@link #createUnstarted(Ticker)} or {@link #createStarted(Ticker)} to
65   * supply a fake or mock ticker.
66   * <!-- TODO(kevinb): restore the "such as" --> This allows you to
67   * simulate any valid behavior of the stopwatch.
68   *
69   * <p><b>Note:</b> This class is not thread-safe.
70   *
71   * @author Kevin Bourrillion
72   * @since 10.0
73   */
74  @Beta
75  @GwtCompatible(emulated = true)
76  public final class Stopwatch {
77    private final Ticker ticker;
78    private boolean isRunning;
79    private long elapsedNanos;
80    private long startTick;
81  
82    /**
83     * Creates (but does not start) a new stopwatch using {@link System#nanoTime}
84     * as its time source.
85     *
86     * @since 15.0
87     */
88    public static Stopwatch createUnstarted() {
89      return new Stopwatch();
90    }
91  
92    /**
93     * Creates (but does not start) a new stopwatch, using the specified time
94     * source.
95     *
96     * @since 15.0
97     */
98    public static Stopwatch createUnstarted(Ticker ticker) {
99      return new Stopwatch(ticker);
100   }
101 
102   /**
103    * Creates (and starts) a new stopwatch using {@link System#nanoTime}
104    * as its time source.
105    *
106    * @since 15.0
107    */
108   public static Stopwatch createStarted() {
109     return new Stopwatch().start();
110   }
111 
112   /**
113    * Creates (and starts) a new stopwatch, using the specified time
114    * source.
115    *
116    * @since 15.0
117    */
118   public static Stopwatch createStarted(Ticker ticker) {
119     return new Stopwatch(ticker).start();
120   }
121 
122   /**
123    * Creates (but does not start) a new stopwatch using {@link System#nanoTime}
124    * as its time source.
125    *
126    * @deprecated Use {@link Stopwatch#createUnstarted()} instead.
127    */
128   @Deprecated
129   Stopwatch() {
130     this(Ticker.systemTicker());
131   }
132 
133   /**
134    * Creates (but does not start) a new stopwatch, using the specified time
135    * source.
136    *
137    * @deprecated Use {@link Stopwatch#createUnstarted(Ticker)} instead.
138    */
139   @Deprecated
140   Stopwatch(Ticker ticker) {
141     this.ticker = checkNotNull(ticker, "ticker");
142   }
143 
144   /**
145    * Returns {@code true} if {@link #start()} has been called on this stopwatch,
146    * and {@link #stop()} has not been called since the last call to {@code
147    * start()}.
148    */
149   public boolean isRunning() {
150     return isRunning;
151   }
152 
153   /**
154    * Starts the stopwatch.
155    *
156    * @return this {@code Stopwatch} instance
157    * @throws IllegalStateException if the stopwatch is already running.
158    */
159   public Stopwatch start() {
160     checkState(!isRunning, "This stopwatch is already running.");
161     isRunning = true;
162     startTick = ticker.read();
163     return this;
164   }
165 
166   /**
167    * Stops the stopwatch. Future reads will return the fixed duration that had
168    * elapsed up to this point.
169    *
170    * @return this {@code Stopwatch} instance
171    * @throws IllegalStateException if the stopwatch is already stopped.
172    */
173   public Stopwatch stop() {
174     long tick = ticker.read();
175     checkState(isRunning, "This stopwatch is already stopped.");
176     isRunning = false;
177     elapsedNanos += tick - startTick;
178     return this;
179   }
180 
181   /**
182    * Sets the elapsed time for this stopwatch to zero,
183    * and places it in a stopped state.
184    *
185    * @return this {@code Stopwatch} instance
186    */
187   public Stopwatch reset() {
188     elapsedNanos = 0;
189     isRunning = false;
190     return this;
191   }
192 
193   private long elapsedNanos() {
194     return isRunning ? ticker.read() - startTick + elapsedNanos : elapsedNanos;
195   }
196 
197   /**
198    * Returns the current elapsed time shown on this stopwatch, expressed
199    * in the desired time unit, with any fraction rounded down.
200    *
201    * <p>Note that the overhead of measurement can be more than a microsecond, so
202    * it is generally not useful to specify {@link TimeUnit#NANOSECONDS}
203    * precision here.
204    *
205    * @since 14.0 (since 10.0 as {@code elapsedTime()})
206    */
207   public long elapsed(TimeUnit desiredUnit) {
208     return desiredUnit.convert(elapsedNanos(), NANOSECONDS);
209   }
210 
211   /**
212    * Returns a string representation of the current elapsed time.
213    */
214   @GwtIncompatible("String.format()")
215   @Override public String toString() {
216     long nanos = elapsedNanos();
217 
218     TimeUnit unit = chooseUnit(nanos);
219     double value = (double) nanos / NANOSECONDS.convert(1, unit);
220 
221     // Too bad this functionality is not exposed as a regular method call
222     return String.format("%.4g %s", value, abbreviate(unit));
223   }
224 
225   private static TimeUnit chooseUnit(long nanos) {
226     if (DAYS.convert(nanos, NANOSECONDS) > 0) {
227       return DAYS;
228     }
229     if (HOURS.convert(nanos, NANOSECONDS) > 0) {
230       return HOURS;
231     }
232     if (MINUTES.convert(nanos, NANOSECONDS) > 0) {
233       return MINUTES;
234     }
235     if (SECONDS.convert(nanos, NANOSECONDS) > 0) {
236       return SECONDS;
237     }
238     if (MILLISECONDS.convert(nanos, NANOSECONDS) > 0) {
239       return MILLISECONDS;
240     }
241     if (MICROSECONDS.convert(nanos, NANOSECONDS) > 0) {
242       return MICROSECONDS;
243     }
244     return NANOSECONDS;
245   }
246 
247   private static String abbreviate(TimeUnit unit) {
248     switch (unit) {
249       case NANOSECONDS:
250         return "ns";
251       case MICROSECONDS:
252         return "\u03bcs"; // μs
253       case MILLISECONDS:
254         return "ms";
255       case SECONDS:
256         return "s";
257       case MINUTES:
258         return "min";
259       case HOURS:
260         return "h";
261       case DAYS:
262         return "d";
263       default:
264         throw new AssertionError();
265     }
266   }
267 }