Skip to content

Commit 3681372

Browse files
authored
Merge pull request #48328 from brunobat/otel-jdk17-metrics
2 parents 43f2aa9 + f6e64ed commit 3681372

File tree

9 files changed

+562
-82
lines changed

9 files changed

+562
-82
lines changed

docs/src/main/asciidoc/opentelemetry-metrics.adoc

Lines changed: 151 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -536,6 +536,8 @@ See the main xref:opentelemetry.adoc#resource[OpenTelemetry Guide resources] sec
536536

537537
== Automatic instrumentation
538538

539+
=== Microprofile 2.0
540+
539541
We provide automatic instrumentation for JVM metrics and HTTP server requests metrics according to the https://github.com/eclipse/microprofile-telemetry/blob/2.0/spec/src/main/asciidoc/metrics.adoc[Microprofile Metrics 2.0 specification].
540542

541543
These metrics can be disabled by setting the following properties to `false`:
@@ -546,12 +548,159 @@ quarkus.otel.instrument.jvm-metrics=false
546548
quarkus.otel.instrument.http-server-metrics=false
547549
----
548550

551+
These are the metrics produced by the OpenTelemetry extension when metrics are enabled, as of June 12, 2025:
552+
553+
|===
554+
|Metric Name |Description |Type |Available on JVM? |Available on Native?|MP 2.0?
555+
556+
|http.server.request.duration
557+
|Duration of HTTP server requests
558+
|HISTOGRAM
559+
|Y
560+
|Y
561+
|Y
562+
563+
|jvm.memory.committed
564+
|Measure of memory committed
565+
|LONG_SUM
566+
|Y
567+
|No data produced
568+
|Y
569+
570+
|jvm.memory.used
571+
|Measure of memory used
572+
|LONG_SUM
573+
|Y
574+
|No data produced
575+
|Y
576+
577+
|jvm.memory.limit
578+
|Measure of max obtainable memory
579+
|LONG_SUM
580+
|Y
581+
|Not present
582+
|Y
583+
584+
|jvm.memory.used_after_last_gc
585+
|Measure of memory used, as measured after the most recent garbage collection event on this pool.
586+
|LONG_SUM
587+
|Y
588+
|No data produced
589+
|Y
590+
591+
|jvm.gc.duration
592+
|Duration of JVM garbage collection actions
593+
|HISTOGRAM
594+
|Y
595+
|Not present
596+
|Y
597+
598+
|jvm.class.count
599+
|Number of classes currently loaded.
600+
|LONG_SUM
601+
|Y
602+
|No data produced
603+
|Y
604+
605+
|jvm.class.loaded
606+
|Number of classes loaded since JVM start.
607+
|LONG_SUM
608+
|Y
609+
|No data produced
610+
|Y
611+
612+
|jvm.class.unloaded
613+
|Number of classes unloaded since JVM start.
614+
|LONG_SUM
615+
|Y
616+
|No data produced
617+
|Y
618+
619+
|jvm.cpu.count
620+
|Number of processors available to the Java virtual machine.
621+
|LONG_SUM
622+
|Y
623+
|Y
624+
|N
625+
626+
|jvm.cpu.limit
627+
|
628+
|LONG_SUM
629+
|Y
630+
|No data produced
631+
|N
632+
633+
|jvm.cpu.time
634+
|CPU time used by the process as reported by the JVM.
635+
|DOUBLE_SUM
636+
|Y
637+
|Not present
638+
|N
639+
640+
|jvm.system.cpu.utilization
641+
|CPU time used by the process as reported by the JVM.
642+
|DOUBLE_SUM
643+
|Not present
644+
|No data produced
645+
|N
646+
647+
|jvm.cpu.recent_utilization
648+
|Recent CPU utilization for the process as reported by the JVM.
649+
|DOUBLE_GAUGE
650+
|Y
651+
|No data produced
652+
|N
653+
654+
|jvm.cpu.longlock
655+
|Long lock times
656+
|HISTOGRAM
657+
|Y
658+
|Y
659+
|N
660+
661+
|jvm.cpu.context_switch
662+
|
663+
|DOUBLE_SUM
664+
|Y
665+
|No data produced
666+
|N
667+
668+
// not on native
669+
|jvm.network.io
670+
|Network read/write bytes.
671+
|HISTOGRAM
672+
|Y
673+
|Not present
674+
|N
675+
676+
|jvm.network.time
677+
|Network read/write duration.
678+
|HISTOGRAM
679+
|Y
680+
|Not present
681+
|N
682+
683+
|jvm.thread.count
684+
|Number of executing platform threads.
685+
|LONG_SUM
686+
|Y
687+
|Y
688+
|Y
689+
690+
|===
691+
692+
693+
The native image assessment above was performed using GraalVM 23.0.2. Work will be done in the future to improve the metrics support on the native image builds.
694+
549695
[NOTE]
550696
====
551-
- It is recommended to disable these instrumentations if you are using the Micrometer extension as well.
697+
- It is recommended to disable these instrumentations if you are using the `quarkus-micrometer` or the `quarkus-micrometer-opentelemetry` extensions as well.
552698
====
553699

