/* * ModeShape (http://www.modeshape.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.modeshape.common.statistic; import org.modeshape.common.annotation.NotThreadSafe; import org.modeshape.common.math.Duration; import org.modeshape.common.math.DurationOperations; /** * Provides a mechanism to measure time in the same way as a physical stopwatch. */ @NotThreadSafe public class Stopwatch implements Comparable<Stopwatch> { private long lastStarted; private final SimpleStatistics<Duration> stats; private final DetailedStatistics<Duration> detailedStats; private String description; public Stopwatch() { this(true); } public Stopwatch( boolean detailedStats ) { this(detailedStats, null); } public Stopwatch( boolean detailedStats, String description ) { this.description = description != null ? description : ""; this.detailedStats = detailedStats ? new DetailedStatistics<Duration>(new DurationOperations()) : null; this.stats = detailedStats ? this.detailedStats : new SimpleStatistics<Duration>(new DurationOperations()); reset(); } public String getDescription() { return this.description; } /** * Start the stopwatch and begin recording the statistics a new run. This method does nothing if the stopwatch is already * {@link #isRunning() running} * * @see #isRunning() */ public void start() { if (!this.isRunning()) { this.lastStarted = System.nanoTime(); } } /** * Stop the stopwatch and record the statistics for the latest run. This method does nothing if the stopwatch is not currently * {@link #isRunning() running} * * @see #isRunning() */ public void stop() { if (this.isRunning()) { long duration = System.nanoTime() - this.lastStarted; this.lastStarted = 0l; this.stats.add(new Duration(duration)); } } /** * Record the statistics for the latest run, but keep the stopwatch going. This method does nothing if the stopwatch is not * currently {@link #isRunning() running} * * @see #isRunning() */ public void lap() { if (this.isRunning()) { long now = System.nanoTime(); long duration = now - this.lastStarted; this.lastStarted = now; this.stats.add(new Duration(duration)); } } /** * Return the number of runs (complete starts and stops) this stopwatch has undergone. * * @return the number of runs. * @see #isRunning() */ public int getCount() { return this.stats.getCount(); } /** * Return whether this stopwatch is currently running. * * @return true if running, or false if not */ public boolean isRunning() { return this.lastStarted != 0; } /** * Get the total duration that this stopwatch has recorded. * * @return the total duration, or an empty duration if this stopwatch has not been used since creation or being * {@link #reset() reset} */ public Duration getTotalDuration() { return this.stats.getTotal(); } /** * Get the average duration that this stopwatch has recorded. * * @return the average duration, or an empty duration if this stopwatch has not been used since creation or being * {@link #reset() reset} */ public Duration getAverageDuration() { return this.stats.getMean(); } /** * Get the median duration that this stopwatch has recorded. * * @return the median duration, or an empty duration if this stopwatch has not been used since creation or being * {@link #reset() reset} */ public Duration getMedianDuration() { return this.detailedStats != null ? this.detailedStats.getMedian() : new Duration(0l); } /** * Get the minimum duration that this stopwatch has recorded. * * @return the total minimum, or an empty duration if this stopwatch has not been used since creation or being * {@link #reset() reset} */ public Duration getMinimumDuration() { return this.stats.getMinimum(); } /** * Get the maximum duration that this stopwatch has recorded. * * @return the maximum duration, or an empty duration if this stopwatch has not been used since creation or being * {@link #reset() reset} */ public Duration getMaximumDuration() { return this.stats.getMaximum(); } /** * Return this stopwatch's simple statistics. * * @return the statistics * @see #getDetailedStatistics() */ public SimpleStatistics<Duration> getSimpleStatistics() { return this.stats; } /** * Return this stopwatch's detailed statistics, if they are being kept. * * @return the statistics * @see #getSimpleStatistics() */ public DetailedStatistics<Duration> getDetailedStatistics() { return this.detailedStats; } /** * Return true if detailed statistics are being kept. * * @return true if {@link #getDetailedStatistics() detailed statistics} are being kept, or false if only * {@link #getSimpleStatistics() simple statistics} are being kept. */ public boolean isDetailedStatistics() { return this.detailedStats != null; } /** * Return the histogram of this stopwatch's individual runs. Two different kinds of histograms can be created. The first kind * is a histogram where all of the buckets are distributed normally and all have the same width. In this case, the 'numSigmas' * should be set to 0. * <p> * <i>Note: if only {@link #getSimpleStatistics() simple statistics} are being kept, the resulting histogram is always empty. * <p> * The second kind of histogram is more useful when most of the data that is clustered near one value. This histogram is * focused around the values that are up to 'numSigmas' above and below the {@link #getMedianDuration() median}, and all * values outside of this range are placed in the first and last bucket. * </p> * * @param numSigmas the number of standard deviations from the {@link #getMedianDuration() median}, or 0 if the buckets of the * histogram should be evenly distributed * @return the histogram */ public Histogram<Duration> getHistogram( int numSigmas ) { return this.detailedStats != null ? this.detailedStats.getHistogram(numSigmas) : new Histogram<Duration>( this.stats.getMathOperations()); } /** * Reset this stopwatch and clear all statistics. */ public void reset() { this.lastStarted = 0l; this.stats.reset(); } @Override public int compareTo( Stopwatch that ) { return this.getTotalDuration().compareTo(that.getTotalDuration()); } @Override public String toString() { StringBuilder sb = new StringBuilder(); sb.append(this.getTotalDuration()); if (this.stats.getCount() > 1) { sb.append(" ("); sb.append(this.stats.getCount()).append(" samples, avg="); sb.append(this.getAverageDuration()); sb.append("; median="); sb.append(this.getMedianDuration()); sb.append("; min="); sb.append(this.getMinimumDuration()); sb.append("; max="); sb.append(this.getMaximumDuration()); sb.append(")"); } return sb.toString(); } }