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();
    }
}

@@ -1090,16 +1090,49 @@
 %5D values
+1;%0A        private long%5B%5D values2
 ;%0A%0A     
@@ -1201,37 +1201,202 @@
 lues
+1
  = 
-new long%5BNUMBER_OF_ENTRIES
+makeValues(NUMBER_OF_ENTRIES);%0A            values2 = makeValues(NUMBER_OF_ENTRIES);%0A        %7D%0A%0A        private long%5B%5D makeValues(int size)%0A        %7B%0A            long%5B%5D values = new long%5Bsize
 %5D;%0A 
@@ -1426,33 +1426,20 @@
  0; i %3C 
-NUMBER_OF_ENTRIES
+size
 ; i++) %7B
@@ -1701,24 +1701,626 @@
            %7D
+%0A%0A            return values;%0A        %7D%0A    %7D%0A%0A    @State(Scope.Thread)%0A    public static class Digest%0A    %7B%0A        private QuantileDigest digest1;%0A        private QuantileDigest digest2;%0A%0A        @Setup%0A        public void setup(Data data)%0A        %7B%0A            digest1 = makeDigest(data.values1);%0A            digest2 = makeDigest(data.values2);%0A        %7D%0A%0A        private QuantileDigest makeDigest(long%5B%5D values)%0A        %7B%0A            QuantileDigest result = new QuantileDigest(0.01);%0A            for (long value : values) %7B%0A                result.add(value);%0A            %7D%0A            return result;
 %0A        %7D%0A 
@@ -2540,24 +2540,25 @@
  data.values
+1
 ) %7B%0A        
@@ -2617,24 +2617,361 @@
 est;%0A    %7D%0A%0A
+    @Benchmark%0A    public QuantileDigest benchmarkCopy(Digest data)%0A    %7B%0A        return new QuantileDigest(data.digest1);%0A    %7D%0A%0A    @Benchmark%0A    public QuantileDigest benchmarkMerge(Digest data)%0A    %7B%0A        QuantileDigest merged = new QuantileDigest(data.digest1);%0A        merged.merge(data.digest2);%0A        return merged;%0A    %7D%0A%0A
     public s

Add benchmark for quantile digest copy and merge