From 5130e00e976aaf4ac18c5efae2cc653a7fe4389f Mon Sep 17 00:00:00 2001 From: Cristian Velazquez Date: Wed, 18 Jan 2023 10:39:06 -0800 Subject: [PATCH 01/10] Check scope existence lock free first (#110) --- core/build.gradle | 21 +++++--- core/jmhFixtures.gradle | 4 ++ .../uber/m3/tally/ScopeImplConcurrent.java | 53 +++++++++++++++++++ .../java/com/uber/m3/tally/ScopeImpl.java | 27 +++++++--- 4 files changed, 90 insertions(+), 15 deletions(-) create mode 100644 core/src/jmh/java/com/uber/m3/tally/ScopeImplConcurrent.java diff --git a/core/build.gradle b/core/build.gradle index d8d34dd..cf49331 100644 --- a/core/build.gradle +++ b/core/build.gradle @@ -42,8 +42,15 @@ task runJmhTests(type: JavaExec, dependsOn: jmhClasses) { args '-rff', resultFile // Profile using GC, Threading profilers - args '-prof', 'gc' - args '-prof', 'hs_thr' + if (project.properties.get('busegc', 'true') == 'true') { + args '-prof', 'gc' + args '-prof', 'hs_thr' + + // Force GC after every iterations, to make sure that one iteration + // doesn't affect the other one + args '-gc', 'true' + } + // Profile using async-profiling // @@ -51,11 +58,11 @@ task runJmhTests(type: JavaExec, dependsOn: jmhClasses) { // - Available in LD_LIBRARY_PATH (Linux), DYLD_LIBRARY_PATH (Mac) // - Available in '-Djava.library.path' // - Explicitly specified with 'async:libPath=' - args '-prof', 'async:event=cpu;direction=forward;output=flamegraph' + args '-prof', project.properties.get('bprof', 'async:event=cpu;direction=forward;output=flamegraph') - // Force GC after every iterations, to make sure that one iteration - // doesn't affect the other one - args '-gc', 'true' + args '-bm', project.properties.get('bm', 'thrpt') + args '-t', project.properties.get('bthreads', '1') + args 'com.uber.m3.tally.' + project.properties.get('benchclass', '') } -classes.finalizedBy(jmhClasses) \ No newline at end of file +classes.finalizedBy(jmhClasses) diff --git a/core/jmhFixtures.gradle b/core/jmhFixtures.gradle index 30c6bec..127030a 100644 --- a/core/jmhFixtures.gradle +++ b/core/jmhFixtures.gradle @@ -54,4 +54,8 @@ dependencies { jmhFixturesUsageCompile project(project.path) jmhFixturesCompile('org.openjdk.jmh:jmh-core:1.27') jmhFixturesCompile('org.openjdk.jmh:jmh-generator-annprocess:1.27') + + if (project.gradle.gradleVersion > '5') { + jmhAnnotationProcessor("org.openjdk.jmh:jmh-generator-annprocess:1.27") + } } diff --git a/core/src/jmh/java/com/uber/m3/tally/ScopeImplConcurrent.java b/core/src/jmh/java/com/uber/m3/tally/ScopeImplConcurrent.java new file mode 100644 index 0000000..9022950 --- /dev/null +++ b/core/src/jmh/java/com/uber/m3/tally/ScopeImplConcurrent.java @@ -0,0 +1,53 @@ +package com.uber.m3.tally; + +import com.uber.m3.util.Duration; +import com.uber.m3.util.ImmutableMap; +import org.openjdk.jmh.annotations.*; +import org.openjdk.jmh.infra.Blackhole; + +import java.util.concurrent.TimeUnit; + +@BenchmarkMode(Mode.Throughput) +@OutputTimeUnit(TimeUnit.MILLISECONDS) +@Fork(value = 2, jvmArgsAppend = { "-server", "-XX:+UseG1GC" }) +public class ScopeImplConcurrent { + private static String KEYS[] = new String[]{ + " ", "0", "@", "P", + }; + + @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(key, common); + assert scope != null; + bh.consume(scope); + } + } + } + + @State(org.openjdk.jmh.annotations.Scope.Benchmark) + public static class BenchmarkState { + + private ScopeImpl scope; + + @Setup + public void setup() { + this.scope = + (ScopeImpl) new RootScopeBuilder() + .reporter(new TestStatsReporter()) + .reportEvery(Duration.MAX_VALUE); + + for (String key : KEYS) { + scope.computeSubscopeIfAbsent(key, new ImmutableMap.Builder().build()); + } + } + + @TearDown + public void teardown() { + scope.close(); + } + } +} 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 8ac3c6b..6c35e38 100644 --- a/core/src/main/java/com/uber/m3/tally/ScopeImpl.java +++ b/core/src/main/java/com/uber/m3/tally/ScopeImpl.java @@ -101,6 +101,7 @@ public Histogram histogram(String name, @Nullable Buckets buckets) { ); } + @Override public Scope tagged(Map tags) { return subScopeHelper(prefix, tags); @@ -280,15 +281,25 @@ private Scope subScopeHelper(String prefix, Map tags) { String key = keyForPrefixedStringMap(prefix, mergedTags); + return computeSubscopeIfAbsent(key, mergedTags); + } + + // This method must only be called on unit tests or benchmarks + protected Scope computeSubscopeIfAbsent(String key, ImmutableMap mergedTags) { + Scope scope = registry.subscopes.get(key); + if (scope != null) { + return scope; + } + return registry.subscopes.computeIfAbsent( - key, - (k) -> new ScopeBuilder(scheduler, registry) - .reporter(reporter) - .prefix(prefix) - .separator(separator) - .tags(mergedTags) - .defaultBuckets(defaultBuckets) - .build() + key, + (k) -> new ScopeBuilder(scheduler, registry) + .reporter(reporter) + .prefix(prefix) + .separator(separator) + .tags(mergedTags) + .defaultBuckets(defaultBuckets) + .build() ); } From 946455dd62eaaf28ce5bba6ce18f36937557eec5 Mon Sep 17 00:00:00 2001 From: Justin Chan Date: Fri, 20 Jan 2023 11:00:22 -0500 Subject: [PATCH 02/10] Release v0.12.0 (#111) --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index a92047a..aa633d2 100644 --- a/build.gradle +++ b/build.gradle @@ -45,7 +45,7 @@ configurations.archives.artifacts.clear() allprojects { group = 'com.uber.m3' - version = '0.11.1' + version = '0.12.0' apply plugin: 'java' apply plugin: 'maven' From 894abd70c3bb15efa183ebbbd85c2223b6c7940b Mon Sep 17 00:00:00 2001 From: Justin Chan Date: Fri, 20 Jan 2023 11:07:56 -0500 Subject: [PATCH 03/10] Revert "Release v0.12.0 (#111)" (#112) This reverts commit 946455dd62eaaf28ce5bba6ce18f36937557eec5. --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index aa633d2..a92047a 100644 --- a/build.gradle +++ b/build.gradle @@ -45,7 +45,7 @@ configurations.archives.artifacts.clear() allprojects { group = 'com.uber.m3' - version = '0.12.0' + version = '0.11.1' apply plugin: 'java' apply plugin: 'maven' From ef251ac1574c88fea2a7122fa7c11115a393854e Mon Sep 17 00:00:00 2001 From: Cristian Velazquez Date: Tue, 24 Jan 2023 09:40:01 -0800 Subject: [PATCH 04/10] Fix shadowed prefix variable (#113) --- core/src/jmh/java/com/uber/m3/tally/ScopeImplConcurrent.java | 4 ++-- core/src/main/java/com/uber/m3/tally/ScopeImpl.java | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) 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 9022950..46b671a 100644 --- a/core/src/jmh/java/com/uber/m3/tally/ScopeImplConcurrent.java +++ b/core/src/jmh/java/com/uber/m3/tally/ScopeImplConcurrent.java @@ -21,7 +21,7 @@ public void hotkeyLockContention(Blackhole bh, BenchmarkState state) { for (int i = 0; i < 10000; i++) { for (String key : KEYS) { - Scope scope = state.scope.computeSubscopeIfAbsent(key, common); + Scope scope = state.scope.computeSubscopeIfAbsent("prefix", key, common); assert scope != null; bh.consume(scope); } @@ -41,7 +41,7 @@ public void setup() { .reportEvery(Duration.MAX_VALUE); for (String key : KEYS) { - scope.computeSubscopeIfAbsent(key, new ImmutableMap.Builder().build()); + scope.computeSubscopeIfAbsent("prefix", key, 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 6c35e38..25157fd 100644 --- a/core/src/main/java/com/uber/m3/tally/ScopeImpl.java +++ b/core/src/main/java/com/uber/m3/tally/ScopeImpl.java @@ -281,11 +281,11 @@ private Scope subScopeHelper(String prefix, Map tags) { String key = keyForPrefixedStringMap(prefix, mergedTags); - return computeSubscopeIfAbsent(key, mergedTags); + return computeSubscopeIfAbsent(prefix, key, mergedTags); } // This method must only be called on unit tests or benchmarks - protected Scope computeSubscopeIfAbsent(String key, ImmutableMap mergedTags) { + protected Scope computeSubscopeIfAbsent(String prefix, String key, ImmutableMap mergedTags) { Scope scope = registry.subscopes.get(key); if (scope != null) { return scope; From 5ed5fd976802a6454524fe48be736d1a59cc0496 Mon Sep 17 00:00:00 2001 From: Justin Chan Date: Tue, 24 Jan 2023 13:16:11 -0500 Subject: [PATCH 05/10] Fix checkstyle (#114) --- .../uber/m3/tally/ScopeImplConcurrent.java | 22 ++++++++++++++++++- .../java/com/uber/m3/tally/ScopeImpl.java | 17 +++++++------- 2 files changed, 29 insertions(+), 10 deletions(-) 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 46b671a..7579a23 100644 --- a/core/src/jmh/java/com/uber/m3/tally/ScopeImplConcurrent.java +++ b/core/src/jmh/java/com/uber/m3/tally/ScopeImplConcurrent.java @@ -1,3 +1,23 @@ +// 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; @@ -11,7 +31,7 @@ @OutputTimeUnit(TimeUnit.MILLISECONDS) @Fork(value = 2, jvmArgsAppend = { "-server", "-XX:+UseG1GC" }) public class ScopeImplConcurrent { - private static String KEYS[] = new String[]{ + private static final String[] KEYS = new String[]{ " ", "0", "@", "P", }; 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 25157fd..1f93594 100644 --- a/core/src/main/java/com/uber/m3/tally/ScopeImpl.java +++ b/core/src/main/java/com/uber/m3/tally/ScopeImpl.java @@ -101,7 +101,6 @@ public Histogram histogram(String name, @Nullable Buckets buckets) { ); } - @Override public Scope tagged(Map tags) { return subScopeHelper(prefix, tags); @@ -292,14 +291,14 @@ protected Scope computeSubscopeIfAbsent(String prefix, String key, ImmutableMap< } return registry.subscopes.computeIfAbsent( - key, - (k) -> new ScopeBuilder(scheduler, registry) - .reporter(reporter) - .prefix(prefix) - .separator(separator) - .tags(mergedTags) - .defaultBuckets(defaultBuckets) - .build() + key, + (k) -> new ScopeBuilder(scheduler, registry) + .reporter(reporter) + .prefix(prefix) + .separator(separator) + .tags(mergedTags) + .defaultBuckets(defaultBuckets) + .build() ); } From 23c39f9ec4d98c0e9311edd1063a78e1ca55a465 Mon Sep 17 00:00:00 2001 From: Justin Chan Date: Tue, 24 Jan 2023 13:18:08 -0500 Subject: [PATCH 06/10] Release v0.12.0 (#115) --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index a92047a..aa633d2 100644 --- a/build.gradle +++ b/build.gradle @@ -45,7 +45,7 @@ configurations.archives.artifacts.clear() allprojects { group = 'com.uber.m3' - version = '0.11.1' + version = '0.12.0' apply plugin: 'java' apply plugin: 'maven' From fa530e2390f2343cd5f9ef5f9293bc1f900ba408 Mon Sep 17 00:00:00 2001 From: Sairam Cherupally Date: Tue, 14 Feb 2023 01:05:09 +0530 Subject: [PATCH 07/10] Define a custom Scope Key for reducing memory allocations (#116) * save progress * revert to master * sync with master * add gradle files * gradle cleanup * add unit tests and cleanup code * self review * self review * review comments address * fix flaky test as pointed in review * add benchmark * self review * nit pick --- core/benchmark-tests.txt | 44 ++++++-- core/build.gradle | 8 +- core/gradle.properties | 3 + .../com/uber/m3/tally/ScopeImplBenchmark.java | 8 ++ .../uber/m3/tally/ScopeImplConcurrent.java | 16 +-- .../java/com/uber/m3/tally/ScopeImpl.java | 100 +++++++----------- .../main/java/com/uber/m3/tally/ScopeKey.java | 60 +++++++++++ .../main/java/com/uber/m3/tally/Snapshot.java | 8 +- .../java/com/uber/m3/tally/SnapshotImpl.java | 16 +-- .../java/com/uber/m3/tally/ScopeImplTest.java | 70 ++++++------ .../java/com/uber/m3/tally/ScopeKeyTest.java | 34 ++++++ .../com/uber/m3/tally/TestStatsReporter.java | 24 ++++- 12 files changed, 263 insertions(+), 128 deletions(-) create mode 100644 core/gradle.properties create mode 100644 core/src/main/java/com/uber/m3/tally/ScopeKey.java create mode 100644 core/src/test/java/com/uber/m3/tally/ScopeKeyTest.java 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); + } } } From 4a7bba5e3add45f5a1b7afd95d1c07bd33acd685 Mon Sep 17 00:00:00 2001 From: Sokol Andrey <2789641+SokolAndrey@users.noreply.github.com> Date: Mon, 13 Feb 2023 15:05:41 -0500 Subject: [PATCH 08/10] Release v0.13.0 (#117) --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index aa633d2..0ea9c04 100644 --- a/build.gradle +++ b/build.gradle @@ -45,7 +45,7 @@ configurations.archives.artifacts.clear() allprojects { group = 'com.uber.m3' - version = '0.12.0' + version = '0.13.0' apply plugin: 'java' apply plugin: 'maven' From 774a8c1953752179c5ba52accfa2ac1987137c8f Mon Sep 17 00:00:00 2001 From: Zameer Manji Date: Mon, 18 Dec 2023 17:44:11 -0500 Subject: [PATCH 09/10] Add TestScope (#120) This commit plucks select changes from uber-java/tally#69 and uber-java/tally#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. --- .../com/uber/m3/tally/NullStatsReporter.java | 70 +++++++++++ .../java/com/uber/m3/tally/ScopeImpl.java | 12 +- .../main/java/com/uber/m3/tally/ScopeKey.java | 4 +- .../java/com/uber/m3/tally/TestScope.java | 58 +++++++++ .../java/com/uber/m3/util/ImmutableMap.java | 4 + .../uber/m3/tally/NullStatsReporterTest.java | 38 ++++++ .../java/com/uber/m3/tally/ScopeImplTest.java | 10 +- .../java/com/uber/m3/tally/TestScopeTest.java | 110 ++++++++++++++++++ 8 files changed, 297 insertions(+), 9 deletions(-) create mode 100644 core/src/main/java/com/uber/m3/tally/NullStatsReporter.java create mode 100644 core/src/main/java/com/uber/m3/tally/TestScope.java create mode 100644 core/src/test/java/com/uber/m3/tally/NullStatsReporterTest.java create mode 100644 core/src/test/java/com/uber/m3/tally/TestScopeTest.java 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..9903a5e --- /dev/null +++ b/core/src/main/java/com/uber/m3/tally/NullStatsReporter.java @@ -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 tags, long value) { + + } + + @Override + public void reportGauge(String name, Map tags, double value) { + + } + + @Override + 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) { + + } + + @Override + public void reportHistogramDurationSamples(String name, Map tags, Buckets buckets, Duration bucketLowerBound, Duration bucketUpperBound, long samples) { + + } +} 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 70e269f..09e8377 100644 --- a/core/src/main/java/com/uber/m3/tally/ScopeImpl.java +++ b/core/src/main/java/com/uber/m3/tally/ScopeImpl.java @@ -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; @@ -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 tags = new ImmutableMap.Builder() + .putAll(this.tags) + .putAll(subscope.tags) + .build(); + for (Map.Entry counter : subscope.counters.entrySet()) { String name = subscope.fullyQualifiedName(counter.getKey()); diff --git a/core/src/main/java/com/uber/m3/tally/ScopeKey.java b/core/src/main/java/com/uber/m3/tally/ScopeKey.java index 4185948..cf2188e 100644 --- a/core/src/main/java/com/uber/m3/tally/ScopeKey.java +++ b/core/src/main/java/com/uber/m3/tally/ScopeKey.java @@ -33,8 +33,8 @@ public final class ScopeKey { private final ImmutableMap tags; public ScopeKey(String prefix, ImmutableMap tags) { - this.prefix = prefix; - this.tags = tags; + this.prefix = (prefix == null) ? "" : prefix; + this.tags = (tags == null) ? ImmutableMap.EMPTY : tags; } @Override diff --git a/core/src/main/java/com/uber/m3/tally/TestScope.java b/core/src/main/java/com/uber/m3/tally/TestScope.java new file mode 100644 index 0000000..af6c5b0 --- /dev/null +++ b/core/src/main/java/com/uber/m3/tally/TestScope.java @@ -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 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(); +} diff --git a/core/src/main/java/com/uber/m3/util/ImmutableMap.java b/core/src/main/java/com/uber/m3/util/ImmutableMap.java index 1fe4ec0..7aa7a54 100644 --- a/core/src/main/java/com/uber/m3/util/ImmutableMap.java +++ b/core/src/main/java/com/uber/m3/util/ImmutableMap.java @@ -223,6 +223,10 @@ public Builder put(K key, V value) { } public Builder putAll(Map otherMap) { + if (otherMap == null) { + return this; + } + map.putAll(otherMap); return this; 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..1141b40 --- /dev/null +++ b/core/src/test/java/com/uber/m3/tally/NullStatsReporterTest.java @@ -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()); + } +} + 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 cb89303..39a5e3a 100644 --- a/core/src/test/java/com/uber/m3/tally/ScopeImplTest.java +++ b/core/src/test/java/com/uber/m3/tally/ScopeImplTest.java @@ -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 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 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) diff --git a/core/src/test/java/com/uber/m3/tally/TestScopeTest.java b/core/src/test/java/com/uber/m3/tally/TestScopeTest.java new file mode 100644 index 0000000..ccfc3aa --- /dev/null +++ b/core/src/test/java/com/uber/m3/tally/TestScopeTest.java @@ -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 tags = ImmutableMap.of("key", "value"); + + testScope.tagged(tags).counter("counter").inc(1); + + Snapshot snapshot = testScope.snapshot(); + assertNotNull(snapshot); + + Map 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 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 counters = snapshot.counters(); + assertNotNull(counters); + assertEquals(1, counters.size()); + + ImmutableMap 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 tags = ImmutableMap.of("key", "value"); + TestScope testScope = TestScope.create("", tags); + + ImmutableMap subScopeTags = ImmutableMap.of("key", "other_value"); + testScope.tagged(subScopeTags).subScope("subscope").counter("counter").inc(1); + + Snapshot snapshot = testScope.snapshot(); + assertNotNull(snapshot); + + Map 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()); + } +} + From 749c486e81279bfadc02091e786d197aa9387d63 Mon Sep 17 00:00:00 2001 From: Sokol Andrey <2789641+SokolAndrey@users.noreply.github.com> Date: Thu, 21 Dec 2023 16:52:43 -0500 Subject: [PATCH 10/10] Add GitHub action to build java project Adding default configuration for Java with Gradle GitHub action --- .github/workflows/gradle.yml | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 .github/workflows/gradle.yml diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml new file mode 100644 index 0000000..393133a --- /dev/null +++ b/.github/workflows/gradle.yml @@ -0,0 +1,34 @@ +# This workflow uses actions that are not certified by GitHub. +# They are provided by a third-party and are governed by +# separate terms of service, privacy policy, and support +# documentation. +# This workflow will build a Java project with Gradle and cache/restore any dependencies to improve the workflow execution time +# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-java-with-gradle + +name: Java CI with Gradle + +on: + push: + branches: [ "master" ] + pull_request: + branches: [ "master" ] + +permissions: + contents: read + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + - name: Set up JDK 11 + uses: actions/setup-java@v3 + with: + java-version: '11' + distribution: 'temurin' + - name: Build with Gradle + uses: gradle/gradle-build-action@bd5760595778326ba7f1441bcf7e88b49de61a25 # v2.6.0 + with: + arguments: build