    001/*
    002 * Licensed to the Apache Software Foundation (ASF) under one or more
    003 * contributor license agreements.  See the NOTICE file distributed with
    004 * this work for additional information regarding copyright ownership.
    005 * The ASF licenses this file to You under the Apache License, Version 2.0
    006 * (the "License"); you may not use this file except in compliance with
    007 * the License.  You may obtain a copy of the License at
    008 *
    009 *      https://www.apache.org/licenses/LICENSE-2.0
    010 *
    011 * Unless required by applicable law or agreed to in writing, software
    012 * distributed under the License is distributed on an "AS IS" BASIS,
    013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    014 * See the License for the specific language governing permissions and
    015 * limitations under the License.
    016 */
    017
    018package org.apache.commons.lang3.time;
    019
    020import java.time.Duration;
    021import java.time.Instant;
    022import java.util.Objects;
    023import java.util.concurrent.TimeUnit;
    024import java.util.function.Supplier;
    025
    026import org.apache.commons.lang3.StringUtils;
    027import org.apache.commons.lang3.function.FailableConsumer;
    028import org.apache.commons.lang3.function.FailableRunnable;
    029import org.apache.commons.lang3.function.FailableSupplier;
    030
    031/**
    032 * {@link StopWatch} provides a convenient API for timings.
    033 *
    034 * <p>
    035 * To start the watch, call {@link #start()} or {@link StopWatch#createStarted()}. At this point you can:
    036 * </p>
    037 * <ul>
    038 * <li>{@link #split()} the watch to get the time whilst the watch continues in the background. {@link #unsplit()} will remove the effect of the split. At this
    039 * point, these three options are available again.</li>
    040 * <li>{@link #suspend()} the watch to pause it. {@link #resume()} allows the watch to continue. Any time between the suspend and resume will not be counted in
    041 * the total. At this point, these three options are available again.</li>
    042 * <li>{@link #stop()} the watch to complete the timing session.</li>
    043 * </ul>
    044 *
    045 * <p>
    046 * It is intended that the output methods {@link #toString()} and {@link #getTime()} should only be called after stop, split or suspend, however a suitable
    047 * result will be returned at other points.
    048 * </p>
    049 *
    050 * <p>
    051 * NOTE: As from v2.1, the methods protect against inappropriate calls. Thus you cannot now call stop before start, resume before suspend or unsplit before
    052 * split.
    053 * </p>
    054 *
    055 * <ol>
    056 * <li>{@link #split()}, {@link #suspend()}, or {@link #stop()} cannot be invoked twice</li>
    057 * <li>{@link #unsplit()} may only be called if the watch has been {@link #split()}</li>
    058 * <li>{@link #resume()} may only be called if the watch has been {@link #suspend()}</li>
    059 * <li>{@link #start()} cannot be called twice without calling {@link #reset()}</li>
    060 * </ol>
    061 *
    062 * <p>
    063 * This class is not thread-safe
    064 * </p>
    065 *
    066 * @see DurationUtils#of(FailableRunnable)
    067 * @see DurationUtils#of(FailableConsumer)
    068 * @since 2.0
    069 */
    070public class StopWatch {
    071
    072    /**
    073     * Enumeration type which indicates the split status of a StopWatch.
    074     */
    075    private enum SplitState {
    076        SPLIT, UNSPLIT
    077    }
    078
    079    /**
    080     * Enumeration type which indicates the status of a StopWatch.
    081     */
    082    private enum State {
    083
    084        RUNNING {
    085            @Override
    086            boolean isStarted() {
    087                return true;
    088            }
    089
    090            @Override
    091            boolean isStopped() {
    092                return false;
    093            }
    094
    095            @Override
    096            boolean isSuspended() {
    097                return false;
    098            }
    099        },
    100
    101        STOPPED {
    102            @Override
    103            boolean isStarted() {
    104                return false;
    105            }
    106
    107            @Override
    108            boolean isStopped() {
    109                return true;
    110            }
    111
    112            @Override
    113            boolean isSuspended() {
    114                return false;
    115            }
    116        },
    117
    118        SUSPENDED {
    119            @Override
    120            boolean isStarted() {
    121                return true;
    122            }
    123
    124            @Override
    125            boolean isStopped() {
    126                return false;
    127            }
    128
    129            @Override
    130            boolean isSuspended() {
    131                return true;
    132            }
    133        },
    134
    135        UNSTARTED {
    136            @Override
    137            boolean isStarted() {
    138                return false;
    139            }
    140
    141            @Override
    142            boolean isStopped() {
    143                return true;
    144            }
    145
    146            @Override
    147            boolean isSuspended() {
    148                return false;
    149            }
    150        };
    151
    152        /**
    153         * Tests whether this StopWatch is started. A suspended StopWatch is also started.
    154         *
    155         * @return boolean If this StopWatch is started.
    156         */
    157        abstract boolean isStarted();
    158
    159        /**
    160         * Tests whether this StopWatch is stopped. A StopWatch which is not yet started and explicitly stopped is considered stopped.
    161         *
    162         * @return boolean If this StopWatch is stopped.
    163         */
    164        abstract boolean isStopped();
    165
    166        /**
    167         * Tests whether this StopWatch is suspended.
    168         *
    169         * @return boolean If this StopWatch is suspended.
    170         */
    171        abstract boolean isSuspended();
    172    }
    173
    174    private static final long NANO_2_MILLIS = 1_000_000L;
    175
    176    /**
    177     * Creates a StopWatch.
    178     *
    179     * @return StopWatch a StopWatch.
    180     * @since 3.10
    181     */
    182    public static StopWatch create() {
    183        return new StopWatch();
    184    }
    185
    186    /**
    187     * Creates and starts a StopWatch.
    188     *
    189     * @return StopWatch a started StopWatch.
    190     * @since 3.5
    191     */
    192    public static StopWatch createStarted() {
    193        final StopWatch sw = new StopWatch();
    194        sw.start();
    195        return sw;
    196    }
    197
    198    /**
    199     * A message for string presentation.
    200     *
    201     * @since 3.10
    202     */
    203    private final String message;
    204
    205    /**
    206     * The current running state of this StopWatch.
    207     */
    208    private State runningState = State.UNSTARTED;
    209
    210    /**
    211     * Whether this StopWatch has a split time recorded.
    212     */
    213    private SplitState splitState = SplitState.UNSPLIT;
    214
    215    /**
    216     * The start time in nanoseconds.
    217     *
    218     * This field can be removed once we move off of Java 8.
    219     */
    220    private long startTimeNanos;
    221
    222    /**
    223     * The start Instant.
    224     * <p>
    225     * nanoTime is only for elapsed time so we need to also store the currentTimeMillis to maintain the old getStartTime API.
    226     * </p>
    227     * <p>
    228     * On Java 8, Instant has millisecond precision, later versions use nanoseconds.
    229     * </p>
    230     */
    231    private Instant startInstant;
    232
    233    /**
    234     * The end Instant.
    235     * <p>
    236     * nanoTime is only for elapsed time so we need to also store the currentTimeMillis to maintain the old getStartTime API.
    237     * </p>
    238     * <p>
    239     * On Java 8, Instant has millisecond precision, later versions use nanoseconds.
    240     * </p>
    241     */
    242    private Instant stopInstant;
    243
    244    /**
    245     * The stop time in nanoseconds.
    246     *
    247     * This field can be removed once we move off of Java 8.
    248     */
    249    private long stopTimeNanos;
    250
    251    /**
    252     * Constructs a new instance.
    253     */
    254    public StopWatch() {
    255        this(null);
    256    }
    257
    258    /**
    259     * Constructs a new instance.
    260     *
    261     * @param message A message for string presentation.
    262     * @since 3.10
    263     */
    264    public StopWatch(final String message) {
    265        this.message = message;
    266    }
    267
    268    /**
    269     * Formats the split time with {@link DurationFormatUtils#formatDurationHMS}.
    270     *
    271     * @return the split time formatted by {@link DurationFormatUtils#formatDurationHMS}.
    272     * @since 3.10
    273     */
    274    public String formatSplitTime() {
    275        return DurationFormatUtils.formatDurationHMS(getSplitDuration().toMillis());
    276    }
    277
    278    /**
    279     * Formats the time formatted with {@link DurationFormatUtils#formatDurationHMS}.
    280     *
    281     * @return the time formatted by {@link DurationFormatUtils#formatDurationHMS}.
    282     * @since 3.10
    283     */
    284    public String formatTime() {
    285        return DurationFormatUtils.formatDurationHMS(getTime());
    286    }
    287
    288    /**
    289     * Delegates to {@link Supplier#get()} while recording the duration of the call.
    290     *
    291     * @param <T>      the type of results supplied by this supplier.
    292     * @param supplier The supplier to {@link Supplier#get()}.
    293     * @return a result from the given Supplier.
    294     * @since 3.18.0
    295     */
    296    public <T> T get(final Supplier<T> supplier) {
    297        startResume();
    298        try {
    299            return supplier.get();
    300        } finally {
    301            suspend();
    302        }
    303    }
    304
    305    /**
    306     * Gets the Duration on this StopWatch.
    307     *
    308     * <p>
    309     * This is either the Duration between the start and the moment this method is called, or the Duration between start and stop.
    310     * </p>
    311     *
    312     * @return the Duration.
    313     * @since 3.16.0
    314     */
    315    public Duration getDuration() {
    316        return Duration.ofNanos(getNanoTime());
    317    }
    318
    319    /**
    320     * Gets the message for string presentation.
    321     *
    322     * @return the message for string presentation.
    323     * @since 3.10
    324     */
    325    public String getMessage() {
    326        return message;
    327    }
    328
    329    /**
    330     * Gets the <em>elapsed</em> time in nanoseconds.
    331     *
    332     * <p>
    333     * This is either the time between the start and the moment this method is called, or the amount of time between start and stop.
    334     * </p>
    335     *
    336     * @return the <em>elapsed</em> time in nanoseconds.
    337     * @see System#nanoTime()
    338     * @since 3.0
    339     */
    340    public long getNanoTime() {
    341        switch (runningState) {
    342        case STOPPED:
    343        case SUSPENDED:
    344            return stopTimeNanos - startTimeNanos;
    345        case UNSTARTED:
    346            return 0;
    347        case RUNNING:
    348            return System.nanoTime() - startTimeNanos;
    349        default:
    350            break;
    351        }
    352        throw new IllegalStateException("Illegal running state has occurred.");
    353    }
    354
    355    /**
    356     * Gets the split Duration on this StopWatch.
    357     *
    358     * <p>
    359     * This is the Duration between start and latest split.
    360     * </p>
    361     *
    362     * @return the split Duration
    363     * @throws IllegalStateException if this StopWatch has not yet been split.
    364     * @since 3.16.0
    365     */
    366    public Duration getSplitDuration() {
    367        return Duration.ofNanos(getSplitNanoTime());
    368    }
    369
    370    /**
    371     * Gets the split time in nanoseconds.
    372     *
    373     * <p>
    374     * This is the time between start and latest split.
    375     * </p>
    376     *
    377     * @return the split time in nanoseconds
    378     * @throws IllegalStateException if this StopWatch has not yet been split.
    379     * @since 3.0
    380     */
    381    public long getSplitNanoTime() {
    382        if (splitState != SplitState.SPLIT) {
    383            throw new IllegalStateException("Stopwatch must be split to get the split time.");
    384        }
    385        return stopTimeNanos - startTimeNanos;
    386    }
    387
    388    /**
    389     * Gets the split time on this StopWatch.
    390     *
    391     * <p>
    392     * This is the time between start and latest split.
    393     * </p>
    394     *
    395     * @return the split time in milliseconds
    396     * @throws IllegalStateException if this StopWatch has not yet been split.
    397     * @since 2.1
    398     * @deprecated Use {@link #getSplitDuration()}.
    399     */
    400    @Deprecated
    401    public long getSplitTime() {
    402        return nanosToMillis(getSplitNanoTime());
    403    }
    404
    405    /**
    406     * Gets the Instant this StopWatch was started, between the current time and midnight, January 1, 1970 UTC.
    407     *
    408     * @return the Instant this StopWatch was started, between the current time and midnight, January 1, 1970 UTC.
    409     * @throws IllegalStateException if this StopWatch has not been started
    410     * @since 3.16.0
    411     */
    412    public Instant getStartInstant() {
    413        if (runningState == State.UNSTARTED) {
    414            throw new IllegalStateException("Stopwatch has not been started");
    415        }
    416        return startInstant;
    417    }
    418
    419    /**
    420     * Gets the time this StopWatch was started in milliseconds, between the current time and midnight, January 1, 1970 UTC.
    421     *
    422     * @return the time this StopWatch was started in milliseconds, between the current time and midnight, January 1, 1970 UTC.
    423     * @throws IllegalStateException if this StopWatch has not been started
    424     * @since 2.4
    425     * @deprecated Use {@link #getStartInstant()}.
    426     */
    427    @Deprecated
    428    public long getStartTime() {
    429        return getStartInstant().toEpochMilli();
    430    }
    431
    432    /**
    433     * Gets the Instant this StopWatch was stopped, between the current time and midnight, January 1, 1970 UTC.
    434     *
    435     * @return the Instant this StopWatch was stopped in milliseconds, between the current time and midnight, January 1, 1970 UTC.
    436     * @throws IllegalStateException if this StopWatch has not been started
    437     * @since 3.16.0
    438     */
    439    public Instant getStopInstant() {
    440        if (runningState == State.UNSTARTED) {
    441            throw new IllegalStateException("Stopwatch has not been started");
    442        }
    443        return stopInstant;
    444    }
    445
    446    /**
    447     * Gets the time this StopWatch was stopped in milliseconds, between the current time and midnight, January 1, 1970 UTC.
    448     *
    449     * @return the time this StopWatch was stopped in milliseconds, between the current time and midnight, January 1, 1970 UTC.
    450     * @throws IllegalStateException if this StopWatch has not been started
    451     * @since 3.12.0
    452     * @deprecated Use {@link #getStopInstant()}.
    453     */
    454    @Deprecated
    455    public long getStopTime() {
    456        // stopTimeNanos stores System.nanoTime() for elapsed time
    457        return getStopInstant().toEpochMilli();
    458    }
    459
    460    /**
    461     * Delegates to {@link FailableSupplier#get()} while recording the duration of the call.
    462     *
    463     * @param <T>      the type of results supplied by this supplier.
    464     * @param <E>      The kind of thrown exception or error.
    465     * @param supplier The supplier to {@link Supplier#get()}.
    466     * @return a result from the given Supplier.
    467     * @throws Throwable if the supplier fails.
    468     * @since 3.18.0
    469     */
    470    public <T, E extends Throwable> T getT(final FailableSupplier<T, E> supplier) throws Throwable {
    471        startResume();
    472        try {
    473            return supplier.get();
    474        } finally {
    475            suspend();
    476        }
    477    }
    478
    479    /**
    480     * Gets the time on this StopWatch.
    481     *
    482     * <p>
    483     * This is either the time between the start and the moment this method is called, or the amount of time between start and stop.
    484     * </p>
    485     *
    486     * @return the time in milliseconds
    487     * @see #getDuration()
    488     */
    489    public long getTime() {
    490        return nanosToMillis(getNanoTime());
    491    }
    492
    493    /**
    494     * Gets the time in the specified TimeUnit.
    495     *
    496     * <p>
    497     * This is either the time between the start and the moment this method is called, or the amount of time between start and stop. The resulting time will be
    498     * expressed in the desired TimeUnit with any remainder rounded down. For example, if the specified unit is {@code TimeUnit.HOURS} and this StopWatch time
    499     * is 59 minutes, then the result returned will be {@code 0}.
    500     * </p>
    501     *
    502     * @param timeUnit the unit of time, not null
    503     * @return the time in the specified TimeUnit, rounded down
    504     * @since 3.5
    505     */
    506    public long getTime(final TimeUnit timeUnit) {
    507        return timeUnit.convert(getNanoTime(), TimeUnit.NANOSECONDS);
    508    }
    509
    510    /**
    511     * Tests whether this StopWatch is started. A suspended StopWatch is also started watch.
    512     *
    513     * @return boolean If this StopWatch is started.
    514     * @since 3.2
    515     */
    516    public boolean isStarted() {
    517        return runningState.isStarted();
    518    }
    519
    520    /**
    521     * Tests whether StopWatch is stopped. this StopWatch which's not yet started and explicitly stopped StopWatch is considered as stopped.
    522     *
    523     * @return boolean If this StopWatch is stopped.
    524     * @since 3.2
    525     */
    526    public boolean isStopped() {
    527        return runningState.isStopped();
    528    }
    529
    530    /**
    531     * Tests whether this StopWatch is suspended.
    532     *
    533     * @return boolean If this StopWatch is suspended.
    534     * @since 3.2
    535     */
    536    public boolean isSuspended() {
    537        return runningState.isSuspended();
    538    }
    539
    540    /**
    541     * Converts nanoseconds to milliseconds.
    542     *
    543     * @param nanos nanoseconds to convert.
    544     * @return milliseconds conversion result.
    545     */
    546    private long nanosToMillis(final long nanos) {
    547        return nanos / NANO_2_MILLIS;
    548    }
    549
    550    /**
    551     * Resets this StopWatch. Stops it if need be.
    552     *
    553     * <p>
    554     * This method clears the internal values to allow the object to be reused.
    555     * </p>
    556     */
    557    public void reset() {
    558        runningState = State.UNSTARTED;
    559        splitState = SplitState.UNSPLIT;
    560    }
    561
    562    /**
    563     * Resumes this StopWatch after a suspend.
    564     *
    565     * <p>
    566     * This method resumes the watch after it was suspended. The watch will not include time between the suspend and resume calls in the total time.
    567     * </p>
    568     *
    569     * @throws IllegalStateException if this StopWatch has not been suspended.
    570     */
    571    public void resume() {
    572        if (runningState != State.SUSPENDED) {
    573            throw new IllegalStateException("Stopwatch must be suspended to resume.");
    574        }
    575        startTimeNanos += System.nanoTime() - stopTimeNanos;
    576        runningState = State.RUNNING;
    577    }
    578
    579    /**
    580     * Delegates to {@link Runnable#run()} while recording the duration of the call.
    581     *
    582     * @param runnable The runnable to {@link Runnable#run()}.
    583     * @since 3.18.0
    584     */
    585    public void run(final Runnable runnable) {
    586        startResume();
    587        try {
    588            runnable.run();
    589        } finally {
    590            suspend();
    591        }
    592    }
    593
    594    /**
    595     * Delegates to {@link FailableRunnable#run()} while recording the duration of the call.
    596     *
    597     * @param <E>      The kind of {@link Throwable}.
    598     * @param runnable The runnable to {@link FailableRunnable#run()}.
    599     * @throws Throwable Thrown by {@link FailableRunnable#run()}.
    600     * @since 3.18.0
    601     */
    602    public <E extends Throwable> void runT(final FailableRunnable<E> runnable) throws Throwable {
    603        startResume();
    604        try {
    605            runnable.run();
    606        } finally {
    607            suspend();
    608        }
    609    }
    610
    611    /**
    612     * Splits the time.
    613     *
    614     * <p>
    615     * This method sets the stop time of the watch to allow a time to be extracted. The start time is unaffected, enabling {@link #unsplit()} to continue the
    616     * timing from the original start point.
    617     * </p>
    618     *
    619     * @throws IllegalStateException if this StopWatch is not running.
    620     */
    621    public void split() {
    622        if (runningState != State.RUNNING) {
    623            throw new IllegalStateException("Stopwatch is not running.");
    624        }
    625        stopTimeNanos = System.nanoTime();
    626        splitState = SplitState.SPLIT;
    627    }
    628
    629    /**
    630     * Starts this StopWatch.
    631     *
    632     * <p>
    633     * This method starts a new timing session, clearing any previous values.
    634     * </p>
    635     *
    636     * @throws IllegalStateException if this StopWatch is already running.
    637     */
    638    public void start() {
    639        if (runningState == State.STOPPED) {
    640            throw new IllegalStateException("Stopwatch must be reset before being restarted.");
    641        }
    642        if (runningState != State.UNSTARTED) {
    643            throw new IllegalStateException("Stopwatch already started.");
    644        }
    645        startTimeNanos = System.nanoTime();
    646        startInstant = Instant.now();
    647        runningState = State.RUNNING;
    648    }
    649
    650    /**
    651     * Starts or resumes this StopWatch.
    652     */
    653    private void startResume() {
    654        if (isStopped()) {
    655            start();
    656        } else if (isSuspended()) {
    657            resume();
    658        }
    659    }
    660
    661    /**
    662     * Stops this StopWatch.
    663     *
    664     * <p>
    665     * This method ends a new timing session, allowing the time to be retrieved.
    666     * </p>
    667     *
    668     * @throws IllegalStateException if this StopWatch is not running.
    669     */
    670    public void stop() {
    671        if (runningState != State.RUNNING && runningState != State.SUSPENDED) {
    672            throw new IllegalStateException("Stopwatch is not running.");
    673        }
    674        if (runningState == State.RUNNING) {
    675            stopTimeNanos = System.nanoTime();
    676            stopInstant = Instant.now();
    677        }
    678        runningState = State.STOPPED;
    679    }
    680
    681    /**
    682     * Suspends this StopWatch for later resumption.
    683     *
    684     * <p>
    685     * This method suspends the watch until it is resumed. The watch will not include time between the suspend and resume calls in the total time.
    686     * </p>
    687     *
    688     * @throws IllegalStateException if this StopWatch is not currently running.
    689     */
    690    public void suspend() {
    691        if (runningState != State.RUNNING) {
    692            throw new IllegalStateException("Stopwatch must be running to suspend.");
    693        }
    694        stopTimeNanos = System.nanoTime();
    695        stopInstant = Instant.now();
    696        runningState = State.SUSPENDED;
    697    }
    698
    699    /**
    700     * Gets a summary of the split time that this StopWatch recorded as a string.
    701     *
    702     * <p>
    703     * The format used is ISO 8601-like, [<em>message</em> ]<em>hours</em>:<em>minutes</em>:<em>seconds</em>.<em>milliseconds</em>.
    704     * </p>
    705     *
    706     * @return the split time as a String
    707     * @since 2.1
    708     * @since 3.10 Returns the prefix {@code "message "} if the message is set.
    709     */
    710    public String toSplitString() {
    711        final String msgStr = Objects.toString(message, StringUtils.EMPTY);
    712        final String formattedTime = formatSplitTime();
    713        return msgStr.isEmpty() ? formattedTime : msgStr + StringUtils.SPACE + formattedTime;
    714    }
    715
    716    /**
    717     * Gets a summary of the time that this StopWatch recorded as a string.
    718     *
    719     * <p>
    720     * The format used is ISO 8601-like, [<em>message</em> ]<em>hours</em>:<em>minutes</em>:<em>seconds</em>.<em>milliseconds</em>.
    721     * </p>
    722     *
    723     * @return the time as a String
    724     * @since 3.10 Returns the prefix {@code "message "} if the message is set.
    725     */
    726    @Override
    727    public String toString() {
    728        final String msgStr = Objects.toString(message, StringUtils.EMPTY);
    729        final String formattedTime = formatTime();
    730        return msgStr.isEmpty() ? formattedTime : msgStr + StringUtils.SPACE + formattedTime;
    731    }
    732
    733    /**
    734     * Removes the split.
    735     *
    736     * <p>
    737     * This method clears the stop time. The start time is unaffected, enabling timing from the original start point to continue.
    738     * </p>
    739     *
    740     * @throws IllegalStateException if this StopWatch has not been split.
    741     */
    742    public void unsplit() {
    743        if (splitState != SplitState.SPLIT) {
    744            throw new IllegalStateException("Stopwatch has not been split.");
    745        }
    746        splitState = SplitState.UNSPLIT;
    747    }
    748
    749}