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.Objects;
23  import java.util.concurrent.TimeUnit;
24  import java.util.function.Supplier;
25  
26  import org.apache.commons.lang3.StringUtils;
27  import org.apache.commons.lang3.function.FailableConsumer;
28  import org.apache.commons.lang3.function.FailableRunnable;
29  import org.apache.commons.lang3.function.FailableSupplier;
30  
31  /**
32   * {@link StopWatch} provides a convenient API for timings.
33   *
34   * <p>
35   * To start the watch, call {@link #start()} or {@link StopWatch#createStarted()}. At this point you can:
36   * </p>
37   * <ul>
38   * <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
39   * point, these three options are available again.</li>
40   * <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
41   * the total. At this point, these three options are available again.</li>
42   * <li>{@link #stop()} the watch to complete the timing session.</li>
43   * </ul>
44   *
45   * <p>
46   * It is intended that the output methods {@link #toString()} and {@link #getTime()} should only be called after stop, split or suspend, however a suitable
47   * result will be returned at other points.
48   * </p>
49   *
50   * <p>
51   * 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
52   * split.
53   * </p>
54   *
55   * <ol>
56   * <li>{@link #split()}, {@link #suspend()}, or {@link #stop()} cannot be invoked twice</li>
57   * <li>{@link #unsplit()} may only be called if the watch has been {@link #split()}</li>
58   * <li>{@link #resume()} may only be called if the watch has been {@link #suspend()}</li>
59   * <li>{@link #start()} cannot be called twice without calling {@link #reset()}</li>
60   * </ol>
61   *
62   * <p>
63   * This class is not thread-safe
64   * </p>
65   *
66   * @see DurationUtils#of(FailableRunnable)
67   * @see DurationUtils#of(FailableConsumer)
68   * @since 2.0
69   */
70  public class StopWatch {
71  
72      /**
73       * Enumeration type which indicates the split status of a StopWatch.
74       */
75      private enum SplitState {
76          SPLIT, UNSPLIT
77      }
78  
79      /**
80       * Enumeration type which indicates the status of a StopWatch.
81       */
82      private enum State {
83  
84          RUNNING {
85              @Override
86              boolean isStarted() {
87                  return true;
88              }
89  
90              @Override
91              boolean isStopped() {
92                  return false;
93              }
94  
95              @Override
96              boolean isSuspended() {
97                  return false;
98              }
99          },
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 }