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