下面列出了怎么用com.codahale.metrics.Reservoir的API类实例代码及写法,或者点击链接到github查看源代码。
@DataProvider(value = {
"42 | DAYS",
"123 | SECONDS",
"999 | MILLISECONDS",
"3 | HOURS"
}, splitBy = "\\|")
@Test
public void RollingWindowTimerBuilder_newMetric_creates_new_timer_with_SlidingTimeWindowArrayReservoir_with_expected_values(
long amount, TimeUnit timeUnit
) {
// given
RollingWindowTimerBuilder rwtb = new RollingWindowTimerBuilder(amount, timeUnit);
// when
Timer timer = rwtb.newMetric();
// then
Histogram histogram = (Histogram) getInternalState(timer, "histogram");
Reservoir reservoir = (Reservoir) getInternalState(histogram, "reservoir");
assertThat(reservoir).isInstanceOf(SlidingTimeWindowArrayReservoir.class);
// The expected value here comes from logic in the SlidingTimeWindowArrayReservoir constructor.
assertThat(getInternalState(reservoir, "window")).isEqualTo(timeUnit.toNanos(amount) * 256);
}
@DataProvider(value = {
"42 | DAYS",
"123 | SECONDS",
"999 | MILLISECONDS",
"3 | HOURS"
}, splitBy = "\\|")
@Test
public void RollingWindowHistogramBuilder_newMetric_creates_new_histogram_with_SlidingTimeWindowArrayReservoir_with_expected_values(
long amount, TimeUnit timeUnit
) {
// given
RollingWindowHistogramBuilder rwhb = new RollingWindowHistogramBuilder(amount, timeUnit);
// when
Histogram histogram = rwhb.newMetric();
// then
Reservoir reservoir = (Reservoir) getInternalState(histogram, "reservoir");
assertThat(reservoir).isInstanceOf(SlidingTimeWindowArrayReservoir.class);
// The expected value here comes from logic in the SlidingTimeWindowArrayReservoir constructor.
assertThat(getInternalState(reservoir, "window")).isEqualTo(timeUnit.toNanos(amount) * 256);
}
@Test
public void testCustomReservoir() throws Exception {
System.setProperty("timer.reservoir", UniformReservoir.class.getName());
System.setProperty("histogram.size", "2048");
System.setProperty("histogram.window", "600");
System.setProperty("histogram.reservoir", SlidingTimeWindowReservoir.class.getName());
NodeConfig cfg = loadNodeConfig();
SolrMetricManager mgr = new SolrMetricManager(cfg.getSolrResourceLoader(), cfg.getMetricsConfig());
assertTrue(mgr.getCounterSupplier() instanceof MetricSuppliers.DefaultCounterSupplier);
assertTrue(mgr.getMeterSupplier() instanceof MetricSuppliers.DefaultMeterSupplier);
assertTrue(mgr.getTimerSupplier() instanceof MetricSuppliers.DefaultTimerSupplier);
assertTrue(mgr.getHistogramSupplier() instanceof MetricSuppliers.DefaultHistogramSupplier);
Reservoir rsv = ((MetricSuppliers.DefaultTimerSupplier)mgr.getTimerSupplier()).getReservoir();
assertTrue(rsv instanceof UniformReservoir);
rsv = ((MetricSuppliers.DefaultHistogramSupplier)mgr.getHistogramSupplier()).getReservoir();
assertTrue(rsv instanceof SlidingTimeWindowReservoir);
}
@Test
public void shouldCacheSnapshot() {
Reservoir reservoir = new HdrBuilder().neverResetReservoir().buildReservoir();
reservoir.update(10);
reservoir.update(20);
Snapshot firstSnapshot = reservoir.getSnapshot();
reservoir.update(30);
reservoir.update(40);
Snapshot secondSnapshot = reservoir.getSnapshot();
assertNotSame(firstSnapshot, secondSnapshot);
assertEquals(10, secondSnapshot.getMin());
assertEquals(40, secondSnapshot.getMax());
reservoir.update(9);
reservoir.update(60);
Snapshot thirdSnapshot = reservoir.getSnapshot();
assertNotSame(secondSnapshot, thirdSnapshot);
assertEquals(9, thirdSnapshot.getMin());
assertEquals(60, thirdSnapshot.getMax());
}
@Test
public void shouldCacheSnapshot() {
Reservoir reservoir = new HdrBuilder().resetReservoirOnSnapshot().buildReservoir();
reservoir.update(10);
reservoir.update(20);
Snapshot firstSnapshot = reservoir.getSnapshot();
reservoir.update(30);
reservoir.update(40);
Snapshot secondSnapshot = reservoir.getSnapshot();
assertNotSame(firstSnapshot, secondSnapshot);
assertEquals(30, secondSnapshot.getMin());
assertEquals(40, secondSnapshot.getMax());
reservoir.update(50);
reservoir.update(60);
Snapshot thirdSnapshot = reservoir.getSnapshot();
assertNotSame(secondSnapshot, thirdSnapshot);
assertEquals(50, thirdSnapshot.getMin());
assertEquals(60, thirdSnapshot.getMax());
}
@Test
public void testSkipBigValues() {
Reservoir reservoir = new HdrBuilder().withHighestTrackableValue(100, OverflowResolver.SKIP).buildReservoir();
reservoir.update(101);
Snapshot snapshot = reservoir.getSnapshot();
assertEquals(0, snapshot.getMax());
reservoir.update(100);
snapshot = reservoir.getSnapshot();
assertEquals(100, snapshot.getMax());
reservoir.update(99);
snapshot = reservoir.getSnapshot();
assertEquals(99, snapshot.getMin());
}
@Test
public void testReduceBigValuesToMax() {
Reservoir reservoir = new HdrBuilder().withHighestTrackableValue(100, OverflowResolver.REDUCE_TO_HIGHEST_TRACKABLE).buildReservoir();
reservoir.update(101);
Snapshot snapshot = reservoir.getSnapshot();
assertEquals(100, snapshot.getMax());
reservoir.update(100);
snapshot = reservoir.getSnapshot();
assertEquals(100, snapshot.getMax());
reservoir.update(99);
snapshot = reservoir.getSnapshot();
assertEquals(99, snapshot.getMin());
}
private void registerMetrics(Class<?> bean, Executable executable) {
MetricResolver.Of<Counted> counted = resolver.counted(bean, executable);
if (counted.isPresent())
registry.counter(counted.metricName());
MetricResolver.Of<ExceptionMetered> exceptionMetered = resolver.exceptionMetered(bean, executable);
if (exceptionMetered.isPresent())
registry.meter(exceptionMetered.metricName());
MetricResolver.Of<Metered> metered = resolver.metered(bean, executable);
if (metered.isPresent())
registry.meter(metered.metricName());
MetricResolver.Of<Timed> timed = resolver.timed(bean, executable);
if (timed.isPresent()) {
extension.<BiFunction<String, Class<? extends Metric>, Optional<Reservoir>>>getParameter(ReservoirFunction)
.flatMap(function -> function.apply(timed.metricName(), Timer.class))
.map(reservoir -> registry.timer(timed.metricName(), () -> new Timer(reservoir)))
.orElseGet(() -> registry.timer(timed.metricName()));
}
}
@Test
public void testBasic() throws Exception {
double maxDelta = 0.00000000001;
Reservoir reservoir = new TDigestReservoir();
assertEquals(0, reservoir.size());
reservoir.update(100);
reservoir.update(101);
reservoir.update(300);
assertEquals(3, reservoir.size());
assertEquals(3, reservoir.getSnapshot().size());
assertEquals(100, reservoir.getSnapshot().getMin());
assertEquals(300, reservoir.getSnapshot().getMax());
assertEquals(101, reservoir.getSnapshot().getMedian(), maxDelta);
assertEquals(101, reservoir.getSnapshot().getValue(0.5), maxDelta);
assertEquals(501.0 / 3, reservoir.getSnapshot().getMean(), maxDelta);
}
public SemanticMetricRegistry(
final ConcurrentMap<MetricId, Metric> metrics,
final Supplier<Reservoir> defaultReservoirSupplier
) {
this.metrics = metrics;
this.listeners = new CopyOnWriteArrayList<SemanticMetricRegistryListener>();
this.defaultReservoirSupplier = defaultReservoirSupplier;
}
public ReservoirWithTtl(
final Reservoir delegate,
final int ttlSeconds,
final int minimumRate,
final Supplier<Instant> now) {
this.delegate = delegate;
this.now = now;
this.ttlSeconds = ttlSeconds;
final int bufferSize = ttlSeconds * minimumRate;
this.valueBuffer = new OverwritingFixedConcurrentRingBuffer<>(DUMMY, bufferSize);
}
public static SemanticMetricBuilder<Timer> timerWithReservoir(
final Supplier<Reservoir> reservoirSupplier) {
return new SemanticMetricBuilder<Timer>() {
@Override
public Timer newMetric() {
return new Timer(reservoirSupplier.get());
}
@Override
public boolean isInstance(final Metric metric) {
return Timer.class.isInstance(metric);
}
};
}
public static SemanticMetricBuilder<Histogram> histogramWithReservoir(
final Supplier<Reservoir> reservoirSupplier) {
return new SemanticMetricBuilder<Histogram>() {
@Override
public Histogram newMetric() {
return new Histogram(reservoirSupplier.get());
}
@Override
public boolean isInstance(final Metric metric) {
return Histogram.class.isInstance(metric);
}
};
}
@Test
public void testDefaults() throws Exception {
NodeConfig cfg = loadNodeConfig();
SolrMetricManager mgr = new SolrMetricManager(cfg.getSolrResourceLoader(), cfg.getMetricsConfig());
assertTrue(mgr.getCounterSupplier() instanceof MetricSuppliers.DefaultCounterSupplier);
assertTrue(mgr.getMeterSupplier() instanceof MetricSuppliers.DefaultMeterSupplier);
assertTrue(mgr.getTimerSupplier() instanceof MetricSuppliers.DefaultTimerSupplier);
assertTrue(mgr.getHistogramSupplier() instanceof MetricSuppliers.DefaultHistogramSupplier);
Clock clk = ((MetricSuppliers.DefaultTimerSupplier)mgr.getTimerSupplier()).clk;
assertTrue(clk instanceof Clock.UserTimeClock);
Reservoir rsv = ((MetricSuppliers.DefaultTimerSupplier)mgr.getTimerSupplier()).getReservoir();
assertTrue(rsv instanceof ExponentiallyDecayingReservoir);
}
@Test(timeout = 32000)
public void testThatConcurrentThreadsNotHung() throws InterruptedException {
Reservoir reservoir = new HdrBuilder()
.resetReservoirPeriodically(Duration.ofSeconds(1))
.buildReservoir();
HistogramUtil.runInParallel(reservoir, TimeUnit.SECONDS.toMillis(30));
}
@Test(timeout = 32000)
public void testThatConcurrentThreadsNotHungWithThreeChunks() throws InterruptedException {
Reservoir reservoir = new HdrBuilder()
.resetReservoirPeriodicallyByChunks(Duration.ofSeconds(3), 3)
.buildReservoir();
HistogramUtil.runInParallel(reservoir, TimeUnit.SECONDS.toMillis(30));
}
public static void runInParallel(Reservoir reservoir, long durationMillis) throws InterruptedException {
AtomicReference<Throwable> errorRef = new AtomicReference<>();
Thread[] threads = new Thread[Runtime.getRuntime().availableProcessors() * 2];
long start = System.currentTimeMillis();
final CountDownLatch latch = new CountDownLatch(threads.length);
for (int i = 0; i < threads.length; i++) {
threads[i] = new Thread(() -> {
try {
// update reservoir 100 times and take snapshot on each cycle
while (errorRef.get() == null && System.currentTimeMillis() - start < durationMillis) {
for (int j = 1; j <= 10; j++) {
reservoir.update(ThreadLocalRandom.current().nextInt(j));
}
reservoir.getSnapshot();
}
} catch (Exception e){
e.printStackTrace();
errorRef.set(e);
} finally {
latch.countDown();
}
});
threads[i].start();
}
latch.await();
//latch.await(duration.toMillis() + 4000, TimeUnit.MILLISECONDS);
if (latch.getCount() > 0) {
throw new IllegalStateException("" + latch.getCount() + " was not completed");
}
if (errorRef.get() != null) {
throw new RuntimeException(errorRef.get());
}
}
@Test
public void testSmartSnapshotCalculation() {
double[] predefinedPercentiles = {0.5, 0.6, 0.75, 0.9, 0.95, 0.98, 0.99, 0.999};
Reservoir reservoir = new HdrBuilder().withPredefinedPercentiles(predefinedPercentiles).buildReservoir();
Snapshot snapshot = snapshotTaker.apply(reservoir);
Histogram hdrHistogram = createEquivalentHistogram();
assertEquals(hdrHistogram.getStdDeviation(), snapshot.getStdDev());
assertEquals(hdrHistogram.getMinValue(), snapshot.getMin());
assertEquals(hdrHistogram.getMean(), snapshot.getMean());
assertEquals(hdrHistogram.getValueAtPercentile(50.0), (long) snapshot.getValue(0.42)); // do not defined percentile should be rounded up to first defined
assertEquals(hdrHistogram.getValueAtPercentile(50.0), (long) snapshot.getMedian());
assertEquals(hdrHistogram.getMaxValue(), snapshot.getMax());
assertEquals(hdrHistogram.getValueAtPercentile(60.0), (long) snapshot.getValue(0.6));
assertEquals(hdrHistogram.getValueAtPercentile(75.0), (long) snapshot.get75thPercentile());
assertEquals(hdrHistogram.getValueAtPercentile(90.0), (long) snapshot.getValue(0.8)); // do not defined percentile should be rounded up to first defined
assertEquals(hdrHistogram.getValueAtPercentile(90.0), (long) snapshot.getValue(0.9));
assertEquals(hdrHistogram.getValueAtPercentile(95.0), (long) snapshot.getValue(0.94)); // do not defined percentile should be rounded up to first defined
assertEquals(hdrHistogram.getValueAtPercentile(95.0), (long) snapshot.get95thPercentile());
assertEquals(hdrHistogram.getValueAtPercentile(98.0), (long) snapshot.get98thPercentile());
assertEquals(hdrHistogram.getValueAtPercentile(99.0), (long) snapshot.get99thPercentile());
assertEquals(hdrHistogram.getValueAtPercentile(99.9), (long) snapshot.get999thPercentile());
assertEquals(hdrHistogram.getMaxValue(), (long) snapshot.getValue(0.9999));
assertEquals(predefinedPercentiles.length, snapshot.size());
assertTrue(Arrays.equals(
snapshot.getValues(),
new long[] {
hdrHistogram.getValueAtPercentile(50.0),
hdrHistogram.getValueAtPercentile(60.0),
hdrHistogram.getValueAtPercentile(75.0),
hdrHistogram.getValueAtPercentile(90.0),
hdrHistogram.getValueAtPercentile(95.0),
hdrHistogram.getValueAtPercentile(98.0),
hdrHistogram.getValueAtPercentile(99.0),
hdrHistogram.getValueAtPercentile(99.9),
}
));
}
@Test
public void testFullSnapshotCalculation() {
Reservoir reservoir = new HdrBuilder().withoutSnapshotOptimization().buildReservoir();
Snapshot snapshot = snapshotTaker.apply(reservoir);
Histogram hdrHistogram = createEquivalentHistogram();
assertEquals(hdrHistogram.getStdDeviation(), snapshot.getStdDev());
assertEquals(hdrHistogram.getMinValue(), snapshot.getMin());
assertEquals(hdrHistogram.getMean(), snapshot.getMean());
assertEquals(hdrHistogram.getValueAtPercentile(50.0), (long) snapshot.getMedian());
assertEquals(hdrHistogram.getMaxValue(), snapshot.getMax());
assertEquals(hdrHistogram.getValueAtPercentile(60.0), (long) snapshot.getValue(0.6));
assertEquals(hdrHistogram.getValueAtPercentile(75.0), (long) snapshot.get75thPercentile());
assertEquals(hdrHistogram.getValueAtPercentile(80.0), (long) snapshot.getValue(0.8));
assertEquals(hdrHistogram.getValueAtPercentile(90.0), (long) snapshot.getValue(0.9));
assertEquals(hdrHistogram.getValueAtPercentile(94.0), (long) snapshot.getValue(0.94));
assertEquals(hdrHistogram.getValueAtPercentile(95.0), (long) snapshot.get95thPercentile());
assertEquals(hdrHistogram.getValueAtPercentile(98.0), (long) snapshot.get98thPercentile());
assertEquals(hdrHistogram.getValueAtPercentile(99.0), (long) snapshot.get99thPercentile());
assertEquals(hdrHistogram.getValueAtPercentile(99.9), (long) snapshot.get999thPercentile());
assertEquals(hdrHistogram.getTotalCount(), snapshot.size());
int i = 0;
long[] values = snapshot.getValues();
for (HistogramIterationValue value : hdrHistogram.recordedValues()) {
assertEquals(value.getValueIteratedTo(), values[i++]);
}
}
@Test
public void testPassThruBigValues2() {
Reservoir reservoir = new HdrBuilder()
.withHighestTrackableValue(100, OverflowResolver.PASS_THRU)
.buildReservoir();
reservoir.update(101);
Snapshot snapshot = reservoir.getSnapshot();
assertEquals(101, snapshot.getMax());
}
@Test
public void testSmartSnapshotPrinting() {
Reservoir reservoir = new HdrBuilder().buildReservoir();
Snapshot snapshot = snapshotTaker.apply(reservoir);
System.out.println(snapshot);
snapshot.dump(new ByteArrayOutputStream());
}
@Test
public void testFullSnapshotPrinting() {
Reservoir reservoir = new HdrBuilder().withoutSnapshotOptimization().buildReservoir();
Snapshot snapshot = snapshotTaker.apply(reservoir);
System.out.println(snapshot);
snapshot.dump(new ByteArrayOutputStream());
}
@Test
public void testGetTaggedHistogram() {
tags.put("a", "b");
tags.put("c", "d");
String name = "foo";
TaggedHistogram counter = registry.taggedHistogram(mock(Reservoir.class), name, tags);
Map<String, String> searchTags = new HashMap<String, String>();
searchTags.put("a", "b");
TaggedHistogram actual = registry.getTaggedHistogram("foo", searchTags);
assertEquals(counter, actual);
}
@Test
public void testTaggedHistogram() {
tags.put("a", "b");
String name = "foo";
TaggedHistogram counter = registry.taggedHistogram(mock(Reservoir.class), name, tags);
assertEquals(1, registry.getHistograms().size());
String expected = name + TaggedMetricRegistry.delimiter + tags.hashCode();
for(Map.Entry<String, Histogram> entry : registry.getHistograms().entrySet()) {
assertEquals(expected, entry.getKey());
assertEquals(counter, entry.getValue());
TaggedHistogram actual = (TaggedHistogram) entry.getValue();
assertEquals(tags, actual.getTags());
}
}
/**
* Create a new {@link com.codahale.metrics.Histogram} instance. These constructors are
* not public in 2.2.0, so we use reflection to find them.
*/
public static Histogram newHistogram(Reservoir sample) {
try {
Constructor<?> ctor =
Histogram.class.getDeclaredConstructor(Reservoir.class);
ctor.setAccessible(true);
return (Histogram) ctor.newInstance(sample);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@Test
public void testBuilder() {
final MetricRegistry metricRegistry = Mockito.mock(MetricRegistry.class);
DataSource dataSource = Mockito.mock(DataSource.class);
PoolAdapterFactory<DataSource> poolAdapterFactory = Mockito.mock(PoolAdapterFactory.class);
ConnectionProxyFactory connectionProxyFactory = Mockito.mock(ConnectionProxyFactory.class);
Metrics metrics = Mockito.mock(Metrics.class);
PoolAdapter poolAdapter = Mockito.mock(PoolAdapter.class);
when(poolAdapterFactory.newInstance(any(ConfigurationProperties.class))).thenReturn(poolAdapter);
Configuration<DataSource> configuration = new Configuration.Builder<DataSource>(
"unique", dataSource, poolAdapterFactory)
.setConnectionProxyFactory(connectionProxyFactory)
.setJmxAutoStart(true)
.setJmxEnabled(true)
.setMetricLogReporterMillis(120)
.setMetricsFactory(new MetricsFactory() {
@Override
public Metrics newInstance(ConfigurationProperties configurationProperties) {
return new DropwizardMetrics(configurationProperties, metricRegistry, new ReservoirFactory() {
@Override
public Reservoir newInstance(Class<? extends Metric> metricClass, String metricName) {
return new ExponentiallyDecayingReservoir();
}
});
}
})
.build();
assertSame("unique", configuration.getUniqueName());
assertSame(connectionProxyFactory, configuration.getConnectionProxyFactory());
assertTrue(configuration.isJmxAutoStart());
assertTrue(configuration.isJmxEnabled());
assertEquals(120, configuration.getMetricLogReporterMillis());
assertSame(poolAdapter, configuration.getPoolAdapter());
assertSame(dataSource, configuration.getTargetDataSource());
}
@Test
public void testBuilder() {
final MetricRegistry metricRegistry = Mockito.mock(MetricRegistry.class);
DataSource dataSource = Mockito.mock(DataSource.class);
PoolAdapterFactory<DataSource> poolAdapterFactory = Mockito.mock(PoolAdapterFactory.class);
ConnectionProxyFactory connectionProxyFactory = Mockito.mock(ConnectionProxyFactory.class);
Metrics metrics = Mockito.mock(Metrics.class);
PoolAdapter poolAdapter = Mockito.mock(PoolAdapter.class);
when(poolAdapterFactory.newInstance(any(ConfigurationProperties.class))).thenReturn(poolAdapter);
Configuration<DataSource> configuration = new Configuration.Builder<DataSource>(
"unique", dataSource, poolAdapterFactory)
.setConnectionProxyFactory(connectionProxyFactory)
.setJmxAutoStart(true)
.setJmxEnabled(true)
.setMetricLogReporterMillis(120)
.setMetricsFactory(new MetricsFactory() {
@Override
public Metrics newInstance(ConfigurationProperties configurationProperties) {
return new CodahaleMetrics(configurationProperties, metricRegistry, new ReservoirFactory() {
@Override
public Reservoir newInstance(Class<? extends Metric> metricClass, String metricName) {
return new ExponentiallyDecayingReservoir();
}
});
}
})
.build();
assertSame("unique", configuration.getUniqueName());
assertSame(connectionProxyFactory, configuration.getConnectionProxyFactory());
assertTrue(configuration.isJmxAutoStart());
assertTrue(configuration.isJmxEnabled());
assertEquals(120, configuration.getMetricLogReporterMillis());
assertSame(poolAdapter, configuration.getPoolAdapter());
assertSame(dataSource, configuration.getTargetDataSource());
}
public static Reservoir getConfiguredReservoir(FluoConfiguration config) {
String clazz = config.getString(FluoConfigurationImpl.METRICS_RESERVOIR_PROP,
HdrHistogramResetOnSnapshotReservoir.class.getName());
try {
return Class.forName(clazz).asSubclass(Reservoir.class).newInstance();
} catch (InstantiationException | IllegalAccessException | ClassNotFoundException e) {
throw new IllegalStateException(e);
}
}
/**
* Exposed for testing.
* @param clock the {@link Clock} to use for the {@link CachedGauge}.
* @param reservoir the {@link Reservoir} to use for the histogram.
* @param timeoutMs the timeout for the value stored in the cache in milliseconds. After this time has passed, a new
* value of the histogram at {@code quantile} will be calculated.
* @param quantile the quantile of the histogram to cache.
*/
CachedHistogram(Clock clock, Reservoir reservoir, long timeoutMs, double quantile) {
super(reservoir);
cache = new CachedGauge<Double>(clock, timeoutMs, TimeUnit.MILLISECONDS) {
@Override
protected Double loadValue() {
return getSnapshot().getValue(quantile);
}
};
}
/**
* Test caching behavior.
*/
@Test
public void testCache() {
AtomicInteger snapshotCalls = new AtomicInteger(0);
MockClock clock = new MockClock();
Reservoir reservoir = new ExponentiallyDecayingReservoir();
CachedHistogram histogram = new CachedHistogram(clock, reservoir, TimeUnit.SECONDS.toMillis(1), 0.50) {
@Override
public Snapshot getSnapshot() {
// count number of calls to test caching
snapshotCalls.getAndIncrement();
return super.getSnapshot();
}
};
long value = 2;
double epsilon = 0.01;
histogram.update(value);
// getSnapshot should be called the first time
assertEquals(value, histogram.getCachedValue(), epsilon);
assertEquals(1, snapshotCalls.get());
// the cached value should be used and getSnapshot should not be called.
assertEquals(value, histogram.getCachedValue(), epsilon);
assertEquals(1, snapshotCalls.get());
// after progressing time, the cached value should expire and getSnapshot should be called
clock.tick(1);
assertEquals(value, histogram.getCachedValue(), epsilon);
assertEquals(2, snapshotCalls.get());
}