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    *      http://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.util.Objects;
21  import java.util.concurrent.TimeUnit;
22  
23  import org.apache.commons.lang3.StringUtils;
24  
25  /**
26   * <p>
27   * {@code StopWatch} provides a convenient API for timings.
28   * </p>
29   *
30   * <p>
31   * To start the watch, call {@link #start()} or {@link StopWatch#createStarted()}. At this point you can:
32   * </p>
33   * <ul>
34   * <li>{@link #split()} the watch to get the time whilst the watch continues in the background. {@link #unsplit()} will
35   * remove the effect of the split. At this point, these three options are available again.</li>
36   * <li>{@link #suspend()} the watch to pause it. {@link #resume()} allows the watch to continue. Any time between the
37   * suspend and resume will not be counted in the total. At this point, these three options are available again.</li>
38   * <li>{@link #stop()} the watch to complete the timing session.</li>
39   * </ul>
40   *
41   * <p>
42   * It is intended that the output methods {@link #toString()} and {@link #getTime()} should only be called after stop,
43   * split or suspend, however a suitable result will be returned at other points.
44   * </p>
45   *
46   * <p>
47   * NOTE: As from v2.1, the methods protect against inappropriate calls. Thus you cannot now call stop before start,
48   * resume before suspend or unsplit before split.
49   * </p>
50   *
51   * <p>
52   * 1. split(), suspend(), or stop() cannot be invoked twice<br>
53   * 2. unsplit() may only be called if the watch has been split()<br>
54   * 3. resume() may only be called if the watch has been suspend()<br>
55   * 4. start() cannot be called twice without calling reset()
56   * </p>
57   *
58   * <p>This class is not thread-safe</p>
59   *
60   * @since 2.0
61   */
62  public class StopWatch {
63  
64      /**
65       * Enumeration type which indicates the split status of stopwatch.
66       */
67      private enum SplitState {
68          SPLIT,
69          UNSPLIT
70      }
71  
72      /**
73       * Enumeration type which indicates the status of stopwatch.
74       */
75      private enum State {
76  
77          RUNNING {
78              @Override
79              boolean isStarted() {
80                  return true;
81              }
82              @Override
83              boolean isStopped() {
84                  return false;
85              }
86              @Override
87              boolean isSuspended() {
88                  return false;
89              }
90          },
91          STOPPED {
92              @Override
93              boolean isStarted() {
94                  return false;
95              }
96              @Override
97              boolean isStopped() {
98                  return true;
99              }
100             @Override
101             boolean isSuspended() {
102                 return false;
103             }
104         },
105         SUSPENDED {
106             @Override
107             boolean isStarted() {
108                 return true;
109             }
110             @Override
111             boolean isStopped() {
112                 return false;
113             }
114             @Override
115             boolean isSuspended() {
116                 return true;
117             }
118         },
119         UNSTARTED {
120             @Override
121             boolean isStarted() {
122                 return false;
123             }
124             @Override
125             boolean isStopped() {
126                 return true;
127             }
128             @Override
129             boolean isSuspended() {
130                 return false;
131             }
132         };
133 
134         /**
135          * <p>
136          * Returns whether the StopWatch is started. A suspended StopWatch is also started watch.
137          * </p>
138          *
139          * @return boolean If the StopWatch is started.
140          */
141         abstract boolean isStarted();
142 
143         /**
144          * <p>
145          * Returns whether the StopWatch is stopped. The stopwatch which's not yet started and explicitly stopped stopwatch is
146          * considered as stopped.
147          * </p>
148          *
149          * @return boolean If the StopWatch is stopped.
150          */
151         abstract boolean isStopped();
152 
153         /**
154          * <p>
155          * Returns whether the StopWatch is suspended.
156          * </p>
157          *
158          * @return boolean
159          *             If the StopWatch is suspended.
160          */
161         abstract boolean isSuspended();
162     }
163 
164     private static final long NANO_2_MILLIS = 1000000L;
165 
166     /**
167      * Creates a stopwatch for convenience.
168      *
169      * @return StopWatch a stopwatch.
170      *
171      * @since 3.10
172      */
173     public static StopWatch create() {
174         return new StopWatch();
175     }
176 
177     /**
178      * Creates a started stopwatch for convenience.
179      *
180      * @return StopWatch a stopwatch that's already been started.
181      *
182      * @since 3.5
183      */
184     public static StopWatch createStarted() {
185         final StopWatch/time/StopWatch.html#StopWatch">StopWatch sw = new StopWatch();
186         sw.start();
187         return sw;
188     }
189 
190     /**
191      * A message for string presentation.
192      *
193      * @since 3.10
194      */
195     private final String message;
196 
197     /**
198      * The current running state of the StopWatch.
199      */
200     private State runningState = State.UNSTARTED;
201 
202     /**
203      * Whether the stopwatch has a split time recorded.
204      */
205     private SplitState splitState = SplitState.UNSPLIT;
206 
207     /**
208      * The start time in nanoseconds.
209      */
210     private long startTimeNanos;
211 
212     /**
213      * The start time in milliseconds - nanoTime is only for elapsed time so we
214      * need to also store the currentTimeMillis to maintain the old
215      * getStartTime API.
216      */
217     private long startTimeMillis;
218 
219     /**
220      * The end time in milliseconds - nanoTime is only for elapsed time so we
221      * need to also store the currentTimeMillis to maintain the old
222      * getStartTime API.
223      */
224     private long stopTimeMillis;
225 
226     /**
227      * The stop time in nanoseconds.
228      */
229     private long stopTimeNanos;
230 
231     /**
232      * <p>
233      * Constructor.
234      * </p>
235      */
236     public StopWatch() {
237         this(null);
238     }
239 
240     /**
241      * <p>
242      * Constructor.
243      * </p>
244      * @param message A message for string presentation.
245      * @since 3.10
246      */
247     public StopWatch(final String message) {
248         this.message = message;
249     }
250 
251     /**
252      * Returns the time formatted by {@link DurationFormatUtils#formatDurationHMS}.
253      *
254      * @return the time formatted by {@link DurationFormatUtils#formatDurationHMS}.
255      * @since 3.10
256      */
257     public String formatSplitTime() {
258         return DurationFormatUtils.formatDurationHMS(getSplitTime());
259     }
260 
261     /**
262      * Returns the split time formatted by {@link DurationFormatUtils#formatDurationHMS}.
263      *
264      * @return the split time formatted by {@link DurationFormatUtils#formatDurationHMS}.
265      * @since 3.10
266      */
267     public String formatTime() {
268         return DurationFormatUtils.formatDurationHMS(getTime());
269     }
270 
271     /**
272      * Gets the message for string presentation.
273      *
274      * @return the message for string presentation.
275      * @since 3.10
276      */
277     public String getMessage() {
278         return message;
279     }
280 
281     /**
282      * <p>
283      * Gets the <em>elapsed</em> time in nanoseconds.
284      * </p>
285      *
286      * <p>
287      * This is either the time between the start and the moment this method is called, or the amount of time between
288      * start and stop.
289      * </p>
290      *
291      * @return the <em>elapsed</em> time in nanoseconds.
292      * @see System#nanoTime()
293      * @since 3.0
294      */
295     public long getNanoTime() {
296         if (this.runningState == State.STOPPED || this.runningState == State.SUSPENDED) {
297             return this.stopTimeNanos - this.startTimeNanos;
298         } else if (this.runningState == State.UNSTARTED) {
299             return 0;
300         } else if (this.runningState == State.RUNNING) {
301             return System.nanoTime() - this.startTimeNanos;
302         }
303         throw new IllegalStateException("Illegal running state has occurred.");
304     }
305 
306     /**
307      * <p>
308      * Gets the split time in nanoseconds.
309      * </p>
310      *
311      * <p>
312      * This is the time between start and latest split.
313      * </p>
314      *
315      * @return the split time in nanoseconds
316      *
317      * @throws IllegalStateException
318      *             if the StopWatch has not yet been split.
319      * @since 3.0
320      */
321     public long getSplitNanoTime() {
322         if (this.splitState != SplitState.SPLIT) {
323             throw new IllegalStateException("Stopwatch must be split to get the split time.");
324         }
325         return this.stopTimeNanos - this.startTimeNanos;
326     }
327 
328     /**
329      * <p>
330      * Gets the split time on the stopwatch.
331      * </p>
332      *
333      * <p>
334      * This is the time between start and latest split.
335      * </p>
336      *
337      * @return the split time in milliseconds
338      *
339      * @throws IllegalStateException
340      *             if the StopWatch has not yet been split.
341      * @since 2.1
342      */
343     public long getSplitTime() {
344         return getSplitNanoTime() / NANO_2_MILLIS;
345     }
346 
347     /**
348      * Gets the time this stopwatch was started in milliseconds, between the current time and midnight, January 1, 1970
349      * UTC.
350      *
351      * @return the time this stopwatch was started in milliseconds, between the current time and midnight, January 1,
352      *         1970 UTC.
353      * @throws IllegalStateException if this StopWatch has not been started
354      * @since 2.4
355      */
356     public long getStartTime() {
357         if (this.runningState == State.UNSTARTED) {
358             throw new IllegalStateException("Stopwatch has not been started");
359         }
360         // System.nanoTime is for elapsed time
361         return this.startTimeMillis;
362     }
363 
364     /**
365      * Gets the time this stopwatch was stopped in milliseconds, between the current time and midnight, January 1, 1970
366      * UTC.
367      *
368      * @return the time this stopwatch was started in milliseconds, between the current time and midnight, January 1,
369      *         1970 UTC.
370      * @throws IllegalStateException if this StopWatch has not been started
371      * @since 3.12.0
372      */
373     public long getStopTime() {
374         if (this.runningState == State.UNSTARTED) {
375             throw new IllegalStateException("Stopwatch has not been started");
376         }
377         // System.nanoTime is for elapsed time
378         return this.stopTimeMillis;
379     }
380 
381     /**
382      * <p>
383      * Gets the time on the stopwatch.
384      * </p>
385      *
386      * <p>
387      * This is either the time between the start and the moment this method is called, or the amount of time between
388      * start and stop.
389      * </p>
390      *
391      * @return the time in milliseconds
392      */
393     public long getTime() {
394         return getNanoTime() / NANO_2_MILLIS;
395     }
396 
397     /**
398      * <p>
399      * Gets the time in the specified TimeUnit.
400      * </p>
401      *
402      * <p>
403      * This is either the time between the start and the moment this method is called, or the amount of time between
404      * start and stop. The resulting time will be expressed in the desired TimeUnit with any remainder rounded down.
405      * For example, if the specified unit is {@code TimeUnit.HOURS} and the stopwatch time is 59 minutes, then the
406      * result returned will be {@code 0}.
407      * </p>
408      *
409      * @param timeUnit the unit of time, not null
410      * @return the time in the specified TimeUnit, rounded down
411      * @since 3.5
412      */
413     public long getTime(final TimeUnit timeUnit) {
414         return timeUnit.convert(getNanoTime(), TimeUnit.NANOSECONDS);
415     }
416 
417     /**
418      * <p>
419      * Returns whether the StopWatch is started. A suspended StopWatch is also started watch.
420      * </p>
421      *
422      * @return boolean If the StopWatch is started.
423      * @since 3.2
424      */
425     public boolean isStarted() {
426         return runningState.isStarted();
427     }
428 
429     /**
430      * <p>
431      * Returns whether StopWatch is stopped. The stopwatch which's not yet started and explicitly stopped stopwatch is considered
432      * as stopped.
433      * </p>
434      *
435      * @return boolean If the StopWatch is stopped.
436      * @since 3.2
437      */
438     public boolean isStopped() {
439         return runningState.isStopped();
440     }
441 
442     /**
443      * <p>
444      * Returns whether the StopWatch is suspended.
445      * </p>
446      *
447      * @return boolean
448      *             If the StopWatch is suspended.
449      * @since 3.2
450      */
451     public boolean isSuspended() {
452         return runningState.isSuspended();
453     }
454 
455     /**
456      * <p>
457      * Resets the stopwatch. Stops it if need be.
458      * </p>
459      *
460      * <p>
461      * This method clears the internal values to allow the object to be reused.
462      * </p>
463      */
464     public void reset() {
465         this.runningState = State.UNSTARTED;
466         this.splitState = SplitState.UNSPLIT;
467     }
468 
469     /**
470      * <p>
471      * Resumes the stopwatch after a suspend.
472      * </p>
473      *
474      * <p>
475      * This method resumes the watch after it was suspended. The watch will not include time between the suspend and
476      * resume calls in the total time.
477      * </p>
478      *
479      * @throws IllegalStateException
480      *             if the StopWatch has not been suspended.
481      */
482     public void resume() {
483         if (this.runningState != State.SUSPENDED) {
484             throw new IllegalStateException("Stopwatch must be suspended to resume. ");
485         }
486         this.startTimeNanos += System.nanoTime() - this.stopTimeNanos;
487         this.runningState = State.RUNNING;
488     }
489 
490     /**
491      * <p>
492      * Splits the time.
493      * </p>
494      *
495      * <p>
496      * This method sets the stop time of the watch to allow a time to be extracted. The start time is unaffected,
497      * enabling {@link #unsplit()} to continue the timing from the original start point.
498      * </p>
499      *
500      * @throws IllegalStateException
501      *             if the StopWatch is not running.
502      */
503     public void split() {
504         if (this.runningState != State.RUNNING) {
505             throw new IllegalStateException("Stopwatch is not running. ");
506         }
507         this.stopTimeNanos = System.nanoTime();
508         this.splitState = SplitState.SPLIT;
509     }
510 
511     /**
512      * <p>
513      * Starts the stopwatch.
514      * </p>
515      *
516      * <p>
517      * This method starts a new timing session, clearing any previous values.
518      * </p>
519      *
520      * @throws IllegalStateException
521      *             if the StopWatch is already running.
522      */
523     public void start() {
524         if (this.runningState == State.STOPPED) {
525             throw new IllegalStateException("Stopwatch must be reset before being restarted. ");
526         }
527         if (this.runningState != State.UNSTARTED) {
528             throw new IllegalStateException("Stopwatch already started. ");
529         }
530         this.startTimeNanos = System.nanoTime();
531         this.startTimeMillis = System.currentTimeMillis();
532         this.runningState = State.RUNNING;
533     }
534 
535     /**
536      * <p>
537      * Stops the stopwatch.
538      * </p>
539      *
540      * <p>
541      * This method ends a new timing session, allowing the time to be retrieved.
542      * </p>
543      *
544      * @throws IllegalStateException
545      *             if the StopWatch is not running.
546      */
547     public void stop() {
548         if (this.runningState != State.RUNNING && this.runningState != State.SUSPENDED) {
549             throw new IllegalStateException("Stopwatch is not running. ");
550         }
551         if (this.runningState == State.RUNNING) {
552             this.stopTimeNanos = System.nanoTime();
553             this.stopTimeMillis = System.currentTimeMillis();
554         }
555         this.runningState = State.STOPPED;
556     }
557 
558     /**
559      * <p>
560      * Suspends the stopwatch for later resumption.
561      * </p>
562      *
563      * <p>
564      * This method suspends the watch until it is resumed. The watch will not include time between the suspend and
565      * resume calls in the total time.
566      * </p>
567      *
568      * @throws IllegalStateException
569      *             if the StopWatch is not currently running.
570      */
571     public void suspend() {
572         if (this.runningState != State.RUNNING) {
573             throw new IllegalStateException("Stopwatch must be running to suspend. ");
574         }
575         this.stopTimeNanos = System.nanoTime();
576         this.stopTimeMillis = System.currentTimeMillis();
577         this.runningState = State.SUSPENDED;
578     }
579 
580     /**
581      * <p>
582      * Gets a summary of the split time that the stopwatch recorded as a string.
583      * </p>
584      *
585      * <p>
586      * The format used is ISO 8601-like, [<i>message</i> ]<i>hours</i>:<i>minutes</i>:<i>seconds</i>.<i>milliseconds</i>.
587      * </p>
588      *
589      * @return the split time as a String
590      * @since 2.1
591      * @since 3.10 Returns the prefix {@code "message "} if the message is set.
592      */
593     public String toSplitString() {
594         final String msgStr = Objects.toString(message, StringUtils.EMPTY);
595         final String formattedTime = formatSplitTime();
596         return msgStr.isEmpty() ? formattedTime : msgStr + StringUtils.SPACE + formattedTime;
597     }
598 
599     /**
600      * <p>
601      * Gets a summary of the time that the stopwatch recorded as a string.
602      * </p>
603      *
604      * <p>
605      * The format used is ISO 8601-like, [<i>message</i> ]<i>hours</i>:<i>minutes</i>:<i>seconds</i>.<i>milliseconds</i>.
606      * </p>
607      *
608      * @return the time as a String
609      * @since 3.10 Returns the prefix {@code "message "} if the message is set.
610      */
611     @Override
612     public String toString() {
613         final String msgStr = Objects.toString(message, StringUtils.EMPTY);
614         final String formattedTime = formatTime();
615         return msgStr.isEmpty() ? formattedTime : msgStr + StringUtils.SPACE + formattedTime;
616     }
617 
618     /**
619      * <p>
620      * Removes a split.
621      * </p>
622      *
623      * <p>
624      * This method clears the stop time. The start time is unaffected, enabling timing from the original start point to
625      * continue.
626      * </p>
627      *
628      * @throws IllegalStateException
629      *             if the StopWatch has not been split.
630      */
631     public void unsplit() {
632         if (this.splitState != SplitState.SPLIT) {
633             throw new IllegalStateException("Stopwatch has not been split. ");
634         }
635         this.splitState = SplitState.UNSPLIT;
636     }
637 
638 }