Skip to main content
aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSimone Bordet2014-09-11 13:38:38 +0000
committerSimone Bordet2014-09-11 13:38:38 +0000
commita707596691dc6a3a9b930f1c80923f7f7adaae12 (patch)
tree74ad2e5ff6b465bff4d1822d6db6e89125e0e6f8
parenta6a4d512a6fdd6ebfc999f7c95fcfac94a162ef1 (diff)
downloadorg.eclipse.jetty.toolchain-a707596691dc6a3a9b930f1c80923f7f7adaae12.tar.gz
org.eclipse.jetty.toolchain-a707596691dc6a3a9b930f1c80923f7f7adaae12.tar.xz
org.eclipse.jetty.toolchain-a707596691dc6a3a9b930f1c80923f7f7adaae12.zip
Introduced MeasureRecorder to record and print the distribution graph.
-rw-r--r--jetty-perf-helper/src/main/java/org/eclipse/jetty/toolchain/perf/MeasureRecorder.java213
1 files changed, 213 insertions, 0 deletions
diff --git a/jetty-perf-helper/src/main/java/org/eclipse/jetty/toolchain/perf/MeasureRecorder.java b/jetty-perf-helper/src/main/java/org/eclipse/jetty/toolchain/perf/MeasureRecorder.java
new file mode 100644
index 0000000..9fee800
--- /dev/null
+++ b/jetty-perf-helper/src/main/java/org/eclipse/jetty/toolchain/perf/MeasureRecorder.java
@@ -0,0 +1,213 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.toolchain.perf;
+
+import java.util.Iterator;
+import java.util.Map;
+import java.util.TreeMap;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.atomic.AtomicLong;
+
+public class MeasureRecorder
+{
+ private final AtomicLong count = new AtomicLong();
+ private final AtomicLong min = new AtomicLong();
+ private final AtomicLong max = new AtomicLong();
+ private final AtomicLong total = new AtomicLong();
+ private final ConcurrentMap<Long, AtomicLong> measures = new ConcurrentHashMap<>();
+ private final Converter converter;
+ private final String name;
+ private final String unit;
+
+ public MeasureRecorder(Converter converter, String name, String unit)
+ {
+ this.converter = converter;
+ this.name = name;
+ this.unit = unit;
+ }
+
+ public void reset()
+ {
+ count.set(0);
+ min.set(Long.MAX_VALUE);
+ max.set(Long.MIN_VALUE);
+ total.set(0);
+ measures.clear();
+ }
+
+ public void record(long measure, boolean distribution)
+ {
+ count.incrementAndGet();
+ updateMin(min, measure);
+ updateMax(max, measure);
+ total.addAndGet(measure);
+
+ if (distribution)
+ {
+ AtomicLong count = measures.get(measure);
+ if (count == null)
+ {
+ count = new AtomicLong();
+ AtomicLong existing = measures.putIfAbsent(measure, count);
+ if (existing != null)
+ count = existing;
+ }
+ count.incrementAndGet();
+ }
+ }
+
+ public Snapshot snapshot()
+ {
+ Map<Long, Long> copy = new TreeMap<>();
+ for (Map.Entry<Long, AtomicLong> entry : measures.entrySet())
+ copy.put(entry.getKey(), entry.getValue().get());
+ return new Snapshot(count.get(), min.get(), max.get(), total.get(), copy);
+ }
+
+ public static void updateMin(AtomicLong currentMin, long newValue)
+ {
+ long oldValue = currentMin.get();
+ while (newValue < oldValue)
+ {
+ if (currentMin.compareAndSet(oldValue, newValue))
+ break;
+ oldValue = currentMin.get();
+ }
+ }
+
+ public static void updateMax(AtomicLong currentMax, long newValue)
+ {
+ long oldValue = currentMax.get();
+ while (newValue > oldValue)
+ {
+ if (currentMax.compareAndSet(oldValue, newValue))
+ break;
+ oldValue = currentMax.get();
+ }
+ }
+
+ public class Snapshot
+ {
+ public final long count;
+ public final long min;
+ public final long max;
+ public final long total;
+ public final Map<Long, Long> measures;
+
+ private Snapshot(long count, long min, long max, long total, Map<Long, Long> measures)
+ {
+ this.count = count;
+ this.min = min;
+ this.max = max;
+ this.total = total;
+ this.measures = measures;
+ }
+
+ @Override
+ public String toString()
+ {
+ String eol = System.lineSeparator();
+ StringBuilder builder = new StringBuilder();
+
+ long measureAt50thPercentile = 0;
+ long measureAt99thPercentile = 0;
+
+ if (count == 1)
+ {
+ measureAt50thPercentile = min;
+ measureAt99thPercentile = max;
+ }
+ else if (count > 1)
+ {
+ long samples = 0;
+ long maxLatencyBucketFrequency = 0;
+ long previousMeasure = 0;
+ long[] measureBucketFrequencies = new long[20];
+ long minMeasure = min;
+ long measureRange = max - minMeasure;
+ for (Iterator<Map.Entry<Long, Long>> entries = measures.entrySet().iterator(); entries.hasNext();)
+ {
+ Map.Entry<Long, Long> entry = entries.next();
+ long latency = entry.getKey();
+ Long bucketIndex = measureRange == 0 ? 0 : (latency - minMeasure) * measureBucketFrequencies.length / measureRange;
+ int index = bucketIndex.intValue() == measureBucketFrequencies.length ? measureBucketFrequencies.length - 1 : bucketIndex.intValue();
+ long value = entry.getValue();
+ samples += value;
+ measureBucketFrequencies[index] += value;
+ if (measureBucketFrequencies[index] > maxLatencyBucketFrequency)
+ maxLatencyBucketFrequency = measureBucketFrequencies[index];
+ if (measureAt50thPercentile == 0 && samples > count / 2)
+ measureAt50thPercentile = (previousMeasure + latency) / 2;
+ if (measureAt99thPercentile == 0 && samples > count - count / 100)
+ measureAt99thPercentile = (previousMeasure + latency) / 2;
+ previousMeasure = latency;
+ entries.remove();
+ }
+
+ builder.append(name).append(" - distribution curve (x axis: frequency, y axis: ").append(name).append("):").append(eol);
+ double percentile = 0.0;
+ for (int i = 0; i < measureBucketFrequencies.length; ++i)
+ {
+ long latencyBucketFrequency = measureBucketFrequencies[i];
+ int value = maxLatencyBucketFrequency == 0 ? 0 : Math.round(latencyBucketFrequency * (float)measureBucketFrequencies.length / maxLatencyBucketFrequency);
+ if (value == measureBucketFrequencies.length)
+ value = value - 1;
+ for (int j = 0; j < value; ++j)
+ builder.append(" ");
+ builder.append("@");
+ for (int j = value + 1; j < measureBucketFrequencies.length; ++j)
+ builder.append(" ");
+ builder.append(" _ ");
+ double percentage = 100D * latencyBucketFrequency / samples;
+ builder.append(converter.convert((measureRange * (i + 1) / measureBucketFrequencies.length) + minMeasure));
+ builder.append(String.format(" %s (%d, %.2f%%)", unit, latencyBucketFrequency, percentage));
+ double last = percentile;
+ percentile += percentage;
+ if (last < 50.0 && percentile >= 50.0)
+ builder.append(" ^50%");
+ if (last < 85.0 && percentile >= 85.0)
+ builder.append(" ^85%");
+ if (last < 95.0 && percentile >= 95.0)
+ builder.append(" ^95%");
+ if (last < 99.0 && percentile >= 99.0)
+ builder.append(" ^99%");
+ if (last < 99.9 && percentile >= 99.9)
+ builder.append(" ^99.9%");
+ builder.append(eol);
+ }
+ }
+
+ builder.append(String.format("%s - %d samples | 50th%%/99th%%/100th%% = %d/%d/%d %s%n",
+ name,
+ count,
+ converter.convert(measureAt50thPercentile),
+ converter.convert(measureAt99thPercentile),
+ converter.convert(max),
+ unit));
+
+ return builder.toString();
+ }
+ }
+
+ public interface Converter
+ {
+ public long convert(long measure);
+ }
+}

Back to the top