changeset 1342:da0cc0a41bed

7901781: Benchmark results should be able to produce the derivative results
author shade
date Wed, 07 Sep 2016 10:19:25 +0300
parents 14809d1f5c61
children 183e50c96c54
files jmh-core/src/main/java/org/openjdk/jmh/generators/core/BenchmarkGenerator.java jmh-core/src/main/java/org/openjdk/jmh/profile/AbstractHotspotProfiler.java jmh-core/src/main/java/org/openjdk/jmh/profile/ClassloaderProfiler.java jmh-core/src/main/java/org/openjdk/jmh/profile/CompilerProfiler.java jmh-core/src/main/java/org/openjdk/jmh/profile/Defaults.java jmh-core/src/main/java/org/openjdk/jmh/profile/GCProfiler.java jmh-core/src/main/java/org/openjdk/jmh/profile/HotspotCompilationProfiler.java jmh-core/src/main/java/org/openjdk/jmh/profile/HotspotRuntimeProfiler.java jmh-core/src/main/java/org/openjdk/jmh/profile/HotspotThreadProfiler.java jmh-core/src/main/java/org/openjdk/jmh/profile/LinuxPerfNormProfiler.java jmh-core/src/main/java/org/openjdk/jmh/profile/LinuxPerfProfiler.java jmh-core/src/main/java/org/openjdk/jmh/profile/ProfilerResult.java jmh-core/src/main/java/org/openjdk/jmh/profile/ProfilerResultAggregator.java jmh-core/src/main/java/org/openjdk/jmh/results/BenchmarkResult.java jmh-core/src/main/java/org/openjdk/jmh/results/Defaults.java jmh-core/src/main/java/org/openjdk/jmh/results/IterationResult.java jmh-core/src/main/java/org/openjdk/jmh/results/Result.java jmh-core/src/main/java/org/openjdk/jmh/results/ResultRole.java jmh-core/src/main/java/org/openjdk/jmh/results/SampleTimeResult.java jmh-core/src/main/java/org/openjdk/jmh/results/ScalarDerivativeResult.java jmh-core/src/main/java/org/openjdk/jmh/results/ScalarResult.java jmh-core/src/test/java/org/openjdk/jmh/results/TestBenchmarkResult.java
diffstat 22 files changed, 360 insertions(+), 275 deletions(-) [+]
line wrap: on
line diff
--- a/jmh-core/src/main/java/org/openjdk/jmh/generators/core/BenchmarkGenerator.java	Mon Sep 05 10:37:29 2016 +0300
+++ b/jmh-core/src/main/java/org/openjdk/jmh/generators/core/BenchmarkGenerator.java	Wed Sep 07 10:19:25 2016 +0300
@@ -648,8 +648,10 @@
             writer.println(ident(3) + "res.measuredOps /= batchSize;");
 
             writer.println(ident(3) + "BenchmarkTaskResult results = new BenchmarkTaskResult(res.allOps, res.measuredOps);");
