changeset 1340:2e5a7761ce12

7901769: JSON output should include histogram data where available Contributed-by: Petr Stefan <ptr.stef@gmail.com>
author shade
date Tue, 16 Aug 2016 16:18:15 +0300
parents 397891feda1b
children 14809d1f5c61
files jmh-core/src/main/java/org/openjdk/jmh/results/format/JSONResultFormat.java jmh-core/src/main/java/org/openjdk/jmh/util/ListStatistics.java jmh-core/src/main/java/org/openjdk/jmh/util/MultisetStatistics.java jmh-core/src/main/java/org/openjdk/jmh/util/SingletonStatistics.java jmh-core/src/main/java/org/openjdk/jmh/util/Statistics.java jmh-core/src/main/java/org/openjdk/jmh/util/Utils.java jmh-core/src/test/java/org/openjdk/jmh/util/TestListStatistics.java jmh-core/src/test/java/org/openjdk/jmh/util/TestMultisetStatistics.java jmh-core/src/test/java/org/openjdk/jmh/util/TestSingletonStatistics.java jmh-core/src/test/resources/org/openjdk/jmh/results/format/output-golden.json
diffstat 10 files changed, 225 insertions(+), 26 deletions(-) [+]
line wrap: on
line diff
--- a/jmh-core/src/main/java/org/openjdk/jmh/results/format/JSONResultFormat.java	Tue Jul 26 00:02:32 2016 +0300
+++ b/jmh-core/src/main/java/org/openjdk/jmh/results/format/JSONResultFormat.java	Tue Aug 16 16:18:15 2016 +0300
@@ -30,6 +30,7 @@
 import org.openjdk.jmh.results.Result;
 import org.openjdk.jmh.results.RunResult;
 import org.openjdk.jmh.util.Statistics;
+import org.openjdk.jmh.util.Utils;
 
 import java.io.PrintStream;
 import java.io.PrintWriter;
