// License: GPL. For details, see LICENSE file. package org.openstreetmap.josm; import java.util.ArrayList; import java.util.Collections; import org.openstreetmap.josm.io.XmlWriter; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; /** * Timer utilities for performance tests. * @author Michael Zangl */ public final class PerformanceTestUtils { private static final int TIMES_WARMUP = 2; private static final int TIMES_RUN = 8; /** * A helper class that captures the time from object creation until #done() was called. * @author Michael Zangl */ public static class PerformanceTestTimerCapture { private final long time; protected PerformanceTestTimerCapture() { time = System.nanoTime(); } /** * Get the time since this object was created. * @return The time. */ public long getTimeSinceCreation() { return (System.nanoTime() - time) / 1000000; } } /** * A timer that measures the time from it's creation to the {@link #done()} call. * @author Michael Zangl */ public static class PerformanceTestTimer extends PerformanceTestTimerCapture { private final String name; private boolean measurementPlotsPlugin = true; protected PerformanceTestTimer(String name) { this.name = name; } /** * Activate output for the Jenkins Measurement Plots Plugin. * @param active true if it should be activated */ public void setMeasurementPlotsPluginOutput(boolean active) { measurementPlotsPlugin = active; } /** * Prints the time since this timer was created. */ public void done() { long dTime = getTimeSinceCreation(); if (measurementPlotsPlugin) { measurementPlotsPluginOutput(name + "(ms)", dTime); } else { System.out.println("TIMER " + name + ": " + dTime + "ms"); } } } private PerformanceTestUtils() { } /** * Starts a new performance timer. The timer will output the measurements in a format understood by Jenkins. * <p> * The timer can only be used to meassure one value. * @param name The name/description of the timer. * @return A {@link PerformanceTestTimer} object of which you can call {@link PerformanceTestTimer#done()} when done. */ @SuppressFBWarnings(value = "DM_GC", justification = "Performance test code") public static PerformanceTestTimer startTimer(String name) { cleanSystem(); return new PerformanceTestTimer(name); } /** * Runs the given performance test several (approx. 10) times and prints the median run time. * @param name The name to use in the output * @param testRunner The test to run */ public static void runPerformanceTest(String name, Runnable testRunner) { for (int i = 0; i < TIMES_WARMUP; i++) { cleanSystem(); PerformanceTestTimerCapture capture = new PerformanceTestTimerCapture(); testRunner.run(); capture.getTimeSinceCreation(); } ArrayList<Long> times = new ArrayList<>(); for (int i = 0; i < TIMES_RUN; i++) { cleanSystem(); PerformanceTestTimerCapture capture = new PerformanceTestTimerCapture(); testRunner.run(); times.add(capture.getTimeSinceCreation()); } System.out.println(times); Collections.sort(times); // Sort out e.g. GC during test run. double avg = times.subList(2, times.size() - 2).stream().mapToLong(l -> l).average().getAsDouble(); measurementPlotsPluginOutput(name, avg); } @SuppressFBWarnings(value = "DM_GC") private static void cleanSystem() { System.gc(); System.runFinalization(); } /** * Emit one data value for the Jenkins Measurement Plots Plugin. * * The plugin collects the values over multiple builds and plots them in a diagram. * * @param name the name / title of the measurement * @param value the value * @see "https://wiki.jenkins-ci.org/display/JENKINS/Measurement+Plots+Plugin" */ public static void measurementPlotsPluginOutput(String name, double value) { System.err.println("<measurement><name>"+XmlWriter.encode(name)+"</name><value>"+value+"</value></measurement>"); } }