All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.debezium.util.Stopwatch Maven / Gradle / Ivy

/*
 * Copyright Debezium Authors.
 *
 * Licensed under the Apache Software License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
 */
package io.debezium.util;

import java.text.DecimalFormat;
import java.time.Duration;
import java.util.LongSummaryStatistics;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Consumer;

import io.debezium.annotation.ThreadSafe;

/**
 * A stopwatch for measuring durations. All NewStopwatch implementations are threadsafe, although using a single stopwatch
 * object across threads requires caution and care.
 *
 * @author Randall Hauch
 */
@ThreadSafe
public abstract class Stopwatch {

    /**
     * Start the stopwatch. Calling this method on an already-started stopwatch has no effect.
     *
     * @return this object to enable chaining methods
     * @see #stop
     */
    public abstract Stopwatch start();

    /**
     * Stop the stopwatch. Calling this method on an already-stopped stopwatch has no effect.
     *
     * @return this object to enable chaining methods
     * @see #start()
     */
    public abstract Stopwatch stop();

    /**
     * Get the total and average durations measured by this stopwatch.
     *
     * @return the durations; never null
     */
    public abstract Durations durations();

    /**
     * The average and total durations as measured by one or more stopwatches.
     */
    @ThreadSafe
    public static interface Durations {

        /**
         * Get the statistics for the durations in nanoseconds.
         *
         * @return the statistics; never null
         */
        Statistics statistics();
    }

    /**
     * The timing statistics for a recorded set of samples.
     */
    public static interface Statistics {
        /**
         * Returns the count of durations recorded.
         *
         * @return the count of durations
         */
        public long getCount();

        /**
         * Returns the total of all recorded durations.
         *
         * @return The total duration; never null but possibly {@link Duration#ZERO}.
         */
        public Duration getTotal();

        /**
         * Returns the minimum of all recorded durations.
         *
         * @return The minimum duration; never null but possibly {@link Duration#ZERO}.
         */
        public Duration getMinimum();

        /**
         * Returns the maximum of all recorded durations.
         *
         * @return The maximum duration; never null but possibly {@link Duration#ZERO}.
         */
        public Duration getMaximum();

        /**
         * Returns the arithmetic mean of all recorded durations.
         *
         * @return The average duration; never null but possibly {@link Duration#ZERO}.
         */
        public Duration getAverage();

        /**
         * Returns a string representation of the total of all recorded durations.
         *
         * @return the string representation of the total duration; never null but possibly {@link Duration#ZERO}.
         */
        default public String getTotalAsString() {
            return asString(getTotal());
        }

        /**
         * Returns a string representation of the minimum of all recorded durations.
         *
         * @return the string representation of the minimum duration; never null but possibly {@link Duration#ZERO}.
         */
        default public String getMinimumAsString() {
            return asString(getMinimum());
        }

        /**
         * Returns a string representation of the maximum of all recorded durations.
         *
         * @return the string representation of the maximum duration; never null but possibly {@link Duration#ZERO}.
         */
        default public String getMaximumAsString() {
            return asString(getMaximum());
        }

        /**
         * Returns a string representation of the arithmetic mean of all recorded durations.
         *
         * @return the string representation of the average duration; never null but possibly {@link Duration#ZERO}.
         */
        default public String getAverageAsString() {
            return asString(getAverage());
        }
    }

