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