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.concurrent.TimeUnit; 021 022/** 023 * <p> 024 * <code>StopWatch</code> provides a convenient API for timings. 025 * </p> 026 * 027 * <p> 028 * To start the watch, call {@link #start()} or {@link StopWatch#createStarted()}. At this point you can: 029 * </p> 030 * <ul> 031 * <li>{@link #split()} the watch to get the time whilst the watch continues in the background. {@link #unsplit()} will 032 * remove the effect of the split. At this point, these three options are available again.</li> 033 * <li>{@link #suspend()} the watch to pause it. {@link #resume()} allows the watch to continue. Any time between the 034 * suspend and resume will not be counted in the total. At this point, these three options are available again.</li> 035 * <li>{@link #stop()} the watch to complete the timing session.</li> 036 * </ul> 037 * 038 * <p> 039 * It is intended that the output methods {@link #toString()} and {@link #getTime()} should only be called after stop, 040 * split or suspend, however a suitable result will be returned at other points. 041 * </p> 042 * 043 * <p> 044 * NOTE: As from v2.1, the methods protect against inappropriate calls. Thus you cannot now call stop before start, 045 * resume before suspend or unsplit before split. 046 * </p> 047 * 048 * <p> 049 * 1. split(), suspend(), or stop() cannot be invoked twice<br> 050 * 2. unsplit() may only be called if the watch has been split()<br> 051 * 3. resume() may only be called if the watch has been suspend()<br> 052 * 4. start() cannot be called twice without calling reset() 053 * </p> 054 * 055 * <p>This class is not thread-safe</p> 056 * 057 * @since 2.0 058 */ 059public class StopWatch { 060 061 private static final long NANO_2_MILLIS = 1000000L; 062 063 064 /** 065 * Provides a started stopwatch for convenience. 066 * 067 * @return StopWatch a stopwatch that's already been started. 068 * 069 * @since 3.5 070 */ 071 public static StopWatch createStarted() { 072 StopWatch sw = new StopWatch(); 073 sw.start(); 074 return sw; 075 } 076 077 /** 078 * Enumeration type which indicates the status of stopwatch. 079 */ 080 private enum State { 081 082 UNSTARTED { 083 @Override boolean isStarted() { return false; } 084 @Override boolean isStopped() { return true; } 085 @Override boolean isSuspended() { return false; } 086 }, 087 RUNNING { 088 @Override boolean isStarted() { return true; } 089 @Override boolean isStopped() { return false; } 090 @Override boolean isSuspended() { return false; } 091 }, 092 STOPPED { 093 @Override boolean isStarted() { return false; } 094 @Override boolean isStopped() { return true; } 095 @Override boolean isSuspended() { return false; } 096 }, 097 SUSPENDED { 098 @Override boolean isStarted() { return true; } 099 @Override boolean isStopped() { return false; } 100 @Override boolean isSuspended() { return true; } 101 }; 102 103 /** 104 * <p> 105 * The method is used to find out if the StopWatch is started. A suspended 106 * StopWatch is also started watch. 107 * </p> 108 109 * @return boolean 110 * If the StopWatch is started. 111 */ 112 abstract boolean isStarted(); 113 114 /** 115 * <p> 116 * This method is used to find out whether the StopWatch is stopped. The 117 * stopwatch which's not yet started and explicitly stopped stopwatch is 118 * considered as stopped. 119 * </p> 120 * 121 * @return boolean 122 * If the StopWatch is stopped. 123 */ 124 abstract boolean isStopped(); 125 126 /** 127 * <p> 128 * This method is used to find out whether the StopWatch is suspended. 129 * </p> 130 * 131 * @return boolean 132 * If the StopWatch is suspended. 133 */ 134 abstract boolean isSuspended(); 135 } 136 137 /** 138 * Enumeration type which indicates the split status of stopwatch. 139 */ 140 private enum SplitState { 141 SPLIT, 142 UNSPLIT 143 } 144 /** 145 * The current running state of the StopWatch. 146 */ 147 private State runningState = State.UNSTARTED; 148 149 /** 150 * Whether the stopwatch has a split time recorded. 151 */ 152 private SplitState splitState = SplitState.UNSPLIT; 153 154 /** 155 * The start time. 156 */ 157 private long startTime; 158 159 /** 160 * The start time in Millis - nanoTime is only for elapsed time so we 161 * need to also store the currentTimeMillis to maintain the old 162 * getStartTime API. 163 */ 164 private long startTimeMillis; 165 166 /** 167 * The stop time. 168 */ 169 private long stopTime; 170 171 /** 172 * <p> 173 * Constructor. 174 * </p> 175 */ 176 public StopWatch() { 177 super(); 178 } 179 180 /** 181 * <p> 182 * Start the stopwatch. 183 * </p> 184 * 185 * <p> 186 * This method starts a new timing session, clearing any previous values. 187 * </p> 188 * 189 * @throws IllegalStateException 190 * if the StopWatch is already running. 191 */ 192 public void start() { 193 if (this.runningState == State.STOPPED) { 194 throw new IllegalStateException("Stopwatch must be reset before being restarted. "); 195 } 196 if (this.runningState != State.UNSTARTED) { 197 throw new IllegalStateException("Stopwatch already started. "); 198 } 199 this.startTime = System.nanoTime(); 200 this.startTimeMillis = System.currentTimeMillis(); 201 this.runningState = State.RUNNING; 202 } 203 204 205 /** 206 * <p> 207 * Stop the stopwatch. 208 * </p> 209 * 210 * <p> 211 * This method ends a new timing session, allowing the time to be retrieved. 212 * </p> 213 * 214 * @throws IllegalStateException 215 * if the StopWatch is not running. 216 */ 217 public void stop() { 218 if (this.runningState != State.RUNNING && this.runningState != State.SUSPENDED) { 219 throw new IllegalStateException("Stopwatch is not running. "); 220 } 221 if (this.runningState == State.RUNNING) { 222 this.stopTime = System.nanoTime(); 223 } 224 this.runningState = State.STOPPED; 225 } 226 227 /** 228 * <p> 229 * Resets the stopwatch. Stops it if need be. 230 * </p> 231 * 232 * <p> 233 * This method clears the internal values to allow the object to be reused. 234 * </p> 235 */ 236 public void reset() { 237 this.runningState = State.UNSTARTED; 238 this.splitState = SplitState.UNSPLIT; 239 } 240 241 /** 242 * <p> 243 * Split the time. 244 * </p> 245 * 246 * <p> 247 * This method sets the stop time of the watch to allow a time to be extracted. The start time is unaffected, 248 * enabling {@link #unsplit()} to continue the timing from the original start point. 249 * </p> 250 * 251 * @throws IllegalStateException 252 * if the StopWatch is not running. 253 */ 254 public void split() { 255 if (this.runningState != State.RUNNING) { 256 throw new IllegalStateException("Stopwatch is not running. "); 257 } 258 this.stopTime = System.nanoTime(); 259 this.splitState = SplitState.SPLIT; 260 } 261 262 /** 263 * <p> 264 * Remove a split. 265 * </p> 266 * 267 * <p> 268 * This method clears the stop time. The start time is unaffected, enabling timing from the original start point to 269 * continue. 270 * </p> 271 * 272 * @throws IllegalStateException 273 * if the StopWatch has not been split. 274 */ 275 public void unsplit() { 276 if (this.splitState != SplitState.SPLIT) { 277 throw new IllegalStateException("Stopwatch has not been split. "); 278 } 279 this.splitState = SplitState.UNSPLIT; 280 } 281 282 /** 283 * <p> 284 * Suspend the stopwatch for later resumption. 285 * </p> 286 * 287 * <p> 288 * This method suspends the watch until it is resumed. The watch will not include time between the suspend and 289 * resume calls in the total time. 290 * </p> 291 * 292 * @throws IllegalStateException 293 * if the StopWatch is not currently running. 294 */ 295 public void suspend() { 296 if (this.runningState != State.RUNNING) { 297 throw new IllegalStateException("Stopwatch must be running to suspend. "); 298 } 299 this.stopTime = System.nanoTime(); 300 this.runningState = State.SUSPENDED; 301 } 302 303 /** 304 * <p> 305 * Resume the stopwatch after a suspend. 306 * </p> 307 * 308 * <p> 309 * This method resumes the watch after it was suspended. The watch will not include time between the suspend and 310 * resume calls in the total time. 311 * </p> 312 * 313 * @throws IllegalStateException 314 * if the StopWatch has not been suspended. 315 */ 316 public void resume() { 317 if (this.runningState != State.SUSPENDED) { 318 throw new IllegalStateException("Stopwatch must be suspended to resume. "); 319 } 320 this.startTime += System.nanoTime() - this.stopTime; 321 this.runningState = State.RUNNING; 322 } 323 324 /** 325 * <p> 326 * Get the time on the stopwatch. 327 * </p> 328 * 329 * <p> 330 * This is either the time between the start and the moment this method is called, or the amount of time between 331 * start and stop. 332 * </p> 333 * 334 * @return the time in milliseconds 335 */ 336 public long getTime() { 337 return getNanoTime() / NANO_2_MILLIS; 338 } 339 340 /** 341 * <p> 342 * Get the time on the stopwatch in the specified TimeUnit. 343 * </p> 344 * 345 * <p> 346 * This is either the time between the start and the moment this method is called, or the amount of time between 347 * start and stop. The resulting time will be expressed in the desired TimeUnit with any remainder rounded down. 348 * For example, if the specified unit is {@code TimeUnit.HOURS} and the stopwatch time is 59 minutes, then the 349 * result returned will be {@code 0}. 350 * </p> 351 * 352 * @param timeUnit the unit of time, not null 353 * @return the time in the specified TimeUnit, rounded down 354 * @since 3.5 355 */ 356 public long getTime(final TimeUnit timeUnit) { 357 return timeUnit.convert(getNanoTime(), TimeUnit.NANOSECONDS); 358 } 359 360 /** 361 * <p> 362 * Get the time on the stopwatch in nanoseconds. 363 * </p> 364 * 365 * <p> 366 * This is either the time between the start and the moment this method is called, or the amount of time between 367 * start and stop. 368 * </p> 369 * 370 * @return the time in nanoseconds 371 * @since 3.0 372 */ 373 public long getNanoTime() { 374 if (this.runningState == State.STOPPED || this.runningState == State.SUSPENDED) { 375 return this.stopTime - this.startTime; 376 } else if (this.runningState == State.UNSTARTED) { 377 return 0; 378 } else if (this.runningState == State.RUNNING) { 379 return System.nanoTime() - this.startTime; 380 } 381 throw new RuntimeException("Illegal running state has occurred."); 382 } 383 384 /** 385 * <p> 386 * Get the split time on the stopwatch. 387 * </p> 388 * 389 * <p> 390 * This is the time between start and latest split. 391 * </p> 392 * 393 * @return the split time in milliseconds 394 * 395 * @throws IllegalStateException 396 * if the StopWatch has not yet been split. 397 * @since 2.1 398 */ 399 public long getSplitTime() { 400 return getSplitNanoTime() / NANO_2_MILLIS; 401 } 402 /** 403 * <p> 404 * Get the split time on the stopwatch in nanoseconds. 405 * </p> 406 * 407 * <p> 408 * This is the time between start and latest split. 409 * </p> 410 * 411 * @return the split time in nanoseconds 412 * 413 * @throws IllegalStateException 414 * if the StopWatch has not yet been split. 415 * @since 3.0 416 */ 417 public long getSplitNanoTime() { 418 if (this.splitState != SplitState.SPLIT) { 419 throw new IllegalStateException("Stopwatch must be split to get the split time. "); 420 } 421 return this.stopTime - this.startTime; 422 } 423 424 /** 425 * Returns the time this stopwatch was started. 426 * 427 * @return the time this stopwatch was started 428 * @throws IllegalStateException 429 * if this StopWatch has not been started 430 * @since 2.4 431 */ 432 public long getStartTime() { 433 if (this.runningState == State.UNSTARTED) { 434 throw new IllegalStateException("Stopwatch has not been started"); 435 } 436 // System.nanoTime is for elapsed time 437 return this.startTimeMillis; 438 } 439 440 /** 441 * <p> 442 * Gets a summary of the time that the stopwatch recorded as a string. 443 * </p> 444 * 445 * <p> 446 * The format used is ISO 8601-like, <i>hours</i>:<i>minutes</i>:<i>seconds</i>.<i>milliseconds</i>. 447 * </p> 448 * 449 * @return the time as a String 450 */ 451 @Override 452 public String toString() { 453 return DurationFormatUtils.formatDurationHMS(getTime()); 454 } 455 456 /** 457 * <p> 458 * Gets a summary of the split time that the stopwatch recorded as a string. 459 * </p> 460 * 461 * <p> 462 * The format used is ISO 8601-like, <i>hours</i>:<i>minutes</i>:<i>seconds</i>.<i>milliseconds</i>. 463 * </p> 464 * 465 * @return the split time as a String 466 * @since 2.1 467 */ 468 public String toSplitString() { 469 return DurationFormatUtils.formatDurationHMS(getSplitTime()); 470 } 471 472 /** 473 * <p> 474 * The method is used to find out if the StopWatch is started. A suspended 475 * StopWatch is also started watch. 476 * </p> 477 * 478 * @return boolean 479 * If the StopWatch is started. 480 * @since 3.2 481 */ 482 public boolean isStarted() { 483 return runningState.isStarted(); 484 } 485 486 /** 487 * <p> 488 * This method is used to find out whether the StopWatch is suspended. 489 * </p> 490 * 491 * @return boolean 492 * If the StopWatch is suspended. 493 * @since 3.2 494 */ 495 public boolean isSuspended() { 496 return runningState.isSuspended(); 497 } 498 499 /** 500 * <p> 501 * This method is used to find out whether the StopWatch is stopped. The 502 * stopwatch which's not yet started and explicitly stopped stopwatch is 503 * considered as stopped. 504 * </p> 505 * 506 * @return boolean 507 * If the StopWatch is stopped. 508 * @since 3.2 509 */ 510 public boolean isStopped() { 511 return runningState.isStopped(); 512 } 513 514}