Skip to content

Commit eaa74f9

Browse files
author
Mike Turbe
committed
Add support for virtual threads in OtlpMetricRegistry configuration
1 parent c7c73be commit eaa74f9

File tree

5 files changed

+138
-0
lines changed

5 files changed

+138
-0
lines changed

spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/otlp/OtlpMetricsExportAutoConfiguration.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,9 @@ OtlpConfig otlpConfig(OpenTelemetryProperties openTelemetryProperties,
7373
@Bean
7474
@ConditionalOnMissingBean
7575
public OtlpMeterRegistry otlpMeterRegistry(OtlpConfig otlpConfig, Clock clock) {
76+
if (this.properties.isVirtualThreadsEnabled()) {
77+
return new OtlpMeterRegistry(otlpConfig, clock, Thread.ofVirtual().factory());
78+
}
7679
return new OtlpMeterRegistry(otlpConfig, clock);
7780
}
7881

spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/otlp/OtlpProperties.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,11 @@ public class OtlpProperties extends StepRegistryProperties {
7979
*/
8080
private TimeUnit baseTimeUnit = TimeUnit.MILLISECONDS;
8181

82+
/**
83+
* Whether virtual threads should be used for publishing metrics.
84+
*/
85+
private boolean virtualThreadsEnabled = false;
86+
8287
public String getUrl() {
8388
return this.url;
8489
}
@@ -146,4 +151,12 @@ public void setBaseTimeUnit(TimeUnit baseTimeUnit) {
146151
this.baseTimeUnit = baseTimeUnit;
147152
}
148153

154+
public boolean isVirtualThreadsEnabled() {
155+
return this.virtualThreadsEnabled;
156+
}
157+
158+
public void setVirtualThreadsEnabled(boolean virtualThreadsEnabled) {
159+
this.virtualThreadsEnabled = virtualThreadsEnabled;
160+
}
161+
149162
}

spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/export/otlp/OtlpMetricsExportAutoConfigurationTests.java

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616

1717
package org.springframework.boot.actuate.autoconfigure.metrics.export.otlp;
1818

19+
import java.util.concurrent.ScheduledExecutorService;
20+
1921
import io.micrometer.core.instrument.Clock;
2022
import io.micrometer.registry.otlp.OtlpConfig;
2123
import io.micrometer.registry.otlp.OtlpMeterRegistry;
@@ -24,6 +26,7 @@
2426
import org.springframework.boot.actuate.autoconfigure.metrics.export.otlp.OtlpMetricsExportAutoConfiguration.PropertiesOtlpMetricsConnectionDetails;
2527
import org.springframework.boot.autoconfigure.AutoConfigurations;
2628
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
29+
import org.springframework.boot.testsupport.assertj.ScheduledExecutorServiceAssert;
2730
import org.springframework.context.annotation.Bean;
2831
import org.springframework.context.annotation.Configuration;
2932
import org.springframework.context.annotation.Import;
@@ -76,6 +79,35 @@ void allowsCustomConfigToBeUsed() {
7679
.hasBean("customConfig"));
7780
}
7881

