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 * http://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.util.Objects; 021import java.util.concurrent.TimeUnit; 022 023import org.apache.commons.lang3.StringUtils; 024 025/** 026 * <p> 027 * {@code StopWatch} provides a convenient API for timings. 028 * </p> 029 * 030 * <p> 031 * To start the watch, call {@link #start()} or {@link StopWatch#createStarted()}. At this point you can: 032 * </p> 033 * <ul> 034 * <li>{@link #split()} the watch to get the time whilst the watch continues in the background. {@link #unsplit()} will 035 * remove the effect of the split. At this point, these three options are available again.</li> 036 * <li>{@link #suspend()} the watch to pause it. {@link #resume()} allows the watch to continue. Any time between the 037 * suspend and resume will not be counted in the total. At this point, these three options are available again.</li> 038 * <li>{@link #stop()} the watch to complete the timing session.</li> 039 * </ul> 040 * 041 * <p> 042 * It is intended that the output methods {@link #toString()} and {@link #getTime()} should only be called after stop, 043 * split or suspend, however a suitable result will be returned at other points. 044 * </p> 045 * 046 * <p> 047 * NOTE: As from v2.1, the methods protect against inappropriate calls. Thus you cannot now call stop before start, 048 * resume before suspend or unsplit before split. 049 * </p> 050 * 051 * <p> 052 * 1. split(), suspend(), or stop() cannot be invoked twice<br> 053 * 2. unsplit() may only be called if the watch has been split()<br> 054 * 3. resume() may only be called if the watch has been suspend()<br> 055 * 4. start() cannot be called twice without calling reset() 056 * </p> 057 * 058 * <p>This class is not thread-safe</p> 059 * 060 * @since 2.0 061 */ 062public class StopWatch { 063 064 /** 065 * Enumeration type which indicates the split status of stopwatch. 066 */ 067 private enum SplitState { 068 SPLIT, 069 UNSPLIT 070 } 071 072 /** 073 * Enumeration type which indicates the status of stopwatch. 074 */ 075 private enum State { 076 077 RUNNING { 078 @Override 079 boolean isStarted() { 080 return true; 081 } 082 @Override 083 boolean isStopped() { 084 return false; 085 } 086 @Override 087 boolean isSuspended() { 088 return false; 089 } 090 }, 091 STOPPED { 092 @Override 093 boolean isStarted() { 094 return false; 095 } 096 @Override 097 boolean isStopped() { 098 return true; 099 } 100 @Override 101 boolean isSuspended() { 102 return false; 103 } 104 }, 105 SUSPENDED { 106 @Override 107 boolean isStarted() { 108 return true; 109 } 110 @Override 111 boolean isStopped() { 112 return false; 113 } 114 @Override 115 boolean isSuspended() { 116 return true; 117 } 118 }, 119 UNSTARTED { 120 @Override 121 boolean isStarted() { 122 return false; 123 } 124 @Override 125 boolean isStopped() { 126 return true; 127 } 128 @Override 129 boolean isSuspended() { 130 return false; 131 } 132 }; 133 134 /** 135 * <p> 136 * Returns whether the StopWatch is started. A suspended StopWatch is also started watch. 137 * </p> 138 * 139 * @return boolean If the StopWatch is started. 140 */ 141 abstract boolean isStarted(); 142 143 /** 144 * <p> 145 * Returns whether the StopWatch is stopped. The stopwatch which's not yet started and explicitly stopped stopwatch is 146 * considered as stopped. 147 * </p> 148 * 149 * @return boolean If the StopWatch is stopped. 150 */ 151 abstract boolean isStopped(); 152 153 /** 154 * <p> 155 * Returns whether the StopWatch is suspended. 156 * </p> 157 * 158 * @return boolean 159 * If the StopWatch is suspended. 160 */ 161 abstract boolean isSuspended(); 162 } 163 164 private static final long NANO_2_MILLIS = 1000000L; 165 166 /** 167 * Creates a stopwatch for convenience. 168 * 169 * @return StopWatch a stopwatch. 170 * 171 * @since 3.10 172 */ 173 public static StopWatch create() { 174 return new StopWatch(); 175 } 176 177 /** 178 * Creates a started stopwatch for convenience. 179 * 180 * @return StopWatch a stopwatch that's already been started. 181 * 182 * @since 3.5 183 */ 184 public static StopWatch createStarted() { 185 final StopWatch sw = new StopWatch(); 186 sw.start(); 187 return sw; 188 } 189 190 /** 191 * A message for string presentation. 192 * 193 * @since 3.10 194 */ 195 private final String message; 196 197 /** 198 * The current running state of the StopWatch. 199 */ 200 private State runningState = State.UNSTARTED; 201 202 /** 203 * Whether the stopwatch has a split time recorded. 204 */ 205 private SplitState splitState = SplitState.UNSPLIT; 206 207 /** 208 * The start time in nanoseconds. 209 */ 210 private long startTimeNanos; 211 212 /** 213 * The start time in milliseconds - nanoTime is only for elapsed time so we 214 * need to also store the currentTimeMillis to maintain the old 215 * getStartTime API. 216 */ 217 private long startTimeMillis; 218 219 /** 220 * The end time in milliseconds - nanoTime is only for elapsed time so we 221 * need to also store the currentTimeMillis to maintain the old 222 * getStartTime API. 223 */ 224 private long stopTimeMillis; 225 226 /** 227 * The stop time in nanoseconds. 228 */ 229 private long stopTimeNanos; 230 231 /** 232 * <p> 233 * Constructor. 234 * </p> 235 */ 236 public StopWatch() { 237 this(null); 238 } 239 240 /** 241 * <p> 242 * Constructor. 243 * </p> 244 * @param message A message for string presentation. 245 * @since 3.10 246 */ 247 public StopWatch(final String message) { 248 this.message = message; 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 formatSplitTime() { 258 return DurationFormatUtils.formatDurationHMS(getSplitTime()); 259 } 260 261 /** 262 * Returns the split time formatted by {@link DurationFormatUtils#formatDurationHMS}. 263 * 264 * @return the split time formatted by {@link DurationFormatUtils#formatDurationHMS}. 265 * @since 3.10 266 */ 267 public String formatTime() { 268 return DurationFormatUtils.formatDurationHMS(getTime()); 269 } 270 271 /** 272 * Gets the message for string presentation. 273 * 274 * @return the message for string presentation. 275 * @since 3.10 276 */ 277 public String getMessage() { 278 return message; 279 } 280 281 /** 282 * <p> 283 * Gets the <em>elapsed</em> time in nanoseconds. 284 * </p> 285 * 286 * <p> 287 * This is either the time between the start and the moment this method is called, or the amount of time between 288 * start and stop. 289 * </p> 290 * 291 * @return the <em>elapsed</em> time in nanoseconds. 292 * @see System#nanoTime() 293 * @since 3.0 294 */ 295 public long getNanoTime() { 296 if (this.runningState == State.STOPPED || this.runningState == State.SUSPENDED) { 297 return this.stopTimeNanos - this.startTimeNanos; 298 } else if (this.runningState == State.UNSTARTED) { 299 return 0; 300 } else 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 * <p> 308 * Gets the split time in nanoseconds. 309 * </p> 310 * 311 * <p> 312 * This is the time between start and latest split. 313 * </p> 314 * 315 * @return the split time in nanoseconds 316 * 317 * @throws IllegalStateException 318 * if the StopWatch has not yet been split. 319 * @since 3.0 320 */ 321 public long getSplitNanoTime() { 322 if (this.splitState != SplitState.SPLIT) { 323 throw new IllegalStateException("Stopwatch must be split to get the split time."); 324 } 325 return this.stopTimeNanos - this.startTimeNanos; 326 } 327 328 /** 329 * <p> 330 * Gets the split time on the stopwatch. 331 * </p> 332 * 333 * <p> 334 * This is the time between start and latest split. 335 * </p> 336 * 337 * @return the split time in milliseconds 338 * 339 * @throws IllegalStateException 340 * if the StopWatch has not yet been split. 341 * @since 2.1 342 */ 343 public long getSplitTime() { 344 return getSplitNanoTime() / NANO_2_MILLIS; 345 } 346 347 /** 348 * Gets the time this stopwatch was started in milliseconds, between the current time and midnight, January 1, 1970 349 * UTC. 350 * 351 * @return the time this stopwatch was started in milliseconds, between the current time and midnight, January 1, 352 * 1970 UTC. 353 * @throws IllegalStateException if this StopWatch has not been started 354 * @since 2.4 355 */ 356 public long getStartTime() { 357 if (this.runningState == State.UNSTARTED) { 358 throw new IllegalStateException("Stopwatch has not been started"); 359 } 360 // System.nanoTime is for elapsed time 361 return this.startTimeMillis; 362 } 363 364 /** 365 * Gets the time this stopwatch was stopped in milliseconds, between the current time and midnight, January 1, 1970 366 * UTC. 367 * 368 * @return the time this stopwatch was started in milliseconds, between the current time and midnight, January 1, 369 * 1970 UTC. 370 * @throws IllegalStateException if this StopWatch has not been started 371 * @since 3.12.0 372 */ 373 public long getStopTime() { 374 if (this.runningState == State.UNSTARTED) { 375 throw new IllegalStateException("Stopwatch has not been started"); 376 } 377 // System.nanoTime is for elapsed time 378 return this.stopTimeMillis; 379 } 380 381 /** 382 * <p> 383 * Gets the time on the stopwatch. 384 * </p> 385 * 386 * <p> 387 * This is either the time between the start and the moment this method is called, or the amount of time between 388 * start and stop. 389 * </p> 390 * 391 * @return the time in milliseconds 392 */ 393 public long getTime() { 394 return getNanoTime() / NANO_2_MILLIS; 395 } 396 397 /** 398 * <p> 399 * Gets the time in the specified TimeUnit. 400 * </p> 401 * 402 * <p> 403 * This is either the time between the start and the moment this method is called, or the amount of time between 404 * start and stop. The resulting time will be expressed in the desired TimeUnit with any remainder rounded down. 405 * For example, if the specified unit is {@code TimeUnit.HOURS} and the stopwatch time is 59 minutes, then the 406 * result returned will be {@code 0}. 407 * </p> 408 * 409 * @param timeUnit the unit of time, not null 410 * @return the time in the specified TimeUnit, rounded down 411 * @since 3.5 412 */ 413 public long getTime(final TimeUnit timeUnit) { 414 return timeUnit.convert(getNanoTime(), TimeUnit.NANOSECONDS); 415 } 416 417 /** 418 * <p> 419 * Returns whether the StopWatch is started. A suspended StopWatch is also started watch. 420 * </p> 421 * 422 * @return boolean If the StopWatch is started. 423 * @since 3.2 424 */ 425 public boolean isStarted() { 426 return runningState.isStarted(); 427 } 428 429 /** 430 * <p> 431 * Returns whether StopWatch is stopped. The stopwatch which's not yet started and explicitly stopped stopwatch is considered 432 * as stopped. 433 * </p> 434 * 435 * @return boolean If the StopWatch is stopped. 436 * @since 3.2 437 */ 438 public boolean isStopped() { 439 return runningState.isStopped(); 440 } 441 442 /** 443 * <p> 444 * Returns whether the StopWatch is suspended. 445 * </p> 446 * 447 * @return boolean 448 * If the StopWatch is suspended. 449 * @since 3.2 450 */ 451 public boolean isSuspended() { 452 return runningState.isSuspended(); 453 } 454 455 /** 456 * <p> 457 * Resets the stopwatch. Stops it if need be. 458 * </p> 459 * 460 * <p> 461 * This method clears the internal values to allow the object to be reused. 462 * </p> 463 */ 464 public void reset() { 465 this.runningState = State.UNSTARTED; 466 this.splitState = SplitState.UNSPLIT; 467 } 468 469 /** 470 * <p> 471 * Resumes the stopwatch after a suspend. 472 * </p> 473 * 474 * <p> 475 * This method resumes the watch after it was suspended. The watch will not include time between the suspend and 476 * resume calls in the total time. 477 * </p> 478 * 479 * @throws IllegalStateException 480 * if the StopWatch has not been suspended. 481 */ 482 public void resume() { 483 if (this.runningState != State.SUSPENDED) { 484 throw new IllegalStateException("Stopwatch must be suspended to resume. "); 485 } 486 this.startTimeNanos += System.nanoTime() - this.stopTimeNanos; 487 this.runningState = State.RUNNING; 488 } 489 490 /** 491 * <p> 492 * Splits the time. 493 * </p> 494 * 495 * <p> 496 * This method sets the stop time of the watch to allow a time to be extracted. The start time is unaffected, 497 * enabling {@link #unsplit()} to continue the timing from the original start point. 498 * </p> 499 * 500 * @throws IllegalStateException 501 * if the StopWatch is not running. 502 */ 503 public void split() { 504 if (this.runningState != State.RUNNING) { 505 throw new IllegalStateException("Stopwatch is not running. "); 506 } 507 this.stopTimeNanos = System.nanoTime(); 508 this.splitState = SplitState.SPLIT; 509 } 510 511 /** 512 * <p> 513 * Starts the stopwatch. 514 * </p> 515 * 516 * <p> 517 * This method starts a new timing session, clearing any previous values. 518 * </p> 519 * 520 * @throws IllegalStateException 521 * if the StopWatch is already running. 522 */ 523 public void start() { 524 if (this.runningState == State.STOPPED) { 525 throw new IllegalStateException("Stopwatch must be reset before being restarted. "); 526 } 527 if (this.runningState != State.UNSTARTED) { 528 throw new IllegalStateException("Stopwatch already started. "); 529 } 530 this.startTimeNanos = System.nanoTime(); 531 this.startTimeMillis = System.currentTimeMillis(); 532 this.runningState = State.RUNNING; 533 } 534 535 /** 536 * <p> 537 * Stops the stopwatch. 538 * </p> 539 * 540 * <p> 541 * This method ends a new timing session, allowing the time to be retrieved. 542 * </p> 543 * 544 * @throws IllegalStateException 545 * if the StopWatch is not running. 546 */ 547 public void stop() { 548 if (this.runningState != State.RUNNING && this.runningState != State.SUSPENDED) { 549 throw new IllegalStateException("Stopwatch is not running. "); 550 } 551 if (this.runningState == State.RUNNING) { 552 this.stopTimeNanos = System.nanoTime(); 553 this.stopTimeMillis = System.currentTimeMillis(); 554 } 555 this.runningState = State.STOPPED; 556 } 557 558 /** 559 * <p> 560 * Suspends the stopwatch for later resumption. 561 * </p> 562 * 563 * <p> 564 * This method suspends the watch until it is resumed. The watch will not include time between the suspend and 565 * resume calls in the total time. 566 * </p> 567 * 568 * @throws IllegalStateException 569 * if the StopWatch is not currently running. 570 */ 571 public void suspend() { 572 if (this.runningState != State.RUNNING) { 573 throw new IllegalStateException("Stopwatch must be running to suspend. "); 574 } 575 this.stopTimeNanos = System.nanoTime(); 576 this.stopTimeMillis = System.currentTimeMillis(); 577 this.runningState = State.SUSPENDED; 578 } 579 580 /** 581 * <p> 582 * Gets a summary of the split time that the stopwatch recorded as a string. 583 * </p> 584 * 585 * <p> 586 * The format used is ISO 8601-like, [<i>message</i> ]<i>hours</i>:<i>minutes</i>:<i>seconds</i>.<i>milliseconds</i>. 587 * </p> 588 * 589 * @return the split time as a String 590 * @since 2.1 591 * @since 3.10 Returns the prefix {@code "message "} if the message is set. 592 */ 593 public String toSplitString() { 594 final String msgStr = Objects.toString(message, StringUtils.EMPTY); 595 final String formattedTime = formatSplitTime(); 596 return msgStr.isEmpty() ? formattedTime : msgStr + StringUtils.SPACE + formattedTime; 597 } 598 599 /** 600 * <p> 601 * Gets a summary of the time that the stopwatch recorded as a string. 602 * </p> 603 * 604 * <p> 605 * The format used is ISO 8601-like, [<i>message</i> ]<i>hours</i>:<i>minutes</i>:<i>seconds</i>.<i>milliseconds</i>. 606 * </p> 607 * 608 * @return the time as a String 609 * @since 3.10 Returns the prefix {@code "message "} if the message is set. 610 */ 611 @Override 612 public String toString() { 613 final String msgStr = Objects.toString(message, StringUtils.EMPTY); 614 final String formattedTime = formatTime(); 615 return msgStr.isEmpty() ? formattedTime : msgStr + StringUtils.SPACE + formattedTime; 616 } 617 618 /** 619 * <p> 620 * Removes a split. 621 * </p> 622 * 623 * <p> 624 * This method clears the stop time. The start time is unaffected, enabling timing from the original start point to 625 * continue. 626 * </p> 627 * 628 * @throws IllegalStateException 629 * if the StopWatch has not been split. 630 */ 631 public void unsplit() { 632 if (this.splitState != SplitState.SPLIT) { 633 throw new IllegalStateException("Stopwatch has not been split. "); 634 } 635 this.splitState = SplitState.UNSPLIT; 636 } 637 638}