Skip to content

Commit 774a8c1

Browse files
authored
Add TestScope (#120)
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.
1 parent 4a7bba5 commit 774a8c1

File tree

8 files changed

+297
-9
lines changed

8 files changed

+297
-9
lines changed
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
// Copyright (c) 2023 Uber Technologies, Inc.
2+
//
3+
// Permission is hereby granted, free of charge, to any person obtaining a copy
4+
// of this software and associated documentation files (the "Software"), to deal
5+
// in the Software without restriction, including without limitation the rights
6+
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7+
// copies of the Software, and to permit persons to whom the Software is
8+
// furnished to do so, subject to the following conditions:
9+
//
10+
// The above copyright notice and this permission notice shall be included in
11+
// all copies or substantial portions of the Software.
12+
//
13+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14+
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15+
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16+
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17+
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18+
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19+
// THE SOFTWARE.
20+
21+
package com.uber.m3.tally;
22+
23+
import com.uber.m3.util.Duration;
24+
25+
import java.util.Map;
26+
27+
/**
28+
* NullStatsReporter is a noop implementation of StatsReporter.
29+
*/
30+
public class NullStatsReporter implements StatsReporter {
31+
@Override
32+
public Capabilities capabilities() {
33+
return CapableOf.NONE;
34+
}
35+
36+
@Override
37+
public void flush() {
38+
39+
}
40+
41+
@Override
42+
public void close() {
43+
44+
}
45+
46+
@Override
47+
public void reportCounter(String name, Map<String, String> tags, long value) {
48+
49+
}
50+
51+
@Override
52+
public void reportGauge(String name, Map<String, String> tags, double value) {
53+
54+
}
55+
56+
@Override
57+
public void reportTimer(String name, Map<String, String> tags, Duration interval) {
58+
59+
}
60+
61+
@Override
62+
public void reportHistogramValueSamples(String name, Map<String, String> tags, Buckets buckets, double bucketLowerBound, double bucketUpperBound, long samples) {
63+
64+
}
65+
66+
@Override
67+
public void reportHistogramDurationSamples(String name, Map<String, String> tags, Buckets buckets, Duration bucketLowerBound, Duration bucketUpperBound, long samples) {
68+
69+
}
70+
}

core/src/main/java/com/uber/m3/tally/ScopeImpl.java

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333
/**
3434
* Default {@link Scope} implementation.
3535
*/
36-
class ScopeImpl implements Scope {
36+
class ScopeImpl implements Scope, TestScope {
3737
private StatsReporter reporter;
3838
private String prefix;
3939
private String separator;
@@ -163,13 +163,21 @@ String fullyQualifiedName(String name) {
163163
}
164164

165165
/**
166-
* Returns a {@link Snapshot} of this {@link Scope}.
166+
* Snapshot returns a copy of all values since the last report execution
167+
* This is an expensive operation and should only be used for testing purposes.
168+
*
167169
* @return a {@link Snapshot} of this {@link Scope}
168170
*/
171+
@Override
169172
public Snapshot snapshot() {
170173
Snapshot snap = new SnapshotImpl();
171174

172175
for (ScopeImpl subscope : registry.subscopes.values()) {
176+
ImmutableMap<String, String> tags = new ImmutableMap.Builder<String, String>()
177+
.putAll(this.tags)
178+
.putAll(subscope.tags)
179+
.build();
180+
173181
for (Map.Entry<String, CounterImpl> counter : subscope.counters.entrySet()) {
174182
String name = subscope.fullyQualifiedName(counter.getKey());
175183

core/src/main/java/com/uber/m3/tally/ScopeKey.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,8 @@ public final class ScopeKey {
3333
private final ImmutableMap<String, String> tags;
3434

3535
public ScopeKey(String prefix, ImmutableMap<String, String> tags) {
36-
this.prefix = prefix;
37-
this.tags = tags;
36+
this.prefix = (prefix == null) ? "" : prefix;
37+
this.tags = (tags == null) ? ImmutableMap.EMPTY : tags;
3838
}
3939

4040
@Override
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
// Copyright (c) 2023 Uber Technologies, Inc.
2+
//
3+
// Permission is hereby granted, free of charge, to any person obtaining a copy
4+
// of this software and associated documentation files (the "Software"), to deal
5+
// in the Software without restriction, including without limitation the rights
6+
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7+
// copies of the Software, and to permit persons to whom the Software is
8+
// furnished to do so, subject to the following conditions:
9+
//
10+
// The above copyright notice and this permission notice shall be included in
11+
// all copies or substantial portions of the Software.
12+
//
13+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14+
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15+
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16+
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17+
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18+
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19+
// THE SOFTWARE.
20+
21+
package com.uber.m3.tally;
22+
23+
import java.util.Map;
24+
25+
/**
26+
* TestScope is a metrics collector that has no reporting, ensuring that
27+
* all emitted values have a given prefix or set of tags.
28+
*/
29+
public interface TestScope extends Scope {
30+
31+
/**
32+
* Creates a new TestScope that adds the ability to take snapshots of
33+
* metrics emitted to it.
34+
*/
35+
static TestScope create() {
36+
return new RootScopeBuilder()
37+
.reporter(new NullStatsReporter())
38+
.build();
39+
}
40+
41+
/**
42+
* Creates a new TestScope with given prefix/tags that adds the ability to
43+
* take snapshots of metrics emitted to it.
44+
*/
45+
static TestScope create(String prefix, Map<String, String> tags) {
46+
return new RootScopeBuilder()
47+
.prefix(prefix)
48+
.tags(tags)
49+
.reporter(new NullStatsReporter())
50+
.build();
51+
}
52+
53+
/**
54+
* Snapshot returns a copy of all values since the last report execution
55+
* This is an expensive operation and should only be used for testing purposes.
56+
*/
57+
Snapshot snapshot();
58+
}

core/src/main/java/com/uber/m3/util/ImmutableMap.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,10 @@ public Builder<K, V> put(K key, V value) {
223223
}
224224

225225
public Builder<K, V> putAll(Map<K, V> otherMap) {
226+
if (otherMap == null) {
227+
return this;
228+
}
229+
226230
map.putAll(otherMap);
227231

228232
return this;
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
// Copyright (c) 2023 Uber Technologies, Inc.
2+
//
3+
// Permission is hereby granted, free of charge, to any person obtaining a copy
4+
// of this software and associated documentation files (the "Software"), to deal
5+
// in the Software without restriction, including without limitation the rights
6+
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7+
// copies of the Software, and to permit persons to whom the Software is
8+
// furnished to do so, subject to the following conditions:
9+
//
10+
// The above copyright notice and this permission notice shall be included in
11+
// all copies or substantial portions of the Software.
12+
//
13+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14+
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15+
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16+
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17+
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18+
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19+
// THE SOFTWARE.
20+
21+
package com.uber.m3.tally;
22+
23+
import org.junit.Test;
24+
25+
import static org.junit.Assert.assertFalse;
26+
import static org.junit.Assert.assertNotNull;
27+
28+
public class NullStatsReporterTest {
29+
30+
@Test
31+
public void capabilities() {
32+
NullStatsReporter reporter = new NullStatsReporter();
33+
assertNotNull(reporter.capabilities());
34+
assertFalse(reporter.capabilities().reporting());
35+
assertFalse(reporter.capabilities().tagging());
36+
}
37+
}
38+

core/src/test/java/com/uber/m3/tally/ScopeImplTest.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -231,30 +231,30 @@ public void snapshot() {
231231
assertEquals(1, counters.size());
232232
CounterSnapshot counterSnapshotActual = counters.get(ScopeImpl.keyForPrefixedStringMap("snapshot-counter", null));
233233
assertEquals("snapshot-counter", counterSnapshotActual.name());
234-
assertEquals(null, counterSnapshotActual.tags());
234+
assertEquals(ImmutableMap.EMPTY, counterSnapshotActual.tags());
235235

236236
Map<ScopeKey, GaugeSnapshot> gauges = snapshot.gauges();
237237
assertEquals(3, gauges.size());
238238
GaugeSnapshot gaugeSnapshotActual = gauges.get(ScopeImpl.keyForPrefixedStringMap("snapshot-gauge", null));
239239
assertEquals("snapshot-gauge", gaugeSnapshotActual.name());
240-
assertEquals(null, gaugeSnapshotActual.tags());
240+
assertEquals(ImmutableMap.EMPTY, gaugeSnapshotActual.tags());
241241
assertEquals(120, gaugeSnapshotActual.value(), EPSILON);
242242

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

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

253253
Map<ScopeKey, TimerSnapshot> timers = snapshot.timers();
254254
assertEquals(1, timers.size());
255255
TimerSnapshot timerSnapshotActual = timers.get(ScopeImpl.keyForPrefixedStringMap("snapshot-timer", null));
256256
assertEquals("snapshot-timer", timerSnapshotActual.name());
257-
assertEquals(null, timerSnapshotActual.tags());
257+
assertEquals(ImmutableMap.EMPTY, timerSnapshotActual.tags());
258258
}
259259

260260
@Test(expected = IllegalArgumentException.class)
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
// Copyright (c) 2023 Uber Technologies, Inc.
2+
//
3+
// Permission is hereby granted, free of charge, to any person obtaining a copy
4+
// of this software and associated documentation files (the "Software"), to deal
5+
// in the Software without restriction, including without limitation the rights
6+
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7+
// copies of the Software, and to permit persons to whom the Software is
8+
// furnished to do so, subject to the following conditions:
9+
//
10+
// The above copyright notice and this permission notice shall be included in
11+
// all copies or substantial portions of the Software.
12+
//
13+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14+
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15+
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16+
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17+
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18+
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19+
// THE SOFTWARE.
20+
21+
package com.uber.m3.tally;
22+
23+
import com.uber.m3.util.ImmutableMap;
24+
import org.junit.Test;
25+
26+
import java.util.Map;
27+
28+
import static org.hamcrest.CoreMatchers.instanceOf;
29+
import static org.junit.Assert.assertEquals;
30+
import static org.junit.Assert.assertFalse;
31+
import static org.junit.Assert.assertNotNull;
32+
import static org.junit.Assert.assertThat;
33+
34+
public class TestScopeTest {
35+
36+
@Test
37+
public void testCreate() {
38+
TestScope testScope = TestScope.create();
39+
assertNotNull(testScope);
40+
assertThat(testScope, instanceOf(Scope.class));
41+
42+
assertNotNull(testScope.capabilities());
43+
assertFalse(testScope.capabilities().reporting());
44+
assertFalse(testScope.capabilities().tagging());
45+
46+
ImmutableMap<String, String> tags = ImmutableMap.of("key", "value");
47+
48+
testScope.tagged(tags).counter("counter").inc(1);
49+
50+
Snapshot snapshot = testScope.snapshot();
51+
assertNotNull(snapshot);
52+
53+
Map<ScopeKey, CounterSnapshot> counters = snapshot.counters();
54+
assertNotNull(counters);
55+
assertEquals(1, counters.size());
56+
57+
CounterSnapshot counterSnapshot = counters.get(new ScopeKey("counter", tags));
58+
assertNotNull(counterSnapshot);
59+
60+
assertEquals("counter", counterSnapshot.name());
61+
assertEquals(tags, counterSnapshot.tags());
62+
assertEquals(1, counterSnapshot.value());
63+
}
64+
65+
@Test
66+
public void createWithPrefixAndTags() {
67+
Map<String, String> tags = ImmutableMap.of("key", "value");
68+
TestScope testScope = TestScope.create("prefix", tags);
69+
testScope.tagged(ImmutableMap.of("other_key", "other_value")).counter("counter").inc(1);
70+
71+
Snapshot snapshot = testScope.snapshot();
72+
assertNotNull(snapshot);
73+
74+
Map<ScopeKey, CounterSnapshot> counters = snapshot.counters();
75+
assertNotNull(counters);
76+
assertEquals(1, counters.size());
77+
78+
ImmutableMap<String, String> totalTags = ImmutableMap.of("key", "value", "other_key", "other_value");
79+
CounterSnapshot counterSnapshot = counters.get(new ScopeKey("prefix.counter", totalTags));
80+
81+
assertNotNull(counterSnapshot);
82+
assertEquals("prefix.counter", counterSnapshot.name());
83+
assertEquals(totalTags, counterSnapshot.tags());
84+
assertEquals(1, counterSnapshot.value());
85+
}
86+
87+
@Test
88+
public void testCreateWithTagsAndSubscope() {
89+
ImmutableMap<String, String> tags = ImmutableMap.of("key", "value");
90+
TestScope testScope = TestScope.create("", tags);
91+
92+
ImmutableMap<String, String> subScopeTags = ImmutableMap.of("key", "other_value");
93+
testScope.tagged(subScopeTags).subScope("subscope").counter("counter").inc(1);
94+
95+
Snapshot snapshot = testScope.snapshot();
96+
assertNotNull(snapshot);
97+
98+
Map<ScopeKey, CounterSnapshot> counters = snapshot.counters();
99+
assertNotNull(counters);
100+
assertEquals(1, counters.size());
101+
102+
CounterSnapshot counterSnapshot = counters.get(new ScopeKey("subscope.counter", subScopeTags));
103+
assertNotNull(counterSnapshot);
104+
105+
assertEquals("subscope.counter", counterSnapshot.name());
106+
assertEquals(subScopeTags, counterSnapshot.tags());
107+
assertEquals(1, counterSnapshot.value());
108+
}
109+
}
110+

0 commit comments

Comments
 (0)