Skip to content

Make OtlpMeterRegistry virtual thread aware #42407

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down Expand Up @@ -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;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,4 +43,10 @@ void defaultValuesAreConsistent() {
assertThat(properties.getBaseTimeUnit()).isSameAs(config.baseTimeUnit());
}

@Test
void virtualThreadsDisabledByDefault() {
OtlpProperties properties = new OtlpProperties();
assertThat(properties.isVirtualThreadsEnabled()).isFalse();
}

}
Original file line number Diff line number Diff line change
@@ -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<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(() -> 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);
}

}
Loading