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