    private static Statistics createStatistics(LongSummaryStatistics stats) {
        boolean some = stats.getCount() > 0L;
        return new Statistics() {
            @Override
            public long getCount() {
                return stats.getCount();
            }

            @Override
            public Duration getMaximum() {
                return some ? Duration.ofNanos(stats.getMax()) : Duration.ZERO;
            }

            @Override
            public Duration getMinimum() {
                return some ? Duration.ofNanos(stats.getMin()) : Duration.ZERO;
            }

            @Override
            public Duration getTotal() {
                return some ? Duration.ofNanos(stats.getSum()) : Duration.ZERO;
            }

            @Override
            public Duration getAverage() {
                return some ? Duration.ofNanos((long) stats.getAverage()) : Duration.ZERO;
            }

            private String fixedLengthSeconds(Duration duration) {
                double seconds = duration.toNanos() * 1e-9;
                String result = new DecimalFormat("##0.00000").format(seconds) + "s";
                if (result.length() == 8) {
                    return "  " + result;
                }
                if (result.length() == 9) {
                    return " " + result;
                }
                return result;
            }

            private String fixedLength(long count) {
                String result = new DecimalFormat("###0").format(count);
                if (result.length() == 1) {
                    return "   " + result;
                }
                if (result.length() == 2) {
                    return "  " + result;
                }
                if (result.length() == 3) {
                    return " " + result;
                }
                return result;
            }

            @Override
            public String toString() {
                StringBuilder sb = new StringBuilder();
                sb.append(fixedLengthSeconds(getTotal()) + " total;");
                sb.append(fixedLength(getCount()) + " samples;");
                sb.append(fixedLengthSeconds(getAverage()) + " avg;");
                sb.append(fixedLengthSeconds(getMinimum()) + " min;");
                sb.append(fixedLengthSeconds(getMaximum()) + " max");
                return sb.toString();
            }
        };
    }