-            writer.println(ident(3) + "results.add(new ThroughputResult(ResultRole.PRIMARY, \"" + method.getName() + "\", res.measuredOps, res.getTime(), benchmarkParams.getTimeUnit()));");
-            if (!isSingleMethod) {
+            if (isSingleMethod) {
+                writer.println(ident(3) + "results.add(new ThroughputResult(ResultRole.PRIMARY, \"" + method.getName() + "\", res.measuredOps, res.getTime(), benchmarkParams.getTimeUnit()));");
+            } else {
+                writer.println(ident(3) + "results.add(new ThroughputResult(ResultRole.PRIMARY, \"" + methodGroup.getName() + "\", res.measuredOps, res.getTime(), benchmarkParams.getTimeUnit()));");
                 writer.println(ident(3) + "results.add(new ThroughputResult(ResultRole.SECONDARY, \"" + method.getName() + "\", res.measuredOps, res.getTime(), benchmarkParams.getTimeUnit()));");
             }
             for (String ops : states.getAuxResultNames(method)) {
@@ -774,8 +776,10 @@
             writer.println(ident(3) + "res.measuredOps /= batchSize;");
 
             writer.println(ident(3) + "BenchmarkTaskResult results = new BenchmarkTaskResult(res.allOps, res.measuredOps);");
-            writer.println(ident(3) + "results.add(new AverageTimeResult(ResultRole.PRIMARY, \"" + method.getName() + "\", res.measuredOps, res.getTime(), benchmarkParams.getTimeUnit()));");
-            if (!isSingleMethod) {
+            if (isSingleMethod) {
+                writer.println(ident(3) + "results.add(new AverageTimeResult(ResultRole.PRIMARY, \"" + method.getName() + "\", res.measuredOps, res.getTime(), benchmarkParams.getTimeUnit()));");
+            } else {
+                writer.println(ident(3) + "results.add(new AverageTimeResult(ResultRole.PRIMARY, \"" + methodGroup.getName() + "\", res.measuredOps, res.getTime(), benchmarkParams.getTimeUnit()));");
                 writer.println(ident(3) + "results.add(new AverageTimeResult(ResultRole.SECONDARY, \"" + method.getName() + "\", res.measuredOps, res.getTime(), benchmarkParams.getTimeUnit()));");
             }
             for (String ops : states.getAuxResultNames(method)) {
@@ -928,8 +932,10 @@
             writer.println(ident(3) + "res.measuredOps *= opsPerInv;");
 
             writer.println(ident(3) + "BenchmarkTaskResult results = new BenchmarkTaskResult(res.allOps, res.measuredOps);");
-            writer.println(ident(3) + "results.add(new SampleTimeResult(ResultRole.PRIMARY, \"" + method.getName() + "\", buffer, benchmarkParams.getTimeUnit()));");
-            if (!isSingleMethod) {
+            if (isSingleMethod) {
+                writer.println(ident(3) + "results.add(new SampleTimeResult(ResultRole.PRIMARY, \"" + method.getName() + "\", buffer, benchmarkParams.getTimeUnit()));");
+            } else {
+                writer.println(ident(3) + "results.add(new SampleTimeResult(ResultRole.PRIMARY, \"" + methodGroup.getName() + "\", buffer, benchmarkParams.getTimeUnit()));");
                 writer.println(ident(3) + "results.add(new SampleTimeResult(ResultRole.SECONDARY, \"" + method.getName() + "\", buffer, benchmarkParams.getTimeUnit()));");
             }
             methodEpilog(writer, methodGroup);
@@ -1030,8 +1036,10 @@
             writer.println(ident(3) + "long totalOps = opsPerInv;");
 
             writer.println(ident(3) + "BenchmarkTaskResult results = new BenchmarkTaskResult(totalOps, totalOps);");
-            writer.println(ident(3) + "results.add(new SingleShotResult(ResultRole.PRIMARY, \"" + method.getName() + "\", res.getTime(), benchmarkParams.getTimeUnit()));");
-            if (!isSingleMethod) {
+            if (isSingleMethod) {
+                writer.println(ident(3) + "results.add(new SingleShotResult(ResultRole.PRIMARY, \"" + method.getName() + "\", res.getTime(), benchmarkParams.getTimeUnit()));");
+            } else {
+                writer.println(ident(3) + "results.add(new SingleShotResult(ResultRole.PRIMARY, \"" + methodGroup.getName() + "\", res.getTime(), benchmarkParams.getTimeUnit()));");
                 writer.println(ident(3) + "results.add(new SingleShotResult(ResultRole.SECONDARY, \"" + method.getName() + "\", res.getTime(), benchmarkParams.getTimeUnit()));");
             }
             methodEpilog(writer, methodGroup);
--- a/jmh-core/src/main/java/org/openjdk/jmh/profile/AbstractHotspotProfiler.java	Mon Sep 05 10:37:29 2016 +0300
+++ b/jmh-core/src/main/java/org/openjdk/jmh/profile/AbstractHotspotProfiler.java	Wed Sep 07 10:19:25 2016 +0300
@@ -26,9 +26,7 @@
 
 import org.openjdk.jmh.infra.BenchmarkParams;
 import org.openjdk.jmh.infra.IterationParams;
-import org.openjdk.jmh.results.AggregationPolicy;
-import org.openjdk.jmh.results.IterationResult;
-import org.openjdk.jmh.results.Result;
+import org.openjdk.jmh.results.*;
 import sun.management.counter.Counter;
 
 import java.lang.reflect.InvocationTargetException;
@@ -55,9 +53,9 @@
     @Override
     public Collection<? extends Result> afterIteration(BenchmarkParams benchmarkParams, IterationParams iterationParams, IterationResult result) {
         HotspotInternalResult res = counters();
-        Collection<ProfilerResult> results = new ArrayList<ProfilerResult>();
+        Collection<ScalarResult> results = new ArrayList<ScalarResult>();
         for (Map.Entry<String, Long> e : res.getDiff().entrySet()) {
-            results.add(new ProfilerResult(Defaults.PREFIX + "." + e.getKey(), e.getValue(), "?", AggregationPolicy.AVG));
+            results.add(new ScalarResult(Defaults.PREFIX + e.getKey(), e.getValue(), "?", AggregationPolicy.AVG));
         }
         return results;
     }
--- a/jmh-core/src/main/java/org/openjdk/jmh/profile/ClassloaderProfiler.java	Mon Sep 05 10:37:29 2016 +0300
+++ b/jmh-core/src/main/java/org/openjdk/jmh/profile/ClassloaderProfiler.java	Wed Sep 07 10:19:25 2016 +0300
@@ -26,9 +26,7 @@
 
 import org.openjdk.jmh.infra.BenchmarkParams;
 import org.openjdk.jmh.infra.IterationParams;
-import org.openjdk.jmh.results.AggregationPolicy;
-import org.openjdk.jmh.results.IterationResult;
-import org.openjdk.jmh.results.Result;
+import org.openjdk.jmh.results.*;
 
 import java.lang.management.ClassLoadingMXBean;
 import java.lang.management.ManagementFactory;
@@ -81,8 +79,8 @@
         try {
             long loadedClassCount = cl.getTotalLoadedClassCount();
             long loaded = loadedClassCount - loadedClasses;
-            results.add(new ProfilerResult(Defaults.PREFIX + "class.load", loaded / time, "classes/sec", AggregationPolicy.AVG));
-            results.add(new ProfilerResult(Defaults.PREFIX + "class.load.norm", 1.0 * loaded / allOps, "classes/op", AggregationPolicy.AVG));
+            results.add(new ScalarResult(Defaults.PREFIX + "class.load", loaded / time, "classes/sec", AggregationPolicy.AVG));
+            results.add(new ScalarResult(Defaults.PREFIX + "class.load.norm", 1.0 * loaded / allOps, "classes/op", AggregationPolicy.AVG));
         } catch (UnsupportedOperationException e) {
             // do nothing
         }
@@ -90,8 +88,8 @@
         try {
             long unloadedClassCount = cl.getUnloadedClassCount();
             long unloaded = unloadedClassCount - unloadedClasses;
-            results.add(new ProfilerResult(Defaults.PREFIX + "class.unload", unloaded / time, "classes/sec", AggregationPolicy.AVG));
-            results.add(new ProfilerResult(Defaults.PREFIX + "class.unload.norm", 1.0 * unloaded / allOps, "classes/op", AggregationPolicy.AVG));
+            results.add(new ScalarResult(Defaults.PREFIX + "class.unload", unloaded / time, "classes/sec", AggregationPolicy.AVG));
+            results.add(new ScalarResult(Defaults.PREFIX + "class.unload.norm", 1.0 * unloaded / allOps, "classes/op", AggregationPolicy.AVG));
 
         } catch (UnsupportedOperationException e) {
             // do nothing
--- a/jmh-core/src/main/java/org/openjdk/jmh/profile/CompilerProfiler.java	Mon Sep 05 10:37:29 2016 +0300
+++ b/jmh-core/src/main/java/org/openjdk/jmh/profile/CompilerProfiler.java	Wed Sep 07 10:19:25 2016 +0300
@@ -26,9 +26,7 @@
 
 import org.openjdk.jmh.infra.BenchmarkParams;
 import org.openjdk.jmh.infra.IterationParams;
-import org.openjdk.jmh.results.AggregationPolicy;
-import org.openjdk.jmh.results.IterationResult;
-import org.openjdk.jmh.results.Result;
+import org.openjdk.jmh.results.*;
 
 import java.lang.management.CompilationMXBean;
 import java.lang.management.ManagementFactory;
@@ -68,8 +66,8 @@
         try {
             long curTime = comp.getTotalCompilationTime();
             return Arrays.asList(
-                new ProfilerResult(Defaults.PREFIX + "compiler.time.profiled", curTime - startCompTime, "ms", AggregationPolicy.SUM),
-                new ProfilerResult(Defaults.PREFIX + "compiler.time.total", curTime, "ms", AggregationPolicy.MAX)
+                new ScalarResult(Defaults.PREFIX + "compiler.time.profiled", curTime - startCompTime, "ms", AggregationPolicy.SUM),
+                new ScalarResult(Defaults.PREFIX + "compiler.time.total", curTime, "ms", AggregationPolicy.MAX)
             );
         } catch (UnsupportedOperationException e) {
             return Collections.emptyList();
--- a/jmh-core/src/main/java/org/openjdk/jmh/profile/Defaults.java	Mon Sep 05 10:37:29 2016 +0300
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,31 +0,0 @@
-/*
- * Copyright (c) 2014, 2015, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-package org.openjdk.jmh.profile;
-
-class Defaults {
-
-    public static final String PREFIX = "\u00b7";
-
-}
--- a/jmh-core/src/main/java/org/openjdk/jmh/profile/GCProfiler.java	Mon Sep 05 10:37:29 2016 +0300
+++ b/jmh-core/src/main/java/org/openjdk/jmh/profile/GCProfiler.java	Wed Sep 07 10:19:25 2016 +0300
@@ -26,9 +26,7 @@
 
 import org.openjdk.jmh.infra.BenchmarkParams;
 import org.openjdk.jmh.infra.IterationParams;
-import org.openjdk.jmh.results.AggregationPolicy;
-import org.openjdk.jmh.results.IterationResult;
-import org.openjdk.jmh.results.Result;
+import org.openjdk.jmh.results.*;
 import org.openjdk.jmh.util.HashMultiset;
 import org.openjdk.jmh.util.Multiset;
 
@@ -86,25 +84,25 @@
             gcTime += bean.getCollectionTime();
         }
 
-        List<ProfilerResult> results = new ArrayList<ProfilerResult>();
+        List<ScalarResult> results = new ArrayList<ScalarResult>();
 
         if (beforeAllocated == HotspotAllocationSnapshot.EMPTY) {
             // When allocation profiling fails, make sure it is distinguishable in report
-            results.add(new ProfilerResult(Defaults.PREFIX + "gc.alloc.rate",
+            results.add(new ScalarResult(Defaults.PREFIX + "gc.alloc.rate",
                     Double.NaN,
                     "MB/sec", AggregationPolicy.AVG));
         } else {
             HotspotAllocationSnapshot newSnapshot = VMSupport.getSnapshot();
             long allocated = newSnapshot.subtract(beforeAllocated);
             // When no allocations measured, we still need to report results to avoid user confusion
-            results.add(new ProfilerResult(Defaults.PREFIX + "gc.alloc.rate",
+            results.add(new ScalarResult(Defaults.PREFIX + "gc.alloc.rate",
                             (afterTime != beforeTime) ?
                                     1.0 * allocated / 1024 / 1024 * TimeUnit.SECONDS.toNanos(1) / (afterTime - beforeTime) :
                                     Double.NaN,
                             "MB/sec", AggregationPolicy.AVG));
             if (allocated != 0) {
                 long allOps = iResult.getMetadata().getAllOps();
-                results.add(new ProfilerResult(Defaults.PREFIX + "gc.alloc.rate.norm",
+                results.add(new ScalarResult(Defaults.PREFIX + "gc.alloc.rate.norm",
                                 (allOps != 0) ?
                                         1.0 * allocated / allOps :
                                         Double.NaN,
@@ -112,14 +110,14 @@
             }
         }
 
-        results.add(new ProfilerResult(
+        results.add(new ScalarResult(
                 Defaults.PREFIX + "gc.count",
                 gcCount - beforeGCCount,
                 "counts",
                 AggregationPolicy.SUM));
 
         if (gcCount != beforeGCCount || gcTime != beforeGCTime) {
-            results.add(new ProfilerResult(
+            results.add(new ScalarResult(
                     Defaults.PREFIX + "gc.time",
                     gcTime - beforeGCTime,
                     "ms",
@@ -136,13 +134,14 @@
 
             String spaceName = space.replaceAll(" ", "_");
 
-            results.add(new ProfilerResult(
+            results.add(new ScalarResult(
                     Defaults.PREFIX + "gc.churn." + spaceName + "",
                     churnRate,
                     "MB/sec",
                     AggregationPolicy.AVG));
 
-            results.add(new ProfilerResult(Defaults.PREFIX + "gc.churn." + spaceName + ".norm",
+            results.add(new ScalarResult(
+                    Defaults.PREFIX + "gc.churn." + spaceName + ".norm",
                     churnNorm,
                     "B/op",
                     AggregationPolicy.AVG));
--- a/jmh-core/src/main/java/org/openjdk/jmh/profile/HotspotCompilationProfiler.java	Mon Sep 05 10:37:29 2016 +0300
+++ b/jmh-core/src/main/java/org/openjdk/jmh/profile/HotspotCompilationProfiler.java	Wed Sep 07 10:19:25 2016 +0300
@@ -26,9 +26,7 @@
 
 import org.openjdk.jmh.infra.BenchmarkParams;
 import org.openjdk.jmh.infra.IterationParams;
-import org.openjdk.jmh.results.AggregationPolicy;
-import org.openjdk.jmh.results.IterationResult;
-import org.openjdk.jmh.results.Result;
+import org.openjdk.jmh.results.*;
 import sun.management.HotspotCompilationMBean;
 import sun.management.counter.Counter;
 
@@ -57,51 +55,51 @@
     public Collection<? extends Result> afterIteration(BenchmarkParams benchmarkParams, IterationParams iterationParams, IterationResult result) {
         Map<String, Long> current = counters().getCurrent();
         return Arrays.asList(
-                new ProfilerResult(Defaults.PREFIX + "compiler.totalTime",
+                new ScalarResult(Defaults.PREFIX + "compiler.totalTime",
                         current.get("java.ci.totalTime") * 1D / TimeUnit.MILLISECONDS.toNanos(1),
                         "ms", AggregationPolicy.MAX),
 
-                new ProfilerResult(Defaults.PREFIX + "compiler.totalCompiles",
+                new ScalarResult(Defaults.PREFIX + "compiler.totalCompiles",
                         current.get("sun.ci.totalCompiles"),
                         "methods", AggregationPolicy.MAX),
 
-                new ProfilerResult(Defaults.PREFIX + "compiler.totalBailouts",
+                new ScalarResult(Defaults.PREFIX + "compiler.totalBailouts",
                         current.get("sun.ci.totalBailouts"),
                         "methods", AggregationPolicy.MAX),
 
-                new ProfilerResult(Defaults.PREFIX + "compiler.totalInvalidates",
+                new ScalarResult(Defaults.PREFIX + "compiler.totalInvalidates",
                         current.get("sun.ci.totalInvalidates"),
                         "methods", AggregationPolicy.MAX),
 
-                new ProfilerResult(Defaults.PREFIX + "compiler.nmethodCodeSize",
+                new ScalarResult(Defaults.PREFIX + "compiler.nmethodCodeSize",
                         current.get("sun.ci.nmethodCodeSize") / 1024d,
                         "Kb", AggregationPolicy.MAX),
 
-                new ProfilerResult(Defaults.PREFIX + "compiler.nmethodSize",
+                new ScalarResult(Defaults.PREFIX + "compiler.nmethodSize",
                         current.get("sun.ci.nmethodSize") / 1024d,
                         "Kb", AggregationPolicy.MAX),
 
-                new ProfilerResult(Defaults.PREFIX + "compiler.osrCompiles",
+                new ScalarResult(Defaults.PREFIX + "compiler.osrCompiles",
                         current.get("sun.ci.osrCompiles"),
                         "methods", AggregationPolicy.MAX),
 
-                new ProfilerResult(Defaults.PREFIX + "compiler.osrBytes",
+                new ScalarResult(Defaults.PREFIX + "compiler.osrBytes",
                         current.get("sun.ci.osrBytes") / 1024d,
                         "Kb", AggregationPolicy.MAX),
 
-                new ProfilerResult(Defaults.PREFIX + "compiler.osrTime",
+                new ScalarResult(Defaults.PREFIX + "compiler.osrTime",
                         current.get("sun.ci.osrTime") * 1d / TimeUnit.MILLISECONDS.toNanos(1),
                         "ms", AggregationPolicy.MAX),
 
-                new ProfilerResult(Defaults.PREFIX + "compiler.standardCompiles",
+                new ScalarResult(Defaults.PREFIX + "compiler.standardCompiles",
                         current.get("sun.ci.standardCompiles"),
                         "methods", AggregationPolicy.MAX),
 
-                new ProfilerResult(Defaults.PREFIX + "compiler.standardBytes",
+                new ScalarResult(Defaults.PREFIX + "compiler.standardBytes",
                         current.get("sun.ci.standardBytes") / 1024d,
                         "Kb", AggregationPolicy.MAX),
 
-                new ProfilerResult(Defaults.PREFIX + "compiler.standardTime",
+                new ScalarResult(Defaults.PREFIX + "compiler.standardTime",
                         current.get("sun.ci.standardTime") * 1d / TimeUnit.MILLISECONDS.toNanos(1),
                         "ms", AggregationPolicy.MAX)
         );
--- a/jmh-core/src/main/java/org/openjdk/jmh/profile/HotspotRuntimeProfiler.java	Mon Sep 05 10:37:29 2016 +0300
+++ b/jmh-core/src/main/java/org/openjdk/jmh/profile/HotspotRuntimeProfiler.java	Wed Sep 07 10:19:25 2016 +0300
@@ -26,9 +26,7 @@
 
 import org.openjdk.jmh.infra.BenchmarkParams;
 import org.openjdk.jmh.infra.IterationParams;
-import org.openjdk.jmh.results.AggregationPolicy;
-import org.openjdk.jmh.results.IterationResult;
-import org.openjdk.jmh.results.Result;
+import org.openjdk.jmh.results.*;
 import sun.management.HotspotRuntimeMBean;
 import sun.management.counter.Counter;
 
@@ -57,43 +55,43 @@
     public Collection<? extends Result> afterIteration(BenchmarkParams benchmarkParams, IterationParams iterationParams, IterationResult result) {
         Map<String, Long> current = counters().getCurrent();
         return Arrays.asList(
-                new ProfilerResult(Defaults.PREFIX + "rt.sync.fatMonitors",
+                new ScalarResult(Defaults.PREFIX + "rt.sync.fatMonitors",
                         current.get("sun.rt._sync_MonExtant"),
                         "monitors", AggregationPolicy.MAX),
 
-                new ProfilerResult(Defaults.PREFIX + "rt.sync.monitorInflations",
+                new ScalarResult(Defaults.PREFIX + "rt.sync.monitorInflations",
                         current.get("sun.rt._sync_Inflations"),
                         "monitors", AggregationPolicy.MAX),
 
-                new ProfilerResult(Defaults.PREFIX + "rt.sync.monitorDeflations",
+                new ScalarResult(Defaults.PREFIX + "rt.sync.monitorDeflations",
                         current.get("sun.rt._sync_Deflations"),
                         "monitors", AggregationPolicy.MAX),
 
-                new ProfilerResult(Defaults.PREFIX + "rt.sync.contendedLockAttempts",
+                new ScalarResult(Defaults.PREFIX + "rt.sync.contendedLockAttempts",
                         current.get("sun.rt._sync_ContendedLockAttempts"),
                         "locks", AggregationPolicy.MAX),
 
-                new ProfilerResult(Defaults.PREFIX + "rt.sync.parks",
+                new ScalarResult(Defaults.PREFIX + "rt.sync.parks",
                         current.get("sun.rt._sync_Parks"),
                         "counts", AggregationPolicy.MAX),
 
-                new ProfilerResult(Defaults.PREFIX + "rt.sync.notifications",
+                new ScalarResult(Defaults.PREFIX + "rt.sync.notifications",
                         current.get("sun.rt._sync_Notifications"),
                         "counts", AggregationPolicy.MAX),
 
-                new ProfilerResult(Defaults.PREFIX + "rt.sync.futileWakeups",
+                new ScalarResult(Defaults.PREFIX + "rt.sync.futileWakeups",
                         current.get("sun.rt._sync_FutileWakeups"),
                         "counts", AggregationPolicy.MAX),
 
-                new ProfilerResult(Defaults.PREFIX + "rt.safepoints",
+                new ScalarResult(Defaults.PREFIX + "rt.safepoints",
                         current.get("sun.rt.safepoints"),
                         "counts", AggregationPolicy.MAX),
 
-                new ProfilerResult(Defaults.PREFIX + "rt.safepointSyncTime",
+                new ScalarResult(Defaults.PREFIX + "rt.safepointSyncTime",
                         current.get("sun.rt.safepointSyncTime") * 1d / TimeUnit.MILLISECONDS.toNanos(1),
                         "ms", AggregationPolicy.MAX),
 
-                new ProfilerResult(Defaults.PREFIX + "rt.safepointTime",
+                new ScalarResult(Defaults.PREFIX + "rt.safepointTime",
                         current.get("sun.rt.safepointTime") * 1d / TimeUnit.MILLISECONDS.toNanos(1),
                         "ms", AggregationPolicy.MAX)
         );
--- a/jmh-core/src/main/java/org/openjdk/jmh/profile/HotspotThreadProfiler.java	Mon Sep 05 10:37:29 2016 +0300
+++ b/jmh-core/src/main/java/org/openjdk/jmh/profile/HotspotThreadProfiler.java	Wed Sep 07 10:19:25 2016 +0300
@@ -26,9 +26,7 @@
 
 import org.openjdk.jmh.infra.BenchmarkParams;
 import org.openjdk.jmh.infra.IterationParams;
-import org.openjdk.jmh.results.AggregationPolicy;
-import org.openjdk.jmh.results.IterationResult;
-import org.openjdk.jmh.results.Result;
+import org.openjdk.jmh.results.*;
 import sun.management.HotspotThreadMBean;
 import sun.management.counter.Counter;
 
@@ -56,15 +54,15 @@
     public Collection<? extends Result> afterIteration(BenchmarkParams benchmarkParams, IterationParams iterationParams, IterationResult result) {
         Map<String, Long> current = counters().getCurrent();
         return Arrays.asList(
-                new ProfilerResult(Defaults.PREFIX + "threads.alive",
+                new ScalarResult(Defaults.PREFIX + "threads.alive",
                         current.get("java.threads.live"),
                         "threads", AggregationPolicy.AVG),
 
-                new ProfilerResult(Defaults.PREFIX + "threads.daemon",
+                new ScalarResult(Defaults.PREFIX + "threads.daemon",
                         current.get("java.threads.daemon"),
                         "threads", AggregationPolicy.AVG),
 
-                new ProfilerResult(Defaults.PREFIX + "threads.started",
+                new ScalarResult(Defaults.PREFIX + "threads.started",
                         current.get("java.threads.started"),
                         "threads", AggregationPolicy.MAX)
         );
--- a/jmh-core/src/main/java/org/openjdk/jmh/profile/LinuxPerfNormProfiler.java	Mon Sep 05 10:37:29 2016 +0300
+++ b/jmh-core/src/main/java/org/openjdk/jmh/profile/LinuxPerfNormProfiler.java	Wed Sep 07 10:19:25 2016 +0300
@@ -319,53 +319,19 @@
         }
     }
 
-    static class PerfResult extends Result<PerfResult> {
+    static class PerfResult extends ScalarResult {
         private static final long serialVersionUID = -1262685915873231436L;
 
-        private final String key;
-
         public PerfResult(String key, double value) {
-            this(key, of(value));
-        }
-
-        public PerfResult(String key, Statistics stat) {
-            super(ResultRole.SECONDARY, Defaults.PREFIX + key, stat, "#/op", AggregationPolicy.AVG);
-            this.key = key;
-        }
-
-        @Override
-        protected Aggregator<PerfResult> getThreadAggregator() {
-            return new PerfResultAggregator();
-        }
-
-        @Override
-        protected Aggregator<PerfResult> getIterationAggregator() {
-            return new PerfResultAggregator();
-        }
-
-        @Override
-        public String toString() {
-            return String.format(" %s %s/op", ScoreFormatter.format(getScore()), key);
+            super(key, value, "#/op", AggregationPolicy.AVG);
         }
 
         @Override
         public String extendedInfo() {
+            // omit printing in extended info
             return "";
         }
     }
 
-    static class PerfResultAggregator implements Aggregator<PerfResult> {
-
-        @Override
-        public PerfResult aggregate(Collection<PerfResult> results) {
-            String key = "";
-            ListStatistics stat = new ListStatistics();
-            for (PerfResult r : results) {
-                key = r.key;
-                stat.addValue(r.getScore());
-            }
-            return new PerfResult(key, stat);
-        }
-    }
 
 }
--- a/jmh-core/src/main/java/org/openjdk/jmh/profile/LinuxPerfProfiler.java	Mon Sep 05 10:37:29 2016 +0300
+++ b/jmh-core/src/main/java/org/openjdk/jmh/profile/LinuxPerfProfiler.java	Wed Sep 07 10:19:25 2016 +0300
@@ -194,7 +194,7 @@
         private final long instructions;
 
         public PerfResult(String output, long cycles, long instructions) {
-            super(ResultRole.SECONDARY, Defaults.PREFIX + "cpi", of(1.0 * cycles / instructions), "CPI", AggregationPolicy.AVG);
+            super(ResultRole.SECONDARY, Defaults.PREFIX + "perf", of(Double.NaN), "---", AggregationPolicy.AVG);
             this.output = output;
             this.cycles = cycles;
             this.instructions = instructions;
@@ -211,6 +211,13 @@
         }
 
         @Override
+        protected Collection<? extends Result> getDerivativeResults() {
+            return Collections.singletonList(
+                    new ScalarDerivativeResult(Defaults.PREFIX + "cpi", 1.0 * cycles / instructions, "CPI", AggregationPolicy.AVG)
+            );
+        }
+
+        @Override
         public String toString() {
             return String.format("%s cycles per instruction", ScoreFormatter.format(1.0 * cycles / instructions));
         }
--- a/jmh-core/src/main/java/org/openjdk/jmh/profile/ProfilerResult.java	Mon Sep 05 10:37:29 2016 +0300
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,58 +0,0 @@
-/*
- * Copyright (c) 2014, 2014, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-package org.openjdk.jmh.profile;
-
-import org.openjdk.jmh.results.AggregationPolicy;
-import org.openjdk.jmh.results.Aggregator;
-import org.openjdk.jmh.results.Result;
-import org.openjdk.jmh.results.ResultRole;
-import org.openjdk.jmh.util.Statistics;
-
-public class ProfilerResult extends Result<ProfilerResult> {
-    private static final long serialVersionUID = 3407232747805728586L;
-
-    public ProfilerResult(String label, double n, String unit, AggregationPolicy policy) {
-        this(label, of(n), unit, policy);
-    }
-
-    ProfilerResult(String label, Statistics s, String unit, AggregationPolicy policy) {
-        super(ResultRole.SECONDARY, label, s, unit, policy);
-    }
-
-    @Override
-    protected Aggregator<ProfilerResult> getThreadAggregator() {
-        return new ProfilerResultAggregator();
-    }
-
-    @Override
-    protected Aggregator<ProfilerResult> getIterationAggregator() {
-        return new ProfilerResultAggregator();
-    }
-
-    @Override
-    protected ProfilerResult getZeroResult() {
-        return new ProfilerResult(label, 0, unit, policy);
-    }
-}
--- a/jmh-core/src/main/java/org/openjdk/jmh/profile/ProfilerResultAggregator.java	Mon Sep 05 10:37:29 2016 +0300
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,47 +0,0 @@
-/*
- * Copyright (c) 2014, 2014, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-package org.openjdk.jmh.profile;
-
-import org.openjdk.jmh.results.AggregatorUtils;
-import org.openjdk.jmh.results.Aggregator;
-import org.openjdk.jmh.util.ListStatistics;
-
-import java.util.Collection;
-
-public class ProfilerResultAggregator implements Aggregator<ProfilerResult> {
-    @Override
-    public ProfilerResult aggregate(Collection<ProfilerResult> results) {
-        ListStatistics stats = new ListStatistics();
-        for (ProfilerResult r : results) {
-            stats.addValue(r.getScore());
-        }
-        return new ProfilerResult(
-                AggregatorUtils.aggregateLabels(results),
-                stats,
-                AggregatorUtils.aggregateUnits(results),
-                AggregatorUtils.aggregatePolicies(results)
-        );
-    }
-}
--- a/jmh-core/src/main/java/org/openjdk/jmh/results/BenchmarkResult.java	Mon Sep 05 10:37:29 2016 +0300
+++ b/jmh-core/src/main/java/org/openjdk/jmh/results/BenchmarkResult.java	Wed Sep 07 10:19:25 2016 +0300
@@ -29,10 +29,7 @@
 import org.openjdk.jmh.util.Multimap;
 
 import java.io.Serializable;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Map;
-import java.util.TreeMap;
+import java.util.*;
 
 /**
  * Benchmark result.
@@ -106,6 +103,9 @@
         for (IterationResult ir : iterationResults) {
             Map<String, Result> secondaryResults = ir.getSecondaryResults();
             for (Map.Entry<String, Result> entry : secondaryResults.entrySet()) {
+                // skip derivatives from aggregation here
+                if (entry.getValue().getRole().isDerivative()) continue;
+
                 allSecondary.put(entry.getKey(), entry.getValue());
             }
         }
@@ -142,7 +142,7 @@
             Aggregator<Result> aggregator = null;
             Collection<Result> results = new ArrayList<Result>();
             for (Result r : benchmarkResults.get(label)) {
-                if (r.getRole().isSecondary()) {
+                if (r.getRole().isSecondary() && !r.getRole().isDerivative()) {
                     results.add(r);
                     aggregator = r.getIterationAggregator();
                 }
@@ -152,9 +152,27 @@
             }
         }
 
+        // put all secondary derivative results on top: from primaries
+        answers.putAll(produceDerivative(getPrimaryResult()));
+
+        // add all derivative results on top: from secondaries
+        Map<String, Result> adds = new HashMap<String, Result>();
+        for (Result r : answers.values()) {
+            adds.putAll(produceDerivative(r));
+        }
+        answers.putAll(adds);
+
         return answers;
     }
 
+    private Map<String, Result> produceDerivative(Result r) {
+        Map<String, Result> map = new HashMap<String, Result>();
+        for (Object rr : r.getDerivativeResults()) {
+            map.put(((Result) rr).getLabel(), (Result) rr);
+        }
+        return map;
+    }
+
     public String getScoreUnit() {
         return getPrimaryResult().getScoreUnit();
     }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jmh-core/src/main/java/org/openjdk/jmh/results/Defaults.java	Wed Sep 07 10:19:25 2016 +0300
@@ -0,0 +1,31 @@
+/*
+ * Copyright (c) 2014, 2015, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package org.openjdk.jmh.results;
+
+public class Defaults {
+
+    public static final String PREFIX = "\u00b7";
+
+}
--- a/jmh-core/src/main/java/org/openjdk/jmh/results/IterationResult.java	Mon Sep 05 10:37:29 2016 +0300
+++ b/jmh-core/src/main/java/org/openjdk/jmh/results/IterationResult.java	Wed Sep 07 10:19:25 2016 +0300
@@ -30,12 +30,7 @@
 import org.openjdk.jmh.util.TreeMultimap;
 
 import java.io.Serializable;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.List;
-import java.util.Map;
-import java.util.TreeMap;
+import java.util.*;
 
 /**
  * Class contains all info returned by benchmark iteration or/and collected during benchmark iteration.
@@ -112,9 +107,28 @@
             Result result = aggregator.aggregate(results);
             answer.put(label, result);
         }
+
+        // put all secondary derivative results on top: from primaries
+        answer.putAll(produceDerivative(getPrimaryResult()));
+
+        // add all secondary derivative results on top: from secondaries
+        Map<String, Result> adds = new HashMap<String, Result>();
+        for (Result r : answer.values()) {
+            adds.putAll(produceDerivative(r));
+        }
+        answer.putAll(adds);
+
         return answer;
     }
 
+    private Map<String, Result> produceDerivative(Result r) {
+        Map<String, Result> map = new HashMap<String, Result>();
+        for (Object rr : r.getDerivativeResults()) {
+            map.put(((Result) rr).getLabel(), (Result) rr);
+        }
+        return map;
+    }
+
     public Result getPrimaryResult() {
         @SuppressWarnings("unchecked")
         Aggregator<Result> aggregator = primaryResults.iterator().next().getThreadAggregator();
--- a/jmh-core/src/main/java/org/openjdk/jmh/results/Result.java	Mon Sep 05 10:37:29 2016 +0300
+++ b/jmh-core/src/main/java/org/openjdk/jmh/results/Result.java	Wed Sep 07 10:19:25 2016 +0300
@@ -32,6 +32,8 @@
 import java.io.PrintWriter;
 import java.io.Serializable;
 import java.io.StringWriter;
+import java.util.Collection;
+import java.util.Collections;
 
 /**
  * Base class for all types of results that can be returned by a benchmark.
@@ -180,6 +182,15 @@
     }
 
     /**
+     * Get derivative results for this result. These do not participate in aggregation,
+     * and computed on the spot from the aggregated result.
+     * @return
+     */
+    protected Collection<? extends Result> getDerivativeResults() {
+        return Collections.emptyList();
+    }
+
+    /**
      * Result as represented by a String.
      *
      * @return String with the result and unit
--- a/jmh-core/src/main/java/org/openjdk/jmh/results/ResultRole.java	Mon Sep 05 10:37:29 2016 +0300
+++ b/jmh-core/src/main/java/org/openjdk/jmh/results/ResultRole.java	Wed Sep 07 10:19:25 2016 +0300
@@ -37,6 +37,11 @@
     SECONDARY,
 
     /**
+     * Same as {@link #SECONDARY}, but always recomputed.
+     */
+    SECONDARY_DERIVATIVE,
+
+    /**
      * Does not participate in any metric, garbage result.
      */
     OMITTED,
@@ -47,7 +52,11 @@
     }
 
     public boolean isSecondary() {
-        return this == SECONDARY;
+        return this == SECONDARY || this == SECONDARY_DERIVATIVE;
+    }
+
+    public boolean isDerivative() {
+        return this == SECONDARY_DERIVATIVE;
     }
 
 }
--- a/jmh-core/src/main/java/org/openjdk/jmh/results/SampleTimeResult.java	Mon Sep 05 10:37:29 2016 +0300
+++ b/jmh-core/src/main/java/org/openjdk/jmh/results/SampleTimeResult.java	Wed Sep 07 10:19:25 2016 +0300
@@ -28,6 +28,7 @@
 import org.openjdk.jmh.util.SampleBuffer;
 import org.openjdk.jmh.util.Statistics;
 
+import java.util.Arrays;
 import java.util.Collection;
 import java.util.concurrent.TimeUnit;
 
@@ -62,25 +63,17 @@
     }
 
     @Override
-    public String toString() {
-        Statistics stats = getStatistics();
-
-        StringBuilder sb = new StringBuilder();
-        sb.append("n = ").append(stats.getN()).append(", ");
-        sb.append(String.format("mean = %.0f %s",
-                stats.getMean(),
-                getScoreUnit()));
-        sb.append(String.format(", p{0.00, 0.50, 0.90, 0.95, 0.99, 0.999, 0.9999, 1.00} = %.0f, %.0f, %.0f, %.0f, %.0f, %.0f, %.0f, %.0f %s",
-                stats.getPercentile(0),
-                stats.getPercentile(50),
-                stats.getPercentile(90),
-                stats.getPercentile(95),
-                stats.getPercentile(99),
-                stats.getPercentile(99.9),
-                stats.getPercentile(99.99),
-                stats.getPercentile(100),
-                getScoreUnit()));
-        return sb.toString();
+    protected Collection<? extends Result> getDerivativeResults() {
+        return Arrays.asList(
+                new ScalarDerivativeResult(label + Defaults.PREFIX + "p0.00",   statistics.getPercentile(0),        getScoreUnit(), AggregationPolicy.AVG),
+                new ScalarDerivativeResult(label + Defaults.PREFIX + "p0.50",   statistics.getPercentile(50),       getScoreUnit(), AggregationPolicy.AVG),
+                new ScalarDerivativeResult(label + Defaults.PREFIX + "p0.90",   statistics.getPercentile(90),       getScoreUnit(), AggregationPolicy.AVG),
+                new ScalarDerivativeResult(label + Defaults.PREFIX + "p0.95",   statistics.getPercentile(95),       getScoreUnit(), AggregationPolicy.AVG),
+                new ScalarDerivativeResult(label + Defaults.PREFIX + "p0.99",   statistics.getPercentile(99),       getScoreUnit(), AggregationPolicy.AVG),
+                new ScalarDerivativeResult(label + Defaults.PREFIX + "p0.999",  statistics.getPercentile(99.9),     getScoreUnit(), AggregationPolicy.AVG),
+                new ScalarDerivativeResult(label + Defaults.PREFIX + "p0.9999", statistics.getPercentile(99.99),    getScoreUnit(), AggregationPolicy.AVG),
+                new ScalarDerivativeResult(label + Defaults.PREFIX + "p1.00",   statistics.getPercentile(100),      getScoreUnit(), AggregationPolicy.AVG)
+        );
     }
 
     @Override
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jmh-core/src/main/java/org/openjdk/jmh/results/ScalarDerivativeResult.java	Wed Sep 07 10:19:25 2016 +0300
@@ -0,0 +1,78 @@
+/*
+ * Copyright (c) 2014, 2014, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package org.openjdk.jmh.results;
+
+import org.openjdk.jmh.util.ListStatistics;
+import org.openjdk.jmh.util.Statistics;
+
+import java.util.Collection;
+
+public class ScalarDerivativeResult extends Result<ScalarDerivativeResult> {
+    private static final long serialVersionUID = 3407232747805728586L;
+
+    public ScalarDerivativeResult(String label, double n, String unit, AggregationPolicy policy) {
+        this(label, of(n), unit, policy);
+    }
+
+    ScalarDerivativeResult(String label, Statistics s, String unit, AggregationPolicy policy) {
+        super(ResultRole.SECONDARY_DERIVATIVE, label, s, unit, policy);
+    }
+
+    @Override
+    protected Aggregator<ScalarDerivativeResult> getThreadAggregator() {
+        return new ScalarResultAggregator();
+    }
+
+    @Override
+    protected Aggregator<ScalarDerivativeResult> getIterationAggregator() {
+        return new ScalarResultAggregator();
+    }
+
+    @Override
+    protected ScalarDerivativeResult getZeroResult() {
+        return new ScalarDerivativeResult(label, 0, unit, policy);
+    }
+
+    static class ScalarResultAggregator implements Aggregator<ScalarDerivativeResult> {
+        @Override
+        public ScalarDerivativeResult aggregate(Collection<ScalarDerivativeResult> results) {
+            ListStatistics stats = new ListStatistics();
+            for (ScalarDerivativeResult r : results) {
+                stats.addValue(r.getScore());
+            }
+            return new ScalarDerivativeResult(
+                    AggregatorUtils.aggregateLabels(results),
+                    stats,
+                    AggregatorUtils.aggregateUnits(results),
+                    AggregatorUtils.aggregatePolicies(results)
+            );
+        }
+    }
+
+    @Override
+    public String extendedInfo() {
+        return "";
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jmh-core/src/main/java/org/openjdk/jmh/results/ScalarResult.java	Wed Sep 07 10:19:25 2016 +0300
@@ -0,0 +1,73 @@
+/*
+ * Copyright (c) 2014, 2014, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package org.openjdk.jmh.results;
+
+import org.openjdk.jmh.util.ListStatistics;
+import org.openjdk.jmh.util.Statistics;
+
+import java.util.Collection;
+
+public class ScalarResult extends Result<ScalarResult> {
+    private static final long serialVersionUID = 3407232747805728586L;
+
+    public ScalarResult(String label, double n, String unit, AggregationPolicy policy) {
+        this(label, of(n), unit, policy);
+    }
+
+    ScalarResult(String label, Statistics s, String unit, AggregationPolicy policy) {
+        super(ResultRole.SECONDARY, label, s, unit, policy);
+    }
+
+    @Override
+    protected Aggregator<ScalarResult> getThreadAggregator() {
+        return new ScalarResultAggregator();
+    }
+
+    @Override
+    protected Aggregator<ScalarResult> getIterationAggregator() {
+        return new ScalarResultAggregator();
+    }
+
+    @Override
+    protected ScalarResult getZeroResult() {
+        return new ScalarResult(label, 0, unit, policy);
+    }
+
+    static class ScalarResultAggregator implements Aggregator<ScalarResult> {
+        @Override
+        public ScalarResult aggregate(Collection<ScalarResult> results) {
+            ListStatistics stats = new ListStatistics();
+            for (ScalarResult r : results) {
+                stats.addValue(r.getScore());
+            }
+            return new ScalarResult(
+                    AggregatorUtils.aggregateLabels(results),
+                    stats,
+                    AggregatorUtils.aggregateUnits(results),
+                    AggregatorUtils.aggregatePolicies(results)
+            );
+        }
+    }
+}
--- a/jmh-core/src/test/java/org/openjdk/jmh/results/TestBenchmarkResult.java	Mon Sep 05 10:37:29 2016 +0300
+++ b/jmh-core/src/test/java/org/openjdk/jmh/results/TestBenchmarkResult.java	Wed Sep 07 10:19:25 2016 +0300
@@ -37,10 +37,13 @@
     @Test
     public void testMissingSecondaries() {
         IterationResult ir1 = new IterationResult(null, null, null);
+        ir1.addResult(new PrimaryResult());
         ir1.addResult(new SecondaryResult("label1", 1));
         IterationResult ir2 = new IterationResult(null, null, null);
+        ir2.addResult(new PrimaryResult());
         ir2.addResult(new SecondaryResult("label2", 2));
         IterationResult ir3 = new IterationResult(null, null, null);
+        ir3.addResult(new PrimaryResult());
         ir3.addResult(new SecondaryResult("label2", 3));
         BenchmarkResult br = new BenchmarkResult(null, Arrays.asList(ir1, ir2, ir3));
 
@@ -50,6 +53,22 @@
         Assert.assertEquals(5.0D, sr.get("label2").getScore(), 0.001);
     }
 
+    public static class PrimaryResult extends Result<PrimaryResult> {
+        public PrimaryResult() {
+            super(ResultRole.PRIMARY, "Boo", of(1.0D), "unit", AggregationPolicy.SUM);
+        }
+
+        @Override
+        protected Aggregator<PrimaryResult> getThreadAggregator() {
+            return new PrimaryResultAggregator();
+        }
+
+        @Override
+        protected Aggregator<PrimaryResult> getIterationAggregator() {
+            return new PrimaryResultAggregator();
+        }
+    }
+
     public static class SecondaryResult extends Result<SecondaryResult> {
         public SecondaryResult(String label, double val) {
             super(ResultRole.SECONDARY, label, of(val), "unit", AggregationPolicy.SUM);
@@ -65,6 +84,13 @@
         }
     }
 
+    public static class PrimaryResultAggregator implements Aggregator<PrimaryResult> {
+        @Override
+        public PrimaryResult aggregate(Collection<PrimaryResult> results) {
+            return new PrimaryResult();
+        }
+    }
+
     public static class SecondaryResultAggregator implements Aggregator<SecondaryResult> {
         @Override
         public SecondaryResult aggregate(Collection<SecondaryResult> results) {