-
Notifications
You must be signed in to change notification settings - Fork 356
added sliding window histogram #200
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -17,8 +17,6 @@ | |
|
||
import com.netflix.servo.monitor.MonitorConfig; | ||
import com.netflix.servo.tag.Tag; | ||
import org.HdrHistogram.ConcurrentHistogram; | ||
import org.HdrHistogram.Histogram; | ||
|
||
import java.util.Arrays; | ||
import java.util.List; | ||
|
@@ -29,7 +27,11 @@ | |
* The buckets are min, max, 50%, 90%, 99%, 99.9%, and 99.99% | ||
*/ | ||
public class HdrHistogramServoTimer { | ||
private final Histogram histogram = new ConcurrentHistogram(TimeUnit.MINUTES.toNanos(1), 2); | ||
private final SlidingWindowHistogram histogram = new SlidingWindowHistogram(); | ||
|
||
private static final long TIMEOUT = TimeUnit.MINUTES.toMillis(1); | ||
|
||
private volatile long lastCleared = System.currentTimeMillis(); | ||
|
||
private HdrHistogramMinGauge min; | ||
|
||
|
@@ -46,31 +48,26 @@ public class HdrHistogramServoTimer { | |
private HdrHistogramGauge p99_99; | ||
|
||
private HdrHistogramServoTimer(String label) { | ||
histogram.setAutoResize(true); | ||
|
||
min = new HdrHistogramMinGauge(MonitorConfig.builder(label + "_min").build(), histogram); | ||
max = new HdrHistogramMaxGauge(MonitorConfig.builder(label + "_max").build(), histogram); | ||
min = new HdrHistogramMinGauge(MonitorConfig.builder(label).withTag("value", "min").build(), histogram); | ||
max = new HdrHistogramMaxGauge(MonitorConfig.builder(label).withTag("value", "max").build(), histogram); | ||
|
||
p50 = new HdrHistogramGauge(MonitorConfig.builder(label + "_p50").build(), histogram, 50); | ||
p90 = new HdrHistogramGauge(MonitorConfig.builder(label + "_p90").build(), histogram, 90); | ||
p99 = new HdrHistogramGauge(MonitorConfig.builder(label + "_p99").build(), histogram, 99); | ||
p99_9 = new HdrHistogramGauge(MonitorConfig.builder(label + "_p99_9").build(), histogram, 99.9); | ||
p99_99 = new HdrHistogramGauge(MonitorConfig.builder(label + "_p99_99").build(), histogram, 99.99); | ||
p50 = new HdrHistogramGauge(MonitorConfig.builder(label).withTag("value", "p50").build(), histogram, 50, this::slide); | ||
p90 = new HdrHistogramGauge(MonitorConfig.builder(label).withTag("value", "p90").build(), histogram, 90, this::slide); | ||
p99 = new HdrHistogramGauge(MonitorConfig.builder(label).withTag("value", "p99").build(), histogram, 99, this::slide); | ||
p99_9 = new HdrHistogramGauge(MonitorConfig.builder(label).withTag("value", "p99_9").build(), histogram, 99.9, this::slide); | ||
p99_99 = new HdrHistogramGauge(MonitorConfig.builder(label).withTag("value", "p99_99").build(), histogram, 99.99, this::slide); | ||
} | ||
|
||
|
||
private HdrHistogramServoTimer(String label, List<Tag> tags) { | ||
histogram.setAutoResize(true); | ||
|
||
|
||
min = new HdrHistogramMinGauge(MonitorConfig.builder(label + "_min").withTags(tags).build(), histogram); | ||
max = new HdrHistogramMaxGauge(MonitorConfig.builder(label + "_max").withTags(tags).build(), histogram); | ||
|
||
p50 = new HdrHistogramGauge(MonitorConfig.builder(label + "_p50").withTags(tags).build(), histogram, 50); | ||
p90 = new HdrHistogramGauge(MonitorConfig.builder(label + "_p90").withTags(tags).build(), histogram, 90); | ||
p99 = new HdrHistogramGauge(MonitorConfig.builder(label + "_p99").withTags(tags).build(), histogram, 99); | ||
p99_9 = new HdrHistogramGauge(MonitorConfig.builder(label + "_p99_9").withTags(tags).build(), histogram, 99.9); | ||
p99_99 = new HdrHistogramGauge(MonitorConfig.builder(label + "_p99_99").withTags(tags).build(), histogram, 99.99); | ||
min = new HdrHistogramMinGauge(MonitorConfig.builder(label).withTag("value", "min").withTags(tags).build(), histogram); | ||
max = new HdrHistogramMaxGauge(MonitorConfig.builder(label).withTag("value", "min").withTags(tags).build(), histogram); | ||
|
||
p50 = new HdrHistogramGauge(MonitorConfig.builder(label).withTag("value", "p50").withTags(tags).build(), histogram, 50, this::slide); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This will rotate the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. get min and max don't reset the histogram There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. My bad. You are correct that min and max don't but 50,90,99,99_9,99_99 all do. |
||
p90 = new HdrHistogramGauge(MonitorConfig.builder(label).withTag("value", "p90").withTags(tags).build(), histogram, 90, this::slide); | ||
p99 = new HdrHistogramGauge(MonitorConfig.builder(label).withTag("value", "p90").withTags(tags).build(), histogram, 99, this::slide); | ||
p99_9 = new HdrHistogramGauge(MonitorConfig.builder(label).withTag("value", "p99_9").withTags(tags).build(), histogram, 99.9, this::slide); | ||
p99_99 = new HdrHistogramGauge(MonitorConfig.builder(label).withTag("value", "p99_99").withTags(tags).build(), histogram, 99.99, this::slide); | ||
} | ||
|
||
public static HdrHistogramServoTimer newInstance(String label) { | ||
|
@@ -87,6 +84,7 @@ public static HdrHistogramServoTimer newInstance(String label, List<Tag> tags) { | |
|
||
/** | ||
* Records a value for to the histogram and updates the Servo counter buckets | ||
* | ||
* @param value the value to update | ||
*/ | ||
public void record(long value) { | ||
|
@@ -120,4 +118,12 @@ public Long getP99_9() { | |
public Long getP99_99() { | ||
return p99_99.getValue(); | ||
} | ||
|
||
private synchronized void slide() { | ||
if (System.currentTimeMillis() - lastCleared > TIMEOUT) { | ||
histogram.rotateHistogram(); | ||
lastCleared = System.currentTimeMillis(); | ||
} | ||
} | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
package io.reactivesocket.loadbalancer.servo.internal; | ||
|
||
import org.HdrHistogram.ConcurrentHistogram; | ||
import org.HdrHistogram.Histogram; | ||
|
||
import java.io.PrintStream; | ||
import java.util.ArrayDeque; | ||
import java.util.concurrent.TimeUnit; | ||
|
||
/** | ||
* Wraps HdrHistogram to create a sliding window of n histogramQueue. Default window number is five. | ||
*/ | ||
public class SlidingWindowHistogram { | ||
private volatile Histogram liveHistogram; | ||
|
||
private final ArrayDeque<Histogram> histogramQueue; | ||
|
||
private final Object LOCK = new Object(); | ||
|
||
public SlidingWindowHistogram() { | ||
this(5); | ||
} | ||
|
||
public SlidingWindowHistogram(final int numOfWindows) { | ||
if (numOfWindows < 2) { | ||
throw new IllegalArgumentException("number of windows must be greater than 1"); | ||
} | ||
this.histogramQueue = new ArrayDeque<>(numOfWindows - 1); | ||
this.liveHistogram = createHistogram(); | ||
|
||
for (int i = 0; i < numOfWindows - 1; i++) { | ||
histogramQueue.offer(createHistogram()); | ||
} | ||
} | ||
|
||
private static Histogram createHistogram() { | ||
ConcurrentHistogram histogram = new ConcurrentHistogram(TimeUnit.MINUTES.toNanos(1), 2); | ||
histogram.setAutoResize(true); | ||
return histogram; | ||
} | ||
|
||
/** | ||
* Records a value to the in window liveHistogram | ||
* | ||
* @param value value to record | ||
*/ | ||
public void recordValue(long value) { | ||
liveHistogram.recordValue(value); | ||
} | ||
|
||
/** | ||
* Slides the Histogram window. Pops a Histogram off a queue, resets it, and places the old | ||
* on in the queue. | ||
*/ | ||
public void rotateHistogram() { | ||
synchronized (LOCK) { | ||
Histogram onDeck = histogramQueue.poll(); | ||
if (onDeck != null) { | ||
onDeck.reset(); | ||
Histogram old = liveHistogram; | ||
liveHistogram = onDeck; | ||
histogramQueue.offer(old); | ||
} | ||
} | ||
} | ||
|
||
/** | ||
* Aggregates the Histograms into a single Histogram and returns it. | ||
* | ||
* @return Aggregated liveHistogram | ||
*/ | ||
public Histogram aggregateHistogram() { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Since gets will most likely not be concurrent, done by the poller, do we need threadlocal isolation of aggregation? Synchronized aggregation may be simpler and efficient? Also, you are already synchronizing additions to the aggregate. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No it's be bad because sync would block the aggregations. One line 86 it adds the live one, and then in the sync block it adds the ones in the queue. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes the sync will block aggregations but one histogram will be used by one metric which will have one poller and hence there would only be a single thread calling |
||
Histogram aggregate = createHistogram(); | ||
|
||
synchronized (LOCK) { | ||
aggregate.add(liveHistogram); | ||
histogramQueue | ||
.forEach(aggregate::add); | ||
} | ||
|
||
return aggregate; | ||
} | ||
|
||
/** | ||
* Prints HdrHistogram to System.out | ||
*/ | ||
public void print() { | ||
print(System.out); | ||
} | ||
|
||
public void print(PrintStream printStream) { | ||
aggregateHistogram().outputPercentileDistribution(printStream, 1000.0); | ||
} | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
package io.reactivesocket.loadbalancer.servo.internal; | ||
|
||
import org.HdrHistogram.Histogram; | ||
import org.junit.Assert; | ||
import org.junit.Test; | ||
|
||
public class SlidingWindowHistogramTest { | ||
@Test | ||
public void test() { | ||
SlidingWindowHistogram slidingWindowHistogram = new SlidingWindowHistogram(2); | ||
|
||
for (int i = 0; i < 100_000; i++) { | ||
slidingWindowHistogram.recordValue(i); | ||
} | ||
|
||
slidingWindowHistogram.print(); | ||
slidingWindowHistogram.rotateHistogram(); | ||
Histogram histogram = | ||
slidingWindowHistogram.aggregateHistogram(); | ||
|
||
long totalCount = histogram.getTotalCount(); | ||
Assert.assertTrue(totalCount == 100_000); | ||
|
||
long p90 = histogram.getValueAtPercentile(90); | ||
Assert.assertTrue(p90 < 100_000); | ||
|
||
for (int i = 0; i < 100_000; i++) { | ||
slidingWindowHistogram.recordValue(i * 10_000); | ||
} | ||
|
||
slidingWindowHistogram.print(); | ||
slidingWindowHistogram.rotateHistogram(); | ||
|
||
histogram = | ||
slidingWindowHistogram.aggregateHistogram(); | ||
|
||
p90 = histogram.getValueAtPercentile(90); | ||
Assert.assertTrue(p90 >= 100_000); | ||
|
||
for (int i = 0; i < 100_000; i++) { | ||
slidingWindowHistogram.recordValue(i); | ||
} | ||
|
||
slidingWindowHistogram.print(); | ||
slidingWindowHistogram.rotateHistogram(); | ||
|
||
for (int i = 0; i < 100_000; i++) { | ||
slidingWindowHistogram.recordValue(i); | ||
} | ||
|
||
slidingWindowHistogram.print(); | ||
|
||
histogram = | ||
slidingWindowHistogram.aggregateHistogram(); | ||
|
||
totalCount = histogram.getTotalCount(); | ||
Assert.assertTrue(totalCount == 200_000); | ||
|
||
p90 = histogram.getValueAtPercentile(90); | ||
Assert.assertTrue(p90 < 100_000); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This could lead to unintentional sliding of the window. eg: If a user action fetches the value (eg: from a JMX console), it will reset the histogram.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
slide is passed in as a closure, and that method has a 1 minute timeout
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes but do you intend to slide (and in the other class, today it rotates the histogram) on every
get
?Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes - it'll only rotate now when the the timeout expires
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My bad, didn't see the timeout condition, thanks!