DoubleHistogramDataAccessTest.java
/**
* HistogramDataAccessTest.java
* Written by Gil Tene of Azul Systems, and released to the public domain,
* as explained at http://creativecommons.org/publicdomain/zero/1.0/
*
* @author Gil Tene
*/
package org.HdrHistogram;
import org.junit.Assert;
import org.junit.jupiter.api.Test;
/**
* JUnit test for {@link Histogram}
*/
public class DoubleHistogramDataAccessTest {
static final long highestTrackableValue = 3600L * 1000 * 1000; // 1 hour in usec units
static final int numberOfSignificantValueDigits = 3; // Maintain at least 3 decimal points of accuracy
static final DoubleHistogram histogram;
static final DoubleHistogram scaledHistogram;
static final DoubleHistogram rawHistogram;
static final DoubleHistogram scaledRawHistogram;
static final DoubleHistogram postCorrectedHistogram;
static final DoubleHistogram postCorrectedScaledHistogram;
static {
histogram = new DoubleHistogram(highestTrackableValue, numberOfSignificantValueDigits);
scaledHistogram = new DoubleHistogram(highestTrackableValue / 2 , numberOfSignificantValueDigits);
rawHistogram = new DoubleHistogram(highestTrackableValue, numberOfSignificantValueDigits);
scaledRawHistogram = new DoubleHistogram(highestTrackableValue / 2, numberOfSignificantValueDigits);
// Log hypothetical scenario: 100 seconds of "perfect" 1msec results, sampled
// 100 times per second (10,000 results), followed by a 100 second pause with
// a single (100 second) recorded result. Recording is done indicating an expected
// interval between samples of 10 msec:
for (int i = 0; i < 10000; i++) {
histogram.recordValueWithExpectedInterval(1000 /* 1 msec */, 10000 /* 10 msec expected interval */);
scaledHistogram.recordValueWithExpectedInterval(1000 * 512 /* 1 msec */, 10000 * 512 /* 10 msec expected interval */);
rawHistogram.recordValue(1000 /* 1 msec */);
scaledRawHistogram.recordValue(1000 * 512/* 1 msec */);
}
histogram.recordValueWithExpectedInterval(100000000L /* 100 sec */, 10000 /* 10 msec expected interval */);
scaledHistogram.recordValueWithExpectedInterval(100000000L * 512 /* 100 sec */, 10000 * 512 /* 10 msec expected interval */);
rawHistogram.recordValue(100000000L /* 100 sec */);
scaledRawHistogram.recordValue(100000000L * 512 /* 100 sec */);
postCorrectedHistogram = rawHistogram.copyCorrectedForCoordinatedOmission(10000 /* 10 msec expected interval */);
postCorrectedScaledHistogram = scaledRawHistogram.copyCorrectedForCoordinatedOmission(10000 * 512 /* 10 msec expected interval */);
}
@Test
public void testScalingEquivalence() {
Assert.assertEquals("averages should be equivalent",
histogram.getMean() * 512,
scaledHistogram.getMean(), scaledHistogram.getMean() * 0.000001);
Assert.assertEquals("total count should be the same",
histogram.getTotalCount(),
scaledHistogram.getTotalCount());
Assert.assertEquals("99%'iles should be equivalent",
scaledHistogram.highestEquivalentValue(histogram.getValueAtPercentile(99.0) * 512),
scaledHistogram.highestEquivalentValue(scaledHistogram.getValueAtPercentile(99.0)),
scaledHistogram.highestEquivalentValue(scaledHistogram.getValueAtPercentile(99.0)) * 0.000001);
Assert.assertEquals("Max should be equivalent",
scaledHistogram.highestEquivalentValue(histogram.getMaxValue() * 512),
scaledHistogram.getMaxValue(),
scaledHistogram.getMaxValue() * 0.000001);
// Same for post-corrected:
Assert.assertEquals("averages should be equivalent",
histogram.getMean() * 512,
scaledHistogram.getMean(), scaledHistogram.getMean() * 0.000001);
Assert.assertEquals("total count should be the same",
postCorrectedHistogram.getTotalCount(),
postCorrectedScaledHistogram.getTotalCount());
Assert.assertEquals("99%'iles should be equivalent",
postCorrectedHistogram.lowestEquivalentValue(postCorrectedHistogram.getValueAtPercentile(99.0)) * 512,
postCorrectedScaledHistogram.lowestEquivalentValue(postCorrectedScaledHistogram.getValueAtPercentile(99.0)),
postCorrectedScaledHistogram.lowestEquivalentValue(postCorrectedScaledHistogram.getValueAtPercentile(99.0)) * 0.000001
);
Assert.assertEquals("Max should be equivalent",
postCorrectedScaledHistogram.highestEquivalentValue(postCorrectedHistogram.getMaxValue() * 512),
postCorrectedScaledHistogram.getMaxValue(),
postCorrectedScaledHistogram.getMaxValue() * 0.000001
);
}
@Test
public void testPreVsPostCorrectionValues() {
// Loop both ways (one would be enough, but good practice just for fun:
Assert.assertEquals("pre and post corrected count totals ",
histogram.getTotalCount(), postCorrectedHistogram.getTotalCount());
// The following comparison loops would have worked in a perfect accuracy world, but since post
// correction is done based on the value extracted from the bucket, and the during-recording is done
// based on the actual (not pixelized) value, there will be subtle differences due to roundoffs:
// for (HistogramIterationValue v : histogram.allValues()) {
// long preCorrectedCount = v.getCountAtValueIteratedTo();
// long postCorrectedCount = postCorrectedHistogram.getCountAtValue(v.getValueIteratedTo());
// Assert.assertEquals("pre and post corrected count at value " + v.getValueIteratedTo(),
// preCorrectedCount, postCorrectedCount);
// }
//
// for (HistogramIterationValue v : postCorrectedHistogram.allValues()) {
// long preCorrectedCount = v.getCountAtValueIteratedTo();
// long postCorrectedCount = histogram.getCountAtValue(v.getValueIteratedTo());
// Assert.assertEquals("pre and post corrected count at value " + v.getValueIteratedTo(),
// preCorrectedCount, postCorrectedCount);
// }
}
@Test
public void testGetTotalCount() throws Exception {
// The overflow value should count in the total count:
Assert.assertEquals("Raw total count is 10,001",
10001L, rawHistogram.getTotalCount());
Assert.assertEquals("Total count is 20,000",
20000L, histogram.getTotalCount());
}
@Test
public void testGetMaxValue() throws Exception {
Assert.assertTrue(
histogram.valuesAreEquivalent(100L * 1000 * 1000,
histogram.getMaxValue()));
}
@Test
public void testGetMinValue() throws Exception {
Assert.assertTrue(
histogram.valuesAreEquivalent(1000,
histogram.getMinValue()));
}
@Test
public void testGetMean() throws Exception {
double expectedRawMean = ((10000.0 * 1000) + (1.0 * 100000000))/10001; /* direct avg. of raw results */
double expectedMean = (1000.0 + 50000000.0)/2; /* avg. 1 msec for half the time, and 50 sec for other half */
// We expect to see the mean to be accurate to ~3 decimal points (~0.1%):
Assert.assertEquals("Raw mean is " + expectedRawMean + " +/- 0.1%",
expectedRawMean, rawHistogram.getMean(), expectedRawMean * 0.001);
Assert.assertEquals("Mean is " + expectedMean + " +/- 0.1%",
expectedMean, histogram.getMean(), expectedMean * 0.001);
}
@Test
public void testGetStdDeviation() throws Exception {
double expectedRawMean = ((10000.0 * 1000) + (1.0 * 100000000))/10001; /* direct avg. of raw results */
double expectedRawStdDev =
Math.sqrt(
((10000.0 * Math.pow((1000.0 - expectedRawMean), 2)) +
Math.pow((100000000.0 - expectedRawMean), 2)) /
10001);
double expectedMean = (1000.0 + 50000000.0)/2; /* avg. 1 msec for half the time, and 50 sec for other half */
double expectedSquareDeviationSum = 10000 * Math.pow((1000.0 - expectedMean), 2);
for (long value = 10000; value <= 100000000; value += 10000) {
expectedSquareDeviationSum += Math.pow((value - expectedMean), 2);
}
double expectedStdDev = Math.sqrt(expectedSquareDeviationSum / 20000);
// We expect to see the standard deviations to be accurate to ~3 decimal points (~0.1%):
Assert.assertEquals("Raw standard deviation is " + expectedRawStdDev + " +/- 0.1%",
expectedRawStdDev, rawHistogram.getStdDeviation(), expectedRawStdDev * 0.001);
Assert.assertEquals("Standard deviation is " + expectedStdDev + " +/- 0.1%",
expectedStdDev, histogram.getStdDeviation(), expectedStdDev * 0.001);
}
@Test
public void testGetValueAtPercentile() throws Exception {
Assert.assertEquals("raw 30%'ile is 1 msec +/- 0.1%",
1000.0, (double) rawHistogram.getValueAtPercentile(30.0),
1000.0 * 0.001);
Assert.assertEquals("raw 99%'ile is 1 msec +/- 0.1%",
1000.0, (double) rawHistogram.getValueAtPercentile(99.0),
1000.0 * 0.001);
Assert.assertEquals("raw 99.99%'ile is 1 msec +/- 0.1%",
1000.0, (double) rawHistogram.getValueAtPercentile(99.99)
, 1000.0 * 0.001);
Assert.assertEquals("raw 99.999%'ile is 100 sec +/- 0.1%",
100000000.0, (double) rawHistogram.getValueAtPercentile(99.999),
100000000.0 * 0.001);
Assert.assertEquals("raw 100%'ile is 100 sec +/- 0.1%",
100000000.0, (double) rawHistogram.getValueAtPercentile(100.0),
100000000.0 * 0.001);
Assert.assertEquals("30%'ile is 1 msec +/- 0.1%",
1000.0, (double) histogram.getValueAtPercentile(30.0),
1000.0 * 0.001);
Assert.assertEquals("50%'ile is 1 msec +/- 0.1%",
1000.0, (double) histogram.getValueAtPercentile(50.0),
1000.0 * 0.001);
Assert.assertEquals("75%'ile is 50 sec +/- 0.1%",
50000000.0, (double) histogram.getValueAtPercentile(75.0),
50000000.0 * 0.001);
Assert.assertEquals("90%'ile is 80 sec +/- 0.1%",
80000000.0, (double) histogram.getValueAtPercentile(90.0),
80000000.0 * 0.001);
Assert.assertEquals("99%'ile is 98 sec +/- 0.1%",
98000000.0, (double) histogram.getValueAtPercentile(99.0),
98000000.0 * 0.001);
Assert.assertEquals("99.999%'ile is 100 sec +/- 0.1%",
100000000.0, (double) histogram.getValueAtPercentile(99.999),
100000000.0 * 0.001);
Assert.assertEquals("100%'ile is 100 sec +/- 0.1%",
100000000.0, (double) histogram.getValueAtPercentile(100.0),
100000000.0 * 0.001);
}
@Test
public void testGetValueAtPercentileForLargeHistogram() {
long largestValue = 1000000000000L;
Histogram h = new Histogram(largestValue, 5);
h.recordValue(largestValue);
Assert.assertTrue(h.getValueAtPercentile(100.0) > 0);
}
@Test
public void testGetPercentileAtOrBelowValue() throws Exception {
Assert.assertEquals("Raw percentile at or below 5 msec is 99.99% +/- 0.0001",
99.99,
rawHistogram.getPercentileAtOrBelowValue(5000), 0.0001);
Assert.assertEquals("Percentile at or below 5 msec is 50% +/- 0.0001%",
50.0,
histogram.getPercentileAtOrBelowValue(5000), 0.0001);
Assert.assertEquals("Percentile at or below 100 sec is 100% +/- 0.0001%",
100.0,
histogram.getPercentileAtOrBelowValue(100000000L), 0.0001);
}
@Test
public void testGetCountBetweenValues() throws Exception {
Assert.assertEquals("Count of raw values between 1 msec and 1 msec is 1",
10000, rawHistogram.getCountBetweenValues(1000L, 1000L), 10000 * 0.000001);
Assert.assertEquals("Count of raw values between 5 msec and 150 sec is 1",
1, rawHistogram.getCountBetweenValues(5000L, 150000000L), 1 * 0.000001);
Assert.assertEquals("Count of values between 5 msec and 150 sec is 10,000",
10000, histogram.getCountBetweenValues(5000L, 150000000L), 10000 * 0.000001);
}
@Test
public void testGetCountAtValue() throws Exception {
Assert.assertEquals("Count of raw values at 10 msec is 0",
0, rawHistogram.getCountBetweenValues(10000L, 10010L), 0.000001);
Assert.assertEquals("Count of values at 10 msec is 0",
1, histogram.getCountBetweenValues(10000L, 10010L), 0.000001);
Assert.assertEquals("Count of raw values at 1 msec is 10,000",
10000, rawHistogram.getCountAtValue(1000L), 10000 * 0.000001);
Assert.assertEquals("Count of values at 1 msec is 10,000",
10000, histogram.getCountAtValue(1000L), 10000 * 0.000001);
}
@Test
public void testPercentiles() throws Exception {
int i = 0;
for (DoubleHistogramIterationValue v : histogram.percentiles(5 /* ticks per half */)) {
Assert.assertEquals("i = " + i + ", Value at Iterated-to Percentile is the same as the matching getValueAtPercentile():\n" +
"getPercentileLevelIteratedTo = " + v.getPercentileLevelIteratedTo() +
"\ngetValueIteratedTo = " + v.getValueIteratedTo() +
"\ngetValueIteratedFrom = " + v.getValueIteratedFrom() +
"\ngetValueAtPercentile(getPercentileLevelIteratedTo()) = " +
histogram.getValueAtPercentile(v.getPercentileLevelIteratedTo()) +
"\ngetPercentile = " + v.getPercentile() +
"\ngetValueAtPercentile(getPercentile())" +
histogram.getValueAtPercentile(v.getPercentile()) +
"\nequivalent1 = " +
histogram.highestEquivalentValue(histogram.getValueAtPercentile(v.getPercentileLevelIteratedTo())) +
"\nequivalent2 = " +
histogram.highestEquivalentValue(histogram.getValueAtPercentile(v.getPercentile())) +
"\n"
,
v.getValueIteratedTo(),
histogram.highestEquivalentValue(histogram.getValueAtPercentile(v.getPercentile())),
v.getValueIteratedTo() * 0.001);
}
}
@Test
public void testLinearBucketValues() throws Exception {
int index = 0;
// Note that using linear buckets should work "as expected" as long as the number of linear buckets
// is lower than the resolution level determined by largestValueWithSingleUnitResolution
// (2000 in this case). Above that count, some of the linear buckets can end up rounded up in size
// (to the nearest local resolution unit level), which can result in a smaller number of buckets that
// expected covering the range.
// Iterate raw data using linear buckets of 100 msec each.
for (DoubleHistogramIterationValue v : rawHistogram.linearBucketValues(100000)) {
long countAddedInThisBucket = v.getCountAddedInThisIterationStep();
if (index == 0) {
Assert.assertEquals("Raw Linear 100 msec bucket # 0 added a count of 10000",
10000, countAddedInThisBucket);
} else if (index == 999) {
Assert.assertEquals("Raw Linear 100 msec bucket # 999 added a count of 1",
1, countAddedInThisBucket);
} else {
Assert.assertEquals("Raw Linear 100 msec bucket # " + index + " added a count of 0",
0 , countAddedInThisBucket);
}
index++;
}
Assert.assertEquals(1000, index);
index = 0;
long totalAddedCounts = 0;
// Iterate data using linear buckets of 10 msec each.
for (DoubleHistogramIterationValue v : histogram.linearBucketValues(10000)) {
long countAddedInThisBucket = v.getCountAddedInThisIterationStep();
if (index == 0) {
Assert.assertEquals("Linear 1 sec bucket # 0 [" +
v.getValueIteratedFrom() + ".." + v.getValueIteratedTo() +
"] added a count of 10000",
10000, countAddedInThisBucket);
}
// Because value resolution is low enough (3 digits) that multiple linear buckets will end up
// residing in a single value-equivalent range, some linear buckets will have counts of 2 or
// more, and some will have 0 (when the first bucket in the equivalent range was the one that
// got the total count bump).
// However, we can still verify the sum of counts added in all the buckets...
totalAddedCounts += v.getCountAddedInThisIterationStep();
index++;
}
Assert.assertEquals("There should be 10000 linear buckets of size 10000 usec between 0 and 100 sec.",
10000, index);
Assert.assertEquals("Total added counts should be 20000", 20000, totalAddedCounts);
index = 0;
totalAddedCounts = 0;
// Iterate data using linear buckets of 1 msec each.
for (DoubleHistogramIterationValue v : histogram.linearBucketValues(1000)) {
long countAddedInThisBucket = v.getCountAddedInThisIterationStep();
if (index == 1) {
Assert.assertEquals("Linear 1 sec bucket # 0 [" +
v.getValueIteratedFrom() + ".." + v.getValueIteratedTo() +
"] added a count of 10000",
10000, countAddedInThisBucket);
}
// Because value resolution is low enough (3 digits) that multiple linear buckets will end up
// residing in a single value-equivalent range, some linear buckets will have counts of 2 or
// more, and some will have 0 (when the first bucket in the equivalent range was the one that
// got the total count bump).
// However, we can still verify the sum of counts added in all the buckets...
totalAddedCounts += v.getCountAddedInThisIterationStep();
index++;
}
// You may ask "why 100007 and not 100000?" for the value below? The answer is that at this fine
// a linear stepping resolution, the final populated sub-bucket (at 100 seconds with 3 decimal
// point resolution) is larger than our liner stepping, and holds more than one linear 1 msec
// step in it.
// Since we only know we're done with linear iteration when the next iteration step will step
// out of the last populated bucket, there is not way to tell if the iteration should stop at
// 100000 or 100007 steps. The proper thing to do is to run to the end of the sub-bucket quanta...
Assert.assertEquals("There should be 100007 linear buckets of size 1000 usec between 0 and 100 sec.",
100007, index);
Assert.assertEquals("Total added counts should be 20000", 20000, totalAddedCounts);
}
@Test
public void testLogarithmicBucketValues() throws Exception {
int index = 0;
// Iterate raw data using logarithmic buckets starting at 10 msec.
for (DoubleHistogramIterationValue v : rawHistogram.logarithmicBucketValues(10000, 2)) {
long countAddedInThisBucket = v.getCountAddedInThisIterationStep();
if (index == 0) {
Assert.assertEquals("Raw Logarithmic 10 msec bucket # 0 added a count of 10000",
10000, countAddedInThisBucket);
} else if (index == 14) {
Assert.assertEquals("Raw Logarithmic 10 msec bucket # 14 added a count of 1",
1, countAddedInThisBucket);
} else {
Assert.assertEquals("Raw Logarithmic 100 msec bucket # " + index + " added a count of 0",
0, countAddedInThisBucket);
}
index++;
}
Assert.assertEquals(14, index - 1);
index = 0;
long totalAddedCounts = 0;
// Iterate data using linear buckets of 1 sec each.
for (DoubleHistogramIterationValue v : histogram.logarithmicBucketValues(10000, 2)) {
long countAddedInThisBucket = v.getCountAddedInThisIterationStep();
if (index == 0) {
Assert.assertEquals("Logarithmic 10 msec bucket # 0 [" +
v.getValueIteratedFrom() + ".." + v.getValueIteratedTo() +
"] added a count of 10000",
10000, countAddedInThisBucket);
}
totalAddedCounts += v.getCountAddedInThisIterationStep();
index++;
}
Assert.assertEquals("There should be 14 Logarithmic buckets of size 10000 usec between 0 and 100 sec.",
14, index - 1);
Assert.assertEquals("Total added counts should be 20000", 20000, totalAddedCounts);
}
@Test
public void testRecordedValues() throws Exception {
int index = 0;
// Iterate raw data by stepping through every value that has a count recorded:
for (DoubleHistogramIterationValue v : rawHistogram.recordedValues()) {
long countAddedInThisBucket = v.getCountAddedInThisIterationStep();
if (index == 0) {
Assert.assertEquals("Raw recorded value bucket # 0 added a count of 10000",
10000, countAddedInThisBucket);
} else {
Assert.assertEquals("Raw recorded value bucket # " + index + " added a count of 1",
1, countAddedInThisBucket);
}
index++;
}
Assert.assertEquals(2, index);
index = 0;
long totalAddedCounts = 0;
// Iterate data using linear buckets of 1 sec each.
for (DoubleHistogramIterationValue v : histogram.recordedValues()) {
long countAddedInThisBucket = v.getCountAddedInThisIterationStep();
if (index == 0) {
Assert.assertEquals("Recorded bucket # 0 [" +
v.getValueIteratedFrom() + ".." + v.getValueIteratedTo() +
"] added a count of 10000",
10000, countAddedInThisBucket);
}
Assert.assertTrue("The count in recorded bucket #" + index + " is not 0",
v.getCountAtValueIteratedTo() != 0);
Assert.assertEquals("The count in recorded bucket #" + index +
" is exactly the amount added since the last iteration ",
v.getCountAtValueIteratedTo(), v.getCountAddedInThisIterationStep());
totalAddedCounts += v.getCountAddedInThisIterationStep();
index++;
}
Assert.assertEquals("Total added counts should be 20000", 20000, totalAddedCounts);
}
@Test
public void testAllValues() throws Exception {
int index = 0;
double latestValueAtIndex = 0;
double totalCountToThisPoint = 0;
double totalValueToThisPoint = 0;
// Iterate raw data by stepping through every value that ahs a count recorded:
for (DoubleHistogramIterationValue v : rawHistogram.allValues()) {
long countAddedInThisBucket = v.getCountAddedInThisIterationStep();
if (index == 2000) {
Assert.assertEquals("Raw allValues bucket # 0 added a count of 10000",
10000, countAddedInThisBucket);
} else if (histogram.valuesAreEquivalent(v.getValueIteratedTo(), 100000000)) {
Assert.assertEquals("Raw allValues value bucket # " + index + " added a count of 1",
1, countAddedInThisBucket);
} else {
Assert.assertEquals("Raw allValues value bucket # " + index + " added a count of 0",
0, countAddedInThisBucket);
}
latestValueAtIndex = v.getValueIteratedTo();
totalCountToThisPoint += v.getCountAtValueIteratedTo();
Assert.assertEquals("total Count should match", totalCountToThisPoint, v.getTotalCountToThisValue(), 1e-8);
totalValueToThisPoint += v.getCountAtValueIteratedTo() * latestValueAtIndex;
Assert.assertEquals("total Value should match", totalValueToThisPoint, v.getTotalValueToThisValue(), 1e-8);
index++;
}
Assert.assertEquals("index should be equal to countsArrayLength",
histogram.integerValuesHistogram.countsArrayLength, index);
index = 0;
long totalAddedCounts = 0;
// Iterate data using linear buckets of 1 sec each.
for (DoubleHistogramIterationValue v : histogram.allValues()) {
long countAddedInThisBucket = v.getCountAddedInThisIterationStep();
if (index == 2000) {
Assert.assertEquals("AllValues bucket # 0 [" +
v.getValueIteratedFrom() + ".." + v.getValueIteratedTo() +
"] added a count of 10000",
10000, countAddedInThisBucket);
}
Assert.assertEquals("The count in AllValues bucket #" + index +
" is exactly the amount added since the last iteration ",
v.getCountAtValueIteratedTo(), v.getCountAddedInThisIterationStep());
totalAddedCounts += v.getCountAddedInThisIterationStep();
index++;
}
Assert.assertEquals("index should be equal to countsArrayLength",
histogram.integerValuesHistogram.countsArrayLength, index);
Assert.assertEquals("Total added counts should be 20000", 20000, totalAddedCounts);
}
}