Merge pull request #43852 from nosan
* pr/43852: Auto-configure VirtualThreadMetrics Closes gh-43852
This commit is contained in:
		
						commit
						ec94e07dc9
					
				| 
						 | 
				
			
			@ -29,6 +29,7 @@ dependencies {
 | 
			
		|||
	optional("io.lettuce:lettuce-core")
 | 
			
		||||
	optional("io.micrometer:micrometer-observation")
 | 
			
		||||
	optional("io.micrometer:micrometer-jakarta9")
 | 
			
		||||
	optional("io.micrometer:micrometer-java21")
 | 
			
		||||
	optional("io.micrometer:micrometer-tracing")
 | 
			
		||||
	optional("io.micrometer:micrometer-tracing-bridge-brave")
 | 
			
		||||
	optional("io.micrometer:micrometer-tracing-bridge-otel")
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,5 +1,5 @@
 | 
			
		|||
/*
 | 
			
		||||
 * Copyright 2012-2022 the original author or authors.
 | 
			
		||||
 * Copyright 2012-2025 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.
 | 
			
		||||
| 
						 | 
				
			
			@ -16,6 +16,8 @@
 | 
			
		|||
 | 
			
		||||
package org.springframework.boot.actuate.autoconfigure.metrics;
 | 
			
		||||
 | 
			
		||||
import java.io.Closeable;
 | 
			
		||||
 | 
			
		||||
import io.micrometer.core.instrument.MeterRegistry;
 | 
			
		||||
import io.micrometer.core.instrument.binder.jvm.ClassLoaderMetrics;
 | 
			
		||||
import io.micrometer.core.instrument.binder.jvm.JvmCompilationMetrics;
 | 
			
		||||
| 
						 | 
				
			
			@ -25,12 +27,21 @@ import io.micrometer.core.instrument.binder.jvm.JvmInfoMetrics;
 | 
			
		|||
import io.micrometer.core.instrument.binder.jvm.JvmMemoryMetrics;
 | 
			
		||||
import io.micrometer.core.instrument.binder.jvm.JvmThreadMetrics;
 | 
			
		||||
 | 
			
		||||
import org.springframework.aot.hint.MemberCategory;
 | 
			
		||||
import org.springframework.aot.hint.RuntimeHints;
 | 
			
		||||
import org.springframework.aot.hint.RuntimeHintsRegistrar;
 | 
			
		||||
import org.springframework.beans.BeanUtils;
 | 
			
		||||
import org.springframework.beans.factory.BeanClassLoaderAware;
 | 
			
		||||
import org.springframework.beans.factory.DisposableBean;
 | 
			
		||||
import org.springframework.beans.factory.FactoryBean;
 | 
			
		||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
 | 
			
		||||
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.context.annotation.Bean;
 | 
			
		||||
import org.springframework.context.annotation.ImportRuntimeHints;
 | 
			
		||||
import org.springframework.util.ClassUtils;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * {@link EnableAutoConfiguration Auto-configuration} for JVM metrics.
 | 
			
		||||
| 
						 | 
				
			
			@ -44,6 +55,8 @@ import org.springframework.context.annotation.Bean;
 | 
			
		|||
@ConditionalOnBean(MeterRegistry.class)
 | 
			
		||||
public class JvmMetricsAutoConfiguration {
 | 
			
		||||
 | 
			
		||||
	private static final String VIRTUAL_THREAD_METRICS_CLASS = "io.micrometer.java21.instrument.binder.jdk.VirtualThreadMetrics";
 | 
			
		||||
 | 
			
		||||
	@Bean
 | 
			
		||||
	@ConditionalOnMissingBean
 | 
			
		||||
	public JvmGcMetrics jvmGcMetrics() {
 | 
			
		||||
| 
						 | 
				
			
			@ -86,4 +99,62 @@ public class JvmMetricsAutoConfiguration {
 | 
			
		|||
		return new JvmCompilationMetrics();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Bean
 | 
			
		||||
	@ConditionalOnClass(name = VIRTUAL_THREAD_METRICS_CLASS)
 | 
			
		||||
	@ConditionalOnMissingBean(type = VIRTUAL_THREAD_METRICS_CLASS)
 | 
			
		||||
	@ImportRuntimeHints(VirtualThreadMetricsRuntimeHintsRegistrar.class)
 | 
			
		||||
	VirtualThreadMetricsFactoryBean virtualThreadMetrics() {
 | 
			
		||||
		return new VirtualThreadMetricsFactoryBean();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	static final class VirtualThreadMetricsFactoryBean
 | 
			
		||||
			implements FactoryBean<Object>, BeanClassLoaderAware, DisposableBean {
 | 
			
		||||
 | 
			
		||||
		private ClassLoader classLoader;
 | 
			
		||||
 | 
			
		||||
		private Class<?> instanceType;
 | 
			
		||||
 | 
			
		||||
		private Object instance;
 | 
			
		||||
 | 
			
		||||
		@Override
 | 
			
		||||
		public void setBeanClassLoader(ClassLoader classLoader) {
 | 
			
		||||
			this.classLoader = classLoader;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		@Override
 | 
			
		||||
		public Object getObject() {
 | 
			
		||||
			if (this.instance == null) {
 | 
			
		||||
				this.instance = BeanUtils.instantiateClass(getObjectType());
 | 
			
		||||
			}
 | 
			
		||||
			return this.instance;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		@Override
 | 
			
		||||
		public Class<?> getObjectType() {
 | 
			
		||||
			if (this.instanceType == null) {
 | 
			
		||||
				this.instanceType = ClassUtils.resolveClassName(VIRTUAL_THREAD_METRICS_CLASS, this.classLoader);
 | 
			
		||||
			}
 | 
			
		||||
			return this.instanceType;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		@Override
 | 
			
		||||
		public void destroy() throws Exception {
 | 
			
		||||
			if (this.instance instanceof Closeable closeable) {
 | 
			
		||||
				closeable.close();
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	static final class VirtualThreadMetricsRuntimeHintsRegistrar implements RuntimeHintsRegistrar {
 | 
			
		||||
 | 
			
		||||
		@Override
 | 
			
		||||
		public void registerHints(RuntimeHints hints, ClassLoader classLoader) {
 | 
			
		||||
			hints.reflection()
 | 
			
		||||
				.registerTypeIfPresent(classLoader, VIRTUAL_THREAD_METRICS_CLASS,
 | 
			
		||||
						MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,5 +1,5 @@
 | 
			
		|||
/*
 | 
			
		||||
 * Copyright 2012-2023 the original author or authors.
 | 
			
		||||
 * Copyright 2012-2025 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.
 | 
			
		||||
| 
						 | 
				
			
			@ -16,6 +16,7 @@
 | 
			
		|||
 | 
			
		||||
package org.springframework.boot.actuate.autoconfigure.metrics;
 | 
			
		||||
 | 
			
		||||
import io.micrometer.core.instrument.binder.MeterBinder;
 | 
			
		||||
import io.micrometer.core.instrument.binder.jvm.ClassLoaderMetrics;
 | 
			
		||||
import io.micrometer.core.instrument.binder.jvm.JvmCompilationMetrics;
 | 
			
		||||
import io.micrometer.core.instrument.binder.jvm.JvmGcMetrics;
 | 
			
		||||
| 
						 | 
				
			
			@ -24,7 +25,14 @@ import io.micrometer.core.instrument.binder.jvm.JvmInfoMetrics;
 | 
			
		|||
import io.micrometer.core.instrument.binder.jvm.JvmMemoryMetrics;
 | 
			
		||||
import io.micrometer.core.instrument.binder.jvm.JvmThreadMetrics;
 | 
			
		||||
import org.junit.jupiter.api.Test;
 | 
			
		||||
import org.junit.jupiter.api.condition.EnabledForJreRange;
 | 
			
		||||
import org.junit.jupiter.api.condition.JRE;
 | 
			
		||||
 | 
			
		||||
import org.springframework.aot.hint.MemberCategory;
 | 
			
		||||
import org.springframework.aot.hint.RuntimeHints;
 | 
			
		||||
import org.springframework.aot.hint.TypeReference;
 | 
			
		||||
import org.springframework.aot.hint.predicate.RuntimeHintsPredicates;
 | 
			
		||||
import org.springframework.beans.BeanUtils;
 | 
			
		||||
import org.springframework.boot.actuate.autoconfigure.metrics.test.MetricsRun;
 | 
			
		||||
import org.springframework.boot.autoconfigure.AutoConfigurations;
 | 
			
		||||
import org.springframework.boot.test.context.assertj.AssertableApplicationContext;
 | 
			
		||||
| 
						 | 
				
			
			@ -32,6 +40,7 @@ import org.springframework.boot.test.context.runner.ApplicationContextRunner;
 | 
			
		|||
import org.springframework.boot.test.context.runner.ContextConsumer;
 | 
			
		||||
import org.springframework.context.annotation.Bean;
 | 
			
		||||
import org.springframework.context.annotation.Configuration;
 | 
			
		||||
import org.springframework.util.ClassUtils;
 | 
			
		||||
 | 
			
		||||
import static org.assertj.core.api.Assertions.assertThat;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -95,6 +104,36 @@ class JvmMetricsAutoConfigurationTests {
 | 
			
		|||
			.run(assertMetricsBeans().andThen((context) -> assertThat(context).hasBean("customJvmCompilationMetrics")));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Test
 | 
			
		||||
	@EnabledForJreRange(min = JRE.JAVA_21)
 | 
			
		||||
	void autoConfiguresJvmMetricsWithVirtualThreadsMetrics() {
 | 
			
		||||
		this.contextRunner.run(assertMetricsBeans()
 | 
			
		||||
			.andThen((context) -> assertThat(context).hasSingleBean(getVirtualThreadMetricsClass())));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Test
 | 
			
		||||
	@EnabledForJreRange(min = JRE.JAVA_21)
 | 
			
		||||
	void allowCustomVirtualThreadMetricsToBeUsed() {
 | 
			
		||||
		Class<MeterBinder> virtualThreadMetricsClass = getVirtualThreadMetricsClass();
 | 
			
		||||
		this.contextRunner
 | 
			
		||||
			.withBean("customVirtualThreadMetrics", virtualThreadMetricsClass,
 | 
			
		||||
					() -> BeanUtils.instantiateClass(virtualThreadMetricsClass))
 | 
			
		||||
			.run(assertMetricsBeans()
 | 
			
		||||
				.andThen((context) -> assertThat(context).hasSingleBean(getVirtualThreadMetricsClass())
 | 
			
		||||
					.hasBean("customVirtualThreadMetrics")));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Test
 | 
			
		||||
	@EnabledForJreRange(min = JRE.JAVA_21)
 | 
			
		||||
	void shouldRegisterVirtualThreadMetricsRuntimeHints() {
 | 
			
		||||
		RuntimeHints hints = new RuntimeHints();
 | 
			
		||||
		new JvmMetricsAutoConfiguration.VirtualThreadMetricsRuntimeHintsRegistrar().registerHints(hints,
 | 
			
		||||
				getClass().getClassLoader());
 | 
			
		||||
		assertThat(RuntimeHintsPredicates.reflection()
 | 
			
		||||
			.onType(TypeReference.of(getVirtualThreadMetricsClass()))
 | 
			
		||||
			.withMemberCategories(MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS)).accepts(hints);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	private ContextConsumer<AssertableApplicationContext> assertMetricsBeans() {
 | 
			
		||||
		return (context) -> assertThat(context).hasSingleBean(JvmGcMetrics.class)
 | 
			
		||||
			.hasSingleBean(JvmHeapPressureMetrics.class)
 | 
			
		||||
| 
						 | 
				
			
			@ -105,6 +144,12 @@ class JvmMetricsAutoConfigurationTests {
 | 
			
		|||
			.hasSingleBean(JvmCompilationMetrics.class);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@SuppressWarnings("unchecked")
 | 
			
		||||
	private static Class<MeterBinder> getVirtualThreadMetricsClass() {
 | 
			
		||||
		return (Class<MeterBinder>) ClassUtils
 | 
			
		||||
			.resolveClassName("io.micrometer.java21.instrument.binder.jdk.VirtualThreadMetrics", null);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Configuration(proxyBeanMethods = false)
 | 
			
		||||
	static class CustomJvmGcMetricsConfiguration {
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue