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

org.nohope.test.stress.result.simplified.SimpleActionResult Maven / Gradle / Ivy

The newest version!
package org.nohope.test.stress.result.simplified;

import com.google.common.base.Objects;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.apache.commons.math.stat.descriptive.rank.Percentile;
import org.nohope.test.stress.util.Measurement;

import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.OptionalLong;
import java.util.Set;
import java.util.TreeSet;
import java.util.function.Function;
import java.util.function.ToLongFunction;
import java.util.stream.Collectors;
import java.util.stream.LongStream;

import static java.util.Map.Entry;
import static java.util.concurrent.TimeUnit.*;
import static org.nohope.test.stress.util.TimeUtils.*;

/**
 * @author ketoth xupack
 * @since 2013-12-27 16:19
 */
public class SimpleActionResult {
    private static final Function DIFF = e -> e.getEndNanos() - e.getStartNanos();

    private final Map> timestampsPerThread = new HashMap<>();
    private final Map, Collection> errorStats = new HashMap<>();
    private final Map, Collection> rootErrorStats = new HashMap<>();
    private final Map startEndForThread;

    private final String name;

    private final double meanRequestTime;
    private final long minTime;
    private final long maxTime;
    private final double totalDeltaNanos;
    private final long operationsCount;
    private final int numberOfThreads;
    private double avgWastedNanos;
    private double avgRuntimeIncludingWastedNanos;
    private final Set percentiles = new TreeSet<>();
    private final Percentile percentile = new Percentile();

    public SimpleActionResult(final String name,
                              final Map> timestampsPerThread,
                              final Map, Collection> errorStats,
                              final int numberOfThreads,
                              final long totalDeltaNanos,
                              final long minTime,
                              final long maxTime) {
        this.name = name;
        this.totalDeltaNanos = totalDeltaNanos;
        this.minTime = minTime;
        this.maxTime = maxTime;

        this.operationsCount = timestampsPerThread.values().stream().flatMap(Collection::stream).count();
        this.meanRequestTime = 1.0 * totalDeltaNanos / operationsCount;

        this.errorStats.putAll(errorStats);
        this.rootErrorStats.putAll(computeRootStats(this.errorStats));
        this.timestampsPerThread.putAll(timestampsPerThread);
        this.numberOfThreads = numberOfThreads;

        startEndForThread = timestampsPerThread
                .entrySet().stream().filter(entries -> !entries.getValue().isEmpty())
                .collect(Collectors.toMap(Entry::getKey, e -> Measurement.of(
                        calc(LongStream::min, e.getValue(), Measurement::getStartNanos),
                        calc(LongStream::max, e.getValue(), Measurement::getEndNanos))));

        avgWastedNanos = 0;
        avgRuntimeIncludingWastedNanos = 0;
        startEndForThread.entrySet().forEach(entry -> {
            final double threadDelta = timestampsPerThread
                    .get(entry.getKey()).stream().map(DIFF).reduce(0L, Long::sum);

            final double delta = DIFF.apply(entry.getValue());
            avgRuntimeIncludingWastedNanos += delta;
            avgWastedNanos += (delta - threadDelta);
        });

        avgWastedNanos /= numberOfThreads;
        avgRuntimeIncludingWastedNanos /= numberOfThreads;
        percentiles.add(99.5d);
        percentiles.add(95d);
        percentiles.add(50d);
    }

    private static Map, List> computeRootStats(final Map, Collection> errorStats) {
        //noinspection ThrowableResultOfMethodCallIgnored
        return errorStats.values().parallelStream().flatMap(Collection::stream)
                         .map(e -> Objects.firstNonNull(ExceptionUtils.getRootCause(e), e))
                         .collect(Collectors.groupingBy(Object::getClass));
    }

    public double getAvgWastedNanos() {
        return avgWastedNanos;
    }

    public double getAvgRuntimeIncludingWastedNanos() {
        return avgRuntimeIncludingWastedNanos;
    }

    /**
     * @return test scenario name
     */
    public String getName() {
        return name;
    }

    /**
     * @return average request time in nanos
     */
    public double getMeanTime() {
        return meanRequestTime;
    }

    /**
     * @return minimum request time in nanos
     */
    public double getMinTime() {
        return minTime;
    }

    /**
     * @return maximum request time in nanos
     */
    public double getMaxTime() {
        return maxTime;
    }

    /**
     * @return overall op/nanos
     */
    public double getThroughput() {
        return getWorkerThroughput() * numberOfThreads;
    }

    /**
     * @return op/nanos per thread
     */
    public double getWorkerThroughput() {
        return operationsCount / totalDeltaNanos;
    }

    /**
     * @return get pure running time in nanos
     */
    public double getRuntime() {
        return totalDeltaNanos;
    }

    /**
     * Per thread timestamps of operation start and end
     *
     * @return in nanoseconds
     */
    public Map> getTimestampsPerThread() {
        return Collections.unmodifiableMap(timestampsPerThread);
    }

