diff --git a/core/benchmark-tests.txt b/core/benchmark-tests.txt index e3651a8..cf77b78 100644 --- a/core/benchmark-tests.txt +++ b/core/benchmark-tests.txt @@ -1,9 +1,35 @@ -Benchmark Mode Cnt Score Error Units -ScopeImplBenchmark.scopeReportingBenchmark thrpt 10 1385.711 ± 55.291 ops/ms -ScopeImplBenchmark.scopeReportingBenchmark:·async thrpt NaN --- -ScopeImplBenchmark.scopeReportingBenchmark:·gc.alloc.rate thrpt 10 ≈ 10⁻⁴ MB/sec -ScopeImplBenchmark.scopeReportingBenchmark:·gc.alloc.rate.norm thrpt 10 ≈ 10⁻⁴ B/op -ScopeImplBenchmark.scopeReportingBenchmark:·gc.count thrpt 10 ≈ 0 counts -ScopeImplBenchmark.scopeReportingBenchmark:·threads.alive thrpt 10 5.800 ± 0.637 threads -ScopeImplBenchmark.scopeReportingBenchmark:·threads.daemon thrpt 10 4.000 ± 0.001 threads -ScopeImplBenchmark.scopeReportingBenchmark:·threads.started thrpt 10 26.000 threads +Benchmark Mode Cnt Score Error Units +ScopeImplBenchmark.scopeReportingBenchmark thrpt 10 1345.606 ± 129.913 ops/ms +ScopeImplBenchmark.scopeReportingBenchmark:·async thrpt NaN --- +ScopeImplBenchmark.scopeReportingBenchmark:·gc.alloc.rate thrpt 10 ≈ 10⁻⁴ MB/sec +ScopeImplBenchmark.scopeReportingBenchmark:·gc.alloc.rate.norm thrpt 10 ≈ 10⁻⁴ B/op +ScopeImplBenchmark.scopeReportingBenchmark:·gc.count thrpt 10 ≈ 0 counts +ScopeImplBenchmark.scopeReportingBenchmark:·threads.alive thrpt 10 5.800 ± 0.637 threads +ScopeImplBenchmark.scopeReportingBenchmark:·threads.daemon thrpt 10 4.000 ± 0.001 threads +ScopeImplBenchmark.scopeReportingBenchmark:·threads.started thrpt 10 26.000 threads +ScopeImplBenchmark.scopeTaggedBenchmark thrpt 10 2440.881 ± 73.028 ops/ms +ScopeImplBenchmark.scopeTaggedBenchmark:·async thrpt NaN --- +ScopeImplBenchmark.scopeTaggedBenchmark:·gc.alloc.rate thrpt 10 1947.506 ± 58.647 MB/sec +ScopeImplBenchmark.scopeTaggedBenchmark:·gc.alloc.rate.norm thrpt 10 880.000 ± 0.001 B/op +ScopeImplBenchmark.scopeTaggedBenchmark:·gc.churn.G1_Eden_Space thrpt 10 1940.575 ± 57.874 MB/sec +ScopeImplBenchmark.scopeTaggedBenchmark:·gc.churn.G1_Eden_Space.norm thrpt 10 876.878 ± 5.246 B/op +ScopeImplBenchmark.scopeTaggedBenchmark:·gc.churn.G1_Old_Gen thrpt 10 0.168 ± 0.001 MB/sec +ScopeImplBenchmark.scopeTaggedBenchmark:·gc.churn.G1_Old_Gen.norm thrpt 10 0.076 ± 0.002 B/op +ScopeImplBenchmark.scopeTaggedBenchmark:·gc.count thrpt 10 1736.000 counts +ScopeImplBenchmark.scopeTaggedBenchmark:·gc.time thrpt 10 1772.000 ms +ScopeImplBenchmark.scopeTaggedBenchmark:·threads.alive thrpt 10 5.800 ± 0.637 threads +ScopeImplBenchmark.scopeTaggedBenchmark:·threads.daemon thrpt 10 4.000 ± 0.001 threads +ScopeImplBenchmark.scopeTaggedBenchmark:·threads.started thrpt 10 26.000 threads +ScopeImplConcurrent.hotkeyLockContention thrpt 10 1.430 ± 0.218 ops/ms +ScopeImplConcurrent.hotkeyLockContention:·async thrpt NaN --- +ScopeImplConcurrent.hotkeyLockContention:·gc.alloc.rate thrpt 10 1244.444 ± 187.772 MB/sec +ScopeImplConcurrent.hotkeyLockContention:·gc.alloc.rate.norm thrpt 10 960144.037 ± 0.011 B/op +ScopeImplConcurrent.hotkeyLockContention:·gc.churn.G1_Eden_Space thrpt 10 1237.763 ± 186.894 MB/sec +ScopeImplConcurrent.hotkeyLockContention:·gc.churn.G1_Eden_Space.norm thrpt 10 954972.637 ± 4468.064 B/op +ScopeImplConcurrent.hotkeyLockContention:·gc.churn.G1_Old_Gen thrpt 10 0.166 ± 0.001 MB/sec +ScopeImplConcurrent.hotkeyLockContention:·gc.churn.G1_Old_Gen.norm thrpt 10 129.528 ± 18.631 B/op +ScopeImplConcurrent.hotkeyLockContention:·gc.count thrpt 10 1564.000 counts +ScopeImplConcurrent.hotkeyLockContention:·gc.time thrpt 10 1336.000 ms +ScopeImplConcurrent.hotkeyLockContention:·threads.alive thrpt 10 5.800 ± 0.637 threads +ScopeImplConcurrent.hotkeyLockContention:·threads.daemon thrpt 10 4.000 ± 0.001 threads +ScopeImplConcurrent.hotkeyLockContention:·threads.started thrpt 10 26.000 threads diff --git a/core/build.gradle b/core/build.gradle index cf49331..c13b98c 100644 --- a/core/build.gradle +++ b/core/build.gradle @@ -21,6 +21,11 @@ description = 'Interfaces and utilities to report metrics to M3' apply from: 'jmhFixtures.gradle' +dependencies { + // https://mvnrepository.com/artifact/nl.jqno.equalsverifier/equalsverifier + testImplementation("nl.jqno.equalsverifier:equalsverifier:3.13") +} + sourceSets { jmh { java.srcDirs = ['src/jmh/java'] @@ -57,9 +62,8 @@ task runJmhTests(type: JavaExec, dependsOn: jmhClasses) { // NOTE: For this to work you need to make sure that async-profiler's library is either // - Available in LD_LIBRARY_PATH (Linux), DYLD_LIBRARY_PATH (Mac) // - Available in '-Djava.library.path' - // - Explicitly specified with 'async:libPath=' + // - Explicitly specified in pprof arg, the value 'async:libPath=' args '-prof', project.properties.get('bprof', 'async:event=cpu;direction=forward;output=flamegraph') - args '-bm', project.properties.get('bm', 'thrpt') args '-t', project.properties.get('bthreads', '1') args 'com.uber.m3.tally.' + project.properties.get('benchclass', '') diff --git a/core/gradle.properties b/core/gradle.properties new file mode 100644 index 0000000..9ec38ec --- /dev/null +++ b/core/gradle.properties @@ -0,0 +1,3 @@ +#bprof=async:event=cpu;direction=forward;output=flamegraph;dir=profile-results;libPath= +#benchclass=ScopeImplBenchmark.* +#output=benchmark-new.txt diff --git a/core/src/jmh/java/com/uber/m3/tally/ScopeImplBenchmark.java b/core/src/jmh/java/com/uber/m3/tally/ScopeImplBenchmark.java index 5844536..31ee6f4 100644 --- a/core/src/jmh/java/com/uber/m3/tally/ScopeImplBenchmark.java +++ b/core/src/jmh/java/com/uber/m3/tally/ScopeImplBenchmark.java @@ -30,6 +30,7 @@ import org.openjdk.jmh.annotations.Setup; import org.openjdk.jmh.annotations.State; import org.openjdk.jmh.annotations.TearDown; +import org.openjdk.jmh.infra.Blackhole; import java.util.Random; import java.util.concurrent.TimeUnit; @@ -41,6 +42,8 @@ public class ScopeImplBenchmark { private static final DurationBuckets EXPONENTIAL_BUCKETS = DurationBuckets.linear(Duration.ofMillis(1), Duration.ofMillis(10), 128); + private static final ImmutableMap TAGS_STRING_MAP = ImmutableMap.of("tag1", "value1", "tag2", "value2", "tag3", "value3"); + private static final String[] COUNTER_NAMES = { "first-counter", "second-counter", @@ -70,6 +73,11 @@ public void scopeReportingBenchmark(BenchmarkState state) { state.scope.reportLoopIteration(); } + @Benchmark + public void scopeTaggedBenchmark(Blackhole blackhole, BenchmarkState state) { + blackhole.consume(state.scope.tagged(TAGS_STRING_MAP)); + } + @State(org.openjdk.jmh.annotations.Scope.Benchmark) public static class BenchmarkState { diff --git a/core/src/jmh/java/com/uber/m3/tally/ScopeImplConcurrent.java b/core/src/jmh/java/com/uber/m3/tally/ScopeImplConcurrent.java index 7579a23..4e4411a 100644 --- a/core/src/jmh/java/com/uber/m3/tally/ScopeImplConcurrent.java +++ b/core/src/jmh/java/com/uber/m3/tally/ScopeImplConcurrent.java @@ -25,23 +25,25 @@ import org.openjdk.jmh.annotations.*; import org.openjdk.jmh.infra.Blackhole; +import java.util.List; import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; +import java.util.stream.Stream; @BenchmarkMode(Mode.Throughput) @OutputTimeUnit(TimeUnit.MILLISECONDS) @Fork(value = 2, jvmArgsAppend = { "-server", "-XX:+UseG1GC" }) public class ScopeImplConcurrent { - private static final String[] KEYS = new String[]{ - " ", "0", "@", "P", - }; + private static final List SCOPE_KEYS = + Stream.of(" ", "0", "@", "P").map(prefix -> new ScopeKey(prefix, null)).collect(Collectors.toList()); @Benchmark public void hotkeyLockContention(Blackhole bh, BenchmarkState state) { ImmutableMap common = new ImmutableMap.Builder().build(); for (int i = 0; i < 10000; i++) { - for (String key : KEYS) { - Scope scope = state.scope.computeSubscopeIfAbsent("prefix", key, common); + for (ScopeKey scopeKey : SCOPE_KEYS) { + Scope scope = state.scope.computeSubscopeIfAbsent("prefix", scopeKey, common); assert scope != null; bh.consume(scope); } @@ -60,8 +62,8 @@ public void setup() { .reporter(new TestStatsReporter()) .reportEvery(Duration.MAX_VALUE); - for (String key : KEYS) { - scope.computeSubscopeIfAbsent("prefix", key, new ImmutableMap.Builder().build()); + for (ScopeKey scopeKey : SCOPE_KEYS) { + scope.computeSubscopeIfAbsent("prefix", scopeKey, new ImmutableMap.Builder().build()); } } diff --git a/core/src/main/java/com/uber/m3/tally/ScopeImpl.java b/core/src/main/java/com/uber/m3/tally/ScopeImpl.java index 1f93594..70e269f 100644 --- a/core/src/main/java/com/uber/m3/tally/ScopeImpl.java +++ b/core/src/main/java/com/uber/m3/tally/ScopeImpl.java @@ -23,11 +23,9 @@ import com.uber.m3.util.ImmutableMap; import javax.annotation.Nullable; -import java.util.Arrays; import java.util.Collection; import java.util.Map; import java.util.Optional; -import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.ScheduledExecutorService; @@ -90,7 +88,7 @@ public Timer timer(String name) { @Override public Histogram histogram(String name, @Nullable Buckets buckets) { return histograms.computeIfAbsent(name, ignored -> - // NOTE: This will called at most once + // NOTE: This will be called at most once new HistogramImpl( this, fullyQualifiedName(name), @@ -152,35 +150,8 @@ void report(StatsReporter reporter) { // Serializes a map to generate a key for a prefix/map combination // Non-generic EMPTY ImmutableMap will never contain any elements - @SuppressWarnings("unchecked") - static String keyForPrefixedStringMap(String prefix, ImmutableMap stringMap) { - if (prefix == null) { - prefix = ""; - } - - if (stringMap == null) { - stringMap = ImmutableMap.EMPTY; - } - - Set keySet = stringMap.keySet(); - String[] sortedKeys = keySet.toArray(new String[keySet.size()]); - Arrays.sort(sortedKeys); - - StringBuilder keyBuffer = new StringBuilder(prefix.length() + sortedKeys.length * 20); - keyBuffer.append(prefix); - keyBuffer.append("+"); - - for (int i = 0; i < sortedKeys.length; i++) { - keyBuffer.append(sortedKeys[i]); - keyBuffer.append("="); - keyBuffer.append(stringMap.get(sortedKeys[i])); - - if (i != sortedKeys.length - 1) { - keyBuffer.append(","); - } - } - - return keyBuffer.toString(); + static ScopeKey keyForPrefixedStringMap(String prefix, ImmutableMap stringMap) { + return new ScopeKey(prefix, stringMap); } String fullyQualifiedName(String name) { @@ -202,61 +173,61 @@ public Snapshot snapshot() { for (Map.Entry counter : subscope.counters.entrySet()) { String name = subscope.fullyQualifiedName(counter.getKey()); - String id = keyForPrefixedStringMap(name, tags); + ScopeKey scopeKey = keyForPrefixedStringMap(name, tags); snap.counters().put( - id, - new CounterSnapshotImpl( - name, - tags, - counter.getValue().snapshot() - ) + scopeKey, + new CounterSnapshotImpl( + name, + tags, + counter.getValue().snapshot() + ) ); } for (Map.Entry gauge : subscope.gauges.entrySet()) { String name = subscope.fullyQualifiedName(gauge.getKey()); - String id = keyForPrefixedStringMap(name, tags); + ScopeKey scopeKey = keyForPrefixedStringMap(name, tags); snap.gauges().put( - id, - new GaugeSnapshotImpl( - name, - tags, - gauge.getValue().snapshot() - ) + scopeKey, + new GaugeSnapshotImpl( + name, + tags, + gauge.getValue().snapshot() + ) ); } for (Map.Entry timer : subscope.timers.entrySet()) { String name = subscope.fullyQualifiedName(timer.getKey()); - String id = keyForPrefixedStringMap(name, tags); + ScopeKey scopeKey = keyForPrefixedStringMap(name, tags); snap.timers().put( - id, - new TimerSnapshotImpl( - name, - tags, - timer.getValue().snapshot() - ) + scopeKey, + new TimerSnapshotImpl( + name, + tags, + timer.getValue().snapshot() + ) ); } for (Map.Entry histogram : subscope.histograms.entrySet()) { String name = subscope.fullyQualifiedName(histogram.getKey()); - String id = keyForPrefixedStringMap(name, tags); + ScopeKey scopeKey = keyForPrefixedStringMap(name, tags); snap.histograms().put( - id, - new HistogramSnapshotImpl( - name, - tags, - histogram.getValue().snapshotValues(), - histogram.getValue().snapshotDurations() - ) + scopeKey, + new HistogramSnapshotImpl( + name, + tags, + histogram.getValue().snapshotValues(), + histogram.getValue().snapshotDurations() + ) ); } } @@ -278,13 +249,13 @@ private Scope subScopeHelper(String prefix, Map tags) { ImmutableMap mergedTags = mapBuilder.build(); - String key = keyForPrefixedStringMap(prefix, mergedTags); + ScopeKey key = keyForPrefixedStringMap(prefix, mergedTags); return computeSubscopeIfAbsent(prefix, key, mergedTags); } // This method must only be called on unit tests or benchmarks - protected Scope computeSubscopeIfAbsent(String prefix, String key, ImmutableMap mergedTags) { + protected Scope computeSubscopeIfAbsent(String prefix, ScopeKey key, ImmutableMap mergedTags) { Scope scope = registry.subscopes.get(key); if (scope != null) { return scope; @@ -342,6 +313,7 @@ private void reportUncaughtException(Exception uncaughtException) { } static class Registry { - Map subscopes = new ConcurrentHashMap<>(); + Map subscopes = new ConcurrentHashMap<>(); } + } diff --git a/core/src/main/java/com/uber/m3/tally/ScopeKey.java b/core/src/main/java/com/uber/m3/tally/ScopeKey.java new file mode 100644 index 0000000..4185948 --- /dev/null +++ b/core/src/main/java/com/uber/m3/tally/ScopeKey.java @@ -0,0 +1,60 @@ +// 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 java.util.Objects; + +/** + * ScopeKey encapsulates the data to uniquely identify the {@link Scope}. + * This object overrides {@link #equals(Object)} and {@link #hashCode()} methods, so it can be used in Hash based {@link java.util.Map} implementations, to retrieve the corresponding {@link Scope}. + */ +public final class ScopeKey { + private final String prefix; + private final ImmutableMap tags; + + public ScopeKey(String prefix, ImmutableMap tags) { + this.prefix = prefix; + this.tags = tags; + } + + @Override + public int hashCode() { + return Objects.hash(prefix, tags); + } + + @Override + public boolean equals(Object otherObj) { + if (this == otherObj) { + return true; + } + if (otherObj == null) { + return false; + } + if (getClass() != otherObj.getClass()) { + return false; + } + ScopeKey other = (ScopeKey) otherObj; + return Objects.equals(this.prefix, other.prefix) && Objects.equals(this.tags, other.tags); + } + +} diff --git a/core/src/main/java/com/uber/m3/tally/Snapshot.java b/core/src/main/java/com/uber/m3/tally/Snapshot.java index 3d1a2da..cc23b30 100644 --- a/core/src/main/java/com/uber/m3/tally/Snapshot.java +++ b/core/src/main/java/com/uber/m3/tally/Snapshot.java @@ -30,23 +30,23 @@ public interface Snapshot { * Returns a {@link CounterSnapshot} of all {@link Counter} summations since last report execution. * @return a {@link CounterSnapshot} of all {@link Counter} summations since last report execution */ - Map counters(); + Map counters(); /** * Returns a {@link GaugeSnapshot} of {@link Gauge} last values since last report execution. * @return a {@link GaugeSnapshot} of {@link Gauge} last values since last report execution */ - Map gauges(); + Map gauges(); /** * Returns a {@link TimerSnapshot} of {@link Timer} values since last report execution. * @return a {@link TimerSnapshot} of {@link Timer} values since last report execution */ - Map timers(); + Map timers(); /** * Returns a {@link HistogramSnapshot} of {@link Histogram} samples since last report execution. * @return a {@link HistogramSnapshot} of {@link Histogram} samples since last report execution */ - Map histograms(); + Map histograms(); } diff --git a/core/src/main/java/com/uber/m3/tally/SnapshotImpl.java b/core/src/main/java/com/uber/m3/tally/SnapshotImpl.java index 9387ac9..d4e98ce 100644 --- a/core/src/main/java/com/uber/m3/tally/SnapshotImpl.java +++ b/core/src/main/java/com/uber/m3/tally/SnapshotImpl.java @@ -27,28 +27,28 @@ * Default implementation of a {@link Snapshot}. */ class SnapshotImpl implements Snapshot { - Map counters = new ConcurrentHashMap<>(); - Map gauges = new ConcurrentHashMap<>(); - Map timers = new ConcurrentHashMap<>(); - Map histograms = new ConcurrentHashMap<>(); + Map counters = new ConcurrentHashMap<>(); + Map gauges = new ConcurrentHashMap<>(); + Map timers = new ConcurrentHashMap<>(); + Map histograms = new ConcurrentHashMap<>(); @Override - public Map counters() { + public Map counters() { return counters; } @Override - public Map gauges() { + public Map gauges() { return gauges; } @Override - public Map timers() { + public Map timers() { return timers; } @Override - public Map histograms() { + public Map histograms() { return histograms; } } 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 d5dbd28..cb89303 100644 --- a/core/src/test/java/com/uber/m3/tally/ScopeImplTest.java +++ b/core/src/test/java/com/uber/m3/tally/ScopeImplTest.java @@ -20,20 +20,20 @@ package com.uber.m3.tally; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import com.uber.m3.util.Duration; +import com.uber.m3.util.ImmutableMap; import java.lang.Thread.UncaughtExceptionHandler; import java.util.HashMap; +import java.util.HashSet; import java.util.Map; +import java.util.Set; 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; - public class ScopeImplTest { private static final double EPSILON = 1e-10; private static final int REPORT_INTERVAL_MILLIS = 10; @@ -170,14 +170,15 @@ public void subscopes() { } catch (InterruptedException e) { System.err.println("Interrupted while sleeping! Let's continue anyway..."); } + final Set> actualCounters = new HashSet<>(); + actualCounters.add(reporter.nextCounter()); + actualCounters.add(reporter.nextCounter()); - TestStatsReporter.MetricStruct counter = reporter.nextCounter(); - assertEquals("root_counter", counter.getName()); - assertEquals(tags, counter.getTags()); + final Set> expectedCounters = new HashSet<>(); + expectedCounters.add(new TestStatsReporter.MetricStruct<>("root_counter", tags, 20L)); + expectedCounters.add(new TestStatsReporter.MetricStruct<>("inner.sub_counter", tags, 25L)); - counter = reporter.nextCounter(); - assertEquals("inner.sub_counter", counter.getName()); - assertEquals(tags, counter.getTags()); + assertEquals(expectedCounters, actualCounters); TestStatsReporter.MetricStruct gauge = reporter.nextGauge(); assertEquals("inner.deeper.sub_sub_gauge", gauge.getName()); @@ -226,27 +227,34 @@ public void snapshot() { Snapshot snapshot = ((ScopeImpl) rootScope).snapshot(); - Map counters = snapshot.counters(); + Map counters = snapshot.counters(); assertEquals(1, counters.size()); - assertEquals("snapshot-counter", counters.get("snapshot-counter+").name()); - assertEquals(null, counters.get("snapshot-counter+").tags()); + CounterSnapshot counterSnapshotActual = counters.get(ScopeImpl.keyForPrefixedStringMap("snapshot-counter", null)); + assertEquals("snapshot-counter", counterSnapshotActual.name()); + assertEquals(null, counterSnapshotActual.tags()); - Map gauges = snapshot.gauges(); + Map gauges = snapshot.gauges(); assertEquals(3, gauges.size()); - assertEquals("snapshot-gauge", gauges.get("snapshot-gauge+").name()); - assertEquals(null, 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()); - assertEquals(220, gauges.get("snapshot-gauge2+").value(), EPSILON); - assertEquals("snapshot-gauge3", gauges.get("snapshot-gauge3+").name()); - assertEquals(null, gauges.get("snapshot-gauge3+").tags()); - assertEquals(320, gauges.get("snapshot-gauge3+").value(), EPSILON); - - Map timers = snapshot.timers(); + GaugeSnapshot gaugeSnapshotActual = gauges.get(ScopeImpl.keyForPrefixedStringMap("snapshot-gauge", null)); + assertEquals("snapshot-gauge", gaugeSnapshotActual.name()); + assertEquals(null, 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(220, gaugeSnapshot2Actual.value(), EPSILON); + + GaugeSnapshot gaugeSnapshot3Actual = gauges.get(ScopeImpl.keyForPrefixedStringMap("snapshot-gauge3", null)); + assertEquals("snapshot-gauge3", gaugeSnapshot3Actual.name()); + assertEquals(null, gaugeSnapshot3Actual.tags()); + assertEquals(320, gaugeSnapshot3Actual.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()); + TimerSnapshot timerSnapshotActual = timers.get(ScopeImpl.keyForPrefixedStringMap("snapshot-timer", null)); + assertEquals("snapshot-timer", timerSnapshotActual.name()); + assertEquals(null, timerSnapshotActual.tags()); } @Test(expected = IllegalArgumentException.class) diff --git a/core/src/test/java/com/uber/m3/tally/ScopeKeyTest.java b/core/src/test/java/com/uber/m3/tally/ScopeKeyTest.java new file mode 100644 index 0000000..b1ef3a2 --- /dev/null +++ b/core/src/test/java/com/uber/m3/tally/ScopeKeyTest.java @@ -0,0 +1,34 @@ +// 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 nl.jqno.equalsverifier.EqualsVerifier; +import org.junit.Test; + +/** + * Tests for {@link com.uber.m3.tally.ScopeKey} + **/ +public class ScopeKeyTest { + @Test + public void testEqualsAndHashCode() { + EqualsVerifier.forClass(ScopeKey.class).verify(); + } +} diff --git a/core/src/test/java/com/uber/m3/tally/TestStatsReporter.java b/core/src/test/java/com/uber/m3/tally/TestStatsReporter.java index ee91e1a..b8a1fed 100644 --- a/core/src/test/java/com/uber/m3/tally/TestStatsReporter.java +++ b/core/src/test/java/com/uber/m3/tally/TestStatsReporter.java @@ -24,6 +24,7 @@ import java.util.HashMap; import java.util.Map; +import java.util.Objects; import java.util.Queue; import java.util.concurrent.ConcurrentLinkedQueue; @@ -137,9 +138,9 @@ public Buckets getBuckets() { } static class MetricStruct { - private String name; - private Map tags; - private T value; + private final String name; + private final Map tags; + private final T value; MetricStruct(String name, Map tags, T value) { this.name = name; @@ -158,5 +159,22 @@ Map getTags() { T getValue() { return value; } + + @Override + public boolean equals(Object otherObj) { + if (this == otherObj) { + return true; + } + if (otherObj == null || getClass() != otherObj.getClass()) { + return false; + } + MetricStruct other = (MetricStruct) otherObj; + return Objects.equals(name, other.name) && Objects.equals(tags, other.tags) && Objects.equals(value, other.value); + } + + @Override + public int hashCode() { + return Objects.hash(name, tags, value); + } } }