/*
* Strongback
* Copyright 2015, Strongback and individual contributors by the @authors tag.
* See the COPYRIGHT.txt in the distribution for a full listing of individual
* contributors.
*
* Licensed under the MIT License; you may not use this file except in
* compliance with the License. You may obtain a copy of the License at
* http://opensource.org/licenses/MIT
* 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.strongback;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
import java.util.function.Consumer;
import org.strongback.Strongback.Configurator;
import org.strongback.annotation.ThreadSafe;
import com.codahale.metrics.Histogram;
import com.codahale.metrics.Snapshot;
import com.codahale.metrics.UniformReservoir;
/**
* A command that measures the {@link Strongback.Configurator#useExecutionPeriod(long, java.util.concurrent.TimeUnit) execution
* rate} of the Strongback {@link Executor}. This is purely for test purposes.
* <p>
*
* @author Randall Hauch
*/
@ThreadSafe
public final class ExecutableTimer implements Executable {
/**
* Measure the timing of the {@link Executor} and print the histogram of timing results to {@link System#out}. The resulting
* timer will be {@link Executor#register(Executable) registered} with the Executor, and upon completion will automatically
* unregister itself.
* <p>
* This method can be used to print a histogram of {@link Strongback#executor() Strongback's executor}. The following
* example uses 1000 samples, which corresponds to 5 seconds if the {@link Configurator#useExecutionPeriod(long, TimeUnit)
* execution rate} is 5 milliseconds:
*
* <pre>
* ExecutableTimer.measureTimingAndPrint(Strongback.executor(), 200 * 5);
* </pre>
*
* The result would be something like the following:
*
* @param executor the Executor to measure; may not be null
* @param desc the description
* @param numberOfSamples the number of samples to measure
* @return the executable timer
*/
public static ExecutableTimer measureTimingAndPrint(Executor executor, String desc, int numberOfSamples) {
return measureTiming(executor, numberOfSamples, snapshot -> {
System.out.println("Execution timing statistics" + (desc != null ? " (" + desc + ")" : ""));
System.out.println(" Size of histogram: " + snapshot.getValues().length);
System.out.println(" Minimum (ms): " + snapshot.getMin());
System.out.println(" Maximum (ms): " + snapshot.getMax());
System.out.println(" Mean (ms): " + snapshot.getMean());
System.out.println(" Median (ms): " + snapshot.getMedian());
System.out.println(" Standard dev (ms): " + snapshot.getStdDev());
System.out.println(" 75th percentile (ms): " + snapshot.get75thPercentile());
System.out.println(" 95th percentile (ms): " + snapshot.get95thPercentile());
System.out.println(" 98th percentile (ms): " + snapshot.get98thPercentile());
System.out.println(" 99th percentile (ms): " + snapshot.get99thPercentile());
System.out.println(" 99.9th percentile (ms): " + snapshot.get999thPercentile());
//snapshot.dump(System.out);
});
}
public static ExecutableTimer measureTiming(Executor executor, int numberOfSamples, Consumer<Snapshot> atCompletion) {
ExecutableTimer timer = new ExecutableTimer(numberOfSamples, (theTimer) -> {
executor.unregister(theTimer);
if (atCompletion != null) atCompletion.accept(theTimer.getResults());
});
// Register the timer with the executor ...
executor.register(timer);
return timer;
}
private final CountDownLatch latch = new CountDownLatch(1);
private final Consumer<ExecutableTimer> resultsHandler;
private final Histogram histogram;
private final int numSamples;
private final AtomicLong lastStartTime = new AtomicLong();
private final AtomicLong count = new AtomicLong(-1);
/**
* Create an executable timer that measures the specified number of samples.
*
* @param numberOfSamples the maximum number of samples to take; must be positive
* @param uponCompletion the function that will be called
*/
private ExecutableTimer(int numberOfSamples, Consumer<ExecutableTimer> uponCompletion) {
histogram = new Histogram(new UniformReservoir(numberOfSamples));
numSamples = numberOfSamples;
resultsHandler = uponCompletion;
}
@Override
public void execute(long timeInMillis) {
long currentCount = count.getAndIncrement();
if (currentCount < 0) {
lastStartTime.set(timeInMillis);
} else if (currentCount < numSamples) {
histogram.update(timeInMillis - lastStartTime.getAndSet(timeInMillis));
} else {
count.set(-1);
// We've completed, so first run the results handler and only then release the latch ...
resultsHandler.accept(this);
latch.countDown();
}
}
public boolean isComplete() {
return latch.getCount() == -1 && histogram.getCount() > 0;
}
public ExecutableTimer await(long timeout, TimeUnit unit) throws InterruptedException {
latch.await(timeout, unit);
return this;
}
public ExecutableTimer await() throws InterruptedException {
latch.await();
return this;
}
public Snapshot getResults() {
return histogram.getSnapshot();
}
}