    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       * @return this {@code Stopwatch} instance
    108       * @throws IllegalStateException if the stopwatch is already running.
    109       */
    110      public Stopwatch start() {
    111        checkState(!isRunning);
    112        isRunning = true;
    113        startTick = ticker.read();
    114        return this;
    115      }
    116
    117      /**
    118       * Stops the stopwatch. Future reads will return the fixed duration that had
    119       * elapsed up to this point.
    120       *
    121       * @return this {@code Stopwatch} instance
    122       * @throws IllegalStateException if the stopwatch is already stopped.
    123       */
    124      public Stopwatch stop() {
    125        long tick = ticker.read();
    126        checkState(isRunning);
    127        isRunning = false;
    128        elapsedNanos += tick - startTick;
    129        return this;
    130      }
    131
    132      /**
    133       * Sets the elapsed time for this stopwatch to zero,
    134       * and places it in a stopped state.
    135       *
    136       * @return this {@code Stopwatch} instance
    137       */
    138      public Stopwatch reset() {
    139        elapsedNanos = 0;
    140        isRunning = false;
    141        return this;
    142      }
    143
    144      private long elapsedNanos() {
    145        return isRunning ? ticker.read() - startTick + elapsedNanos : elapsedNanos;
    146      }
    147
    148      /**
    149       * Returns the current elapsed time shown on this stopwatch, expressed
    150       * in the desired time unit, with any fraction rounded down.
    151       *
    152       * <p>Note that the overhead of measurement can be more than a microsecond, so
    153       * it is generally not useful to specify {@link TimeUnit#NANOSECONDS}
    154       * precision here.
    155       */
    156      public long elapsedTime(TimeUnit desiredUnit) {
    157        return desiredUnit.convert(elapsedNanos(), NANOSECONDS);
    158      }
    159
    160      /**
    161       * Returns the current elapsed time shown on this stopwatch, expressed
    162       * in milliseconds, with any fraction rounded down. This is identical to
    163       * {@code elapsedTime(TimeUnit.MILLISECONDS}.
    164       */
    165      public long elapsedMillis() {
    166        return elapsedTime(MILLISECONDS);
    167      }
    168
    169      /**
    170       * Returns a string representation of the current elapsed time.
    171       */
    172      @GwtIncompatible("String.format()")
    173      @Override public String toString() {
    174        return toString(4);
    175      }
    176
    177      /**
    178       * Returns a string representation of the current elapsed time, choosing an
    179       * appropriate unit and using the specified number of significant figures.
    180       * For example, at the instant when {@code elapsedTime(NANOSECONDS)} would
    181       * return {1234567}, {@code toString(4)} returns {@code "1.235 ms"}.
    182       *
    183       * @deprecated Use {@link #toString()} instead. This method is scheduled
    184       *     to be removed in Guava release 15.0.
    185       */
    186      @Deprecated
    187      @GwtIncompatible("String.format()")
    188      public String toString(int significantDigits) {
    189        long nanos = elapsedNanos();
    190
    191        TimeUnit unit = chooseUnit(nanos);
    192        double value = (double) nanos / NANOSECONDS.convert(1, unit);
    193
    194        // Too bad this functionality is not exposed as a regular method call
    195        return String.format("%." + significantDigits + "g %s",
    196            value, abbreviate(unit));
    197      }
    198
    199      private static TimeUnit chooseUnit(long nanos) {
    200        if (SECONDS.convert(nanos, NANOSECONDS) > 0) {
    201          return SECONDS;
    202        }
    203        if (MILLISECONDS.convert(nanos, NANOSECONDS) > 0) {
    204          return MILLISECONDS;
    205        }
    206        if (MICROSECONDS.convert(nanos, NANOSECONDS) > 0) {
    207          return MICROSECONDS;
    208        }
    209        return NANOSECONDS;
    210      }
    211
    212      private static String abbreviate(TimeUnit unit) {
    213        switch (unit) {
    214          case NANOSECONDS:
    215            return "ns";
    216          case MICROSECONDS:
    217            return "\u03bcs"; // μs
    218          case MILLISECONDS:
    219            return "ms";
    220          case SECONDS:
    221            return "s";
    222          default:
    223            throw new AssertionError();
    224        }
    225      }
    226    }