@@ -40,6 +41,9 @@
 
 class JSONResultFormat implements ResultFormat {
 
+    private static final boolean PRINT_RAW_DATA =
+            Boolean.parseBoolean(System.getProperty("jmh.json.rawData", "true"));
+
     private final PrintStream out;
 
     public JSONResultFormat(PrintStream out) {
@@ -59,6 +63,7 @@
 
             if (first) {
                 first = false;
+                pw.println();
             } else {
                 pw.println(",");
             }
@@ -88,21 +93,19 @@
             pw.println("\"scoreConfidence\" : " + emit(primaryResult.getScoreConfidence()) + ",");
             pw.println(emitPercentiles(primaryResult.getStatistics()));
             pw.println("\"scoreUnit\" : \"" + primaryResult.getScoreUnit() + "\",");
-            pw.println("\"rawData\" :");
 
-            {
-                Collection<String> l1 = new ArrayList<String>();
-                for (BenchmarkResult benchmarkResult : runResult.getBenchmarkResults()) {
-                    Collection<String> scores = new ArrayList<String>();
-                    for (IterationResult r : benchmarkResult.getIterationResults()) {
-                        scores.add(emit(r.getPrimaryResult().getScore()));
-                    }
-                    l1.add(printMultiple(scores, "[", "]"));
-                }
-                pw.println(printMultiple(l1, "[", "]"));
-                pw.println("},");
+            switch (params.getMode()) {
+                case SampleTime:
+                    pw.println("\"rawDataHistogram\" :");
+                    pw.println(getRawData(runResult, true));
+                    break;
+                default:
+                    pw.println("\"rawData\" :");
+                    pw.println(getRawData(runResult, false));
             }
 
+            pw.println("},"); // primaryMetric end
+
             Collection<String> secondaries = new ArrayList<String>();
             for (Map.Entry<String, Result> e : runResult.getSecondaryResults().entrySet()) {
                 String secondaryName = e.getKey();
@@ -137,14 +140,39 @@
             pw.println(printMultiple(secondaries, "", ""));
             pw.println("}");
 
-            pw.println("}");
-
+            pw.print("}"); // benchmark end
         }
         pw.println("]");
 
         out.println(tidy(sw.toString()));
     }
 
+    private String getRawData(RunResult runResult, boolean histogram) {
+        StringBuilder sb = new StringBuilder();
+        Collection<String> runs = new ArrayList<String>();
+
+        if (PRINT_RAW_DATA) {
+            for (BenchmarkResult benchmarkResult : runResult.getBenchmarkResults()) {
+                Collection<String> iterations = new ArrayList<String>();
+                for (IterationResult r : benchmarkResult.getIterationResults()) {
+                    if (histogram) {
+                        Collection<String> singleIter = new ArrayList<String>();
+                        for (Map.Entry<Double, Long> item : Utils.adaptForLoop(r.getPrimaryResult().getStatistics().getRawData())) {
+                            singleIter.add("< " + emit(item.getKey()) + "; " + item.getValue() + " >");
+                        }
+                        iterations.add(printMultiple(singleIter, "[", "]"));
+                    } else {
+                        iterations.add(emit(r.getPrimaryResult().getScore()));
+                    }
+                }
+                runs.add(printMultiple(iterations, "[", "]"));
+            }
+        }
+        sb.append(printMultiple(runs, "[", "]"));
+
+        return sb.toString();
+    }
+
     private String emitParams(BenchmarkParams params) {
         StringBuilder sb = new StringBuilder();
         boolean isFirst = true;
@@ -218,6 +246,11 @@
         s = s.replaceAll("\\}\n,\n", "},\n");
         s = s.replaceAll("\n( *)\n", "\n");
 
+        // Keep these inline:
+        s = s.replaceAll(";", ",");
+        s = s.replaceAll("\\<", "[");
+        s = s.replaceAll("\\>", "]");
+
         String[] lines = s.split("\n");
 
         StringBuilder sb = new StringBuilder();
@@ -228,7 +261,7 @@
             if (prevL != null && (prevL.endsWith("{") || prevL.endsWith("["))) {
                 ident++;
             }
-            if (l.endsWith("}") || l.endsWith("]") || l.endsWith("},") || l.endsWith("],")) {
+            if (l.equals("}") || l.equals("]") || l.equals("},") || l.equals("],")) {
                 ident--;
             }
 
--- a/jmh-core/src/main/java/org/openjdk/jmh/util/ListStatistics.java	Tue Jul 26 00:02:32 2016 +0300
+++ b/jmh-core/src/main/java/org/openjdk/jmh/util/ListStatistics.java	Tue Aug 16 16:18:15 2016 +0300
@@ -26,7 +26,10 @@
 
 import org.apache.commons.math3.stat.descriptive.rank.Percentile;
 
+import java.util.AbstractMap;
 import java.util.Arrays;
+import java.util.Iterator;
+import java.util.Map;
 
 /**
  * Calculate statistics over a list of doubles.
@@ -150,6 +153,11 @@
     }
 
     @Override
+    public Iterator<Map.Entry<Double, Long>> getRawData() {
+        return new ListStatisticsIterator();
+    }
+
+    @Override
     public double getVariance() {
         if (count > 1) {
             double v = 0;
@@ -163,4 +171,23 @@
         }
     }
 
+    private class ListStatisticsIterator implements Iterator<Map.Entry<Double, Long>> {
+        private int currentIndex = 0;
+
+        @Override
+        public boolean hasNext() {
+            return currentIndex < count;
+        }
+
+        @Override
+        public Map.Entry<Double, Long> next() {
+            return new AbstractMap.SimpleImmutableEntry<Double, Long>(values[currentIndex++], 1L);
+        }
+
+        @Override
+        public void remove() {
+            throw new UnsupportedOperationException("Element cannot be removed.");
+        }
+    }
+
 }
--- a/jmh-core/src/main/java/org/openjdk/jmh/util/MultisetStatistics.java	Tue Jul 26 00:02:32 2016 +0300
+++ b/jmh-core/src/main/java/org/openjdk/jmh/util/MultisetStatistics.java	Tue Aug 16 16:18:15 2016 +0300
@@ -24,9 +24,7 @@
  */
 package org.openjdk.jmh.util;
 
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
+import java.util.*;
 
 public class MultisetStatistics extends AbstractStatistics {
     private static final long serialVersionUID = -4401871054963903938L;
@@ -136,4 +134,9 @@
 
         return result;
     }
+
+    @Override
+    public Iterator<Map.Entry<Double, Long>> getRawData() {
+        return values.entrySet().iterator();
+    }
 }
--- a/jmh-core/src/main/java/org/openjdk/jmh/util/SingletonStatistics.java	Tue Jul 26 00:02:32 2016 +0300
+++ b/jmh-core/src/main/java/org/openjdk/jmh/util/SingletonStatistics.java	Tue Aug 16 16:18:15 2016 +0300
@@ -24,6 +24,10 @@
  */
 package org.openjdk.jmh.util;
 
+import java.util.AbstractMap;
+import java.util.Iterator;
+import java.util.Map;
+
 /**
  * Calculate statistics with just a single value.
  */
@@ -78,4 +82,29 @@
         }
         return result;
     }
+
+    @Override
+    public Iterator<Map.Entry<Double, Long>> getRawData() {
+        return new SingletonStatisticsIterator();
+    }
+
+    private class SingletonStatisticsIterator implements Iterator<Map.Entry<Double, Long>> {
+        private boolean entryReturned = false;
+
+        @Override
+        public boolean hasNext() {
+            return !entryReturned;
+        }
+
+        @Override
+        public Map.Entry<Double, Long> next() {
+            entryReturned = true;
+            return new AbstractMap.SimpleImmutableEntry<Double, Long>(value, 1L);
+        }
+
+        @Override
+        public void remove() {
+            throw new UnsupportedOperationException("Element cannot be removed.");
+        }
+    }
 }
