From eaa74f9544e11bfa22808ff79be0abd93ee5e88d Mon Sep 17 00:00:00 2001 From: Mike Turbe Date: Sat, 21 Sep 2024 02:59:31 -0500 Subject: [PATCH 1/3] Add support for virtual threads in OtlpMetricRegistry configuration --- .../OtlpMetricsExportAutoConfiguration.java | 3 + .../metrics/export/otlp/OtlpProperties.java | 13 +++ ...lpMetricsExportAutoConfigurationTests.java | 32 +++++++ .../export/otlp/OtlpPropertiesTests.java | 6 ++ .../ScheduledExecutorServiceAssert.java | 84 +++++++++++++++++++ 5 files changed, 138 insertions(+) create mode 100644 spring-boot-project/spring-boot-tools/spring-boot-test-support/src/main/java/org/springframework/boot/testsupport/assertj/ScheduledExecutorServiceAssert.java diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/otlp/OtlpMetricsExportAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/otlp/OtlpMetricsExportAutoConfiguration.java index c7da21f488d1..afac9b41b940 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/otlp/OtlpMetricsExportAutoConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/otlp/OtlpMetricsExportAutoConfiguration.java @@ -73,6 +73,9 @@ OtlpConfig otlpConfig(OpenTelemetryProperties openTelemetryProperties, @Bean @ConditionalOnMissingBean public OtlpMeterRegistry otlpMeterRegistry(OtlpConfig otlpConfig, Clock clock) { + if (this.properties.isVirtualThreadsEnabled()) { + return new OtlpMeterRegistry(otlpConfig, clock, Thread.ofVirtual().factory()); + } return new OtlpMeterRegistry(otlpConfig, clock); } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/otlp/OtlpProperties.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/otlp/OtlpProperties.java index 5ad71476c6a7..a54e3c3c0002 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/otlp/OtlpProperties.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/otlp/OtlpProperties.java @@ -79,6 +79,11 @@ public class OtlpProperties extends StepRegistryProperties { */ private TimeUnit baseTimeUnit = TimeUnit.MILLISECONDS; + /** + * Whether virtual threads should be used for publishing metrics. + */ + private boolean virtualThreadsEnabled = false; + public String getUrl() { return this.url; } @@ -146,4 +151,12 @@ public void setBaseTimeUnit(TimeUnit baseTimeUnit) { this.baseTimeUnit = baseTimeUnit; } + public boolean isVirtualThreadsEnabled() { + return this.virtualThreadsEnabled; + } + + public void setVirtualThreadsEnabled(boolean virtualThreadsEnabled) { + this.virtualThreadsEnabled = virtualThreadsEnabled; + } + } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/export/otlp/OtlpMetricsExportAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/export/otlp/OtlpMetricsExportAutoConfigurationTests.java index f303e4a2cfdc..0ae4345c65fc 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/export/otlp/OtlpMetricsExportAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/export/otlp/OtlpMetricsExportAutoConfigurationTests.java @@ -16,6 +16,8 @@ package org.springframework.boot.actuate.autoconfigure.metrics.export.otlp; +import java.util.concurrent.ScheduledExecutorService; + import io.micrometer.core.instrument.Clock; import io.micrometer.registry.otlp.OtlpConfig; import io.micrometer.registry.otlp.OtlpMeterRegistry; @@ -24,6 +26,7 @@ import org.springframework.boot.actuate.autoconfigure.metrics.export.otlp.OtlpMetricsExportAutoConfiguration.PropertiesOtlpMetricsConnectionDetails; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.boot.testsupport.assertj.ScheduledExecutorServiceAssert; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; @@ -76,6 +79,35 @@ void allowsCustomConfigToBeUsed() { .hasBean("customConfig")); } + @Test + void allowsPlatformThreadsToBeUsed() { + this.contextRunner.withUserConfiguration(BaseConfiguration.class).run((context) -> { + assertThat(context).hasSingleBean(OtlpMeterRegistry.class); + OtlpProperties properties = context.getBean(OtlpProperties.class); + assertThat(properties.isVirtualThreadsEnabled()).isFalse(); + OtlpMeterRegistry registry = context.getBean(OtlpMeterRegistry.class); + assertThat(registry).extracting("scheduledExecutorService") + .satisfies((executor) -> ScheduledExecutorServiceAssert.assertThat((ScheduledExecutorService) executor) + .usesPlatformThreads()); + }); + } + + @Test + void allowsVirtualThreadsToBeUsed() { + this.contextRunner.withUserConfiguration(BaseConfiguration.class) + .withPropertyValues("management.otlp.metrics.export.virtualThreadsEnabled=true") + .run((context) -> { + assertThat(context).hasSingleBean(OtlpMeterRegistry.class); + OtlpProperties properties = context.getBean(OtlpProperties.class); + assertThat(properties.isVirtualThreadsEnabled()).isTrue(); + OtlpMeterRegistry registry = context.getBean(OtlpMeterRegistry.class); + assertThat(registry).extracting("scheduledExecutorService") + .satisfies( + (executor) -> ScheduledExecutorServiceAssert.assertThat((ScheduledExecutorService) executor) + .usesVirtualThreads()); + }); + } + @Test void allowsRegistryToBeCustomized() { this.contextRunner.withUserConfiguration(CustomRegistryConfiguration.class) diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/export/otlp/OtlpPropertiesTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/export/otlp/OtlpPropertiesTests.java index 1ecf8fb7c4c2..a1f9f3b8f658 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/export/otlp/OtlpPropertiesTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/export/otlp/OtlpPropertiesTests.java @@ -43,4 +43,10 @@ void defaultValuesAreConsistent() { assertThat(properties.getBaseTimeUnit()).isSameAs(config.baseTimeUnit()); } + @Test + void virtualThreadsDisabledByDefault() { + OtlpProperties properties = new OtlpProperties(); + assertThat(properties.isVirtualThreadsEnabled()).isFalse(); + } + } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-test-support/src/main/java/org/springframework/boot/testsupport/assertj/ScheduledExecutorServiceAssert.java b/spring-boot-project/spring-boot-tools/spring-boot-test-support/src/main/java/org/springframework/boot/testsupport/assertj/ScheduledExecutorServiceAssert.java new file mode 100644 index 000000000000..64205574e161 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-test-support/src/main/java/org/springframework/boot/testsupport/assertj/ScheduledExecutorServiceAssert.java @@ -0,0 +1,84 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * 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 org.springframework.boot.testsupport.assertj; + +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +import org.assertj.core.api.AbstractAssert; +import org.assertj.core.api.Assert; + +/** + * AssertJ {@link Assert} for {@link ScheduledThreadPoolExecutor}. + * + * @author Mike Turbe + * @author Moritz Halbritter + */ +public final class ScheduledExecutorServiceAssert + extends AbstractAssert { + + private ScheduledExecutorServiceAssert(ScheduledExecutorService actual) { + super(actual, ScheduledExecutorServiceAssert.class); + } + + /** + * Verifies that the actual executor uses platform threads. + * @return {@code this} assertion object + * @throws AssertionError if the actual executor doesn't use platform threads + */ + public ScheduledExecutorServiceAssert usesPlatformThreads() { + isNotNull(); + if (producesVirtualThreads()) { + failWithMessage("Expected executor to use platform threads, but it uses virtual threads"); + } + return this; + } + + /** + * Verifies that the actual executor uses virtual threads. + * @return {@code this} assertion object + * @throws AssertionError if the actual executor doesn't use virtual threads + */ + public ScheduledExecutorServiceAssert usesVirtualThreads() { + isNotNull(); + if (!producesVirtualThreads()) { + failWithMessage("Expected executor to use virtual threads, but it uses platform threads"); + } + return this; + } + + private boolean producesVirtualThreads() { + try { + return this.actual.schedule(() -> Thread.currentThread().isVirtual(), 0, TimeUnit.SECONDS).get(); + } + catch (InterruptedException | ExecutionException ex) { + throw new AssertionError(ex); + } + } + + /** + * Creates a new assertion class with the given {@link ScheduledExecutorService}. + * @param actual the {@link ScheduledExecutorService} + * @return the assertion class + */ + public static ScheduledExecutorServiceAssert assertThat(ScheduledExecutorService actual) { + return new ScheduledExecutorServiceAssert(actual); + } + +} From ef810f9688d83ea4f955860013593c10c987b707 Mon Sep 17 00:00:00 2001 From: Mike Turbe Date: Wed, 25 Sep 2024 12:38:18 -0500 Subject: [PATCH 2/3] Use ConditionalOnThreading and replace Java 21 specific virtual thread check --- .../OtlpMetricsExportAutoConfiguration.java | 17 +++++++++++++---- .../metrics/export/otlp/OtlpProperties.java | 8 -------- ...OtlpMetricsExportAutoConfigurationTests.java | 9 ++++----- .../export/otlp/OtlpPropertiesTests.java | 6 ------ .../assertj/ScheduledExecutorServiceAssert.java | 9 ++++++++- 5 files changed, 25 insertions(+), 24 deletions(-) diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/otlp/OtlpMetricsExportAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/otlp/OtlpMetricsExportAutoConfiguration.java index afac9b41b940..9dd3b5d872e6 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/otlp/OtlpMetricsExportAutoConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/otlp/OtlpMetricsExportAutoConfiguration.java @@ -30,9 +30,12 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnThreading; +import org.springframework.boot.autoconfigure.thread.Threading; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.core.env.Environment; +import org.springframework.core.task.VirtualThreadTaskExecutor; /** * {@link EnableAutoConfiguration Auto-configuration} for exporting metrics to OTLP. @@ -72,13 +75,19 @@ OtlpConfig otlpConfig(OpenTelemetryProperties openTelemetryProperties, @Bean @ConditionalOnMissingBean - public OtlpMeterRegistry otlpMeterRegistry(OtlpConfig otlpConfig, Clock clock) { - if (this.properties.isVirtualThreadsEnabled()) { - return new OtlpMeterRegistry(otlpConfig, clock, Thread.ofVirtual().factory()); - } + @ConditionalOnThreading(Threading.PLATFORM) + public OtlpMeterRegistry otlpMeterRegistryPlatformThreads(OtlpConfig otlpConfig, Clock clock) { return new OtlpMeterRegistry(otlpConfig, clock); } + @Bean + @ConditionalOnMissingBean + @ConditionalOnThreading(Threading.VIRTUAL) + public OtlpMeterRegistry otlpMeterRegistryVirtualThreads(OtlpConfig otlpConfig, Clock clock) { + VirtualThreadTaskExecutor taskExecutor = new VirtualThreadTaskExecutor("otlp-meter-registry"); + return new OtlpMeterRegistry(otlpConfig, clock, taskExecutor.getVirtualThreadFactory()); + } + /** * Adapts {@link OtlpProperties} to {@link OtlpMetricsConnectionDetails}. */ diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/otlp/OtlpProperties.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/otlp/OtlpProperties.java index a54e3c3c0002..da436e508ea7 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/otlp/OtlpProperties.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/otlp/OtlpProperties.java @@ -151,12 +151,4 @@ public void setBaseTimeUnit(TimeUnit baseTimeUnit) { this.baseTimeUnit = baseTimeUnit; } - public boolean isVirtualThreadsEnabled() { - return this.virtualThreadsEnabled; - } - - public void setVirtualThreadsEnabled(boolean virtualThreadsEnabled) { - this.virtualThreadsEnabled = virtualThreadsEnabled; - } - } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/export/otlp/OtlpMetricsExportAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/export/otlp/OtlpMetricsExportAutoConfigurationTests.java index 0ae4345c65fc..c5caa14cc372 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/export/otlp/OtlpMetricsExportAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/export/otlp/OtlpMetricsExportAutoConfigurationTests.java @@ -22,6 +22,8 @@ import io.micrometer.registry.otlp.OtlpConfig; import io.micrometer.registry.otlp.OtlpMeterRegistry; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.EnabledForJreRange; +import org.junit.jupiter.api.condition.JRE; import org.springframework.boot.actuate.autoconfigure.metrics.export.otlp.OtlpMetricsExportAutoConfiguration.PropertiesOtlpMetricsConnectionDetails; import org.springframework.boot.autoconfigure.AutoConfigurations; @@ -83,8 +85,6 @@ void allowsCustomConfigToBeUsed() { void allowsPlatformThreadsToBeUsed() { this.contextRunner.withUserConfiguration(BaseConfiguration.class).run((context) -> { assertThat(context).hasSingleBean(OtlpMeterRegistry.class); - OtlpProperties properties = context.getBean(OtlpProperties.class); - assertThat(properties.isVirtualThreadsEnabled()).isFalse(); OtlpMeterRegistry registry = context.getBean(OtlpMeterRegistry.class); assertThat(registry).extracting("scheduledExecutorService") .satisfies((executor) -> ScheduledExecutorServiceAssert.assertThat((ScheduledExecutorService) executor) @@ -93,13 +93,12 @@ void allowsPlatformThreadsToBeUsed() { } @Test + @EnabledForJreRange(min = JRE.JAVA_21) void allowsVirtualThreadsToBeUsed() { this.contextRunner.withUserConfiguration(BaseConfiguration.class) - .withPropertyValues("management.otlp.metrics.export.virtualThreadsEnabled=true") + .withPropertyValues("spring.threads.virtual.enabled=true") .run((context) -> { assertThat(context).hasSingleBean(OtlpMeterRegistry.class); - OtlpProperties properties = context.getBean(OtlpProperties.class); - assertThat(properties.isVirtualThreadsEnabled()).isTrue(); OtlpMeterRegistry registry = context.getBean(OtlpMeterRegistry.class); assertThat(registry).extracting("scheduledExecutorService") .satisfies( diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/export/otlp/OtlpPropertiesTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/export/otlp/OtlpPropertiesTests.java index a1f9f3b8f658..1ecf8fb7c4c2 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/export/otlp/OtlpPropertiesTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/export/otlp/OtlpPropertiesTests.java @@ -43,10 +43,4 @@ void defaultValuesAreConsistent() { assertThat(properties.getBaseTimeUnit()).isSameAs(config.baseTimeUnit()); } - @Test - void virtualThreadsDisabledByDefault() { - OtlpProperties properties = new OtlpProperties(); - assertThat(properties.isVirtualThreadsEnabled()).isFalse(); - } - } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-test-support/src/main/java/org/springframework/boot/testsupport/assertj/ScheduledExecutorServiceAssert.java b/spring-boot-project/spring-boot-tools/spring-boot-test-support/src/main/java/org/springframework/boot/testsupport/assertj/ScheduledExecutorServiceAssert.java index 64205574e161..bb8190cddbe4 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-test-support/src/main/java/org/springframework/boot/testsupport/assertj/ScheduledExecutorServiceAssert.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-test-support/src/main/java/org/springframework/boot/testsupport/assertj/ScheduledExecutorServiceAssert.java @@ -65,7 +65,14 @@ public ScheduledExecutorServiceAssert usesVirtualThreads() { private boolean producesVirtualThreads() { try { - return this.actual.schedule(() -> Thread.currentThread().isVirtual(), 0, TimeUnit.SECONDS).get(); + return this.actual.schedule(() -> { + // https://openjdk.org/jeps/444 + // jep 444 specifies that virtual threads will belong to + // a special thread group given the name "VirtualThreads" + ThreadGroup threadGroup = Thread.currentThread().getThreadGroup(); + String threadGroupName = (threadGroup != null) ? threadGroup.getName() : ""; + return threadGroupName.equalsIgnoreCase("VirtualThreads"); + }, 0, TimeUnit.SECONDS).get(); } catch (InterruptedException | ExecutionException ex) { throw new AssertionError(ex); From 12d08eccf14af8f8648c0d5a808bb5d61fda4b75 Mon Sep 17 00:00:00 2001 From: Mike Turbe Date: Wed, 25 Sep 2024 21:28:08 -0500 Subject: [PATCH 3/3] Remove virtualThreadsEnabled from OltpProperties --- .../autoconfigure/metrics/export/otlp/OtlpProperties.java | 5 ----- 1 file changed, 5 deletions(-) diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/otlp/OtlpProperties.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/otlp/OtlpProperties.java index da436e508ea7..5ad71476c6a7 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/otlp/OtlpProperties.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/otlp/OtlpProperties.java @@ -79,11 +79,6 @@ public class OtlpProperties extends StepRegistryProperties { */ private TimeUnit baseTimeUnit = TimeUnit.MILLISECONDS; - /** - * Whether virtual threads should be used for publishing metrics. - */ - private boolean virtualThreadsEnabled = false; - public String getUrl() { return this.url; }