    /**
     * Operation times per thread
     *
     * @return in nanoseconds
     */
    public Map> getPerThreadRuntimes() {
        return timestampsPerThread.entrySet().stream().collect(Collectors.toMap(Entry::getKey, e ->
                e.getValue().stream().map(DIFF).collect(Collectors.toList())));
    }

    /**
     * @return list of all exceptions thrown during test scenario
     */
    public List getErrors() {
        return errorStats.values().stream().flatMap(Collection::stream).collect(Collectors.toList());
    }

    /**
     * @return list of all exceptions split by topmost exception class
     */
    public Map, Collection> getErrorsPerClass() {
        return Collections.unmodifiableMap(errorStats);
    }

    /**
     * @return list of all running times of each thread in nanos
     */
    public final List getRunTimes() {
        return timestampsPerThread.values().parallelStream()
                                  .flatMap(t -> t.stream().map(DIFF))
                                  .collect(Collectors.toList());
    }

    public SimpleActionResult withPercentiles(final double... percentiles) {
        for (final double percentile : percentiles) {
            if (percentile < 0 || percentile > 100) {
                throw new IllegalStateException("Percentile " + percentile + " is out of range [0, 100]");
            }
        }

        this.percentiles.clear();
        this.percentiles.addAll(Arrays.asList(ArrayUtils.toObject(percentiles)));
        return this;
    }

    @Override
    public final String toString() {
        final StringBuilder builder = new StringBuilder();

        builder.append("----- Stats for (name: ")
               .append(name)
               .append(") -----\n");

        builder.append(pad("Operations:"))
               .append(operationsCount)
               .append('\n');

        builder.append(pad("Operation time"))
               .append('\n');

        builder.append(pad("  Min:"))
               .append(String.format("%.3f", timeTo(minTime, MILLISECONDS)))
               .append(" ms")
               .append('\n');

        builder.append(pad("  Max:"))
               .append(String.format("%.3f", timeTo(maxTime, MILLISECONDS)))
               .append(" ms")
               .append('\n');

        builder.append(pad("  Avg:"))
               .append(String.format("%.3f", timeTo(meanRequestTime, MILLISECONDS)))
               .append(" ms")
               .append('\n');

        long fails = errorStats.values().stream().flatMap(Collection::stream).count();
        if (!percentiles.isEmpty()) {
            final List runTimes = getRunTimes();
            Collections.sort(runTimes);

            final double[] data = new double[runTimes.size()];
            int i = 0;
            for (final Long runtime : runTimes) {
                data[i] = runtime;
                i++;
            }

            percentiles.forEach(p ->
                    builder.append(pad(String.format("  %sth percentile:", p)))
                           .append(String.format("%.3f", timeTo(percentile.evaluate(data, p), MILLISECONDS)))
                           .append(" ms")
                           .append('\n'));
        }

        builder.append(pad("Objective avg runtime:"))
               .append(String.format("%.3f", timeTo(totalDeltaNanos / numberOfThreads, SECONDS)))
               .append(" sec\n");

        builder.append(pad(String.format("Total running time (%d workers):", numberOfThreads)))
               .append(String.format("%.3f", timeTo(totalDeltaNanos, SECONDS)))
               .append(" sec\n");

        builder.append(pad("Total time per thread:"))
               .append(String.format("%.3f", timeTo(avgRuntimeIncludingWastedNanos, SECONDS)))
               .append(" sec\n");

        builder.append(pad("Running time per thread:"))
               .append(String.format("%.3f", timeTo(totalDeltaNanos / numberOfThreads, SECONDS)))
               .append(" sec\n");

        builder.append(pad("Avg wasted time per thread:"))
               .append(String.format("%.3e", timeTo(avgWastedNanos, MILLISECONDS)))
               .append(" ms\n");

        builder.append(pad("Avg thread throughput:"))
               .append(String.format("%.3e", throughputTo(getWorkerThroughput(), SECONDS)))
               .append(" op/sec")
               .append('\n');

        builder.append(pad("Avg throughput (inaccurate):"))
               .append(String.format("%.3e", throughputTo(getThroughput(), SECONDS)))
               .append(" op/sec")
               .append('\n');

        builder.append(pad("Errors count:"))
               .append(fails)
               .append('\n');

        errorStats.entrySet().forEach(e ->
                builder.append("| ")
                       .append(e.getKey().getName())
                       .append(" happened ")
                       .append(e.getValue().size())
                       .append(" times")
                       .append('\n'));

        if (!errorStats.isEmpty()) {
            builder.append("Roots:\n");
            rootErrorStats.entrySet().forEach(e ->
                    builder.append("| ")
                           .append(e.getKey().getName())
                           .append(" happened ")
                           .append(e.getValue().size())
                           .append(" times")
                           .append('\n'));
        }

        return builder.toString();
    }

    private static String pad(final String str) {
        final int padSize = 40;
        return StringUtils.rightPad(str, padSize, '.');
    }

    private static  long calc(final Function param,
                                 final Collection measurements,
                                 final ToLongFunction getter) {
        return param.apply(measurements.stream().mapToLong(getter)).getAsLong();
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy