Add support for virtual threads in OtlpMetricRegistry configuration

See gh-42407
This commit is contained in:
Mike Turbe
2024-09-21 02:59:31 -05:00
committed by Moritz Halbritter
parent eb7b6a776d
commit e615eb313a
3 changed files with 135 additions and 1 deletions

View File

@@ -30,9 +30,12 @@ import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
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,10 +75,19 @@ public class OtlpMetricsExportAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public OtlpMeterRegistry otlpMeterRegistry(OtlpConfig otlpConfig, Clock clock) {
@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}.
*/

View File

@@ -16,14 +16,19 @@
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;
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;
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 +81,32 @@ class OtlpMetricsExportAutoConfigurationTests {
.hasBean("customConfig"));
}
@Test
void allowsPlatformThreadsToBeUsed() {
this.contextRunner.withUserConfiguration(BaseConfiguration.class).run((context) -> {
assertThat(context).hasSingleBean(OtlpMeterRegistry.class);
OtlpMeterRegistry registry = context.getBean(OtlpMeterRegistry.class);
assertThat(registry).extracting("scheduledExecutorService")
.satisfies((executor) -> ScheduledExecutorServiceAssert.assertThat((ScheduledExecutorService) executor)
.usesPlatformThreads());
});
}
@Test
@EnabledForJreRange(min = JRE.JAVA_21)
void allowsVirtualThreadsToBeUsed() {
this.contextRunner.withUserConfiguration(BaseConfiguration.class)
.withPropertyValues("spring.threads.virtual.enabled=true")
.run((context) -> {
assertThat(context).hasSingleBean(OtlpMeterRegistry.class);
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)

View File

@@ -0,0 +1,91 @@
/*
* 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<ScheduledExecutorServiceAssert, ScheduledExecutorService> {
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(() -> {
// 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);
}
}
/**
* 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);
}
}