View Javadoc
1   /*
2    * Copyright (C) 2008 The Guava Authors
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5    * in compliance with the License. You may obtain a copy of the License at
6    *
7    * http://www.apache.org/licenses/LICENSE-2.0
8    *
9    * Unless required by applicable law or agreed to in writing, software distributed under the License
10   * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11   * or implied. See the License for the specific language governing permissions and limitations under
12   * the License.
13   */
14  
15  package com.google.common.base;
16  
17  import static com.google.common.base.Preconditions.checkNotNull;
18  import static com.google.common.base.Preconditions.checkState;
19  import static java.util.concurrent.TimeUnit.DAYS;
20  import static java.util.concurrent.TimeUnit.HOURS;
21  import static java.util.concurrent.TimeUnit.MICROSECONDS;
22  import static java.util.concurrent.TimeUnit.MILLISECONDS;
23  import static java.util.concurrent.TimeUnit.MINUTES;
24  import static java.util.concurrent.TimeUnit.NANOSECONDS;
25  import static java.util.concurrent.TimeUnit.SECONDS;
26  
27  import com.google.common.annotations.GwtCompatible;
28  import com.google.common.annotations.GwtIncompatible;
29  import com.google.errorprone.annotations.CanIgnoreReturnValue;
30  import com.google.j2objc.annotations.J2ObjCIncompatible;
31  import java.time.Duration;
32  import java.util.concurrent.TimeUnit;
33  
34  /**
35   * An object that measures elapsed time in nanoseconds. It is useful to measure elapsed time using
36   * this class instead of direct calls to {@link System#nanoTime} for a few reasons:
37   *
38   * <ul>
39   *   <li>An alternate time source can be substituted, for testing or performance reasons.
40   *   <li>As documented by {@code nanoTime}, the value returned has no absolute meaning, and can only
41   *       be interpreted as relative to another timestamp returned by {@code nanoTime} at a different
42   *       time. {@code Stopwatch} is a more effective abstraction because it exposes only these
43   *       relative values, not the absolute ones.
44   * </ul>
45   *
46   * <p>Basic usage:
47   *
48   * <pre>{@code
49   * Stopwatch stopwatch = Stopwatch.createStarted();
50   * doSomething();
51   * stopwatch.stop(); // optional
52   *
53   * Duration duration = stopwatch.elapsed();
54   *
55   * log.info("time: " + 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 stopwatch that is
59   * already in the desired state.
60   *
61   * <p>When testing code that uses this class, use {@link #createUnstarted(Ticker)} or {@link
62   * #createStarted(Ticker)} to supply a fake or mock ticker. This allows you to simulate any valid
63   * behavior of the stopwatch.
64   *
65   * <p><b>Note:</b> This class is not thread-safe.
66   *
67   * <p><b>Warning for Android users:</b> a stopwatch with default behavior may not continue to keep
68   * time while the device is asleep. Instead, create one like this:
69   *
70   * <pre>{@code
71   * Stopwatch.createStarted(
72   *      new Ticker() {
73   *        public long read() {
74   *          return android.os.SystemClock.elapsedRealtimeNanos();
75   *        }
76   *      });
77   * }</pre>
78   *
79   * @author Kevin Bourrillion
80   * @since 10.0
81   */
82  @GwtCompatible(emulated = true)
83  public final class Stopwatch {
84    private final Ticker ticker;
85    private boolean isRunning;
86    private long elapsedNanos;
87    private long startTick;
88  
89    /**
90     * Creates (but does not start) a new stopwatch using {@link System#nanoTime} as its time source.
91     *
92     * @since 15.0
93     */
94    public static Stopwatch createUnstarted() {
95      return new Stopwatch();
96    }
97  
98    /**
99     * Creates (but does not start) a new stopwatch, using the specified time source.
100    *
101    * @since 15.0
102    */
103   public static Stopwatch createUnstarted(Ticker ticker) {
104     return new Stopwatch(ticker);
105   }
106 
107   /**
108    * Creates (and starts) a new stopwatch using {@link System#nanoTime} as its time source.
109    *
110    * @since 15.0
111    */
112   public static Stopwatch createStarted() {
113     return new Stopwatch().start();
114   }
115 
116   /**
117    * Creates (and starts) a new stopwatch, using the specified time source.
118    *
119    * @since 15.0
120    */
121   public static Stopwatch createStarted(Ticker ticker) {
122     return new Stopwatch(ticker).start();
123   }
124 
125   Stopwatch() {
126     this.ticker = Ticker.systemTicker();
127   }
128 
129   Stopwatch(Ticker ticker) {
130     this.ticker = checkNotNull(ticker, "ticker");
131   }
132 
133   /**
134    * Returns {@code true} if {@link #start()} has been called on this stopwatch, and {@link #stop()}
135    * has not been called since the last call to {@code start()}.
136    */
137   public boolean isRunning() {
138     return isRunning;
139   }
140 
141   /**
142    * Starts the stopwatch.
143    *
144    * @return this {@code Stopwatch} instance
145    * @throws IllegalStateException if the stopwatch is already running.
146    */
147   @CanIgnoreReturnValue
148   public Stopwatch start() {
149     checkState(!isRunning, "This stopwatch is already running.");
150     isRunning = true;
151     startTick = ticker.read();
152     return this;
153   }
154 
155   /**
156    * Stops the stopwatch. Future reads will return the fixed duration that had elapsed up to this
157    * point.
158    *
159    * @return this {@code Stopwatch} instance
160    * @throws IllegalStateException if the stopwatch is already stopped.
161    */
162   @CanIgnoreReturnValue
163   public Stopwatch stop() {
164     long tick = ticker.read();
165     checkState(isRunning, "This stopwatch is already stopped.");
166     isRunning = false;
167     elapsedNanos += tick - startTick;
168     return this;
169   }
170 
171   /**
172    * Sets the elapsed time for this stopwatch to zero, and places it in a stopped state.
173    *
174    * @return this {@code Stopwatch} instance
175    */
176   @CanIgnoreReturnValue
177   public Stopwatch reset() {
178     elapsedNanos = 0;
179     isRunning = false;
180     return this;
181   }
182 
183   private long elapsedNanos() {
184     return isRunning ? ticker.read() - startTick + elapsedNanos : elapsedNanos;
185   }
186 
187   /**
188    * Returns the current elapsed time shown on this stopwatch, expressed in the desired time unit,
189    * with any fraction rounded down.
190    *
191    * <p><b>Note:</b> the overhead of measurement can be more than a microsecond, so it is generally
192    * not useful to specify {@link TimeUnit#NANOSECONDS} precision here.
193    *
194    * <p>It is generally not a good idea to use an ambiguous, unitless {@code long} to represent
195    * elapsed time. Therefore, we recommend using {@link #elapsed()} instead, which returns a
196    * strongly-typed {@link Duration} instance.
197    *
198    * @since 14.0 (since 10.0 as {@code elapsedTime()})
199    */
200   public long elapsed(TimeUnit desiredUnit) {
201     return desiredUnit.convert(elapsedNanos(), NANOSECONDS);
202   }
203 
204   /**
205    * Returns the current elapsed time shown on this stopwatch as a {@link Duration}. Unlike {@link
206    * #elapsed(TimeUnit)}, this method does not lose any precision due to rounding.
207    *
208    * @since 22.0
209    */
210   @GwtIncompatible
211   @J2ObjCIncompatible
212   public Duration elapsed() {
213     return Duration.ofNanos(elapsedNanos());
214   }
215 
216   /** Returns a string representation of the current elapsed time. */
217   @Override
218   public String toString() {
219     long nanos = elapsedNanos();
220 
221     TimeUnit unit = chooseUnit(nanos);
222     double value = (double) nanos / NANOSECONDS.convert(1, unit);
223 
224     // Too bad this functionality is not exposed as a regular method call
225     return Platform.formatCompact4Digits(value) + " " + abbreviate(unit);
226   }
227 
228   private static TimeUnit chooseUnit(long nanos) {
229     if (DAYS.convert(nanos, NANOSECONDS) > 0) {
230       return DAYS;
231     }
232     if (HOURS.convert(nanos, NANOSECONDS) > 0) {
233       return HOURS;
234     }
235     if (MINUTES.convert(nanos, NANOSECONDS) > 0) {
236       return MINUTES;
237     }
238     if (SECONDS.convert(nanos, NANOSECONDS) > 0) {
239       return SECONDS;
240     }
241     if (MILLISECONDS.convert(nanos, NANOSECONDS) > 0) {
242       return MILLISECONDS;
243     }
244     if (MICROSECONDS.convert(nanos, NANOSECONDS) > 0) {
245       return MICROSECONDS;
246     }
247     return NANOSECONDS;
248   }
249 
250   private static String abbreviate(TimeUnit unit) {
251     switch (unit) {
252       case NANOSECONDS:
253         return "ns";
254       case MICROSECONDS:
255         return "\u03bcs"; // μs
256       case MILLISECONDS:
257         return "ms";
258       case SECONDS:
259         return "s";
260       case MINUTES:
261         return "min";
262       case HOURS:
263         return "h";
264       case DAYS:
265         return "d";
266       default:
267         throw new AssertionError();
268     }
269   }
270 }