From 8004e472764dc28ef05a55f6ad6dd598a2a469d8 Mon Sep 17 00:00:00 2001 From: Stephan Schroevers Date: Thu, 3 Oct 2024 22:57:35 +0200 Subject: [PATCH 1/4] Skip redundant tag deduplication Both `KeyValues` and `Tags` unconditionally maintain the invariant that they wrap distinct key-value pairs. --- benchmarks/build.gradle | 3 + ...enerConfigurationResolveTagsBenchmark.java | 69 +++++++++++++++++++ gradle/libs.versions.toml | 2 + .../MicrometerMeterListenerConfiguration.java | 7 +- ...meterObservationListenerConfiguration.java | 7 +- .../reactor/core/publisher/FluxMetrics.java | 7 +- 6 files changed, 83 insertions(+), 12 deletions(-) create mode 100644 benchmarks/src/main/java/reactor/core/observability/micrometer/MicrometerMeterListenerConfigurationResolveTagsBenchmark.java diff --git a/benchmarks/build.gradle b/benchmarks/build.gradle index 3307226a59..0ab48e6491 100644 --- a/benchmarks/build.gradle +++ b/benchmarks/build.gradle @@ -13,15 +13,18 @@ configurations { dependencies { // Use the baseline to avoid using new APIs in the benchmarks compileOnly libs.reactor.perfBaseline.core + compileOnly libs.reactor.perfBaseline.coreMicrometer compileOnly libs.jsr305 implementation "org.openjdk.jmh:jmh-core:$jmhVersion" implementation libs.reactor.perfBaseline.extra, { exclude group: 'io.projectreactor', module: 'reactor-core' } + implementation platform(libs.micrometer.bom) annotationProcessor "org.openjdk.jmh:jmh-generator-annprocess:$jmhVersion" current project(':reactor-core') + current project(':reactor-core-micrometer') baseline libs.reactor.perfBaseline.core, { changing = true } diff --git a/benchmarks/src/main/java/reactor/core/observability/micrometer/MicrometerMeterListenerConfigurationResolveTagsBenchmark.java b/benchmarks/src/main/java/reactor/core/observability/micrometer/MicrometerMeterListenerConfigurationResolveTagsBenchmark.java new file mode 100644 index 0000000000..70168cc2af --- /dev/null +++ b/benchmarks/src/main/java/reactor/core/observability/micrometer/MicrometerMeterListenerConfigurationResolveTagsBenchmark.java @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2024 VMware Inc. or its affiliates, All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package reactor.core.observability.micrometer; + +import io.micrometer.core.instrument.Tags; +import java.util.concurrent.TimeUnit; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Level; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.OutputTimeUnit; +import org.openjdk.jmh.annotations.Param; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.Warmup; +import org.reactivestreams.Publisher; +import reactor.core.publisher.Mono; + +@BenchmarkMode({Mode.AverageTime}) +@Warmup(iterations = 5, time = 5, timeUnit = TimeUnit.SECONDS) +@Measurement(iterations = 5, time = 5, timeUnit = TimeUnit.SECONDS) +@Fork(value = 1) +@OutputTimeUnit(TimeUnit.NANOSECONDS) +@State(Scope.Benchmark) +public class MicrometerMeterListenerConfigurationResolveTagsBenchmark { + @Param({"1", "2", "5", "10"}) + private int distinctTagCount; + + @Param({"1", "2", "5", "10"}) + private int totalTagCount; + + private Publisher publisher; + + @Setup(Level.Iteration) + public void setup() { + publisher = addTags(Mono.empty(), distinctTagCount, totalTagCount); + } + + @SuppressWarnings("unused") + @Benchmark + public Tags measureThroughput() { + return MicrometerMeterListenerConfiguration.resolveTags(publisher, Tags.of("k", "v")); + } + + private static Mono addTags(Mono source, int distinct, int total) { + if (total == 0) { + return source; + } + + return addTags(source.tag("k-" + total % distinct, "v-" + total), distinct, total - 1); + } +} diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 20d0921609..32ea1015fa 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -9,6 +9,7 @@ # Baselines, should be updated on every release baseline-core-api = "3.7.0" baselinePerfCore = "3.7.0" +baselinePerfCoreMicrometer = "1.1.9" baselinePerfExtra = "3.5.2" # Other shared versions @@ -46,6 +47,7 @@ kotlin-stdlib = { module = "org.jetbrains.kotlin:kotlin-stdlib", version.ref = " reactiveStreams = { module = "org.reactivestreams:reactive-streams", version.ref = "reactiveStreams" } reactiveStreams-tck = { module = "org.reactivestreams:reactive-streams-tck", version.ref = "reactiveStreams" } reactor-perfBaseline-core = { module = "io.projectreactor:reactor-core", version.ref = "baselinePerfCore" } +reactor-perfBaseline-coreMicrometer = { module = "io.projectreactor:reactor-core-micrometer", version.ref = "baselinePerfCoreMicrometer" } reactor-perfBaseline-extra = { module = "io.projectreactor.addons:reactor-extra", version.ref = "baselinePerfExtra" } [plugins] diff --git a/reactor-core-micrometer/src/main/java/reactor/core/observability/micrometer/MicrometerMeterListenerConfiguration.java b/reactor-core-micrometer/src/main/java/reactor/core/observability/micrometer/MicrometerMeterListenerConfiguration.java index 6699700306..9de3f7f8fe 100644 --- a/reactor-core-micrometer/src/main/java/reactor/core/observability/micrometer/MicrometerMeterListenerConfiguration.java +++ b/reactor-core-micrometer/src/main/java/reactor/core/observability/micrometer/MicrometerMeterListenerConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2022-2024 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -93,9 +93,8 @@ static Tags resolveTags(Publisher source, Tags tags) { Scannable scannable = Scannable.from(source); if (scannable.isScanAvailable()) { - List discoveredTags = scannable.tagsDeduplicated() - .entrySet().stream() - .map(e -> Tag.of(e.getKey(), e.getValue())) + List discoveredTags = scannable.tags() + .map(t -> Tag.of(t.getT1(), t.getT2())) .collect(Collectors.toList()); return tags.and(discoveredTags); } diff --git a/reactor-core-micrometer/src/main/java/reactor/core/observability/micrometer/MicrometerObservationListenerConfiguration.java b/reactor-core-micrometer/src/main/java/reactor/core/observability/micrometer/MicrometerObservationListenerConfiguration.java index 0b87e210a9..337cbefd14 100644 --- a/reactor-core-micrometer/src/main/java/reactor/core/observability/micrometer/MicrometerObservationListenerConfiguration.java +++ b/reactor-core-micrometer/src/main/java/reactor/core/observability/micrometer/MicrometerObservationListenerConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2022-2024 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -70,9 +70,8 @@ static KeyValues resolveKeyValues(Publisher source, KeyValues tags) { Scannable scannable = Scannable.from(source); if (scannable.isScanAvailable()) { - List discoveredTags = scannable.tagsDeduplicated() - .entrySet().stream() - .map(e -> KeyValue.of(e.getKey(), e.getValue())) + List discoveredTags = scannable.tags() + .map(e -> KeyValue.of(e.getT1(), e.getT2())) .collect(Collectors.toList()); return tags.and(discoveredTags); } diff --git a/reactor-core/src/main/java/reactor/core/publisher/FluxMetrics.java b/reactor-core/src/main/java/reactor/core/publisher/FluxMetrics.java index 64704cc26c..395eb3001a 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/FluxMetrics.java +++ b/reactor-core/src/main/java/reactor/core/publisher/FluxMetrics.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018-2022 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2018-2024 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -295,9 +295,8 @@ static Tags resolveTags(Publisher source, Tags tags) { Scannable scannable = Scannable.from(source); if (scannable.isScanAvailable()) { - List discoveredTags = scannable.tagsDeduplicated() - .entrySet().stream() - .map(e -> Tag.of(e.getKey(), e.getValue())) + List discoveredTags = scannable.tags() + .map(t -> Tag.of(t.getT1(), t.getT2())) .collect(Collectors.toList()); return tags.and(discoveredTags); } From 842e7da6e9cef4d731ab15616c2afab08cfcfdb4 Mon Sep 17 00:00:00 2001 From: Stephan Schroevers Date: Fri, 4 Oct 2024 20:01:42 +0200 Subject: [PATCH 2/4] Address feedback --- ...eterListenerConfigurationResolveTagsBenchmark.java | 11 ++++++----- gradle/libs.versions.toml | 2 +- .../MicrometerMeterListenerConfiguration.java | 1 + .../MicrometerObservationListenerConfiguration.java | 1 + .../src/main/java/reactor/core/Scannable.java | 4 +++- .../main/java/reactor/core/publisher/FluxMetrics.java | 1 + 6 files changed, 13 insertions(+), 7 deletions(-) diff --git a/benchmarks/src/main/java/reactor/core/observability/micrometer/MicrometerMeterListenerConfigurationResolveTagsBenchmark.java b/benchmarks/src/main/java/reactor/core/observability/micrometer/MicrometerMeterListenerConfigurationResolveTagsBenchmark.java index 70168cc2af..94305d451d 100644 --- a/benchmarks/src/main/java/reactor/core/observability/micrometer/MicrometerMeterListenerConfigurationResolveTagsBenchmark.java +++ b/benchmarks/src/main/java/reactor/core/observability/micrometer/MicrometerMeterListenerConfigurationResolveTagsBenchmark.java @@ -40,16 +40,17 @@ @OutputTimeUnit(TimeUnit.NANOSECONDS) @State(Scope.Benchmark) public class MicrometerMeterListenerConfigurationResolveTagsBenchmark { - @Param({"1", "2", "5", "10"}) - private int distinctTagCount; - - @Param({"1", "2", "5", "10"}) - private int totalTagCount; + @Param({"1|1", "1|2", "1|5", "1|10", "2|2", "2|5", "2|10", "5|5", "5|10", "10|10"}) + private String testCase; private Publisher publisher; @Setup(Level.Iteration) public void setup() { + String[] arguments = testCase.split("\\|", -1); + int distinctTagCount = Integer.parseInt(arguments[0]); + int totalTagCount = Integer.parseInt(arguments[1]); + publisher = addTags(Mono.empty(), distinctTagCount, totalTagCount); } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 32ea1015fa..d1d3888c5c 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -9,7 +9,7 @@ # Baselines, should be updated on every release baseline-core-api = "3.7.0" baselinePerfCore = "3.7.0" -baselinePerfCoreMicrometer = "1.1.9" +baselinePerfCoreMicrometer = "1.1.10" baselinePerfExtra = "3.5.2" # Other shared versions diff --git a/reactor-core-micrometer/src/main/java/reactor/core/observability/micrometer/MicrometerMeterListenerConfiguration.java b/reactor-core-micrometer/src/main/java/reactor/core/observability/micrometer/MicrometerMeterListenerConfiguration.java index 9de3f7f8fe..396958323d 100644 --- a/reactor-core-micrometer/src/main/java/reactor/core/observability/micrometer/MicrometerMeterListenerConfiguration.java +++ b/reactor-core-micrometer/src/main/java/reactor/core/observability/micrometer/MicrometerMeterListenerConfiguration.java @@ -93,6 +93,7 @@ static Tags resolveTags(Publisher source, Tags tags) { Scannable scannable = Scannable.from(source); if (scannable.isScanAvailable()) { + // `Tags#and` deduplicates tags by key, retaining the last value as required. List discoveredTags = scannable.tags() .map(t -> Tag.of(t.getT1(), t.getT2())) .collect(Collectors.toList()); diff --git a/reactor-core-micrometer/src/main/java/reactor/core/observability/micrometer/MicrometerObservationListenerConfiguration.java b/reactor-core-micrometer/src/main/java/reactor/core/observability/micrometer/MicrometerObservationListenerConfiguration.java index 337cbefd14..ec03acd830 100644 --- a/reactor-core-micrometer/src/main/java/reactor/core/observability/micrometer/MicrometerObservationListenerConfiguration.java +++ b/reactor-core-micrometer/src/main/java/reactor/core/observability/micrometer/MicrometerObservationListenerConfiguration.java @@ -70,6 +70,7 @@ static KeyValues resolveKeyValues(Publisher source, KeyValues tags) { Scannable scannable = Scannable.from(source); if (scannable.isScanAvailable()) { + // `KeyValues#and` deduplicates tags by key, retaining the last value as required. List discoveredTags = scannable.tags() .map(e -> KeyValue.of(e.getT1(), e.getT2())) .collect(Collectors.toList()); diff --git a/reactor-core/src/main/java/reactor/core/Scannable.java b/reactor-core/src/main/java/reactor/core/Scannable.java index 828c0aba03..ca3b1143c3 100644 --- a/reactor-core/src/main/java/reactor/core/Scannable.java +++ b/reactor-core/src/main/java/reactor/core/Scannable.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017-2023 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2017-2024 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -627,7 +627,9 @@ default Stream> tags() { * * @return a {@link Map} of deduplicated tags from this {@link Scannable} and its reachable parents * @see #tags() + * @deprecated Micrometer APIs generally deduplicate tags and key-value pairs by default, so for related use cases prefer {@link #tags()}. */ + @Deprecated default Map tagsDeduplicated() { return tags().collect(Collectors.toMap(Tuple2::getT1, Tuple2::getT2, (s1, s2) -> s2, LinkedHashMap::new)); diff --git a/reactor-core/src/main/java/reactor/core/publisher/FluxMetrics.java b/reactor-core/src/main/java/reactor/core/publisher/FluxMetrics.java index 395eb3001a..18dacb9065 100644 --- a/reactor-core/src/main/java/reactor/core/publisher/FluxMetrics.java +++ b/reactor-core/src/main/java/reactor/core/publisher/FluxMetrics.java @@ -295,6 +295,7 @@ static Tags resolveTags(Publisher source, Tags tags) { Scannable scannable = Scannable.from(source); if (scannable.isScanAvailable()) { + // `Tags#and` deduplicates tags by key, retaining the last value as required. List discoveredTags = scannable.tags() .map(t -> Tag.of(t.getT1(), t.getT2())) .collect(Collectors.toList()); From 3b28862e49b31ba41f71d42a009823b473cb2215 Mon Sep 17 00:00:00 2001 From: Stephan Schroevers Date: Sat, 19 Oct 2024 14:16:57 +0200 Subject: [PATCH 3/4] Add JMH baseline, bump version again --- benchmarks/build.gradle | 3 +++ gradle/libs.versions.toml | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/benchmarks/build.gradle b/benchmarks/build.gradle index 0ab48e6491..a2cf260e29 100644 --- a/benchmarks/build.gradle +++ b/benchmarks/build.gradle @@ -28,6 +28,9 @@ dependencies { baseline libs.reactor.perfBaseline.core, { changing = true } + baseline libs.reactor.perfBaseline.coreMicrometer, { + changing = true + } } task jmhProfilers(type: JavaExec, description:'Lists the available profilers for the jmh task', group: 'Development') { diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index d1d3888c5c..7a7f26e399 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -9,7 +9,7 @@ # Baselines, should be updated on every release baseline-core-api = "3.7.0" baselinePerfCore = "3.7.0" -baselinePerfCoreMicrometer = "1.1.10" +baselinePerfCoreMicrometer = "1.1.11" baselinePerfExtra = "3.5.2" # Other shared versions From 7df3f7077c33688ec9682e9673ef73a4307f3158 Mon Sep 17 00:00:00 2001 From: Stephan Schroevers Date: Thu, 21 Nov 2024 17:58:41 +0100 Subject: [PATCH 4/4] Bump `reactor-core-micrometer` dependency --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 7a7f26e399..70f9fba450 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -9,7 +9,7 @@ # Baselines, should be updated on every release baseline-core-api = "3.7.0" baselinePerfCore = "3.7.0" -baselinePerfCoreMicrometer = "1.1.11" +baselinePerfCoreMicrometer = "1.2.0" baselinePerfExtra = "3.5.2" # Other shared versions