001 package hirondelle.web4j.util;
002
003 import java.math.BigDecimal;
004
005 /**
006 Allows timing of the execution of any block of code.
007
008 Example use case:
009 <PRE>
010 Stopwatch stopwatch = new Stopwatch();
011 stopwatch.start();
012 //..perform operations
013 stopwatch.stop();
014
015 //timed in nanos, but toString is expressed in millis,
016 //to three decimal places, to reflect the real resolution of most systems:
017 System.out.println("The reading on the stopwatch is: " + stopwatch);
018
019 //reuse the same stopwatch again
020 //Note that there is no need to call a reset method.
021 stopwatch.start();
022 //..perform operations
023 stopwatch.stop();
024
025 //perform a numeric comparison, using raw nanoseconds:
026 if ( stopwatch.toValue() > 5 ) {
027 System.out.println("The reading is high: " + stopwatch);
028 }
029 </PRE>
030
031 <P>The value on the stopwatch may be inspected at any time using the
032 {@link #toString} (millis) and {@link #toValue} (nanos) methods.
033
034 <P><b>Example 2</b>
035 <br>To time the various steps in a long startup or initialization task, your code may take the form:
036 <PRE>
037 Stopwatch stopwatch = new Stopwatch();
038 stopwatch.start();
039 //..perform operation 1
040 log("Step 1 completed " + stopwatch + " after start.");
041
042 //perform operation 2
043 log("Step 2 completed " + stopwatch + " after start.");
044
045 //perform the last operation, operation 3
046 stopwatch.stop();
047 log("Final Step 3 completed " + stopwatch + " after start") ;
048 </PRE>
049
050 <P><i>Implementation Note:</i></br>
051 This class uses {@link System#nanoTime()}, not {@link System#currentTimeMillis()}, to
052 perform its timing operations.
053 */
054 public final class Stopwatch {
055
056 /**
057 Start the stopwatch.
058
059 <P>You cannot call this method if the stopwatch is already started.
060 */
061 public void start(){
062 if (fIsRunning) {
063 throw new IllegalStateException("Must stop before calling start again.");
064 }
065 //reset both start and stop
066 fStart = getCurrentTime();
067 fStop = 0;
068 fIsRunning = true;
069 }
070
071 /**
072 Stop the stopwatch.
073
074 <P>You can only call this method if the stopwatch has been started.
075 */
076 public void stop() {
077 if (!fIsRunning) {
078 throw new IllegalStateException("Cannot stop if not currently running.");
079 }
080 fStop = getCurrentTime();
081 fIsRunning = false;
082 }
083
084 /**
085 Return the current "reading" on the stopwatch, in milliseconds, in a format suitable
086 typical use cases.
087
088 <P>Example return value : '<tt>108.236 ms</tt>'. The underlying timing is in nanos,
089 but it's expressed here in millis, for two reasons: the resolution on most systems is
090 in the microsecond range, and the full 9 digits are less easy to reader. If you need
091 the raw nanos, just use {@link #toValue()} instead.
092
093 <P>Ref: https://blogs.oracle.com/dholmes/entry/inside_the_hotspot_vm_clocks
094 */
095 @Override public String toString() {
096 StringBuilder result = new StringBuilder();
097 BigDecimal value = new BigDecimal(toValue());//scale is zero
098 //millis, with 3 decimals:
099 value = value.divide(MILLION, 3, BigDecimal.ROUND_HALF_EVEN);
100 result.append(value);
101 result.append(" ms");
102 return result.toString();
103 }
104
105 /**
106 Return the current "reading" on the stopwatch in raw nanoseconds, as a numeric type.
107 */
108 public long toValue() {
109 long result = fStop == 0 ? (getCurrentTime() - fStart) : (fStop -fStart);
110 return result;
111 }
112
113 // PRIVATE
114 private long fStart;
115 private long fStop;
116 private boolean fIsRunning;
117
118 /** Converts from nanos to millis. */
119 private static final BigDecimal MILLION = new BigDecimal("1000000");
120
121 private long getCurrentTime(){
122 return System.nanoTime();
123 }
124 }