mirror of https://github.com/OpenIdentityPlatform/OpenDJ.git

Gaetan Boismal
30.59.2015 891f8d0bd04e4a4018c354629cb960f0871de4a9
opendj-ldap-toolkit/src/main/java/com/forgerock/opendj/ldap/tools/PerformanceRunner.java
@@ -68,9 +68,7 @@
import com.forgerock.opendj.cli.StringArgument;
import com.forgerock.opendj.util.StaticUtils;
/**
 * Benchmark application framework.
 */
/** Benchmark application framework. */
abstract class PerformanceRunner implements ConnectionEventListener {
    static final class ResponseTimeBuckets {
        private static final long NS_1_US = NANOSECONDS.convert(1, MICROSECONDS);
@@ -233,33 +231,35 @@
    }
    /**
     * Statistics thread base implementation.
     */
    /** Statistics thread base implementation. */
    class StatsThread extends Thread {
        private final String[] additionalColumns;
        private final List<GarbageCollectorMXBean> beans;
        private final double[] percentiles;
        private final int numColumns;
        protected long totalSuccessCount;
        protected long totalResultCount;
        protected long totalOperationCount;
        protected long totalFailedCount;
        protected long totalWaitTime;
        protected int successCount;
        protected int operationCount;
        protected int failedCount;
        protected long waitTime;
        protected long lastStatTime;
        protected long lastGCDuration;
        protected double recentDuration;
        protected double averageDuration;
        protected double totalDurationSec;
        protected long totalWaitTimeNs;
        protected int intervalSuccessCount;
        protected int intervalOperationCount;
        protected int intervalFailedCount;
        protected double intervalDurationSec;
        protected long intervalWaitTimeNs;
        protected long lastStatTimeMs;
        protected long lastGCDurationMs;
        private final int numColumns;
        private final String[] additionalColumns;
        private final double[] percentiles;
        private final List<GarbageCollectorMXBean> gcBeans;
        private final boolean isScriptFriendly = app.isScriptFriendly();
        private MultiColumnPrinter printer;
        public StatsThread(final String... additionalColumns) {
            super("Stats Thread");
            this.additionalColumns = additionalColumns;
            if (!percentilesArgument.isPresent()) {
                this.percentiles = new double[]{99.9, 99.99, 99.999};
                this.percentiles = new double[] { 99.9, 99.99, 99.999 };
            } else {
                this.percentiles = new double[percentilesArgument.getValues().size()];
                int index = 0;
@@ -270,187 +270,211 @@
            }
            this.numColumns = 5 + this.percentiles.length + additionalColumns.length + (isAsync ? 1 : 0);
            this.beans = ManagementFactory.getGarbageCollectorMXBeans();
            this.gcBeans = ManagementFactory.getGarbageCollectorMXBeans();
        }
        private void printResultsTitle() {
            if (isScriptFriendly) {
                printResultsTitleScriptFriendly();
                return;
            }
            printer = new MultiColumnPrinter(numColumns, 2, "-", MultiColumnPrinter.RIGHT, app);
            printer.setTitleAlign(MultiColumnPrinter.RIGHT);
            printResultTitleHeaders();
            printResultTitleDetails();
        }
        private void printResultTitleHeaders() {
            final String[][] titleHeaders = new String[2][numColumns];
            for (final String[] titleLine : titleHeaders) {
                Arrays.fill(titleLine, "");
            }
            titleHeaders[0][0] = "Throughput";
            titleHeaders[0][2] = "Response Time";
            titleHeaders[1][0] = "(ops/second)";
            titleHeaders[1][2] = "(milliseconds)";
            final int[] span = new int[numColumns];
            span[0] = 2;
            span[1] = 0;
            span[2] = 2 + this.percentiles.length;
            Arrays.fill(span, 3, 4 + this.percentiles.length, 0);
            Arrays.fill(span, 4 + this.percentiles.length, span.length, 1);
            for (final String[] titleLine : titleHeaders) {
                printer.addTitle(titleLine, span);
            }
        }
        private void printResultTitleDetails() {
            final String[] titleDetails = new String[numColumns];
            titleDetails[0] = "recent";
            titleDetails[1] = "average";
            titleDetails[2] = "recent";
            titleDetails[3] = "average";
            int i = 4;
            for (double percentile :percentiles) {
                titleDetails[i++] = percentile + "%";
            }
            titleDetails[i++] = "err/sec";
            if (isAsync) {
                titleDetails[i++] = "req/res";
            }
            for (final String column : additionalColumns) {
                titleDetails[i++] = column;
            }
            final int[] span = new int[numColumns];
            Arrays.fill(span, 1);
            printer.addTitle(titleDetails, span);
            printer.printTitle();
        }
        private void printResultsTitleScriptFriendly() {
            final PrintStream out = app.getOutputStream();
            out.print("Time (seconds)");
            out.print(",");
            out.print("Recent throughput (ops/second)");
            out.print(",");
            out.print("Average throughput (ops/second)");
            out.print(",");
            out.print("Recent response time (milliseconds)");
            out.print(",");
            out.print("Average response time (milliseconds)");
            for (final double percentile : this.percentiles) {
                out.print(",");
                out.print(percentile);
                out.print("% response time (milliseconds)");
            }
            out.print(",");
            out.print("Errors/second");
            if (isAsync) {
                out.print(",");
                out.print("Requests/response");
            }
            for (final String column : additionalColumns) {
                out.print(",");
                out.print(column);
            }
            out.println();
        }
        @Override
        public void run() {
            final MultiColumnPrinter printer;
            printResultsTitle();
            if (!app.isScriptFriendly()) {
                printer = new MultiColumnPrinter(numColumns, 2, "-", MultiColumnPrinter.RIGHT, app);
                printer.setTitleAlign(MultiColumnPrinter.RIGHT);
            long totalStatTimeMs = System.currentTimeMillis();
            long gcDurationMs = getGCDuration();
                String[] title = new String[numColumns];
                Arrays.fill(title, "");
                title[0] = "Throughput";
                title[2] = "Response Time";
                int[] span = new int[numColumns];
                span[0] = 2;
                span[1] = 0;
                span[2] = 2 + this.percentiles.length;
                Arrays.fill(span, 3, 4 + this.percentiles.length, 0);
                Arrays.fill(span, 4 + this.percentiles.length, span.length, 1);
                printer.addTitle(title, span);
                title = new String[numColumns];
                Arrays.fill(title, "");
                title[0] = "(ops/second)";
                title[2] = "(milliseconds)";
                printer.addTitle(title, span);
                title = new String[numColumns];
                title[0] = "recent";
                title[1] = "average";
                title[2] = "recent";
                title[3] = "average";
                int i = 4;
                for (double percentile :percentiles) {
                    title[i++] = percentile + "%";
                }
                title[i++] = "err/sec";
                if (isAsync) {
                    title[i++] = "req/res";
                }
                for (final String column : additionalColumns) {
                    title[i++] = column;
                }
                span = new int[numColumns];
                Arrays.fill(span, 1);
                printer.addTitle(title, span);
                printer.printTitle();
            } else {
                final PrintStream out = app.getOutputStream();
                out.print("Time (seconds)");
                out.print(",");
                out.print("Recent throughput (ops/second)");
                out.print(",");
                out.print("Average throughput (ops/second)");
                out.print(",");
                out.print("Recent response time (milliseconds)");
                out.print(",");
                out.print("Average response time (milliseconds)");
                for (final double percentile : this.percentiles) {
                    out.print(",");
                    out.print(percentile);
                    out.print("% response time (milliseconds)");
                }
                out.print(",");
                out.print("Errors/second");
                if (isAsync) {
                    out.print(",");
                    out.print("Requests/response");
                }
                for (final String column : additionalColumns) {
                    out.print(",");
                    out.print(column);
                }
                out.println();
                printer = null;
            }
            final long startTime = System.currentTimeMillis();
            long statTime = startTime;
            long gcDuration = 0;
            for (final GarbageCollectorMXBean bean : beans) {
                gcDuration += bean.getCollectionTime();
            }
            while (!stopRequested) {
                try {
                    sleep(statsInterval);
                } catch (final InterruptedException ie) {
                    // Ignore.
                }
                lastStatTimeMs = totalStatTimeMs;
                totalStatTimeMs = System.currentTimeMillis();
                lastStatTime = statTime;
                statTime = System.currentTimeMillis();
                lastGCDurationMs = gcDurationMs;
                gcDurationMs = getGCDuration();
                final long gcIntervalDurationMs = gcDurationMs - lastGCDurationMs;
                lastGCDuration = gcDuration;
                gcDuration = 0;
                for (final GarbageCollectorMXBean bean : beans) {
                    gcDuration += bean.getCollectionTime();
                }
                computeStatsForInterval(totalStatTimeMs, gcIntervalDurationMs);
                final long intervalResultCount = intervalSuccessCount + intervalFailedCount;
                operationCount = operationRecentCount.getAndSet(0);
                successCount = successRecentCount.getAndSet(0);
                failedCount = failedRecentCount.getAndSet(0);
                waitTime = waitRecentTime.getAndSet(0);
                final String[] printableStats = new String[numColumns];
                printableStats[0] = getDivisionResult(intervalResultCount, intervalDurationSec, 1);
                printableStats[1] = getDivisionResult(totalResultCount, totalDurationSec, 1);
                final int resultCount = successCount + failedCount;
                final long intervalWaitTimeMs = NANOSECONDS.toMillis(intervalWaitTimeNs) - gcIntervalDurationMs;
                printableStats[2] = getDivisionResult(intervalWaitTimeMs, intervalResultCount, 3);
                totalOperationCount += operationCount;
                totalSuccessCount += successCount;
                totalFailedCount += failedCount;
                totalWaitTime += waitTime;
                final long totalResultCount = totalSuccessCount + totalFailedCount;
                recentDuration = statTime - lastStatTime;
                averageDuration = statTime - startTime;
                recentDuration -= gcDuration - lastGCDuration;
                averageDuration -= gcDuration;
                recentDuration /= 1000.0;
                averageDuration /= 1000.0;
                final String[] strings = new String[numColumns];
                strings[0] = String.format(ENGLISH, "%.1f", resultCount / recentDuration);
                strings[1] = String.format(ENGLISH, "%.1f", totalResultCount / averageDuration);
                if (resultCount > 0) {
                    strings[2] = String.format(ENGLISH, "%.3f", (waitTime - (gcDuration - lastGCDuration))
                            / (double) resultCount / 1000000.0);
                } else {
                    strings[2] = "-";
                }
                if (totalResultCount > 0) {
                    strings[3] = String.format(ENGLISH, "%.3f",
                            (totalWaitTime - gcDuration) / (double) totalResultCount / 1000000.0);
                } else {
                    strings[3] = "-";
                }
                final long totalWaitTimeMs = NANOSECONDS.toMillis(totalWaitTimeNs) - gcDurationMs;
                printableStats[3] = getDivisionResult(totalWaitTimeMs, totalResultCount, 3);
                int i = 4;
                List<Long> computedPercentiles = eTimesBuckets.getPercentile(percentiles, totalOperationCount);
                final List<Long> computedPercentiles = eTimesBuckets.getPercentile(percentiles, totalOperationCount);
                for (int j = computedPercentiles.size() - 1; j >= 0; j--) {
                    strings[i++] = String.format(ENGLISH, "%.2f", computedPercentiles.get(j) / 1000.0);
                    printableStats[i++] = getDivisionResult(computedPercentiles.get(j) , 1000.0, 2);
                }
                strings[i++] = String.format(ENGLISH, "%.1f", failedCount / recentDuration);
                printableStats[i++] = getDivisionResult(intervalFailedCount, intervalDurationSec, 1);
                if (isAsync) {
                    strings[i++] = resultCount > 0 ? String.format(ENGLISH, "%.1f", (double) operationCount
                            / resultCount) : "-";
                    printableStats[i++] = getDivisionResult(intervalOperationCount, intervalResultCount, 1);
                }
                for (final String column : getAdditionalColumns()) {
                    strings[i++] = column;
                    printableStats[i++] = column;
                }
                if (printer != null) {
                    printer.printRow(strings);
                if (isScriptFriendly) {
                    printScriptFriendlyStats(printableStats);
                } else {
                    // Script-friendly.
                    final PrintStream out = app.getOutputStream();
                    out.print(averageDuration);
                    for (final String s : strings) {
                        out.print(",");
                        out.print(s);
                    }
                    out.println();
                    printer.printRow(printableStats);
                }
            }
        }
        private void computeStatsForInterval(final long statTime, final long gcIntervalDurationMs) {
            intervalOperationCount = operationRecentCount.getAndSet(0);
            intervalSuccessCount = successRecentCount.getAndSet(0);
            intervalFailedCount = failedRecentCount.getAndSet(0);
            intervalWaitTimeNs = waitRecentTimeNs.getAndSet(0);
            totalOperationCount += intervalOperationCount;
            totalResultCount += intervalSuccessCount + intervalFailedCount;
            totalWaitTimeNs += intervalWaitTimeNs;
            final long intervalDurationMs = statTime - lastStatTimeMs;
            intervalDurationSec = (intervalDurationMs - gcIntervalDurationMs) / 1000.0;
            totalDurationSec += intervalDurationSec;
        }
        private long getGCDuration() {
            long gcDuration = 0;
            for (final GarbageCollectorMXBean bean : gcBeans) {
                gcDuration += bean.getCollectionTime();
            }
            return gcDuration;
        }
        private String getDivisionResult(final long numerator, final double denominator, final int precision) {
            return getDivisionResult(numerator, denominator, precision, "-");
        }
        protected String getDivisionResult(
                final long numerator, final double denominator, final int precision, final String fallBack) {
            return denominator > 0 ? String.format(ENGLISH, "%." + precision + "f", numerator / denominator)
                                   : fallBack;
        }
        private void printScriptFriendlyStats(String[] printableStats) {
            final PrintStream out = app.getOutputStream();
            out.print(String.format(ENGLISH, "%.3f", totalDurationSec));
            for (final String s : printableStats) {
                out.print(",");
                out.print(s);
            }
            out.println();
        }
        String[] getAdditionalColumns() {
            return EMPTY_STRINGS;
        }
        /** Resets both general and recent statistic indicators. */
        void resetStats() {
            failedCount = 0;
            operationCount = 0;
            successCount = 0;
            intervalFailedCount = 0;
            intervalOperationCount = 0;
            intervalSuccessCount = 0;
            operationRecentCount.set(0);
            successRecentCount.set(0);
            failedRecentCount.set(0);
            waitRecentTime.set(0);
            waitRecentTimeNs.set(0);
        }
    }
@@ -506,15 +530,13 @@
        private void updateStats() {
            if (!isWarmingUp) {
                final long eTime = System.nanoTime() - currentTime;
                waitRecentTime.getAndAdd(eTime);
                waitRecentTimeNs.getAndAdd(eTime);
                eTimesBuckets.addTimeToInterval(eTime);
            }
        }
    }
    /**
     * Worker thread base implementation.
     */
    /** Worker thread base implementation. */
    abstract class WorkerThread extends Thread {
        private int count;
        private final Connection connection;
@@ -527,15 +549,15 @@
            this.connectionFactory = connectionFactory;
        }
        public abstract Promise<?, LdapException> performOperation(Connection connection,
                DataSource[] dataSources, long startTime);
        public abstract Promise<?, LdapException> performOperation(
                Connection connection, DataSource[] dataSources, long startTime);
        @Override
        public void run() {
            Promise<?, LdapException> promise;
            Connection connection;
            final double targetTimeInMS = 1000.0 / (targetThroughput / (double) (numThreads * numConnections));
            double sleepTimeInMS = 0;
            final double targetTimeMs = 1000.0 / (targetThroughput / (double) (numThreads * numConnections));
            double sleepTimeMs = 0;
            while (!stopRequested && !localStopRequested
                    && (maxIterations <= 0 || count < maxIterations)) {
@@ -573,8 +595,8 @@
                    }
                }
                long start = System.nanoTime();
                promise = performOperation(connection, dataSources.get(), start);
                long startTimeNs = System.nanoTime();
                promise = performOperation(connection, dataSources.get(), startTimeNs);
                operationRecentCount.getAndIncrement();
                if (!isAsync) {
                    try {
@@ -597,17 +619,18 @@
                }
                if (targetThroughput > 0) {
                    try {
                        if (sleepTimeInMS > 1) {
                            sleep((long) Math.floor(sleepTimeInMS));
                        if (sleepTimeMs > 1) {
                            sleep((long) Math.floor(sleepTimeMs));
                        }
                    } catch (final InterruptedException e) {
                        continue;
                    }
                    sleepTimeInMS += targetTimeInMS - (System.nanoTime() - start) / 1000000.0;
                    if (sleepTimeInMS < -60000) {
                    sleepTimeMs += targetTimeMs - NANOSECONDS.toMillis(System.nanoTime() - startTimeNs);
                    final long oneMinuteMs = MINUTES.toMillis(1);
                    if (sleepTimeMs + oneMinuteMs < 0) {
                        // If we fall behind by 60 seconds, just forget about catching up
                        sleepTimeInMS = -60000;
                        sleepTimeMs = -oneMinuteMs;
                    }
                }
            }
@@ -622,7 +645,7 @@
    private final AtomicInteger operationRecentCount = new AtomicInteger();
    protected final AtomicInteger successRecentCount = new AtomicInteger();
    protected final AtomicInteger failedRecentCount = new AtomicInteger();
    private final AtomicLong waitRecentTime = new AtomicLong();
    private final AtomicLong waitRecentTimeNs = new AtomicLong();
    private final ResponseTimeBuckets eTimesBuckets = new ResponseTimeBuckets();