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