Skip to content

Commit

Permalink
Merge pull request quarkusio#44686 from cescoffier/virtual-thread-met…
Browse files Browse the repository at this point in the history
…rics

Enable Virtual Thread Binder if micrometer-java21 is on the Classpath
  • Loading branch information
brunobat authored Nov 28, 2024
2 parents 9c47990 + feae06c commit d1c1e91
Show file tree
Hide file tree
Showing 16 changed files with 593 additions and 2 deletions.
4 changes: 2 additions & 2 deletions .github/virtual-threads-tests.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
"include": [
{
"category": "Main",
"timeout": 50,
"test-modules": "virtual-threads-disabled, grpc-virtual-threads, mailer-virtual-threads, redis-virtual-threads, rest-client-reactive-virtual-threads, resteasy-reactive-virtual-threads, vertx-event-bus-virtual-threads, scheduler-virtual-threads, quartz-virtual-threads",
"timeout": 60,
"test-modules": "virtual-threads-disabled, grpc-virtual-threads, mailer-virtual-threads, redis-virtual-threads, rest-client-reactive-virtual-threads, resteasy-reactive-virtual-threads, vertx-event-bus-virtual-threads, scheduler-virtual-threads, quartz-virtual-threads, metrics-virtual-threads",
"os-name": "ubuntu-latest"
},
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ public class JavaVersionUtil {
private static boolean IS_JAVA_16_OR_OLDER;
private static boolean IS_JAVA_17_OR_NEWER;
private static boolean IS_JAVA_19_OR_NEWER;
private static boolean IS_JAVA_21_OR_NEWER;

static {
performChecks();
Expand All @@ -28,12 +29,14 @@ static void performChecks() {
IS_JAVA_16_OR_OLDER = (first <= 16);
IS_JAVA_17_OR_NEWER = (first >= 17);
IS_JAVA_19_OR_NEWER = (first >= 19);
IS_JAVA_21_OR_NEWER = (first >= 21);
} else {
IS_JAVA_11_OR_NEWER = false;
IS_JAVA_13_OR_NEWER = false;
IS_JAVA_16_OR_OLDER = false;
IS_JAVA_17_OR_NEWER = false;
IS_JAVA_19_OR_NEWER = false;
IS_JAVA_21_OR_NEWER = false;
}

String vmVendor = System.getProperty("java.vm.vendor");
Expand All @@ -60,6 +63,10 @@ public static boolean isJava19OrHigher() {
return IS_JAVA_19_OR_NEWER;
}

public static boolean isJava21OrHigher() {
return IS_JAVA_21_OR_NEWER;
}

public static boolean isGraalvmJdk() {
return IS_GRAALVM_JDK;
}
Expand Down
32 changes: 32 additions & 0 deletions docs/src/main/asciidoc/virtual-threads.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -550,6 +550,38 @@ public class LoomUnitExampleTest {
}
----

== Virtual thread metrics

You can enable the Micrometer Virtual Thread _binder_ by adding the following artifact to your application:

[source,xml]
----
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-java21</artifactId>
</dependency>
----

This binder keeps track of the number of pinning events and the number of virtual threads failed to be started or un-parked.
See the https://docs.micrometer.io/micrometer/reference/reference/jvm.html#_java_21_metrics[MicroMeter documentation] for more information.

You can explicitly disable the binder by setting the following property in your `application.properties`:

[source,properties]
----
# The binder is automatically enabled if the micrometer-java21 dependency is present
quarkus.micrometer.binder.virtual-threads.enabled=false
----

In addition, if the application is running on a JVM that does not support virtual threads (prior to Java 21), the binder is automatically disabled.

You can associate tags to the collected metrics by setting the following properties in your `application.properties`:

[source,properties]
----
quarkus.micrometer.binder.virtual-threads.tags=tag_1=value_1, tag_2=value_2
----

== Additional references

- https://dl.acm.org/doi/10.1145/3583678.3596895[Considerations for integrating virtual threads in a Java framework: a Quarkus example in a resource-constrained environment]
14 changes: 14 additions & 0 deletions extensions/micrometer/deployment/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -202,5 +202,19 @@
</plugins>
</build>
</profile>

<profile>
<id>Java 21+</id>
<activation>
<jdk>[21,)</jdk>
</activation>
<dependencies>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-java21</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</profile>
</profiles>
</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package io.quarkus.micrometer.deployment.binder;

import java.util.function.BooleanSupplier;

import io.quarkus.arc.deployment.AdditionalBeanBuildItem;
import io.quarkus.deployment.annotations.BuildProducer;
import io.quarkus.deployment.annotations.BuildStep;
import io.quarkus.deployment.builditem.NativeMonitoringBuildItem;
import io.quarkus.deployment.pkg.NativeConfig;
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();
}

@BuildStep(onlyIf = VirtualThreadSupportEnabled.class)
void addNativeMonitoring(BuildProducer<NativeMonitoringBuildItem> nativeMonitoring) {
nativeMonitoring.produce(new NativeMonitoringBuildItem(NativeConfig.MonitoringOption.JFR));
}
}
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());
}

}
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();
}

}
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");
});
}

}
Loading

0 comments on commit d1c1e91

Please sign in to comment.