--- a/jmh-core/src/main/java/org/openjdk/jmh/util/Statistics.java	Tue Jul 26 00:02:32 2016 +0300
+++ b/jmh-core/src/main/java/org/openjdk/jmh/util/Statistics.java	Tue Aug 16 16:18:15 2016 +0300
@@ -27,6 +27,9 @@
 import org.apache.commons.math3.stat.descriptive.StatisticalSummary;
 
 import java.io.Serializable;
+import java.util.Iterator;
+import java.util.Map.Entry;
+
 
 public interface Statistics extends Serializable, StatisticalSummary, Comparable<Statistics> {
 
@@ -133,4 +136,14 @@
      * @return histogram data
      */
     int[] getHistogram(double[] levels);
+
+    /**
+     * Returns the raw data for this statistics. This data can be useful for
+     * custom postprocessing and statistics computations.  Note, that values of
+     * multiple calls may not be unique. Ordering of the values is not specified.
+     *
+     * @return iterator to raw data. Each item is pair of actual value and
+     *          number of occurrences of this value.
+     */
+    Iterator<Entry<Double, Long>> getRawData();
 }
--- a/jmh-core/src/main/java/org/openjdk/jmh/util/Utils.java	Tue Jul 26 00:02:32 2016 +0300
+++ b/jmh-core/src/main/java/org/openjdk/jmh/util/Utils.java	Tue Aug 16 16:18:15 2016 +0300
@@ -477,4 +477,20 @@
         return messages;
     }
 
+    /**
+     * Adapts Iterator for Iterable.
+     * Can be iterated only once!
+     *
+     * @param it iterator
+     * @return iterable for given iterator
+     */
+    public static <T> Iterable<T> adaptForLoop(final Iterator<T> it) {
+        return new Iterable<T>() {
+            @Override
+            public Iterator<T> iterator() {
+                return it;
+            }
+        };
+    }
+
 }
--- a/jmh-core/src/test/java/org/openjdk/jmh/util/TestListStatistics.java	Tue Jul 26 00:02:32 2016 +0300
+++ b/jmh-core/src/test/java/org/openjdk/jmh/util/TestListStatistics.java	Tue Aug 16 16:18:15 2016 +0300
@@ -28,6 +28,9 @@
 import org.junit.BeforeClass;
 import org.junit.Test;
 
+import java.util.Iterator;
+import java.util.Map;
+
 import static org.junit.Assert.assertEquals;
 
 /**
@@ -392,4 +395,19 @@
         );
     }
 
+    /**
+     * Test of iterator which make accessible raw data.
+     */
+    @Test
+    public strictfp void testRawDataIterator() {
+        Iterator<Map.Entry<Double, Long>> listIter = instance.getRawData();
+        for (double item : VALUES) {
+            Assert.assertTrue(listIter.hasNext());
+            Map.Entry<Double, Long> entry = listIter.next();
+            Assert.assertEquals(entry.getKey(), item);
+            Assert.assertEquals(entry.getValue().longValue(), 1L);
+        }
+        Assert.assertFalse(listIter.hasNext());
+    }
+
 }
