001/*
002 * Copyright (C) 2008 The Guava Authors
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
005 * in compliance with the License. You may obtain a copy of the License at
006 *
007 * http://www.apache.org/licenses/LICENSE-2.0
008 *
009 * Unless required by applicable law or agreed to in writing, software distributed under the License
010 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
011 * or implied. See the License for the specific language governing permissions and limitations under
012 * the License.
013 */
014
015package com.google.common.base;
016
017import static com.google.common.base.Preconditions.checkNotNull;
018import static com.google.common.base.Preconditions.checkState;
019import static java.util.concurrent.TimeUnit.DAYS;
020import static java.util.concurrent.TimeUnit.HOURS;
021import static java.util.concurrent.TimeUnit.MICROSECONDS;
022import static java.util.concurrent.TimeUnit.MILLISECONDS;
023import static java.util.concurrent.TimeUnit.MINUTES;
024import static java.util.concurrent.TimeUnit.NANOSECONDS;
025import static java.util.concurrent.TimeUnit.SECONDS;
026
027import com.google.common.annotations.GwtCompatible;
028import com.google.errorprone.annotations.CanIgnoreReturnValue;
029import java.util.concurrent.TimeUnit;
030
031/**
032 * An object that measures elapsed time in nanoseconds. It is useful to measure elapsed time using
033 * this class instead of direct calls to {@link System#nanoTime} for a few reasons:
034 *
035 * <ul>
036 * <li>An alternate time source can be substituted, for testing or performance reasons.
037 * <li>As documented by {@code nanoTime}, the value returned has no absolute meaning, and can only
038 * be interpreted as relative to another timestamp returned by {@code nanoTime} at a different time.
039 * {@code Stopwatch} is a more effective abstraction because it exposes only these relative values,
040 * not the absolute ones.
041 * </ul>
042 *
043 * <p>Basic usage: <pre>   {@code
044 *
045 *   Stopwatch stopwatch = Stopwatch.createStarted();
046 *   doSomething();
047 *   stopwatch.stop(); // optional
048 *
049 *   long millis = stopwatch.elapsed(MILLISECONDS);
050 *
051 *   log.info("time: " + stopwatch); // formatted string like "12.3 ms"}</pre>
052 *
053 * <p>Stopwatch methods are not idempotent; it is an error to start or stop a stopwatch that is
054 * already in the desired state.
055 *
056 * <p>When testing code that uses this class, use {@link #createUnstarted(Ticker)} or
057 * {@link #createStarted(Ticker)} to supply a fake or mock ticker. This allows you to simulate any
058 * valid behavior of the stopwatch.
059 *
060 * <p><b>Note:</b> This class is not thread-safe.
061 *
062 * <p><b>Warning for Android users:</b> a stopwatch with default behavior may not continue to keep
063 * time while the device is asleep. Instead, create one like this: <pre>   {@code
064 *
065 *    Stopwatch.createStarted(
066 *         new Ticker() {
067 *           public long read() {
068 *             return android.os.SystemClock.elapsedRealtime();
069 *           }
070 *         });}</pre>
071 *
072 * @author Kevin Bourrillion
073 * @since 10.0
074 */
075@GwtCompatible
076public final class Stopwatch {
077  private final Ticker ticker;
078  private boolean isRunning;
079  private long elapsedNanos;
080  private long startTick;
081
082  /**
083   * Creates (but does not start) a new stopwatch using {@link System#nanoTime} as its time source.
084   *
085   * @since 15.0
086   */
087  public static Stopwatch createUnstarted() {
088    return new Stopwatch();
089  }
090
091  /**
092   * Creates (but does not start) a new stopwatch, using the specified time source.
093   *
094   * @since 15.0
095   */
096  public static Stopwatch createUnstarted(Ticker ticker) {
097    return new Stopwatch(ticker);
098  }
099
100  /**
101   * Creates (and starts) a new stopwatch using {@link System#nanoTime} as its time source.
102   *
103   * @since 15.0
104   */
105  public static Stopwatch createStarted() {
106    return new Stopwatch().start();
107  }
108
109  /**
110   * Creates (and starts) a new stopwatch, using the specified time source.
111   *
112   * @since 15.0
113   */
114  public static Stopwatch createStarted(Ticker ticker) {
115    return new Stopwatch(ticker).start();
116  }
117
118  Stopwatch() {
119    this.ticker = Ticker.systemTicker();
120  }
121
122  Stopwatch(Ticker ticker) {
123    this.ticker = checkNotNull(ticker, "ticker");
124  }
125
126  /**
127   * Returns {@code true} if {@link #start()} has been called on this stopwatch, and {@link #stop()}
128   * has not been called since the last call to {@code
129   * start()}.
130   */
131  public boolean isRunning() {
132    return isRunning;
133  }
134
135  /**
136   * Starts the stopwatch.
137   *
138   * @return this {@code Stopwatch} instance
139   * @throws IllegalStateException if the stopwatch is already running.
140   */
141  @CanIgnoreReturnValue
142  public Stopwatch start() {
143    checkState(!isRunning, "This stopwatch is already running.");
144    isRunning = true;
145    startTick = ticker.read();
146    return this;
147  }
148
149  /**
150   * Stops the stopwatch. Future reads will return the fixed duration that had elapsed up to this
151   * point.
152   *
153   * @return this {@code Stopwatch} instance
154   * @throws IllegalStateException if the stopwatch is already stopped.
155   */
156  @CanIgnoreReturnValue
157  public Stopwatch stop() {
158    long tick = ticker.read();
159    checkState(isRunning, "This stopwatch is already stopped.");
160    isRunning = false;
161    elapsedNanos += tick - startTick;
162    return this;
163  }
164
165  /**
166   * Sets the elapsed time for this stopwatch to zero, and places it in a stopped state.
167   *
168   * @return this {@code Stopwatch} instance
169   */
170  @CanIgnoreReturnValue
171  public Stopwatch reset() {
172    elapsedNanos = 0;
173    isRunning = false;
174    return this;
175  }
176
177  private long elapsedNanos() {
178    return isRunning ? ticker.read() - startTick + elapsedNanos : elapsedNanos;
179  }
180
181  /**
182   * Returns the current elapsed time shown on this stopwatch, expressed in the desired time unit,
183   * with any fraction rounded down.
184   *
185   * <p>Note that the overhead of measurement can be more than a microsecond, so it is generally not
186   * useful to specify {@link TimeUnit#NANOSECONDS} precision here.
187   *
188   * @since 14.0 (since 10.0 as {@code elapsedTime()})
189   */
190  public long elapsed(TimeUnit desiredUnit) {
191    return desiredUnit.convert(elapsedNanos(), NANOSECONDS);
192  }
193
194  /**
195   * Returns a string representation of the current elapsed time.
196   */
197  @Override
198  public String toString() {
199    long nanos = elapsedNanos();
200
201    TimeUnit unit = chooseUnit(nanos);
202    double value = (double) nanos / NANOSECONDS.convert(1, unit);
203
204    // Too bad this functionality is not exposed as a regular method call
205    return Platform.formatCompact4Digits(value) + " " + abbreviate(unit);
206  }
207
208  private static TimeUnit chooseUnit(long nanos) {
209    if (DAYS.convert(nanos, NANOSECONDS) > 0) {
210      return DAYS;
211    }
212    if (HOURS.convert(nanos, NANOSECONDS) > 0) {
213      return HOURS;
214    }
215    if (MINUTES.convert(nanos, NANOSECONDS) > 0) {
216      return MINUTES;
217    }
218    if (SECONDS.convert(nanos, NANOSECONDS) > 0) {
219      return SECONDS;
220    }
221    if (MILLISECONDS.convert(nanos, NANOSECONDS) > 0) {
222      return MILLISECONDS;
223    }
224    if (MICROSECONDS.convert(nanos, NANOSECONDS) > 0) {
225      return MICROSECONDS;
226    }
227    return NANOSECONDS;
228  }
229
230  private static String abbreviate(TimeUnit unit) {
231    switch (unit) {
232      case NANOSECONDS:
233        return "ns";
234      case MICROSECONDS:
235        return "\u03bcs"; // μs
236      case MILLISECONDS:
237        return "ms";
238      case SECONDS:
239        return "s";
240      case MINUTES:
241        return "min";
242      case HOURS:
243        return "h";
244      case DAYS:
245        return "d";
246      default:
247        throw new AssertionError();
248    }
249  }
250}