Skip to content

Commit

Permalink
Adding NullStatsReporter
Browse files Browse the repository at this point in the history
  • Loading branch information
ravirajj committed May 4, 2020
1 parent 8b78d2e commit 66fdfe0
Show file tree
Hide file tree
Showing 4 changed files with 187 additions and 48 deletions.
83 changes: 83 additions & 0 deletions core/src/main/java/com/uber/m3/tally/NullStatsReporter.java
Original file line number Diff line number Diff line change
@@ -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<String, String> tags, long value) {
// noop
}

@Override
public void reportGauge(String name, Map<String, String> tags, double value) {
// noop
}

@Override
public void reportTimer(String name, Map<String, String> tags, Duration interval) {
// noop
}

@Override
public void reportHistogramValueSamples(
String name, Map<String,
String> tags,
Buckets buckets,
double bucketLowerBound,
double bucketUpperBound,
long samples
) {
// noop
}

@Override
public void reportHistogramDurationSamples(
String name,
Map<String, String> 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
}
}
19 changes: 13 additions & 6 deletions core/src/main/java/com/uber/m3/tally/ScopeBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
37 changes: 37 additions & 0 deletions core/src/test/java/com/uber/m3/tally/NullStatsReporterTest.java
Original file line number Diff line number Diff line change
@@ -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());
}
}
96 changes: 54 additions & 42 deletions core/src/test/java/com/uber/m3/tally/ScopeImplTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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",
Expand All @@ -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
Expand Down Expand Up @@ -151,9 +151,9 @@ public void subscopes() {

ImmutableMap<String, String> additionalTags =
new ImmutableMap.Builder<String, String>(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));
Expand All @@ -180,9 +180,9 @@ public void subscopes() {
assertEquals("tagged_timer", timer.getName());
ImmutableMap<String, String> expectedTags =
new ImmutableMap.Builder<String, String>(4)
.putAll(tags)
.putAll(additionalTags)
.build();
.putAll(tags)
.putAll(additionalTags)
.build();
assertEquals(expectedTags, timer.getTags());
}

Expand Down Expand Up @@ -222,48 +222,52 @@ public void snapshot() {
Map<String, CounterSnapshot> 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<String, GaugeSnapshot> 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<String, TimerSnapshot> 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));
}

@Test
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);

Expand All @@ -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();
}

Expand All @@ -307,17 +309,27 @@ public void reportTimer(String name, Map<String, String> tags, Duration interval
}

@Override
public void reportHistogramValueSamples(String name, Map<String, String> tags,
Buckets buckets, double bucketLowerBound,
double bucketUpperBound, long samples) {
public void reportHistogramValueSamples(
String name,
Map<String, String> tags,
Buckets buckets,
double bucketLowerBound,
double bucketUpperBound,
long samples
) {
reported.incrementAndGet();
throw new RuntimeException();
}

@Override
public void reportHistogramDurationSamples(String name, Map<String, String> tags,
Buckets buckets, Duration bucketLowerBound,
Duration bucketUpperBound, long samples) {
public void reportHistogramDurationSamples(
String name,
Map<String, String> tags,
Buckets buckets,
Duration bucketLowerBound,
Duration bucketUpperBound,
long samples
) {
reported.incrementAndGet();
throw new RuntimeException();
}
Expand Down

0 comments on commit 66fdfe0

Please sign in to comment.