82+
@Test
83+
void allowsPlatformThreadsToBeUsed() {
84+
this.contextRunner.withUserConfiguration(BaseConfiguration.class).run((context) -> {
85+
assertThat(context).hasSingleBean(OtlpMeterRegistry.class);
86+
OtlpProperties properties = context.getBean(OtlpProperties.class);
87+
assertThat(properties.isVirtualThreadsEnabled()).isFalse();
88+
OtlpMeterRegistry registry = context.getBean(OtlpMeterRegistry.class);
89+
assertThat(registry).extracting("scheduledExecutorService")
90+
.satisfies((executor) -> ScheduledExecutorServiceAssert.assertThat((ScheduledExecutorService) executor)
91+
.usesPlatformThreads());
92+
});
93+
}
94+
95+
@Test
96+
void allowsVirtualThreadsToBeUsed() {
97+
this.contextRunner.withUserConfiguration(BaseConfiguration.class)
98+
.withPropertyValues("management.otlp.metrics.export.virtualThreadsEnabled=true")
99+
.run((context) -> {
100+
assertThat(context).hasSingleBean(OtlpMeterRegistry.class);
101+
OtlpProperties properties = context.getBean(OtlpProperties.class);
102+
assertThat(properties.isVirtualThreadsEnabled()).isTrue();
103+
OtlpMeterRegistry registry = context.getBean(OtlpMeterRegistry.class);
104+
assertThat(registry).extracting("scheduledExecutorService")
105+
.satisfies(
106+
(executor) -> ScheduledExecutorServiceAssert.assertThat((ScheduledExecutorService) executor)
107+
.usesVirtualThreads());
108+
});
109+
}
110+
79111
@Test
80112
void allowsRegistryToBeCustomized() {
81113
this.contextRunner.withUserConfiguration(CustomRegistryConfiguration.class)

spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/export/otlp/OtlpPropertiesTests.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,4 +43,10 @@ void defaultValuesAreConsistent() {
4343
assertThat(properties.getBaseTimeUnit()).isSameAs(config.baseTimeUnit());
4444
}
4545

46+
@Test
47+
void virtualThreadsDisabledByDefault() {
48+
OtlpProperties properties = new OtlpProperties();
49+
assertThat(properties.isVirtualThreadsEnabled()).isFalse();
50+
}
51+
4652
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
/*
2+
* Copyright 2012-2024 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.testsupport.assertj;
18+
19+
import java.util.concurrent.ExecutionException;
20+
import java.util.concurrent.ScheduledExecutorService;
21+
import java.util.concurrent.ScheduledThreadPoolExecutor;
22+
import java.util.concurrent.TimeUnit;
23+
24+
import org.assertj.core.api.AbstractAssert;
25+
import org.assertj.core.api.Assert;
26+
27+
/**
28+
* AssertJ {@link Assert} for {@link ScheduledThreadPoolExecutor}.
29+
*
30+
* @author Mike Turbe
31+
* @author Moritz Halbritter
32+
*/
33+
public final class ScheduledExecutorServiceAssert
34+
extends AbstractAssert<ScheduledExecutorServiceAssert, ScheduledExecutorService> {
35+
36+
private ScheduledExecutorServiceAssert(ScheduledExecutorService actual) {
37+
super(actual, ScheduledExecutorServiceAssert.class);
38+
}
39+
40+
/**
41+
* Verifies that the actual executor uses platform threads.
42+
* @return {@code this} assertion object
43+
* @throws AssertionError if the actual executor doesn't use platform threads
44+
*/
45+
public ScheduledExecutorServiceAssert usesPlatformThreads() {
46+
isNotNull();
47+
if (producesVirtualThreads()) {
48+
failWithMessage("Expected executor to use platform threads, but it uses virtual threads");
49+
}
50+
return this;
51+
}
52+
53+
/**
54+
* Verifies that the actual executor uses virtual threads.
55+
* @return {@code this} assertion object
56+
* @throws AssertionError if the actual executor doesn't use virtual threads
57+
*/
58+
public ScheduledExecutorServiceAssert usesVirtualThreads() {
59+
isNotNull();
60+
if (!producesVirtualThreads()) {
61+
failWithMessage("Expected executor to use virtual threads, but it uses platform threads");
62+
}
63+
return this;
64+
}
65+
66+
private boolean producesVirtualThreads() {
67+
try {
68+
return this.actual.schedule(() -> Thread.currentThread().isVirtual(), 0, TimeUnit.SECONDS).get();
69+
}
70+
catch (InterruptedException | ExecutionException ex) {
71+
throw new AssertionError(ex);
72+
}
73+
}
74+
75+
/**
76+
* Creates a new assertion class with the given {@link ScheduledExecutorService}.
77+
* @param actual the {@link ScheduledExecutorService}
78+
* @return the assertion class
79+
*/
80+
public static ScheduledExecutorServiceAssert assertThat(ScheduledExecutorService actual) {
81+
return new ScheduledExecutorServiceAssert(actual);
82+
}
83+
84+
}

0 commit comments

Comments
 (0)