// =================================================================================================
// Copyright 2013 Twitter, Inc.
// -------------------------------------------------------------------------------------------------
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this work except in compliance with the License.
// You may obtain a copy of the License in the LICENSE file, or at:
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// =================================================================================================
package com.twitter.common.metrics;
import org.junit.Test;
import org.pantsbuild.junit.annotations.TestParallel;
import com.twitter.common.quantity.Amount;
import com.twitter.common.quantity.Data;
import com.twitter.common.quantity.Time;
import com.twitter.common.stats.Precision;
import com.twitter.common.util.testing.FakeClock;
import static org.junit.Assert.assertEquals;
import static com.twitter.common.metrics.Histogram.DEFAULT_QUANTILES;
import static com.twitter.common.stats.WindowedApproxHistogram.DEFAULT_MAX_MEMORY;
import static com.twitter.common.stats.WindowedApproxHistogram.DEFAULT_SLICES;
import static com.twitter.common.stats.WindowedApproxHistogram.DEFAULT_WINDOW;
/**
* Tests Histogram.
*/
@TestParallel
public class HistogramTest {
private String name = "hist";
private static final Precision DEFAULT_PRECISION = new Precision(0.02, 100 * 1000);
private static final double ERROR = DEFAULT_PRECISION.getEpsilon() * DEFAULT_PRECISION.getN();
@Test
public void testEmptyHistogram() {
Histogram hist = new Histogram(name);
Snapshot sample = hist.snapshot();
assertEquals(0L, sample.min());
assertEquals(0L, sample.max());
assertEquals(0L, sample.count());
assertEquals(0L, sample.sum());
assertEquals(0.0, sample.avg(), 0.1);
long[] expected = {0L, 0L, 0L, 0L, 0L, 0L};
checkQuantiles(expected, sample, ERROR);
}
@Test
public void testHistogram() {
int n = 10000;
FakeClock clock = new FakeClock();
Precision precision = new Precision(0.001, n);
int slices = 2;
HistogramInterface hist = new Histogram(name, Amount.of(1L, Time.MINUTES), slices, null,
precision, DEFAULT_QUANTILES, clock);
double error = precision.getEpsilon() * precision.getN() * slices;
for (int i = 1; i <= n; ++i) {
hist.add(i);
}
clock.advance(Amount.of(31L, Time.SECONDS));
Snapshot sample = hist.snapshot();
assertEquals(1L, sample.min());
assertEquals((long) n, sample.max());
assertEquals((long) n, sample.count());
assertEquals((long) (n * (n + 1) / 2), sample.sum());
assertEquals(n * (n + 1) / (2.0 * n), sample.avg(), 0.1);
long[] expected = new long[DEFAULT_QUANTILES.length];
for (int i = 0; i < DEFAULT_QUANTILES.length; i++) {
expected[i] = (long) (DEFAULT_QUANTILES[i] * n);
}
checkQuantiles(expected, sample, error);
}
@Test
public void testHistogramWithMemoryConstraints() {
int n = 10000;
FakeClock clock = new FakeClock();
int slices = 2;
double error = DEFAULT_PRECISION.getEpsilon() * n * slices;
// We suppose here that 32KB is enough for the default precision of (slices + 1) histograms
HistogramInterface hist = new Histogram(name, Amount.of(1L, Time.MINUTES), slices,
Amount.of(32L, Data.KB), null, DEFAULT_QUANTILES, clock);
for (int i = 1; i <= n; ++i) {
hist.add(i);
}
clock.advance(Amount.of(31L, Time.SECONDS));
Snapshot sample = hist.snapshot();
assertEquals(1L, sample.min());
assertEquals((long) n, sample.max());
assertEquals((long) n, sample.count());
assertEquals((long) (n * (n + 1) / 2), sample.sum());
assertEquals(n * (n + 1) / (2.0 * n), sample.avg(), 0.1);
long[] expected = new long[DEFAULT_QUANTILES.length];
for (int i = 0; i < DEFAULT_QUANTILES.length; i++) {
expected[i] = (long) (DEFAULT_QUANTILES[i] * n);
}
checkQuantiles(expected, sample, error);
}
@Test
public void testNegative() {
FakeClock clock = new FakeClock();
HistogramInterface hist = new Histogram(name, DEFAULT_WINDOW,
DEFAULT_SLICES, DEFAULT_MAX_MEMORY, null, DEFAULT_QUANTILES, clock);
int[] data = new int[201];
for (int i = 0; i < data.length; ++i) {
data[i] = -100 + i;
}
for (int x : data) {
hist.add(x);
}
clock.advance(Amount.of(31L, Time.SECONDS));
Snapshot sample = hist.snapshot();
assertEquals(-100L, sample.min());
assertEquals(100L, sample.max());
assertEquals((long) data.length, sample.count());
assertEquals(0L, sample.sum());
assertEquals(0.0, sample.avg(), 0.1);
long[] expected = new long[DEFAULT_QUANTILES.length];
for (int i = 0; i < DEFAULT_QUANTILES.length; i++) {
int idx = (int) (DEFAULT_QUANTILES[i] * data.length);
expected[i] = data[idx];
}
checkQuantiles(expected, sample, ERROR);
}
private void checkQuantiles(long[] expectedQuantiles, Snapshot sample, double e) {
assertEquals(expectedQuantiles.length, DEFAULT_QUANTILES.length);
Percentile[] measureQuantiles = sample.percentiles();
for (int i = 0; i < expectedQuantiles.length; i++) {
double expected = expectedQuantiles[i];
double measure = measureQuantiles[i].getValue();
assertEquals(expected, measure, e);
}
}
}