package com.proofpoint.stats;

import com.proofpoint.testing.TestingTicker;
import com.proofpoint.units.Duration;

import java.util.Random;
import java.util.concurrent.TimeUnit;

public class BenchmarkQuantileDigest
{
    public static void main(String[] args)
            throws Exception
    {
        Duration warmupTime = new Duration(3, TimeUnit.SECONDS);
        Duration benchmarkTime = new Duration(5, TimeUnit.SECONDS);

        final QuantileDigest digest = new QuantileDigest(0.01, 0, new TestingTicker(), true);
        final Random random = new Random();

        Benchmark.Results results = Benchmark.run(new Runnable() {
            public void run()
            {
                digest.add(Math.abs(random.nextInt(100000)));
            }
        }, warmupTime, benchmarkTime);

        digest.validate();

        System.out.println(String.format("Processed %s entries in %s ms. Insertion rate = %s entries/s (%.4fµs per operation)",
                results.getOperations(),
                results.getTime().getValue(TimeUnit.MILLISECONDS),
                results.getOperationsPerSecond(),
                results.getTimePerOperation().getValue(TimeUnit.MICROSECONDS)));

        System.out.println(String.format("Compressions: %s, %s entries/compression",
                digest.getCompressions(),
                digest.getCount() / digest.getCompressions()));
    }

}

package com.proofpoint.stats;

import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.Fork;
import org.openjdk.jmh.annotations.Measurement;
import org.openjdk.jmh.annotations.OperationsPerInvocation;
import org.openjdk.jmh.annotations.OutputTimeUnit;
import org.openjdk.jmh.annotations.Scope;
import org.openjdk.jmh.annotations.Setup;
import org.openjdk.jmh.annotations.State;
import org.openjdk.jmh.annotations.Warmup;
import org.openjdk.jmh.profile.GCProfiler;
import org.openjdk.jmh.runner.Runner;
import org.openjdk.jmh.runner.RunnerException;
import org.openjdk.jmh.runner.options.Options;
import org.openjdk.jmh.runner.options.OptionsBuilder;
import org.openjdk.jmh.runner.options.VerboseMode;

import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;

@OutputTimeUnit(TimeUnit.SECONDS)
@Fork(3)
@Warmup(iterations = 10)
@Measurement(iterations = 10)
public class BenchmarkQuantileDigest
{
    private static final int NUMBER_OF_ENTRIES = 10_000;

    @State(Scope.Thread)
    public static class Data
    {
        private long[] values;

        @Setup
        public void setup()
        {
            values = new long[NUMBER_OF_ENTRIES];
            for (int i = 0; i < NUMBER_OF_ENTRIES; i++) {
                // generate values from a large domain but not many distinct values
                long value = Math.abs((long) (ThreadLocalRandom.current().nextGaussian() * 1_000_000_000));
                values[i] = (value / 1_000_000) * 1_000_000;
            }
        }
    }

    @Benchmark
    @OperationsPerInvocation(NUMBER_OF_ENTRIES)
    public QuantileDigest benchmarkInserts(Data data)
    {
        QuantileDigest digest = new QuantileDigest(0.01);

        for (long value : data.values) {
            digest.add(value);
        }

        return digest;
    }

    public static void main(String[] args)
            throws RunnerException
    {
        Options options = new OptionsBuilder()
                .verbosity(VerboseMode.NORMAL)
                .include(".*" + BenchmarkQuantileDigest.class.getSimpleName() + ".*")
                .addProfiler(GCProfiler.class)
                .build();

        new Runner(options).run();
    }
}

Add quantile digest JMH benchmark