Skip to content

Commit

Permalink
Add TestScope (#120)
Browse files Browse the repository at this point in the history
This commit plucks select changes from #69 and #43
to bring uber-java/tally on par with uber-go/tally.

It adds a `TestScope` class which allows users of the library to get a
`Snapshot` of all the metrics emitted by the scope. Users can then assert if
certain metrics were created.
  • Loading branch information
zmanji authored Dec 18, 2023
1 parent 4a7bba5 commit 774a8c1
Show file tree
Hide file tree
Showing 8 changed files with 297 additions and 9 deletions.
70 changes: 70 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,70 @@
// Copyright (c) 2023 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 Capabilities capabilities() {
return CapableOf.NONE;
}

@Override
public void flush() {

}

@Override
public void close() {

}

@Override
public void reportCounter(String name, Map<String, String> tags, long value) {

}

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

}

@Override
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) {

}

@Override
public void reportHistogramDurationSamples(String name, Map<String, String> tags, Buckets buckets, Duration bucketLowerBound, Duration bucketUpperBound, long samples) {

}
}
12 changes: 10 additions & 2 deletions core/src/main/java/com/uber/m3/tally/ScopeImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
/**
* Default {@link Scope} implementation.
*/
class ScopeImpl implements Scope {
class ScopeImpl implements Scope, TestScope {
private StatsReporter reporter;
private String prefix;
private String separator;
Expand Down Expand Up @@ -163,13 +163,21 @@ String fullyQualifiedName(String name) {
}

/**
* Returns a {@link Snapshot} of this {@link Scope}.
* Snapshot returns a copy of all values since the last report execution
* This is an expensive operation and should only be used for testing purposes.
*
* @return a {@link Snapshot} of this {@link Scope}
*/
@Override
public Snapshot snapshot() {
Snapshot snap = new SnapshotImpl();

for (ScopeImpl subscope : registry.subscopes.values()) {
ImmutableMap<String, String> tags = new ImmutableMap.Builder<String, String>()
.putAll(this.tags)
.putAll(subscope.tags)
.build();

for (Map.Entry<String, CounterImpl> counter : subscope.counters.entrySet()) {
String name = subscope.fullyQualifiedName(counter.getKey());

Expand Down
4 changes: 2 additions & 2 deletions core/src/main/java/com/uber/m3/tally/ScopeKey.java
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@ public final class ScopeKey {
private final ImmutableMap<String, String> tags;

public ScopeKey(String prefix, ImmutableMap<String, String> tags) {
this.prefix = prefix;
this.tags = tags;
this.prefix = (prefix == null) ? "" : prefix;
this.tags = (tags == null) ? ImmutableMap.EMPTY : tags;
}

@Override
Expand Down
58 changes: 58 additions & 0 deletions core/src/main/java/com/uber/m3/tally/TestScope.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
// Copyright (c) 2023 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 java.util.Map;

/**
* TestScope is a metrics collector that has no reporting, ensuring that
* all emitted values have a given prefix or set of tags.
*/
public interface TestScope extends Scope {

/**
* Creates a new TestScope that adds the ability to take snapshots of
* metrics emitted to it.
*/
static TestScope create() {
return new RootScopeBuilder()
.reporter(new NullStatsReporter())
.build();
}

/**
* Creates a new TestScope with given prefix/tags that adds the ability to
* take snapshots of metrics emitted to it.
*/
static TestScope create(String prefix, Map<String, String> tags) {
return new RootScopeBuilder()
.prefix(prefix)
.tags(tags)
.reporter(new NullStatsReporter())
.build();
}

/**
* Snapshot returns a copy of all values since the last report execution
* This is an expensive operation and should only be used for testing purposes.
*/
Snapshot snapshot();
}
4 changes: 4 additions & 0 deletions core/src/main/java/com/uber/m3/util/ImmutableMap.java
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,10 @@ public Builder<K, V> put(K key, V value) {
}

public Builder<K, V> putAll(Map<K, V> otherMap) {
if (otherMap == null) {
return this;
}

map.putAll(otherMap);

return this;
Expand Down
38 changes: 38 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,38 @@
// Copyright (c) 2023 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());
}
}

10 changes: 5 additions & 5 deletions core/src/test/java/com/uber/m3/tally/ScopeImplTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -231,30 +231,30 @@ public void snapshot() {
assertEquals(1, counters.size());
CounterSnapshot counterSnapshotActual = counters.get(ScopeImpl.keyForPrefixedStringMap("snapshot-counter", null));
assertEquals("snapshot-counter", counterSnapshotActual.name());
assertEquals(null, counterSnapshotActual.tags());
assertEquals(ImmutableMap.EMPTY, counterSnapshotActual.tags());

Map<ScopeKey, GaugeSnapshot> gauges = snapshot.gauges();
assertEquals(3, gauges.size());
GaugeSnapshot gaugeSnapshotActual = gauges.get(ScopeImpl.keyForPrefixedStringMap("snapshot-gauge", null));
assertEquals("snapshot-gauge", gaugeSnapshotActual.name());
assertEquals(null, gaugeSnapshotActual.tags());
assertEquals(ImmutableMap.EMPTY, gaugeSnapshotActual.tags());
assertEquals(120, gaugeSnapshotActual.value(), EPSILON);

GaugeSnapshot gaugeSnapshot2Actual = gauges.get(ScopeImpl.keyForPrefixedStringMap("snapshot-gauge2", null));
assertEquals("snapshot-gauge2", gaugeSnapshot2Actual.name());
assertEquals(null, gaugeSnapshot2Actual.tags());
assertEquals(ImmutableMap.EMPTY, gaugeSnapshot2Actual.tags());
assertEquals(220, gaugeSnapshot2Actual.value(), EPSILON);

GaugeSnapshot gaugeSnapshot3Actual = gauges.get(ScopeImpl.keyForPrefixedStringMap("snapshot-gauge3", null));
assertEquals("snapshot-gauge3", gaugeSnapshot3Actual.name());
assertEquals(null, gaugeSnapshot3Actual.tags());
assertEquals(ImmutableMap.EMPTY, gaugeSnapshot3Actual.tags());
assertEquals(320, gaugeSnapshot3Actual.value(), EPSILON);

Map<ScopeKey, TimerSnapshot> timers = snapshot.timers();
assertEquals(1, timers.size());
TimerSnapshot timerSnapshotActual = timers.get(ScopeImpl.keyForPrefixedStringMap("snapshot-timer", null));
assertEquals("snapshot-timer", timerSnapshotActual.name());
assertEquals(null, timerSnapshotActual.tags());
assertEquals(ImmutableMap.EMPTY, timerSnapshotActual.tags());
}

@Test(expected = IllegalArgumentException.class)
Expand Down
110 changes: 110 additions & 0 deletions core/src/test/java/com/uber/m3/tally/TestScopeTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
// Copyright (c) 2023 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.ImmutableMap;
import org.junit.Test;

import java.util.Map;

import static org.hamcrest.CoreMatchers.instanceOf;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertThat;

public class TestScopeTest {

@Test
public void testCreate() {
TestScope testScope = TestScope.create();
assertNotNull(testScope);
assertThat(testScope, instanceOf(Scope.class));

assertNotNull(testScope.capabilities());
assertFalse(testScope.capabilities().reporting());
assertFalse(testScope.capabilities().tagging());

ImmutableMap<String, String> tags = ImmutableMap.of("key", "value");

testScope.tagged(tags).counter("counter").inc(1);

Snapshot snapshot = testScope.snapshot();
assertNotNull(snapshot);

Map<ScopeKey, CounterSnapshot> counters = snapshot.counters();
assertNotNull(counters);
assertEquals(1, counters.size());

CounterSnapshot counterSnapshot = counters.get(new ScopeKey("counter", tags));
assertNotNull(counterSnapshot);

assertEquals("counter", counterSnapshot.name());
assertEquals(tags, counterSnapshot.tags());
assertEquals(1, counterSnapshot.value());
}

@Test
public void createWithPrefixAndTags() {
Map<String, String> tags = ImmutableMap.of("key", "value");
TestScope testScope = TestScope.create("prefix", tags);
testScope.tagged(ImmutableMap.of("other_key", "other_value")).counter("counter").inc(1);

Snapshot snapshot = testScope.snapshot();
assertNotNull(snapshot);

Map<ScopeKey, CounterSnapshot> counters = snapshot.counters();
assertNotNull(counters);
assertEquals(1, counters.size());

ImmutableMap<String, String> totalTags = ImmutableMap.of("key", "value", "other_key", "other_value");
CounterSnapshot counterSnapshot = counters.get(new ScopeKey("prefix.counter", totalTags));

assertNotNull(counterSnapshot);
assertEquals("prefix.counter", counterSnapshot.name());
assertEquals(totalTags, counterSnapshot.tags());
assertEquals(1, counterSnapshot.value());
}

@Test
public void testCreateWithTagsAndSubscope() {
ImmutableMap<String, String> tags = ImmutableMap.of("key", "value");
TestScope testScope = TestScope.create("", tags);

ImmutableMap<String, String> subScopeTags = ImmutableMap.of("key", "other_value");
testScope.tagged(subScopeTags).subScope("subscope").counter("counter").inc(1);

Snapshot snapshot = testScope.snapshot();
assertNotNull(snapshot);

Map<ScopeKey, CounterSnapshot> counters = snapshot.counters();
assertNotNull(counters);
assertEquals(1, counters.size());

CounterSnapshot counterSnapshot = counters.get(new ScopeKey("subscope.counter", subScopeTags));
assertNotNull(counterSnapshot);

assertEquals("subscope.counter", counterSnapshot.name());
assertEquals(subScopeTags, counterSnapshot.tags());
assertEquals(1, counterSnapshot.value());
}
}

0 comments on commit 774a8c1

Please sign in to comment.