    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
    017    package com.google.common.base;
    018
    019    import static com.google.common.base.Preconditions.checkNotNull;
    020    import static com.google.common.base.Preconditions.checkState;
    021    import static java.util.concurrent.TimeUnit.MICROSECONDS;
    022    import static java.util.concurrent.TimeUnit.MILLISECONDS;
    023    import static java.util.concurrent.TimeUnit.NANOSECONDS;
    024    import static java.util.concurrent.TimeUnit.SECONDS;
    025
    026    import com.google.common.annotations.Beta;
    027    import com.google.common.annotations.GwtCompatible;
    028    import com.google.common.annotations.GwtIncompatible;
    029
    030    import java.util.concurrent.TimeUnit;
    031
    032    /**
    033     * An object that measures elapsed time in nanoseconds. It is useful to measure
    034     * elapsed time using this class instead of direct calls to {@link
    035     * System#nanoTime} for a few reasons:
    036     *
    037     * <ul>
    038     * <li>An alternate time source can be substituted, for testing or performance
    039     *     reasons.
    040     * <li>As documented by {@code nanoTime}, the value returned has no absolute
    041     *     meaning, and can only be interpreted as relative to another timestamp
    042     *     returned by {@code nanoTime} at a different time. {@code Stopwatch} is a
    043     *     more effective abstraction because it exposes only these relative values,
    044     *     not the absolute ones.
    045     * </ul>
    046     *
    047     * <p>Basic usage:
    048     * <pre>
    049     *   Stopwatch stopwatch = new Stopwatch().{@link #start start}();
    050     *   doSomething();
    051     *   stopwatch.{@link #stop stop}(); // optional
    052     *
    053     *   long millis = stopwatch.{@link #elapsedMillis elapsedMillis}();
    054     *
    055     *   log.info("that took: " + stopwatch); // formatted string like "12.3 ms"
    056     * </pre>
    057     *
    058     * <p>Stopwatch methods are not idempotent; it is an error to start or stop a
    059     * stopwatch that is already in the desired state.
    060     *
    061     * <p>When testing code that uses this class, use the {@linkplain
    062     * #Stopwatch(Ticker) alternate constructor} to supply a fake or mock ticker.
    063     * <!-- TODO(kevinb): restore the "such as" --> This allows you to
    064     * simulate any valid behavior of the stopwatch.
    065     *
    066     * <p><b>Note:</b> This class is not thread-safe.
    067     *
    068     * @author Kevin Bourrillion
    069     * @since 10.0
    070     */
    071    @Beta
    072    @GwtCompatible(emulated=true)
    073    public final class Stopwatch {
    074      private final Ticker ticker;
    075      private boolean isRunning;
    076      private long elapsedNanos;
    077      private long startTick;
    078
    079      /**
    080       * Creates (but does not start) a new stopwatch using {@link System#nanoTime}
    081       * as its time source.
    082       */
    083      public Stopwatch() {
    084        this(Ticker.systemTicker());
    085      }
    086
    087      /**
    088       * Creates (but does not start) a new stopwatch, using the specified time
    089       * source.
    090       */
    091      public Stopwatch(Ticker ticker) {
    092        this.ticker = checkNotNull(ticker);
    093      }
    094
    095      /**
    096       * Returns {@code true} if {@link #start()} has been called on this stopwatch,
    097       * and {@link #stop()} has not been called since the last call to {@code
    098       * start()}.
    099       */
    100      public boolean isRunning() {
    101        return isRunning;
    102      }
    103
    104      /**
    105       * Starts the stopwatch.
    106       *
    107       * @throws IllegalStateException if the stopwatch is already running.
    108       */
    109      public Stopwatch start() {
    110        checkState(!isRunning);
    111        isRunning = true;
    112        startTick = ticker.read();
    113        return this;
    114      }
    115
    116      /**
    117       * Stops the stopwatch. Future reads will return the fixed duration that had
    118       * elapsed up to this point.
    119       *
    120       * @throws IllegalStateException if the stopwatch is already stopped.
    121       */
    122      public Stopwatch stop() {
    123        long tick = ticker.read();
    124        checkState(isRunning);
    125        isRunning = false;
    126        elapsedNanos += tick - startTick;
    127        return this;
    128      }
    129
    130      /**
    131       * Sets the elapsed time for this stopwatch to zero,
    132       * and places it in a stopped state.
    133       */
    134      public Stopwatch reset() {
    135        elapsedNanos = 0;
    136        isRunning = false;
    137        return this;
    138      }
    139
    140      private long elapsedNanos() {
    141        return isRunning ? ticker.read() - startTick + elapsedNanos : elapsedNanos;
    142      }
    143
    144      /**
    145       * Returns the current elapsed time shown on this stopwatch, expressed
    146       * in the desired time unit, with any fraction rounded down.
    147       *
    148       * <p>Note that the overhead of measurement can be more than a microsecond, so
    149       * it is generally not useful to specify {@link TimeUnit#NANOSECONDS}
    150       * precision here.
    151       */
    152      public long elapsedTime(TimeUnit desiredUnit) {
    153        return desiredUnit.convert(elapsedNanos(), NANOSECONDS);
    154      }
    155
    156      /**
    157       * Returns the current elapsed time shown on this stopwatch, expressed
    158       * in milliseconds, with any fraction rounded down. This is identical to
    159       * {@code elapsedTime(TimeUnit.MILLISECONDS}.
    160       */
    161      public long elapsedMillis() {
    162        return elapsedTime(MILLISECONDS);
    163      }
    164
    165      /**
    166       * Returns a string representation of the current elapsed time; equivalent to
    167       * {@code toString(4)} (four significant figures).
    168       */
    169      @GwtIncompatible("String.format()")
    170      @Override public String toString() {
    171        return toString(4);
    172      }
    173
    174      /**
    175       * Returns a string representation of the current elapsed time, choosing an
    176       * appropriate unit and using the specified number of significant figures.
    177       * For example, at the instant when {@code elapsedTime(NANOSECONDS)} would
    178       * return {1234567}, {@code toString(4)} returns {@code "1.235 ms"}.
    179       */
    180      @GwtIncompatible("String.format()")
    181      public String toString(int significantDigits) {
    182        long nanos = elapsedNanos();
    183
    184        TimeUnit unit = chooseUnit(nanos);
    185        double value = (double) nanos / NANOSECONDS.convert(1, unit);
    186
    187        // Too bad this functionality is not exposed as a regular method call
    188        return String.format("%." + significantDigits + "g %s",
    189            value, abbreviate(unit));
    190      }
    191
    192      private static TimeUnit chooseUnit(long nanos) {
    193        if (SECONDS.convert(nanos, NANOSECONDS) > 0) {
    194          return SECONDS;
    195        }
    196        if (MILLISECONDS.convert(nanos, NANOSECONDS) > 0) {
    197          return MILLISECONDS;
    198        }
    199        if (MICROSECONDS.convert(nanos, NANOSECONDS) > 0) {
    200          return MICROSECONDS;
    201        }
    202        return NANOSECONDS;
    203      }
    204
    205      private static String abbreviate(TimeUnit unit) {
    206        switch (unit) {
    207          case NANOSECONDS:
    208            return "ns";
    209          case MICROSECONDS:
    210            return "\u03bcs"; // μs
    211          case MILLISECONDS:
    212            return "ms";
    213          case SECONDS:
    214            return "s";
    215          default:
    216            throw new AssertionError();
    217        }
    218      }
    219    }