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