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