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