    /**
     * Create a new {@link Stopwatch} that can be reused. The resulting {@link Stopwatch#durations()}, however,
     * only reflect the most recently completed stopwatch interval.
     * 

* For example, the following code shows this behavior: * *

     * Stopwatch sw = Stopwatch.reusable();
     * sw.start();
     * sleep(3000); // sleep 3 seconds
     * sw.stop();
     * print(sw.durations()); // total and average duration are each 3 seconds
     *
     * sw.start();
     * sleep(2000); // sleep 2 seconds
     * sw.stop();
     * print(sw.durations()); // total and average duration are each 2 seconds
     * 
* * @return the new stopwatch; never null */ public static Stopwatch reusable() { return createWith(new SingleDuration(), null, null); } /** * Create a new {@link Stopwatch} that records all of the measured durations of the stopwatch. *

* For example, the following code shows this behavior: * *

     * Stopwatch sw = Stopwatch.accumulating();
     * sw.start();
     * sleep(3000); // sleep 3 seconds
     * sw.stop();
     * print(sw.durations()); // total and average duration are each 3 seconds
     *
     * sw.start();
     * sleep(2000); // sleep 2 seconds
     * sw.stop();
     * print(sw.durations()); // total duration is now 5 seconds, average is 2.5 seconds
     * 
* * @return the new stopwatch; never null */ public static Stopwatch accumulating() { return createWith(new MultipleDurations(), null, null); } /** * A set of stopwatches whose durations are combined. New stopwatches can be created at any time, and when * {@link Stopwatch#stop()} will always record their duration with this set. *

* This set is threadsafe, meaning that multiple threads can {@link #create()} new stopwatches concurrently, and each * stopwatch's duration is measured separately. Additionally, all of the other methods of this interface are also threadsafe. *

*/ @ThreadSafe public static interface StopwatchSet extends Durations { /** * Create a new stopwatch that records durations with this set. * * @return the new stopwatch; never null */ Stopwatch create(); /** * Time the given function. * * @param runnable the function to call */ default public void time(Runnable runnable) { time(1, runnable); } /** * Time the given function. * * @param runnable the function that is to be executed; may not be null * @return the result of the operation */ default public T time(Callable runnable) { Stopwatch sw = create().start(); try { return runnable.call(); } catch (RuntimeException e) { throw e; } catch (Exception e) { throw new RuntimeException(e); } finally { sw.stop(); } } /** * Time the given function multiple times. * * @param repeat the number of times to repeat the function call; must be positive * @param runnable the function to call; may not be null */ default public void time(int repeat, Runnable runnable) { for (int i = 0; i != repeat; ++i) { Stopwatch sw = create().start(); try { runnable.run(); } finally { sw.stop(); } } } /** * Time the given function multiple times. * * @param repeat the number of times to repeat the function call; must be positive * @param runnable the function that is to be executed a number of times; may not be null * @param cleanup the function that is to be called after each time call to the runnable function, and not included * in the time measurements; may be null * @throws Exception the exception thrown by the runnable function */ default public void time(int repeat, Callable runnable, Consumer cleanup) throws Exception { for (int i = 0; i != repeat; ++i) { T result = null; Stopwatch sw = create().start(); try { result = runnable.call(); } finally { sw.stop(); if (cleanup != null) { cleanup.accept(result); } } } } /** * Block until all running stopwatches have been {@link Stopwatch#stop() stopped}. This means that if a stopwatch * is {@link #create() created} but never started, this method will not wait for it. Likewise, if a stopwatch * is {@link #create() created} and started, then this method will block until the stopwatch is {@link Stopwatch#stop() * stopped} (even if the same stopwatch is started multiple times). * are stopped. * * @throws InterruptedException if the thread is interrupted before unblocking */ void await() throws InterruptedException; /** * Block until all stopwatches that have been {@link #create() created} and {@link Stopwatch#start() started} are * stopped. * * @param timeout the maximum length of time that this method should block * @param unit the unit for the timeout; may not be null * @throws InterruptedException if the thread is interrupted before unblocking */ void await(long timeout, TimeUnit unit) throws InterruptedException; } /** * Create a new set of stopwatches. The resulting object is threadsafe, and each {@link Stopwatch} created by * {@link StopwatchSet#create()} is also threadsafe. * * @return the stopwatches set; never null */ public static StopwatchSet multiple() { MultipleDurations durations = new MultipleDurations(); VariableLatch latch = new VariableLatch(0); return new StopwatchSet() { @Override public Statistics statistics() { return durations.statistics(); } @Override public Stopwatch create() { return createWith(durations, latch::countUp, latch::countDown); } @Override public void await() throws InterruptedException { latch.await(); } @Override public void await(long timeout, TimeUnit unit) throws InterruptedException { latch.await(timeout, unit); } @Override public String toString() { return statistics().toString(); } }; } /** * Create a new stopwatch that updates the given {@link BaseDurations duration}, and optionally has functions to * be called after the stopwatch is started and stopped. *

* The resulting stopwatch is threadsafe. *

* * @param duration the duration that should be updated; may not be null * @param uponStart the function that should be called when the stopwatch is successfully started (after not running); may be * null * @param uponStop the function that should be called when the stopwatch is successfully stopped (after it was running); may * be null * @return the new stopwatch */ protected static Stopwatch createWith(BaseDurations duration, Runnable uponStart, Runnable uponStop) { return new Stopwatch() { private final AtomicLong started = new AtomicLong(0L); @Override public Stopwatch start() { started.getAndUpdate(existing -> { if (existing == 0L) { // Has not yet been started ... existing = System.nanoTime(); if (uponStart != null) { uponStart.run(); } } return existing; }); return this; } @Override public Stopwatch stop() { started.getAndUpdate(existing -> { if (existing != 0L) { // Is running but has not yet been stopped ... duration.add(Duration.ofNanos(System.nanoTime() - existing)); if (uponStop != null) { uponStop.run(); } return 0L; } return existing; }); return this; } @Override public Durations durations() { return duration; } @Override public String toString() { return durations().toString(); } }; } /** * Compute the readable string representation of the supplied duration. * * @param duration the duration; may not be null * @return the string representation; never null */ protected static String asString(Duration duration) { return duration.toString().substring(2); } /** * Abstract base class for {@link Durations} implementations. */ @ThreadSafe protected static abstract class BaseDurations implements Durations { public abstract void add(Duration duration); @Override public String toString() { return statistics().toString(); } } /** * A {@link Durations} implementation that only remembers the most recently {@link #add(Duration) added} duration. */ @ThreadSafe private static final class SingleDuration extends BaseDurations { private final AtomicReference stats = new AtomicReference<>(); @Override public Statistics statistics() { return stats.get(); } @Override public void add(Duration duration) { LongSummaryStatistics stats = new LongSummaryStatistics(); if (duration != null) { stats.accept(duration.toNanos()); } this.stats.set(createStatistics(stats)); } } /** * A {@link Durations} implementation that accumulates all {@link #add(Duration) added} durations. */ @ThreadSafe private static final class MultipleDurations extends BaseDurations { private final ConcurrentLinkedQueue durations = new ConcurrentLinkedQueue<>(); @Override public Statistics statistics() { return createStatistics(durations.stream().mapToLong(Duration::toNanos).summaryStatistics()); } @Override public void add(Duration duration) { durations.add(duration); } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy