View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      https://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  
18  package org.apache.commons.lang3.time;
19  
20  import java.time.Duration;
21  import java.time.Instant;
22  import java.util.ArrayList;
23  import java.util.Collections;
24  import java.util.List;
25  import java.util.Objects;
26  import java.util.concurrent.TimeUnit;
27  import java.util.function.Supplier;
28  
29  import org.apache.commons.lang3.StringUtils;
30  import org.apache.commons.lang3.function.FailableConsumer;
31  import org.apache.commons.lang3.function.FailableRunnable;
32  import org.apache.commons.lang3.function.FailableSupplier;
33  import org.apache.commons.lang3.tuple.ImmutablePair;
34  
35  /**
36   * {@link StopWatch} provides a convenient API for timings.
37   *
38   * <p>
39   * To start the watch, call {@link #start()} or {@link StopWatch#createStarted()}. At this point you can:
40   * </p>
41   * <ul>
42   * <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
43   * point, these three options are available again.</li>
44   * <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
45   * the total. At this point, these three options are available again.</li>
46   * <li>{@link #stop()} the watch to complete the timing session.</li>
47   * </ul>
48   *
49   * <p>
50   * It is intended that the output methods {@link #toString()} and {@link #getTime()} should only be called after stop, split or suspend, however a suitable
51   * result will be returned at other points.
52   * </p>
53   *
54   * <p>
55   * 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
56   * split.
57   * </p>
58   *
59   * <ol>
60   * <li>{@link #split()}, {@link #suspend()}, or {@link #stop()} cannot be invoked twice</li>
61   * <li>{@link #unsplit()} may only be called if the watch has been {@link #split()}</li>
62   * <li>{@link #resume()} may only be called if the watch has been {@link #suspend()}</li>
63   * <li>{@link #start()} cannot be called twice without calling {@link #reset()}</li>
64   * </ol>
65   *
66   * <p>
67   * This class is not thread-safe.
68   * </p>
69   *
70   * @see DurationUtils#of(FailableRunnable)
71   * @see DurationUtils#of(FailableConsumer)
72   * @since 2.0
73   */
74  public class StopWatch {
75  
76      /**
77       * Stores a split as a label and duration.
78       *
79       * @since 3.20.0
80       */
81      public static final class Split extends ImmutablePair<String, Duration> {
82  
83          /**
84           * Constructs a Split object with label and duration.
85           *
86           * @param label Label for this split.
87           * @param duration Duration for this split.
88           */
89          public Split(String label, Duration duration) {
90              super(label, duration);
91          }
92  
93          /**
94           * Gets the duration of this split.
95           *
96           * @return The duration of this split.
97           */
98          public Duration getDuration() {
99              return getRight();
100         }
101 
102         /**
103          * Gets the label of this split.
104          *
105          * @return The label of this split.
106          */
107         public String getLabel() {
108             return getLeft();
109         }
110 
111         /**
112          * Converts this instance to a string.
113          *
114          * @return this instance to a string.
115          */
116         @Override
117         public String toString() {
118             return String.format("Split [%s, %s])", getLabel(), getDuration());
119         }
120     }
121 
122     /**
123      * Enumeration type which indicates the split status of a StopWatch.
124      */
125     private enum SplitState {
126         SPLIT, UNSPLIT
127     }
128 
129     /**
130      * Enumeration type which indicates the status of a StopWatch.
131      */
132     private enum State {
133 
134         RUNNING {
135             @Override
136             boolean isStarted() {
137                 return true;
138             }
139 
140             @Override
141             boolean isStopped() {
142                 return false;
143             }
144 
145             @Override
146             boolean isSuspended() {
147                 return false;
148             }
149         },
150 
151         STOPPED {
152             @Override
153             boolean isStarted() {
154                 return false;
155             }
156 
157             @Override
158             boolean isStopped() {
159                 return true;
160             }
161 
162             @Override
163             boolean isSuspended() {
164                 return false;
165             }
166         },
167 
168         SUSPENDED {
169             @Override
170             boolean isStarted() {
171                 return true;
172             }
173 
174             @Override
175             boolean isStopped() {
176                 return false;
177             }
178 
179             @Override
180             boolean isSuspended() {
181                 return true;
182             }
183         },
184 
185         UNSTARTED {
186             @Override
187             boolean isStarted() {
188                 return false;
189             }
190 
191             @Override
192             boolean isStopped() {
193                 return true;
194             }
195 
196             @Override
197             boolean isSuspended() {
198                 return false;
199             }
200         };
201 
202         /**
203          * Tests whether this StopWatch is started. A suspended StopWatch is also started.
204          *
205          * @return boolean If this StopWatch is started.
206          */
207         abstract boolean isStarted();
208 
209         /**
210          * Tests whether this StopWatch is stopped. A StopWatch which is not yet started and explicitly stopped is considered stopped.
211          *
212          * @return boolean If this StopWatch is stopped.
213          */
214         abstract boolean isStopped();
215 
216         /**
217          * Tests whether this StopWatch is suspended.
218          *
219          * @return boolean If this StopWatch is suspended.
220          */
221         abstract boolean isSuspended();
222     }
223 
224     private static final long NANO_2_MILLIS = 1_000_000L;
225 
226     /**
227      * Creates a StopWatch.
228      *
229      * @return StopWatch a StopWatch.
230      * @since 3.10
231      */
232     public static StopWatch create() {
233         return new StopWatch();
234     }
235 
236     /**
237      * Creates and starts a StopWatch.
238      *
239      * @return StopWatch a started StopWatch.
240      * @since 3.5
241      */
242     public static StopWatch createStarted() {
243         final StopWatch sw = new StopWatch();
244         sw.start();
245         return sw;
246     }
247 
248     /**
249      * A message for string presentation.
250      *
251      * @since 3.10
252      */
253     private final String message;
254 
255     /**
256      * The current running state of this StopWatch.
257      */
258     private State runningState = State.UNSTARTED;
259 
260     /**
261      * Whether this StopWatch has a split time recorded.
262      */
263     private SplitState splitState = SplitState.UNSPLIT;
264 
265     /**
266      * The start time in nanoseconds.
267      *
268      * This field can be removed once we move off of Java 8.
269      */
270     private long startTimeNanos;
271 
272     /**
273      * The start Instant.
274      * <p>
275      * nanoTime is only for elapsed time so we need to also store the currentTimeMillis to maintain the old getStartTime API.
276      * </p>
277      * <p>
278      * On Java 8, Instant has millisecond precision, later versions use nanoseconds.
279      * </p>
280      */
281     private Instant startInstant;
282 
283     /**
284      * The end Instant.
285      * <p>
286      * nanoTime is only for elapsed time so we need to also store the currentTimeMillis to maintain the old getStartTime API.
287      * </p>
288      * <p>
289      * On Java 8, Instant has millisecond precision, later versions use nanoseconds.
290      * </p>
291      */
292     private Instant stopInstant;
293 
294     /**
295      * The stop time in nanoseconds.
296      *
297      * This field can be removed once we move off of Java 8.
298      */
299     private long stopTimeNanos;
300 
301     /**
302      * The split list.
303      */
304     private final List<Split> splits = new ArrayList<>();
305 
306     /**
307      * Constructs a new instance.
308      */
309     public StopWatch() {
310         this(null);
311     }
312 
313     /**
314      * Constructs a new instance.
315      *
316      * @param message A message for string presentation.
317      * @since 3.10
318      */
319     public StopWatch(final String message) {
320         this.message = message;
321     }
322 
323     /**
324      * Formats the split time with {@link DurationFormatUtils#formatDurationHMS}.
325      *
326      * @return the split time formatted by {@link DurationFormatUtils#formatDurationHMS}.
327      * @since 3.10
328      */
329     public String formatSplitTime() {
330         return DurationFormatUtils.formatDurationHMS(getSplitDuration().toMillis());
331     }
332 
333     /**
334      * Formats the time formatted with {@link DurationFormatUtils#formatDurationHMS}.
335      *
336      * @return the time formatted by {@link DurationFormatUtils#formatDurationHMS}.
337      * @since 3.10
338      */
339     public String formatTime() {
340         return DurationFormatUtils.formatDurationHMS(getTime());
341     }
342 
343     /**
344      * Delegates to {@link Supplier#get()} while recording the duration of the call.
345      *
346      * @param <T>      the type of results supplied by this supplier.
347      * @param supplier The supplier to {@link Supplier#get()}.
348      * @return a result from the given Supplier.
349      * @since 3.18.0
350      */
351     public <T> T get(final Supplier<T> supplier) {
352         startResume();
353         try {
354             return supplier.get();
355         } finally {
356             suspend();
357         }
358     }
359 
360     /**
361      * Gets the Duration on this StopWatch.
362      *
363      * <p>
364      * This is either the Duration between the start and the moment this method is called, or the Duration between start and stop.
365      * </p>
366      *
367      * @return the Duration.
368      * @since 3.16.0
369      */
370     public Duration getDuration() {
371         return Duration.ofNanos(getNanoTime());
372     }
373 
374     /**
375      * Gets the message for string presentation.
376      *
377      * @return the message for string presentation.
378      * @since 3.10
379      */
380     public String getMessage() {
381         return message;
382     }
383 
384     /**
385      * Gets the <em>elapsed</em> time in nanoseconds.
386      *
387      * <p>
388      * This is either the time between the start and the moment this method is called, or the amount of time between start and stop.
389      * </p>
390      *
391      * @return the <em>elapsed</em> time in nanoseconds.
392      * @see System#nanoTime()
393      * @since 3.0
394      */
395     public long getNanoTime() {
396         switch (runningState) {
397         case STOPPED:
398         case SUSPENDED:
399             return stopTimeNanos - startTimeNanos;
400         case UNSTARTED:
401             return 0;
402         case RUNNING:
403             return System.nanoTime() - startTimeNanos;
404         default:
405             break;
406         }
407         throw new IllegalStateException("Illegal running state has occurred.");
408     }
409 
410     /**
411      * Gets the split Duration on this StopWatch.
412      *
413      * <p>
414      * This is the Duration between start and latest split.
415      * </p>
416      *
417      * @return the split Duration.
418      * @throws IllegalStateException if this StopWatch has not yet been split.
419      * @since 3.16.0
420      */
421     public Duration getSplitDuration() {
422         return Duration.ofNanos(getSplitNanoTime());
423     }
424 
425     /**
426      * Gets the split time in nanoseconds.
427      *
428      * <p>
429      * This is the time between start and latest split.
430      * </p>
431      *
432      * @return the split time in nanoseconds.
433      * @throws IllegalStateException if this StopWatch has not yet been split.
434      * @since 3.0
435      */
436     public long getSplitNanoTime() {
437         if (splitState != SplitState.SPLIT) {
438             throw new IllegalStateException("Stopwatch must be split to get the split time.");
439         }
440         return splits.get(splits.size() - 1).getRight().toNanos();
441     }
442 
443     /**
444      * Gets the split list.
445      *
446      * @return the list of splits.
447      * @since 3.20.0
448      */
449     public List<Split> getSplits() {
450         return Collections.unmodifiableList(splits);
451     }
452 
453     /**
454      * Gets the split time on this StopWatch.
455      *
456      * <p>
457      * This is the time between start and latest split.
458      * </p>
459      *
460      * @return the split time in milliseconds.
461      * @throws IllegalStateException if this StopWatch has not yet been split.
462      * @since 2.1
463      * @deprecated Use {@link #getSplitDuration()}.
464      */
465     @Deprecated
466     public long getSplitTime() {
467         return nanosToMillis(getSplitNanoTime());
468     }
469 
470     /**
471      * Gets the Instant this StopWatch was started, between the current time and midnight, January 1, 1970 UTC.
472      *
473      * @return the Instant this StopWatch was started, between the current time and midnight, January 1, 1970 UTC.
474      * @throws IllegalStateException if this StopWatch has not been started.
475      * @since 3.16.0
476      */
477     public Instant getStartInstant() {
478         if (runningState == State.UNSTARTED) {
479             throw new IllegalStateException("Stopwatch has not been started");
480         }
481         return startInstant;
482     }
483 
484     /**
485      * Gets the time this StopWatch was started in milliseconds, between the current time and midnight, January 1, 1970 UTC.
486      *
487      * @return the time this StopWatch was started in milliseconds, between the current time and midnight, January 1, 1970 UTC.
488      * @throws IllegalStateException if this StopWatch has not been started.
489      * @since 2.4
490      * @deprecated Use {@link #getStartInstant()}.
491      */
492     @Deprecated
493     public long getStartTime() {
494         return getStartInstant().toEpochMilli();
495     }
496 
497     /**
498      * Gets the Instant this StopWatch was stopped, between the current time and midnight, January 1, 1970 UTC.
499      *
500      * @return the Instant this StopWatch was stopped in milliseconds, between the current time and midnight, January 1, 1970 UTC.
501      * @throws IllegalStateException if this StopWatch has not been started.
502      * @since 3.16.0
503      */
504     public Instant getStopInstant() {
505         if (runningState == State.UNSTARTED) {
506             throw new IllegalStateException("Stopwatch has not been started");
507         }
508         return stopInstant;
509     }
510 
511     /**
512      * Gets the time this StopWatch was stopped in milliseconds, between the current time and midnight, January 1, 1970 UTC.
513      *
514      * @return the time this StopWatch was stopped in milliseconds, between the current time and midnight, January 1, 1970 UTC.
515      * @throws IllegalStateException if this StopWatch has not been started.
516      * @since 3.12.0
517      * @deprecated Use {@link #getStopInstant()}.
518      */
519     @Deprecated
520     public long getStopTime() {
521         // stopTimeNanos stores System.nanoTime() for elapsed time
522         final Instant stop = getStopInstant();
523         return stop != null ? stop.toEpochMilli() : 0;
524     }
525 
526     /**
527      * Delegates to {@link FailableSupplier#get()} while recording the duration of the call.
528      *
529      * @param <T>      the type of results supplied by this supplier.
530      * @param <E>      The kind of thrown exception or error.
531      * @param supplier The supplier to {@link Supplier#get()}.
532      * @return a result from the given Supplier.
533      * @throws Throwable if the supplier fails.
534      * @since 3.18.0
535      */
536     public <T, E extends Throwable> T getT(final FailableSupplier<T, E> supplier) throws Throwable {
537         startResume();
538         try {
539             return supplier.get();
540         } finally {
541             suspend();
542         }
543     }
544 
545     /**
546      * Gets the time on this StopWatch.
547      *
548      * <p>
549      * This is either the time between the start and the moment this method is called, or the amount of time between start and stop.
550      * </p>
551      *
552      * @return the time in milliseconds.
553      * @see #getDuration()
554      */
555     public long getTime() {
556         return nanosToMillis(getNanoTime());
557     }
558 
559     /**
560      * Gets the time in the specified TimeUnit.
561      *
562      * <p>
563      * 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
564      * expressed in the desired TimeUnit with any remainder rounded down. For example, if the specified unit is {@code TimeUnit.HOURS} and this StopWatch time
565      * is 59 minutes, then the result returned will be {@code 0}.
566      * </p>
567      *
568      * @param timeUnit the unit of time, not null.
569      * @return the time in the specified TimeUnit, rounded down.
570      * @since 3.5
571      */
572     public long getTime(final TimeUnit timeUnit) {
573         return timeUnit.convert(getNanoTime(), TimeUnit.NANOSECONDS);
574     }
575 
576     /**
577      * Tests whether this StopWatch is started. A suspended StopWatch is also started watch.
578      *
579      * @return boolean If this StopWatch is started.
580      * @since 3.2
581      */
582     public boolean isStarted() {
583         return runningState.isStarted();
584     }
585 
586     /**
587      * Tests whether StopWatch is stopped. this StopWatch which's not yet started and explicitly stopped StopWatch is considered as stopped.
588      *
589      * @return boolean If this StopWatch is stopped.
590      * @since 3.2
591      */
592     public boolean isStopped() {
593         return runningState.isStopped();
594     }
595 
596     /**
597      * Tests whether this StopWatch is suspended.
598      *
599      * @return boolean If this StopWatch is suspended.
600      * @since 3.2
601      */
602     public boolean isSuspended() {
603         return runningState.isSuspended();
604     }
605 
606     /**
607      * Converts nanoseconds to milliseconds.
608      *
609      * @param nanos nanoseconds to convert.
610      * @return milliseconds conversion result.
611      */
612     private long nanosToMillis(final long nanos) {
613         return nanos / NANO_2_MILLIS;
614     }
615 
616     /**
617      * Resets this StopWatch. Stops it if need be.
618      *
619      * <p>
620      * This method clears the internal values to allow the object to be reused.
621      * </p>
622      */
623     public void reset() {
624         runningState = State.UNSTARTED;
625         splitState = SplitState.UNSPLIT;
626         splits.clear();
627     }
628 
629     /**
630      * Resumes this StopWatch after a suspend.
631      *
632      * <p>
633      * 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.
634      * </p>
635      *
636      * @throws IllegalStateException if this StopWatch has not been suspended.
637      */
638     public void resume() {
639         if (runningState != State.SUSPENDED) {
640             throw new IllegalStateException("Stopwatch must be suspended to resume.");
641         }
642         startTimeNanos += System.nanoTime() - stopTimeNanos;
643         runningState = State.RUNNING;
644     }
645 
646     /**
647      * Delegates to {@link Runnable#run()} while recording the duration of the call.
648      *
649      * @param runnable The runnable to {@link Runnable#run()}.
650      * @since 3.18.0
651      */
652     public void run(final Runnable runnable) {
653         startResume();
654         try {
655             runnable.run();
656         } finally {
657             suspend();
658         }
659     }
660 
661     /**
662      * Delegates to {@link FailableRunnable#run()} while recording the duration of the call.
663      *
664      * @param <E>      The kind of {@link Throwable}.
665      * @param runnable The runnable to {@link FailableRunnable#run()}.
666      * @throws Throwable Thrown by {@link FailableRunnable#run()}.
667      * @since 3.18.0
668      */
669     public <E extends Throwable> void runT(final FailableRunnable<E> runnable) throws Throwable {
670         startResume();
671         try {
672             runnable.run();
673         } finally {
674             suspend();
675         }
676     }
677 
678     /**
679      * Splits the time.
680      *
681      * <p>
682      * 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
683      * timing from the original start point.
684      * </p>
685      *
686      * @throws IllegalStateException if this StopWatch is not running.
687      */
688     public void split() {
689         if (runningState != State.RUNNING) {
690             throw new IllegalStateException("Stopwatch is not running.");
691         }
692         stopSet();
693         splitState = SplitState.SPLIT;
694         splits.add(new Split(String.valueOf(splits.size()), Duration.ofNanos(stopTimeNanos - startTimeNanos)));
695     }
696 
697     /**
698      * Splits the time with a label.
699      *
700      * <p>
701      * 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
702      * timing from the original start point.
703      * </p>
704      *
705      * @param label A message for string presentation.
706      * @throws IllegalStateException if the StopWatch is not running.
707      * @since 3.20.0
708      */
709     public void split(final String label) {
710         if (runningState != State.RUNNING) {
711             throw new IllegalStateException("Stopwatch is not running.");
712         }
713         stopSet();
714         splitState = SplitState.SPLIT;
715         splits.add(new Split(label, Duration.ofNanos(stopTimeNanos - startTimeNanos)));
716     }
717 
718     /**
719      * Starts this StopWatch.
720      *
721      * <p>
722      * This method starts a new timing session, clearing any previous values.
723      * </p>
724      *
725      * @throws IllegalStateException if this StopWatch is already running.
726      */
727     public void start() {
728         if (runningState == State.STOPPED) {
729             throw new IllegalStateException("Stopwatch must be reset before being restarted.");
730         }
731         if (runningState != State.UNSTARTED) {
732             throw new IllegalStateException("Stopwatch already started.");
733         }
734         startTimeNanos = System.nanoTime();
735         startInstant = Instant.now();
736         runningState = State.RUNNING;
737         splits.clear();
738     }
739 
740     /**
741      * Starts or resumes this StopWatch.
742      */
743     private void startResume() {
744         if (isStopped()) {
745             start();
746         } else if (isSuspended()) {
747             resume();
748         }
749     }
750 
751     /**
752      * Stops this StopWatch.
753      *
754      * <p>
755      * This method ends a new timing session, allowing the time to be retrieved.
756      * </p>
757      *
758      * @throws IllegalStateException if this StopWatch is not running.
759      */
760     public void stop() {
761         if (runningState != State.RUNNING && runningState != State.SUSPENDED) {
762             throw new IllegalStateException("Stopwatch is not running.");
763         }
764         if (runningState == State.RUNNING) {
765             stopSet();
766         }
767         runningState = State.STOPPED;
768     }
769 
770     private void stopSet() {
771         stopTimeNanos = System.nanoTime();
772         stopInstant = Instant.now();
773     }
774 
775     /**
776      * Suspends this StopWatch for later resumption.
777      *
778      * <p>
779      * 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.
780      * </p>
781      *
782      * @throws IllegalStateException if this StopWatch is not currently running.
783      */
784     public void suspend() {
785         if (runningState != State.RUNNING) {
786             throw new IllegalStateException("Stopwatch must be running to suspend.");
787         }
788         stopSet();
789         runningState = State.SUSPENDED;
790     }
791 
792     /**
793      * Gets a summary of the last split time that this StopWatch recorded as a string.
794      *
795      * <p>
796      * The format used is ISO 8601-like, [<em>message</em> ]<em>hours</em>:<em>minutes</em>:<em>seconds</em>.<em>milliseconds</em>.
797      * </p>
798      *
799      * @return the split time as a String.
800      * @since 2.1
801      * @since 3.10 Returns the prefix {@code "message "} if the message is set.
802      */
803     public String toSplitString() {
804         final String msgStr = Objects.toString(message, StringUtils.EMPTY);
805         final String formattedTime = formatSplitTime();
806         return msgStr.isEmpty() ? formattedTime : msgStr + StringUtils.SPACE + formattedTime;
807     }
808 
809     /**
810      * Gets a summary of the time that this StopWatch recorded as a string.
811      *
812      * <p>
813      * The format used is ISO 8601-like, [<em>message</em> ]<em>hours</em>:<em>minutes</em>:<em>seconds</em>.<em>milliseconds</em>.
814      * </p>
815      *
816      * @return the time as a String.
817      * @since 3.10 Returns the prefix {@code "message "} if the message is set.
818      */
819     @Override
820     public String toString() {
821         final String msgStr = Objects.toString(message, StringUtils.EMPTY);
822         final String formattedTime = formatTime();
823         return msgStr.isEmpty() ? formattedTime : msgStr + StringUtils.SPACE + formattedTime;
824     }
825 
826     /**
827      * Removes the split.
828      *
829      * <p>
830      * This method clears the stop time. The start time is unaffected, enabling timing from the original start point to continue.
831      * </p>
832      *
833      * @throws IllegalStateException if this StopWatch has not been split.
834      */
835     public void unsplit() {
836         if (splitState != SplitState.SPLIT) {
837             throw new IllegalStateException("Stopwatch has not been split.");
838         }
839         splitState = SplitState.UNSPLIT;
840         splits.remove(splits.size() - 1);
841     }
842 }