554-
We plan to bridge the existing Quarkus Micrometer extension metrics to OpenTelemetry in the future.
700+
=== Micrometer to OpenTelemetry bridge
701+
702+
The Micrometer to OpenTelemetry bridge unifies all telemetry in Quarkus. It generates Micrometer's metrics but sends
703+
them along the OpenTelemetry telemetry output. For more details please visit the xref:telemetry-micrometer-to-opentelemetry.adoc[Micrometer and OpenTelemetry extension].
555704

556705
== Exporters
557706
See the main xref:opentelemetry.adoc#exporters[OpenTelemetry Guide exporters] section.
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
package io.quarkus.opentelemetry.deployment.metrics;
2+
3+
import static io.opentelemetry.sdk.metrics.data.MetricDataType.*;
4+
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
5+
6+
import java.util.List;
7+
import java.util.Set;
8+
import java.util.stream.Collectors;
9+
10+
import jakarta.inject.Inject;
11+
12+
import io.opentelemetry.sdk.common.InstrumentationScopeInfo;
13+
import io.opentelemetry.sdk.metrics.data.MetricData;
14+
import io.opentelemetry.sdk.metrics.data.MetricDataType;
15+
import io.quarkus.opentelemetry.deployment.common.exporter.InMemoryMetricExporter;
16+
17+
public abstract class BaseJvmMetricsTest {
18+
19+
@Inject
20+
protected InMemoryMetricExporter metricExporter;
21+
22+
void assertMetric(MetricToAssert metric) {
23+
assertMetric(metric.name(), metric.description(), metric.metricUnit(), metric.metricType());
24+
}
25+
26+
void assertMetric(final String metricName,
27+
final String metricDescription, final String metricUnit,
28+
final MetricDataType metricType) {
29+
30+
metricExporter.assertCountAtLeast(metricName, null, 1);
31+
32+
List<MetricData> finishedMetricItems = metricExporter.getFinishedMetricItems(metricName, null);
33+
Set<String> scopeNames = finishedMetricItems.stream()
34+
.map(MetricData::getInstrumentationScopeInfo)
35+
.map(InstrumentationScopeInfo::getName)
36+
.collect(Collectors.toSet());
37+
38+
MetricData foundMetric = finishedMetricItems.size() > 0 ? finishedMetricItems.get(finishedMetricItems.size() - 1)
39+
: null; //get last
40+
41+
assertThat(foundMetric).isNotNull();
42+
assertThat(foundMetric.getName()).isEqualTo(metricName);
43+
assertThat(foundMetric.getDescription())
44+
.withFailMessage(metricName + " Expected: " + metricDescription + " found: " + foundMetric.getDescription())
45+
.isEqualTo(metricDescription);
46+
assertThat(foundMetric.getType())
47+
.withFailMessage(metricName + " Expected: " + metricType + " found: " + foundMetric.getType())
48+
.isEqualTo(metricType);
49+
assertThat(foundMetric.getUnit())
50+
.withFailMessage(metricName + " Expected: " + metricUnit + " found: " + foundMetric.getUnit())
51+
.isEqualTo(metricUnit);
52+
53+
assertThat(scopeNames.size())
54+
.withFailMessage(metricName + " found: " + scopeNames)
55+
.isLessThanOrEqualTo(1);
56+
57+
if (foundMetric.getName().equals("jvm.cpu.limit")) {
58+
return; // skip flaky metrics
59+
}
60+
61+
// only one of them will be present per test
62+
foundMetric.getDoubleSumData().getPoints().stream()
63+
.forEach(point -> {
64+
assertThat(point.getValue())
65+
.withFailMessage(metricName + ": Double" + point.getValue() + " was not an expected result")
66+
.isGreaterThan(0);
67+
});
68+
69+
foundMetric.getLongSumData().getPoints().stream()
70+
.forEach(point -> {
71+
assertThat(point.getValue())
72+
.withFailMessage(metricName + ": Long" + point.getValue() + " was not an expected result")
73+
.isGreaterThanOrEqualTo(0);
74+
});
75+
76+
foundMetric.getDoubleGaugeData().getPoints().stream()
77+
.forEach(point -> {
78+
assertThat(point.getValue())
79+
.withFailMessage(metricName + ": Double" + point.getValue() + " was not an expected result")
80+
.isGreaterThanOrEqualTo(0);
81+
});
82+
83+
foundMetric.getHistogramData().getPoints().stream()
84+
.forEach(point -> {
85+
assertThat(point.hasMin()).isTrue();
86+
assertThat(point.hasMax()).isTrue();
87+
assertThat(point.getCounts().size()).isGreaterThan(0);
88+
});
89+
}
90+
91+
record MetricToAssert(String name, String description, String metricUnit, MetricDataType metricType) {
92+
}
93+
}
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
package io.quarkus.opentelemetry.deployment.metrics;
2+
3+
import static io.opentelemetry.sdk.metrics.data.MetricDataType.*;
4+
import static java.util.concurrent.TimeUnit.SECONDS;
5+
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
6+
import static org.hamcrest.Matchers.is;
7+
import static org.junit.jupiter.api.Assertions.assertEquals;
8+
9+
import java.util.List;
10+
import java.util.Set;
11+
import java.util.stream.Collectors;
12+
13+
import jakarta.ws.rs.GET;
14+
import jakarta.ws.rs.Path;
15+
import jakarta.ws.rs.core.Response;
16+
17+
import org.awaitility.Awaitility;
18+
import org.jboss.shrinkwrap.api.ShrinkWrap;
19+
import org.jboss.shrinkwrap.api.asset.StringAsset;
20+
import org.jboss.shrinkwrap.api.spec.JavaArchive;
21+
import org.junit.jupiter.api.Test;
22+
import org.junit.jupiter.api.extension.RegisterExtension;
23+
24+
import io.opentelemetry.sdk.metrics.data.MetricData;
25+
import io.quarkus.opentelemetry.deployment.common.exporter.InMemoryMetricExporter;
26+
import io.quarkus.opentelemetry.deployment.common.exporter.InMemoryMetricExporterProvider;
27+
import io.quarkus.test.QuarkusUnitTest;
28+
import io.restassured.RestAssured;
29+
30+
/**
31+
* Validate all JVM metrics being produced.
32+
*/
33+
public class JvmMetricsTest extends BaseJvmMetricsTest {
34+
@RegisterExtension
35+
static final QuarkusUnitTest TEST = new QuarkusUnitTest()
36+
.setArchiveProducer(
37+
() -> ShrinkWrap.create(JavaArchive.class)
38+
.addClasses(InMemoryMetricExporter.class, InMemoryMetricExporterProvider.class)
39+
.addAsResource(new StringAsset(InMemoryMetricExporterProvider.class.getCanonicalName()),
40+
"META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.metrics.ConfigurableMetricExporterProvider")
41+
.add(new StringAsset(
42+
"quarkus.otel.metrics.enabled=true\n" +
43+
"quarkus.otel.traces.exporter=none\n" +
44+
"quarkus.otel.logs.exporter=none\n" +
45+
"quarkus.otel.metrics.exporter=in-memory\n" +
46+
"quarkus.otel.metric.export.interval=300ms\n"),
47+
"application.properties"));
48+
49+
@Test
50+
void allMetrics() throws InterruptedException {
51+
Set<MetricToAssert> allMetrics = Set.of(
52+
new JvmMetricsTest.MetricToAssert("http.server.request.duration", "Duration of HTTP server requests.", "s",
53+
HISTOGRAM), // just because we generate load with HTTP
54+
new JvmMetricsTest.MetricToAssert("jvm.memory.committed", "Measure of memory committed.", "By", LONG_SUM),
55+
new JvmMetricsTest.MetricToAssert("jvm.memory.used", "Measure of memory used.", "By", LONG_SUM),
56+
new JvmMetricsTest.MetricToAssert("jvm.memory.limit", "Measure of max obtainable memory.", "By", LONG_SUM),
57+
new JvmMetricsTest.MetricToAssert("jvm.memory.used_after_last_gc",
58+
"Measure of memory used, as measured after the most recent garbage collection event on this pool.",
59+
"By", LONG_SUM),
60+
new JvmMetricsTest.MetricToAssert("jvm.gc.duration", "Duration of JVM garbage collection actions.", "s",
61+
HISTOGRAM),
62+
new JvmMetricsTest.MetricToAssert("jvm.class.count", "Number of classes currently loaded.", "{class}",
63+
LONG_SUM),
64+
new JvmMetricsTest.MetricToAssert("jvm.class.loaded", "Number of classes loaded since JVM start.", "{class}",
65+
LONG_SUM),
66+
new JvmMetricsTest.MetricToAssert("jvm.class.unloaded", "Number of classes unloaded since JVM start.",
67+
"{class}", LONG_SUM),
68+
new JvmMetricsTest.MetricToAssert("jvm.cpu.count",
69+
"Number of processors available to the Java virtual machine.", "{cpu}", LONG_SUM),
70+
new JvmMetricsTest.MetricToAssert("jvm.cpu.limit", "", "1", LONG_SUM),
71+
new JvmMetricsTest.MetricToAssert("jvm.cpu.time", "CPU time used by the process as reported by the JVM.", "s",
72+
DOUBLE_SUM),
73+
new JvmMetricsTest.MetricToAssert("jvm.cpu.recent_utilization",
74+
"Recent CPU utilization for the process as reported by the JVM.", "1", DOUBLE_GAUGE),
75+
new JvmMetricsTest.MetricToAssert("jvm.cpu.longlock", "Long lock times", "s", HISTOGRAM),
76+
new JvmMetricsTest.MetricToAssert("jvm.cpu.context_switch", "", "Hz", DOUBLE_SUM),
77+
new JvmMetricsTest.MetricToAssert("jvm.network.io", "Network read/write bytes.", "By", HISTOGRAM), //
78+
new JvmMetricsTest.MetricToAssert("jvm.network.time", "Network read/write duration.", "s", HISTOGRAM), //
79+
new JvmMetricsTest.MetricToAssert("jvm.thread.count", "Number of executing platform threads.", "{thread}",
80+
LONG_SUM));
81+
82+
// Force GC to run
83+
System.gc();
84+
85+
// only to get some load
86+
RestAssured.when()
87+
.get("/span").then()
88+
.statusCode(200)
89+
.body(is("hello"));
90+
91+
Awaitility.await().atMost(10, SECONDS)
92+
.untilAsserted(() -> assertEquals(allMetrics.size(),
93+
metricExporter.getFinishedMetricItems().stream()
94+
.map(MetricData::getName)
95+
.collect(Collectors.toSet())
96+
.size(),
97+
"Found: " + metricExporter.getFinishedMetricItems().stream()
98+
.map(MetricData::getName)
99+
.collect(Collectors.toSet())));
100+
101+
List<MetricData> finishedMetricItems = metricExporter.getFinishedMetricItems();
102+
103+
Set<String> foundMetricNames = finishedMetricItems.stream()
104+
.map(MetricData::getName)
105+
.collect(Collectors.toSet());
106+
107+
assertThat(foundMetricNames).isEqualTo(allMetrics.stream()
108+
.map(MetricToAssert::name)
109+
.collect(Collectors.toSet()));
110+
111+
allMetrics.forEach(metric -> {
112+
assertMetric(metric);
113+
});
114+
}
115+
116+
@Path("/")
117+
public static class SpanResource {
118+
@GET
119+
@Path("/span")
120+
public Response span() {
121+
return Response.ok("hello").build();
122+
}
123+
}
124+
}
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
import io.quarkus.test.common.http.TestHTTPResource;
3131
import io.restassured.RestAssured;
3232

33-
public class HttpServerMetricsTest {
33+
public class MpHttpServerMetricsTest {
3434

3535
@RegisterExtension
3636
static final QuarkusUnitTest TEST = new QuarkusUnitTest()

0 commit comments

Comments
 (0)