forked from quarkusio/quarkus
-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Enable Virtual Thread Binder if micrometer-java21 is on the Classpath
This commit introduces automatic registration of the virtual thread meter binder when the `io.micrometer:micrometer-java21` dependency is present. The binder collects metrics related to virtual threads pinning and misbehavior (unable to unpark or start) The binder is activated under the following conditions: - The `micrometer-java21` dependency is available on the classpath. - The application is running on Java 21 or higher. - The `quarkus.micrometer.binder.virtual-threads.enabled` property is set to true (default).
- Loading branch information
1 parent
cbd735f
commit f35f7cf
Showing
10 changed files
with
374 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
34 changes: 34 additions & 0 deletions
34
...t/src/main/java/io/quarkus/micrometer/deployment/binder/VirtualThreadBinderProcessor.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
package io.quarkus.micrometer.deployment.binder; | ||
|
||
import java.util.function.BooleanSupplier; | ||
|
||
import io.quarkus.arc.deployment.AdditionalBeanBuildItem; | ||
import io.quarkus.deployment.annotations.BuildStep; | ||
import io.quarkus.micrometer.runtime.MicrometerRecorder; | ||
import io.quarkus.micrometer.runtime.config.MicrometerConfig; | ||
|
||
/** | ||
* Add support for virtual thread metric collections. | ||
*/ | ||
public class VirtualThreadBinderProcessor { | ||
static final String VIRTUAL_THREAD_COLLECTOR_CLASS_NAME = "io.quarkus.micrometer.runtime.binder.virtualthreads.VirtualThreadCollector"; | ||
|
||
static final String VIRTUAL_THREAD_BINDER_CLASS_NAME = "io.micrometer.java21.instrument.binder.jdk.VirtualThreadMetrics"; | ||
static final Class<?> VIRTUAL_THREAD_BINDER_CLASS = MicrometerRecorder.getClassForName(VIRTUAL_THREAD_BINDER_CLASS_NAME); | ||
|
||
static class VirtualThreadSupportEnabled implements BooleanSupplier { | ||
MicrometerConfig mConfig; | ||
|
||
public boolean getAsBoolean() { | ||
return VIRTUAL_THREAD_BINDER_CLASS != null // The binder is in another Micrometer artifact | ||
&& mConfig.checkBinderEnabledWithDefault(mConfig.binder.virtualThreads); | ||
} | ||
} | ||
|
||
@BuildStep(onlyIf = VirtualThreadSupportEnabled.class) | ||
AdditionalBeanBuildItem createCDIEventConsumer() { | ||
return AdditionalBeanBuildItem.builder() | ||
.addBeanClass(VIRTUAL_THREAD_COLLECTOR_CLASS_NAME) | ||
.setUnremovable().build(); | ||
} | ||
} |
41 changes: 41 additions & 0 deletions
41
...c/test/java/io/quarkus/micrometer/deployment/binder/VirtualThreadMetricsDisabledTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
package io.quarkus.micrometer.deployment.binder; | ||
|
||
import static org.junit.jupiter.api.Assertions.assertTrue; | ||
|
||
import jakarta.enterprise.inject.spi.BeanManager; | ||
import jakarta.inject.Inject; | ||
|
||
import org.junit.jupiter.api.Test; | ||
import org.junit.jupiter.api.extension.RegisterExtension; | ||
|
||
import io.quarkus.micrometer.runtime.binder.virtualthreads.VirtualThreadCollector; | ||
import io.quarkus.test.QuarkusUnitTest; | ||
|
||
public class VirtualThreadMetricsDisabledTest { | ||
|
||
@RegisterExtension | ||
static final QuarkusUnitTest config = new QuarkusUnitTest() | ||
.withConfigurationResource("test-logging.properties") | ||
.overrideConfigKey("quarkus.micrometer.binder.virtual-threads.enabled", "true") | ||
|
||
.overrideConfigKey("quarkus.micrometer.binder-enabled-default", "false") | ||
.overrideConfigKey("quarkus.micrometer.registry-enabled-default", "false") | ||
.overrideConfigKey("quarkus.redis.devservices.enabled", "false") | ||
.withEmptyApplication(); | ||
|
||
@Inject | ||
BeanManager beans; | ||
|
||
@Test | ||
void testNoInstancePresentIfDisabled() { | ||
assertTrue( | ||
beans.createInstance().select() | ||
.stream().filter(this::isVirtualThreadCollector).findAny().isEmpty(), | ||
"No VirtualThreadCollector expected"); | ||
} | ||
|
||
private boolean isVirtualThreadCollector(Object bean) { | ||
return bean.getClass().toString().equals(VirtualThreadCollector.class.toString()); | ||
} | ||
|
||
} |
44 changes: 44 additions & 0 deletions
44
...yment/src/test/java/io/quarkus/micrometer/deployment/binder/VirtualThreadMetricsTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
package io.quarkus.micrometer.deployment.binder; | ||
|
||
import static org.assertj.core.api.Assertions.assertThat; | ||
import static org.junit.jupiter.api.Assertions.assertTrue; | ||
|
||
import jakarta.enterprise.inject.Instance; | ||
import jakarta.inject.Inject; | ||
|
||
import org.junit.jupiter.api.Test; | ||
import org.junit.jupiter.api.condition.EnabledForJreRange; | ||
import org.junit.jupiter.api.condition.JRE; | ||
import org.junit.jupiter.api.extension.RegisterExtension; | ||
|
||
import io.quarkus.micrometer.runtime.binder.virtualthreads.VirtualThreadCollector; | ||
import io.quarkus.test.QuarkusUnitTest; | ||
|
||
@EnabledForJreRange(min = JRE.JAVA_21) | ||
public class VirtualThreadMetricsTest { | ||
|
||
@RegisterExtension | ||
static final QuarkusUnitTest config = new QuarkusUnitTest() | ||
.withConfigurationResource("test-logging.properties") | ||
.overrideConfigKey("quarkus.redis.devservices.enabled", "false") | ||
.withEmptyApplication(); | ||
|
||
@Inject | ||
Instance<VirtualThreadCollector> collector; | ||
|
||
@Test | ||
void testInstancePresent() { | ||
assertTrue(collector.isResolvable(), "VirtualThreadCollector expected"); | ||
} | ||
|
||
@Test | ||
void testBinderCreated() { | ||
assertThat(collector.get().getBinder()).isNotNull(); | ||
} | ||
|
||
@Test | ||
void testTags() { | ||
assertThat(collector.get().getTags()).isEmpty(); | ||
} | ||
|
||
} |
53 changes: 53 additions & 0 deletions
53
...c/test/java/io/quarkus/micrometer/deployment/binder/VirtualThreadMetricsWithTagsTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
package io.quarkus.micrometer.deployment.binder; | ||
|
||
import static org.assertj.core.api.Assertions.assertThat; | ||
import static org.junit.jupiter.api.Assertions.assertTrue; | ||
|
||
import jakarta.enterprise.inject.Instance; | ||
import jakarta.inject.Inject; | ||
|
||
import org.junit.jupiter.api.Test; | ||
import org.junit.jupiter.api.condition.EnabledForJreRange; | ||
import org.junit.jupiter.api.condition.JRE; | ||
import org.junit.jupiter.api.extension.RegisterExtension; | ||
|
||
import io.quarkus.micrometer.runtime.binder.virtualthreads.VirtualThreadCollector; | ||
import io.quarkus.test.QuarkusUnitTest; | ||
|
||
@EnabledForJreRange(min = JRE.JAVA_21) | ||
public class VirtualThreadMetricsWithTagsTest { | ||
|
||
@RegisterExtension | ||
static final QuarkusUnitTest config = new QuarkusUnitTest() | ||
.withConfigurationResource("test-logging.properties") | ||
.overrideConfigKey("quarkus.micrometer.binder.virtual-threads.tags", "k1=v1, k2=v2") | ||
.overrideConfigKey("quarkus.redis.devservices.enabled", "false") | ||
.withEmptyApplication(); | ||
|
||
@Inject | ||
Instance<VirtualThreadCollector> collector; | ||
|
||
@Test | ||
void testInstancePresent() { | ||
assertTrue(collector.isResolvable(), "VirtualThreadCollector expected"); | ||
} | ||
|
||
@Test | ||
void testBinderCreated() { | ||
assertThat(collector.get().getBinder()).isNotNull(); | ||
} | ||
|
||
@Test | ||
void testTags() { | ||
assertThat(collector.get().getTags()).hasSize(2) | ||
.anySatisfy(t -> { | ||
assertThat(t.getKey()).isEqualTo("k1"); | ||
assertThat(t.getValue()).isEqualTo("v1"); | ||
}) | ||
.anySatisfy(t -> { | ||
assertThat(t.getKey()).isEqualTo("k2"); | ||
assertThat(t.getValue()).isEqualTo("v2"); | ||
}); | ||
} | ||
|
||
} |
112 changes: 112 additions & 0 deletions
112
...main/java/io/quarkus/micrometer/runtime/binder/virtualthreads/VirtualThreadCollector.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,112 @@ | ||
package io.quarkus.micrometer.runtime.binder.virtualthreads; | ||
|
||
import java.io.Closeable; | ||
import java.io.IOException; | ||
import java.util.List; | ||
import java.util.stream.Collectors; | ||
|
||
import jakarta.enterprise.context.ApplicationScoped; | ||
import jakarta.enterprise.event.Observes; | ||
import jakarta.inject.Inject; | ||
|
||
import org.jboss.logging.Logger; | ||
|
||
import io.micrometer.core.instrument.MeterRegistry; | ||
import io.micrometer.core.instrument.Metrics; | ||
import io.micrometer.core.instrument.Tag; | ||
import io.micrometer.core.instrument.binder.MeterBinder; | ||
import io.quarkus.micrometer.runtime.config.MicrometerConfig; | ||
import io.quarkus.runtime.ShutdownEvent; | ||
import io.quarkus.runtime.StartupEvent; | ||
import io.quarkus.runtime.util.JavaVersionUtil; | ||
|
||
/** | ||
* A component collecting metrics about virtual threads. | ||
* It will be only available when the virtual threads are enabled (Java 21+). | ||
* <p> | ||
* Note that metrics are collected using JFR events. | ||
*/ | ||
@ApplicationScoped | ||
public class VirtualThreadCollector { | ||
|
||
private static final String VIRTUAL_THREAD_BINDER_CLASSNAME = "io.micrometer.java21.instrument.binder.jdk.VirtualThreadMetrics"; | ||
private static final Logger LOGGER = Logger.getLogger(VirtualThreadCollector.class); | ||
|
||
final MeterRegistry registry = Metrics.globalRegistry; | ||
|
||
private final boolean enabled; | ||
private final MeterBinder binder; | ||
private final List<Tag> tags; | ||
|
||
@Inject | ||
public VirtualThreadCollector(MicrometerConfig mc) { | ||
var config = mc.binder.virtualThreads; | ||
this.enabled = JavaVersionUtil.isJava21OrHigher() && config.enabled.orElse(true); | ||
MeterBinder instantiated = null; | ||
if (enabled) { | ||
if (config.tags.isPresent()) { | ||
List<String> list = config.tags.get(); | ||
this.tags = list.stream().map(this::createTagFromEntry).collect(Collectors.toList()); | ||
} else { | ||
this.tags = List.of(); | ||
} | ||
try { | ||
instantiated = instantiate(tags); | ||
} catch (Exception e) { | ||
LOGGER.warnf(e, "Failed to instantiate " + VIRTUAL_THREAD_BINDER_CLASSNAME); | ||
} | ||
} else { | ||
this.tags = List.of(); | ||
} | ||
this.binder = instantiated; | ||
} | ||
|
||
/** | ||
* Use reflection to avoid calling a class touching Java 21+ APIs. | ||
* | ||
* @param tags the tags. | ||
* @return the binder, {@code null} if the instantiation failed. | ||
*/ | ||
public MeterBinder instantiate(List<Tag> tags) { | ||
try { | ||
Class<?> clazz = Class.forName(VIRTUAL_THREAD_BINDER_CLASSNAME); | ||
return (MeterBinder) clazz.getDeclaredConstructor(Iterable.class).newInstance(tags); | ||
} catch (Exception e) { | ||
throw new IllegalStateException("Failed to instantiate " + VIRTUAL_THREAD_BINDER_CLASSNAME, e); | ||
} | ||
} | ||
|
||
private Tag createTagFromEntry(String entry) { | ||
String[] parts = entry.trim().split("="); | ||
if (parts.length == 2) { | ||
return Tag.of(parts[0], parts[1]); | ||
} else { | ||
throw new IllegalStateException("Invalid tag: " + entry + " (expected key=value)"); | ||
} | ||
} | ||
|
||
public MeterBinder getBinder() { | ||
return binder; | ||
} | ||
|
||
public List<Tag> getTags() { | ||
return tags; | ||
} | ||
|
||
public void init(@Observes StartupEvent event) { | ||
if (enabled && binder != null) { | ||
binder.bindTo(registry); | ||
} | ||
} | ||
|
||
public void close(@Observes ShutdownEvent event) { | ||
if (binder instanceof Closeable) { | ||
try { | ||
((Closeable) binder).close(); | ||
} catch (IOException e) { | ||
LOGGER.warnf(e, "Failed to close " + VIRTUAL_THREAD_BINDER_CLASSNAME); | ||
} | ||
} | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.