001package org.javasimon; 002 003import java.util.Iterator; 004import java.util.Map; 005 006import org.javasimon.clock.SimonClock; 007import org.javasimon.utils.SimonUtils; 008 009/** 010 * Represents single time split - one Stopwatch measurement. Object is obtained by {@link org.javasimon.Stopwatch#start()} 011 * and the measurement is ended using {@link #stop()} method on this object. Split will return 0 as the result 012 * if the related Stopwatch was disabled when the Split was obtained. The Split can be stopped in any other thread. 013 * Split measures real time (based on {@link org.javasimon.clock.SimonClock#nanoTime()}), it does not measure CPU time. Split can be garbage collected 014 * and no resource leak occurs if it is not stopped, however Stopwatch's active counter ({@link org.javasimon.Stopwatch#getActive()}) 015 * will be stay incremented. 016 * <p/> 017 * Split can never be running ({@link #isRunning()}) if it is disabled. Enabled split is running until it is stopped. 018 * Stopped split (not running) will never again be running. Split never changes enabled flag after creation. 019 * <p/> 020 * Split implements {@link java.lang.AutoCloseable} hence it can be used in try-with-resource construction. 021 * 022 * @author <a href="mailto:virgo47@gmail.com">Richard "Virgo" Richter</a> 023 * @see Stopwatch 024 */ 025public final class Split implements HasAttributes, AutoCloseable { 026 027 /** Disabled split (implies not running) for cases where monitoring is disabled and {@code null} value is not an option. */ 028 public static final Split DISABLED = new Split(); 029 030 /** Attribute name under which effectively used stopwatch is stored if the split was stopped with {@link #stop(String)}. */ 031 public static final String ATTR_EFFECTIVE_STOPWATCH = "effective-stopwatch"; 032 033 private volatile Stopwatch stopwatch; 034 private final boolean enabled; 035 private final SimonClock clock; 036 private volatile boolean running; 037 038 private volatile long start; 039 private volatile long total; 040 041 private AttributesSupport attributesSupport = new AttributesSupport(); 042 043 private Split() { 044 enabled = false; 045 clock = null; 046 } 047 048 private Split(boolean enabled, SimonClock clock) { 049 this.enabled = enabled; 050 this.clock = clock; 051 start = clock.nanoTime(); 052 } 053 054 /** 055 * Creates a new Split for an enabled Stopwatch with a specific timestamp in nanoseconds - <b>called internally only</b>. 056 * 057 * @param stopwatch owning Stopwatch (enabled) 058 * @param clock Clock for this Split 059 * @param start start timestamp in nanoseconds 060 */ 061 Split(Stopwatch stopwatch, SimonClock clock, long start) { 062 assert start > 0 : "start ns value should not be 0 in this constructor!"; 063 064 this.stopwatch = stopwatch; 065 this.start = start; 066 this.clock = clock; 067 enabled = true; 068 running = true; 069 } 070 071 /** 072 * Creates a new Split for a disabled Stopwatch - <b>called internally only</b>. 073 * 074 * @param stopwatch owning Stopwatch (disabled) 075 * @param clock Clock for this Split 076 */ 077 Split(Stopwatch stopwatch, SimonClock clock) { 078 assert !(stopwatch.isEnabled()) : "stopwatch must be disabled in this constructor!"; 079 this.enabled = false; 080 this.stopwatch = stopwatch; 081 this.clock = clock; 082 } 083 084 /** 085 * Creates a new Split for direct use without {@link Stopwatch} ("anonymous split") based on specified {@link SimonClock}. 086 * Stop will not update any Stopwatch, value can be added to any chosen Stopwatch using 087 * {@link Stopwatch#addSplit(Split)} - even in conjunction with {@link #stop()} like this: 088 * <p/> 089 * <pre>Split split = Split.start(); 090 * ... 091 * stopwatch.addSplit(split.stop());</pre> 092 * <p/> 093 * If the split is not needed afterwards (subject to garbage collection) calling {@link #stop()} is not necessary: 094 * <p/> 095 * <pre>Split split = Split.start(); 096 * ... 097 * stopwatch.addSplit(split);</pre> 098 * <p/> 099 * No callbacks are called for this Split. 100 * 101 * @param clock Clock for this Split 102 * @since 3.5 103 */ 104 public static Split start(SimonClock clock) { 105 Split split = new Split(true, clock); 106 split.running = true; 107 return split; 108 } 109 110 /** 111 * Creates a new Split for direct use without {@link Stopwatch} ("anonymous split") based on system time. 112 * Equivalent of {@code Split.start(Clock.SYSTEM)}. 113 * 114 * @see #start(SimonClock) 115 * @since 3.4 116 */ 117 public static Split start() { 118 return start(SimonClock.SYSTEM); 119 } 120 121 /** 122 * Creates simulated non-running Split that took specific time in nanos. 123 * No callbacks are called for this Split. Start of this Split is set {@code clock.nanoTime()}. 124 * 125 * @param nanos Split's total time in nanos 126 * @param clock Clock for this Split 127 * @return created Split 128 * @since 3.5 129 */ 130 public static Split create(long nanos, SimonClock clock) { 131 Split split = new Split(false, clock); 132 split.total = nanos; 133 return split; 134 } 135 136 /** 137 * Creates simulated non-running Split that took specific time in nanos. 138 * No callbacks are called for this Split. Start of this Split is based on {@link SimonClock#SYSTEM}. 139 * 140 * @param nanos Split's total time in nanos 141 * @return created Split 142 * @see #create(long, SimonClock) 143 * @since 3.5 144 */ 145 public static Split create(long nanos) { 146 return create(nanos, SimonClock.SYSTEM); 147 } 148 149 /** 150 * Returns the stopwatch that this split is running for. May be {@code null} for anonymous splits (directly created). 151 * 152 * @return owning stopwatch, may return {@code null} 153 */ 154 public Stopwatch getStopwatch() { 155 return stopwatch; 156 } 157 158 /** 159 * Stops the split, updates the stopwatch and returns this. Subsequent calls do not change the state of the split. 160 * 161 * @return this split object 162 * @since 3.1 - previously returned split time in ns, call additional {@link #runningFor()} for the same result 163 */ 164 public Split stop() { 165 return stop(null); 166 } 167 168 /** 169 * Stops the split, updates the sub-stopwatch specified by parameter and returns this. Active counter of the original stopwatch 170 * is decreased as expected. Subsequent calls do not change the state of the split. 171 * <p/> 172 * <b>Important:</b> While {@link #stop()} (or this stop with {@code null} argument) 173 * results in {@link org.javasimon.callback.Callback#onStopwatchStop(Split, StopwatchSample)} callback method being invoked, 174 * if sub-simon is affected then {@link org.javasimon.callback.Callback#onStopwatchAdd(Stopwatch, Split, StopwatchSample)} 175 * is called instead. 176 * <p/> 177 * If the split was obtained from disabled Stopwatch, this method does not update sub-simon even if it is enabled, because 178 * split itself is disabled as well. If split is enabled, but sub-simon is disabled, the latter is not updated. 179 * 180 * @param subSimon name of the sub-stopwatch (hierarchy delimiter is added automatically) - if {@code null} 181 * it behaves exactly like {@link #stop()} 182 * @return this split object 183 * @since 3.4 184 */ 185 public Split stop(String subSimon) { 186 if (!running) { 187 return this; 188 } 189 running = false; 190 long nowNanos = clock.nanoTime(); 191 total = nowNanos - start; // we update total before calling the stop so that callbacks can use it 192 if (stopwatch != null) { 193 ((StopwatchImpl) stopwatch).stop(this, start, nowNanos, subSimon); 194 } 195 return this; 196 } 197 198 /** 199 * Returns the current running nano-time from the start to the method call or the total split time 200 * if the Split has been stopped already. 201 * 202 * @return current running nano-time of the split 203 */ 204 public long runningFor() { 205 if (!running) { 206 return total; 207 } 208 if (enabled) { 209 return clock.nanoTime() - start; 210 } 211 return 0; 212 } 213 214 /** 215 * Returns printable form of how long this split was running for. 216 * 217 * @return short information about the Split time as a human readable string 218 * @since 2.2 219 */ 220 public String presentRunningFor() { 221 return SimonUtils.presentNanoTime(runningFor()); 222 } 223 224 /** 225 * Returns true if this split was created from enabled Simon or via {@link #start()}. 226 * 227 * @return true if this split was created from enabled Simon 228 */ 229 public boolean isEnabled() { 230 return enabled; 231 } 232 233 /** 234 * Returns true if this split is still running ({@link #stop()} has not been called yet). 235 * Returns false for disabled Split. 236 * 237 * @return true if this split is still running 238 * @since 3.1.0 239 */ 240 public boolean isRunning() { 241 return running; 242 } 243 244 /** 245 * Returns start nano timer value or 0 if the Split is not enabled (started for disabled Stopwatch). 246 * 247 * @return nano timer value when the Split was started or 0 if the Split is not enabled 248 * @since 3.1 249 */ 250 public long getStart() { 251 return start; 252 } 253 254 /** 255 * Returns millis timestamp when this Split was started - or 0 if the Split is not enabled (started for disabled Stopwatch). 256 * 257 * @return nano timer value when the Split was started or 0 if the Split is not enabled 258 * @since 3.1 259 */ 260 public long getStartMillis() { 261 return clock != null ? clock.millisForNano(start) : 0; 262 } 263 264 /** 265 * Stores an attribute in this Split. Attributes can be used to store any custom objects. 266 * 267 * @param name a String specifying the name of the attribute 268 * @param value the Object to be stored 269 * @since 2.3 270 */ 271 @Override 272 public void setAttribute(String name, Object value) { 273 attributesSupport.setAttribute(name, value); 274 } 275 276 /** 277 * Returns the value of the named attribute as an Object, or {@code null} if no attribute of 278 * the given name exists. 279 * 280 * @param name a String specifying the name of the attribute 281 * @return an Object containing the value of the attribute, or {@code null} if the attribute does not exist 282 * @since 2.3 283 */ 284 @Override 285 public Object getAttribute(String name) { 286 return attributesSupport.getAttribute(name); 287 } 288 289 /** 290 * Returns the value of the named attribute typed to the specified class, or {@code null} if no attribute of 291 * the given name exists. 292 * 293 * @param name a String specifying the name of the attribute 294 * @return the value of the attribute typed to T, or {@code null} if the attribute does not exist 295 * @since 3.4 296 */ 297 @SuppressWarnings("unchecked") 298 @Override 299 public <T> T getAttribute(String name, Class<T> clazz) { 300 return attributesSupport.getAttribute(name, clazz); 301 } 302 303 /** 304 * Removes an attribute from this Split. 305 * 306 * @param name a String specifying the name of the attribute to remove 307 * @since 2.3 308 */ 309 @Override 310 public void removeAttribute(String name) { 311 attributesSupport.removeAttribute(name); 312 } 313 314 /** 315 * Returns an Iterator containing the names of the attributes available to this Split. 316 * This method returns an empty Iterator if the Split has no attributes available to it. 317 * 318 * @return an Iterator of strings containing the names of the Split's attributes 319 * @since 2.3 320 */ 321 @Override 322 public Iterator<String> getAttributeNames() { 323 return attributesSupport.getAttributeNames(); 324 } 325 326 @Override 327 public Map<String, Object> getCopyAsSortedMap() { 328 return attributesSupport.getCopyAsSortedMap(); 329 } 330 331 /** 332 * Allows to use Split as a resource in try-with-resource construct. Calls {@link #stop()} internally. 333 * 334 * @since 4.0 335 */ 336 @Override 337 public void close() { 338 stop(); 339 } 340 341 /** 342 * Returns information about this Split, if it's running, name of the related Stopwatch and split's time. 343 * 344 * @return information about the Split as a human readable string 345 */ 346 @Override 347 public String toString() { 348 if (!enabled) { 349 return "Split created from disabled Stopwatch"; 350 } 351 if (running) { 352 return "Running split" + (stopwatch != null ? " for Stopwatch '" + stopwatch.getName() + "': " : ": ") + SimonUtils.presentNanoTime(runningFor()); 353 } 354 return "Stopped split" + (stopwatch != null ? " for Stopwatch '" + stopwatch.getName() + "': " : ": ") + SimonUtils.presentNanoTime(total); 355 } 356}