diff --git a/core/src/main/java/com/uber/m3/tally/NullStatsReporter.java b/core/src/main/java/com/uber/m3/tally/NullStatsReporter.java new file mode 100644 index 0000000..1ab76f8 --- /dev/null +++ b/core/src/main/java/com/uber/m3/tally/NullStatsReporter.java @@ -0,0 +1,83 @@ +// Copyright (c) 2020 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package com.uber.m3.tally; + +import com.uber.m3.util.Duration; + +import java.util.Map; + +/** + * NullStatsReporter is a noop implementation of StatsReporter. + */ +public class NullStatsReporter implements StatsReporter { + @Override + public void reportCounter(String name, Map tags, long value) { + // noop + } + + @Override + public void reportGauge(String name, Map tags, double value) { + // noop + } + + @Override + public void reportTimer(String name, Map tags, Duration interval) { + // noop + } + + @Override + public void reportHistogramValueSamples( + String name, Map tags, + Buckets buckets, + double bucketLowerBound, + double bucketUpperBound, + long samples + ) { + // noop + } + + @Override + public void reportHistogramDurationSamples( + String name, + Map tags, + Buckets buckets, + Duration bucketLowerBound, + Duration bucketUpperBound, long samples + ) { + // noop + } + + @Override + public Capabilities capabilities() { + return CapableOf.NONE; + } + + @Override + public void flush() { + // noop + } + + @Override + public void close() { + // noop + } +} diff --git a/core/src/main/java/com/uber/m3/tally/ScopeBuilder.java b/core/src/main/java/com/uber/m3/tally/ScopeBuilder.java index 09a00ff..01137a2 100644 --- a/core/src/main/java/com/uber/m3/tally/ScopeBuilder.java +++ b/core/src/main/java/com/uber/m3/tally/ScopeBuilder.java @@ -144,22 +144,29 @@ public Scope reportEvery(Duration interval) { } /** - * Creates a root scope and starts reporting with the specified interval + * Creates a root scope and starts reporting with the specified interval. + * No reporting is done for the {@link NullStatsReporter}. * @param interval duration between each report * @param uncaughtExceptionHandler an {@link java.lang.Thread.UncaughtExceptionHandler} that's * called when there's an uncaught exception in the report loop * @return the root scope created */ - public Scope reportEvery(Duration interval, - Thread.UncaughtExceptionHandler uncaughtExceptionHandler) { - if (interval.compareTo(Duration.ZERO) <= 0) { - throw new IllegalArgumentException("Reporting interval must be a positive Duration"); + public Scope reportEvery( + Duration interval, + Thread.UncaughtExceptionHandler uncaughtExceptionHandler + ) { + if (reporter instanceof NullStatsReporter) { + interval = Duration.ZERO; + } else if (interval.compareTo(Duration.ZERO) <= 0) { + throw new IllegalArgumentException("Reporting interval must be a non-negative Duration"); } ScopeImpl scope = build(); registry.subscopes.put(ScopeImpl.keyForPrefixedStringMap(prefix, tags), scope); - scheduler.scheduleWithFixedDelay(scope.new ReportLoop(uncaughtExceptionHandler), 0, interval.toMillis(), TimeUnit.MILLISECONDS); + if (interval.compareTo(Duration.ZERO) > 0) { + scheduler.scheduleWithFixedDelay(scope.new ReportLoop(uncaughtExceptionHandler), 0, interval.toMillis(), TimeUnit.MILLISECONDS); + } return scope; } diff --git a/core/src/test/java/com/uber/m3/tally/NullStatsReporterTest.java b/core/src/test/java/com/uber/m3/tally/NullStatsReporterTest.java new file mode 100644 index 0000000..47b4174 --- /dev/null +++ b/core/src/test/java/com/uber/m3/tally/NullStatsReporterTest.java @@ -0,0 +1,37 @@ +// Copyright (c) 2020 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package com.uber.m3.tally; + +import org.junit.Test; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; + +public class NullStatsReporterTest { + + @Test + public void capabilities() { + NullStatsReporter reporter = new NullStatsReporter(); + assertNotNull(reporter.capabilities()); + assertFalse(reporter.capabilities().reporting()); + assertFalse(reporter.capabilities().tagging()); + } +} diff --git a/core/src/test/java/com/uber/m3/tally/ScopeImplTest.java b/core/src/test/java/com/uber/m3/tally/ScopeImplTest.java index 89de860..eca2732 100644 --- a/core/src/test/java/com/uber/m3/tally/ScopeImplTest.java +++ b/core/src/test/java/com/uber/m3/tally/ScopeImplTest.java @@ -20,19 +20,19 @@ package com.uber.m3.tally; +import com.uber.m3.util.Duration; +import com.uber.m3.util.ImmutableMap; +import org.junit.Test; + import java.lang.Thread.UncaughtExceptionHandler; import java.util.HashMap; import java.util.Map; import java.util.concurrent.atomic.AtomicInteger; -import org.junit.Test; - -import com.uber.m3.util.Duration; -import com.uber.m3.util.ImmutableMap; - import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertSame; public class ScopeImplTest { private static final double EPSILON = 1e-10; @@ -54,21 +54,21 @@ public void metricCreation() { Counter sameCounter = scope.counter("new-counter"); // Should be the same Counter object and not a new instance - assertTrue(counter == sameCounter); + assertSame(counter, sameCounter); Gauge gauge = scope.gauge("new-gauge"); assertNotNull(gauge); Gauge sameGauge = scope.gauge("new-gauge"); // Should be the same Gauge object and not a new instance - assertTrue(gauge == sameGauge); + assertSame(gauge, sameGauge); Timer timer = scope.timer("new-timer"); assertNotNull(timer); Timer sameTimer = scope.timer("new-timer"); // Should be the same Timer object and not a new instance - assertTrue(timer == sameTimer); + assertSame(timer, sameTimer); Histogram histogram = scope.histogram( "new-histogram", @@ -78,7 +78,7 @@ public void metricCreation() { Histogram sameHistogram = scope.histogram("new-histogram", null); // Should be the same Histogram object and not a new instance - assertTrue(histogram == sameHistogram); + assertSame(histogram, sameHistogram); } @Test @@ -151,9 +151,9 @@ public void subscopes() { ImmutableMap additionalTags = new ImmutableMap.Builder(2) - .put("new_key", "new_val") - .put("baz", "quz") - .build(); + .put("new_key", "new_val") + .put("baz", "quz") + .build(); Scope taggedSubscope = rootScope.tagged(additionalTags); Timer taggedTimer = taggedSubscope.timer("tagged_timer"); taggedTimer.record(Duration.ofSeconds(6)); @@ -180,9 +180,9 @@ public void subscopes() { assertEquals("tagged_timer", timer.getName()); ImmutableMap expectedTags = new ImmutableMap.Builder(4) - .putAll(tags) - .putAll(additionalTags) - .build(); + .putAll(tags) + .putAll(additionalTags) + .build(); assertEquals(expectedTags, timer.getTags()); } @@ -222,28 +222,40 @@ public void snapshot() { Map counters = snapshot.counters(); assertEquals(1, counters.size()); assertEquals("snapshot-counter", counters.get("snapshot-counter+").name()); - assertEquals(null, counters.get("snapshot-counter+").tags()); + assertNull(counters.get("snapshot-counter+").tags()); Map gauges = snapshot.gauges(); assertEquals(3, gauges.size()); assertEquals("snapshot-gauge", gauges.get("snapshot-gauge+").name()); - assertEquals(null, gauges.get("snapshot-gauge+").tags()); + assertNull(gauges.get("snapshot-gauge+").tags()); assertEquals(120, gauges.get("snapshot-gauge+").value(), EPSILON); assertEquals("snapshot-gauge2", gauges.get("snapshot-gauge2+").name()); - assertEquals(null, gauges.get("snapshot-gauge2+").tags()); + assertNull(gauges.get("snapshot-gauge2+").tags()); assertEquals(220, gauges.get("snapshot-gauge2+").value(), EPSILON); assertEquals("snapshot-gauge3", gauges.get("snapshot-gauge3+").name()); - assertEquals(null, gauges.get("snapshot-gauge3+").tags()); + assertNull(gauges.get("snapshot-gauge3+").tags()); assertEquals(320, gauges.get("snapshot-gauge3+").value(), EPSILON); Map timers = snapshot.timers(); assertEquals(1, timers.size()); assertEquals("snapshot-timer", timers.get("snapshot-timer+").name()); - assertEquals(null, timers.get("snapshot-timer+").tags()); + assertNull(timers.get("snapshot-timer+").tags()); + } + + @Test + public void noopNullStatsReporter() { + new RootScopeBuilder().reporter(new NullStatsReporter()).reportEvery(Duration.ofSeconds(-10)); + new RootScopeBuilder().reporter(new NullStatsReporter()).reportEvery(Duration.ZERO); + new RootScopeBuilder().reporter(new NullStatsReporter()).reportEvery(Duration.ofSeconds(10)); + } + + @Test(expected = IllegalArgumentException.class) + public void zeroReportInterval() { + new RootScopeBuilder().reportEvery(Duration.ZERO); } @Test(expected = IllegalArgumentException.class) - public void nonPositiveReportInterval() { + public void negativeReportInterval() { new RootScopeBuilder().reportEvery(Duration.ofSeconds(-10)); } @@ -251,19 +263,11 @@ public void nonPositiveReportInterval() { public void exceptionInReportLoop() throws ScopeCloseException, InterruptedException { final AtomicInteger uncaghtExceptionReported = new AtomicInteger(); ThrowingStatsReporter reporter = new ThrowingStatsReporter(); - final UncaughtExceptionHandler uncaughtExceptionHandler = new UncaughtExceptionHandler() { - @Override - public void uncaughtException(Thread t, Throwable e) { - uncaghtExceptionReported.incrementAndGet(); - } - }; + final UncaughtExceptionHandler uncaughtExceptionHandler = (t, e) -> uncaghtExceptionReported.incrementAndGet(); - Scope scope = new RootScopeBuilder() + try (Scope scope = new RootScopeBuilder() .reporter(reporter) - .reportEvery(Duration.ofMillis(REPORT_INTERVAL_MILLIS), - uncaughtExceptionHandler); - - try { + .reportEvery(Duration.ofMillis(REPORT_INTERVAL_MILLIS), uncaughtExceptionHandler)) { scope.counter("hi").inc(1); Thread.sleep(SLEEP_MILLIS); @@ -276,15 +280,13 @@ public void uncaughtException(Thread t, Throwable e) { assertEquals(2, uncaghtExceptionReported.get()); assertEquals(2, reporter.getNumberOfReportedMetrics()); - } finally { - scope.close(); } } private static class ThrowingStatsReporter implements StatsReporter { private final AtomicInteger reported = new AtomicInteger(); - public int getNumberOfReportedMetrics() { + int getNumberOfReportedMetrics() { return reported.get(); } @@ -307,17 +309,27 @@ public void reportTimer(String name, Map tags, Duration interval } @Override - public void reportHistogramValueSamples(String name, Map tags, - Buckets buckets, double bucketLowerBound, - double bucketUpperBound, long samples) { + public void reportHistogramValueSamples( + String name, + Map tags, + Buckets buckets, + double bucketLowerBound, + double bucketUpperBound, + long samples + ) { reported.incrementAndGet(); throw new RuntimeException(); } @Override - public void reportHistogramDurationSamples(String name, Map tags, - Buckets buckets, Duration bucketLowerBound, - Duration bucketUpperBound, long samples) { + public void reportHistogramDurationSamples( + String name, + Map tags, + Buckets buckets, + Duration bucketLowerBound, + Duration bucketUpperBound, + long samples + ) { reported.incrementAndGet(); throw new RuntimeException(); }