--- a/jmh-core/src/test/java/org/openjdk/jmh/util/TestMultisetStatistics.java	Tue Jul 26 00:02:32 2016 +0300
+++ b/jmh-core/src/test/java/org/openjdk/jmh/util/TestMultisetStatistics.java	Tue Aug 16 16:18:15 2016 +0300
@@ -28,6 +28,8 @@
 import org.junit.BeforeClass;
 import org.junit.Test;
 
+import java.util.Map;
+
 import static org.junit.Assert.assertEquals;
 
 /**
@@ -385,4 +387,50 @@
         );
     }
 
+    /**
+     * Test of iterator which make accessible raw data.
+     * Iterate over default instance with no duplicates.
+     */
+    @Test
+    public strictfp void testRawDataIterator_no_duplicates() {
+        int itemCount = 0;
+        for (Map.Entry<Double, Long> entry : Utils.adaptForLoop(instance.getRawData())) {
+            Assert.assertEquals(entry.getValue().longValue(), 1L);
+
+            // Check if key (the actual data) is in the VALUES collection,
+            // else fail the test (the Multiset was constructed with values
+            // from VALUES collection, so it should be there).
+            boolean keyIsPresent = false;
+            double key = entry.getKey();
+            for (double value : VALUES) {
+                if (Double.compare(value, key) == 0) {
+                    keyIsPresent = true;
+                }
+            }
+            Assert.assertTrue("Value from iterator is not present in source collection", keyIsPresent);
+
+            itemCount++;
+        }
+        Assert.assertEquals(itemCount, VALUES.length);
+    }
+
+    /**
+     * Test of iterator which make accessible raw data.
+     * Iterate over new instance with duplicates.
+     */
+    @Test
+    public strictfp void testRawDataIterator_duplicates() {
+        MultisetStatistics s = new MultisetStatistics();
+        for (int c = 0; c <= 10; c++) {
+            s.addValue(c * 10, c);
+        }
+
+        int itemCount = 0;
+        for (Map.Entry<Double, Long> entry : Utils.adaptForLoop(s.getRawData())) {
+            Assert.assertEquals(entry.getKey(), (double)(entry.getValue() * 10));
+            itemCount++;
+        }
+        Assert.assertEquals(itemCount, 10);
+    }
+
 }
--- a/jmh-core/src/test/java/org/openjdk/jmh/util/TestSingletonStatistics.java	Tue Jul 26 00:02:32 2016 +0300
+++ b/jmh-core/src/test/java/org/openjdk/jmh/util/TestSingletonStatistics.java	Tue Aug 16 16:18:15 2016 +0300
@@ -28,6 +28,9 @@
 import org.junit.BeforeClass;
 import org.junit.Test;
 
+import java.util.Iterator;
+import java.util.Map;
+
 import static org.junit.Assert.assertEquals;
 
 /**
@@ -245,4 +248,17 @@
         );
     }
 
+    /**
+     * Test of iterator which make accessible raw data.
+     */
+    @Test
+    public strictfp void testRawDataIterator() {
+        Iterator<Map.Entry<Double, Long>> singIter = singStats.getRawData();
+        Assert.assertTrue(singIter.hasNext());
+        Map.Entry<Double, Long> entry = singIter.next();
+        Assert.assertEquals(entry.getKey(), VALUE);
+        Assert.assertEquals(entry.getValue().longValue(), 1L);
+        Assert.assertFalse(singIter.hasNext());
+    }
+
 }
--- a/jmh-core/src/test/resources/org/openjdk/jmh/results/format/output-golden.json	Tue Jul 26 00:02:32 2016 +0300
+++ b/jmh-core/src/test/resources/org/openjdk/jmh/results/format/output-golden.json	Tue Aug 16 16:18:15 2016 +0300
@@ -214,8 +214,7 @@
                 ]
             }
         }
-    }
-    ,
+    },
     {
         "benchmark" : "benchmark_1",
         "mode" : "thrpt",
@@ -314,8 +313,7 @@
                 ]
             }
         }
-    }
-    ,
+    },
     {
         "benchmark" : "benchmark_2",
         "mode" : "thrpt",
@@ -476,8 +474,7 @@
                 ]
             }
         }
-    }
-    ,
+    },
     {
         "benchmark" : "benchmark_3",
         "mode" : "thrpt",
@@ -670,8 +667,7 @@
                 ]
             }
         }
-    }
-    ,
+    },
     {
         "benchmark" : "benchmark_4",
         "mode" : "thrpt",