Skip to content

Commit

Permalink
Merge pull request #43852 from nosan
Browse files Browse the repository at this point in the history
* pr/43852:
  Auto-configure VirtualThreadMetrics

Closes gh-43852
  • Loading branch information
mhalbritter committed Jan 24, 2025
2 parents 1e43b00 + fc5c285 commit ec94e07
Show file tree
Hide file tree
Showing 3 changed files with 120 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -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;
Expand All @@ -25,12 +27,21 @@
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.
Expand All @@ -44,6 +55,8 @@
@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() {
Expand Down Expand Up @@ -86,4 +99,62 @@ public JvmCompilationMetrics jvmCompilationMetrics() {
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);
}

}

}
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -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;
Expand All @@ -24,14 +25,22 @@
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;
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;

Expand Down Expand Up @@ -95,6 +104,36 @@ void allowsCustomJvmCompilationMetricsToBeUsed() {
.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)
Expand All @@ -105,6 +144,12 @@ private ContextConsumer<AssertableApplicationContext> assertMetricsBeans() {
.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 {

Expand Down

0 comments on commit ec94e07

Please sign in to comment.