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