Skip to content

Commit dc66266

Browse files
authored
Reuse JMX Insights in Scraper + Tomcat support (#1485)
1 parent b0aae23 commit dc66266

File tree

10 files changed

+568
-38
lines changed

10 files changed

+568
-38
lines changed

jmx-scraper/build.gradle.kts

+14-1
Original file line numberDiff line numberDiff line change
@@ -13,19 +13,32 @@ otelJava.moduleName.set("io.opentelemetry.contrib.jmxscraper")
1313

1414
application.mainClass.set("io.opentelemetry.contrib.jmxscraper.JmxScraper")
1515

16+
repositories {
17+
mavenCentral()
18+
mavenLocal()
19+
// TODO: remove snapshot repository once 2.9.0 is released
20+
maven {
21+
setUrl("https://oss.sonatype.org/content/repositories/snapshots")
22+
}
23+
}
24+
1625
dependencies {
1726
// TODO remove snapshot dependency on upstream once 2.9.0 is released
18-
// api(enforcedPlatform("io.opentelemetry.instrumentation:opentelemetry-instrumentation-bom-alpha:2.9.0-SNAPSHOT-alpha",))
27+
api(enforcedPlatform("io.opentelemetry.instrumentation:opentelemetry-instrumentation-bom-alpha:2.9.0-alpha-SNAPSHOT"))
1928

2029
implementation("io.opentelemetry:opentelemetry-api")
2130
implementation("io.opentelemetry:opentelemetry-sdk")
2231
implementation("io.opentelemetry:opentelemetry-sdk-metrics")
2332
implementation("io.opentelemetry:opentelemetry-sdk-extension-autoconfigure")
2433

34+
runtimeOnly("io.opentelemetry:opentelemetry-exporter-otlp")
35+
runtimeOnly("io.opentelemetry:opentelemetry-exporter-logging")
36+
2537
implementation("io.opentelemetry.instrumentation:opentelemetry-jmx-metrics")
2638

2739
testImplementation("org.junit-pioneer:junit-pioneer")
2840
testImplementation("io.opentelemetry:opentelemetry-sdk-testing")
41+
testImplementation("org.awaitility:awaitility")
2942
}
3043

3144
testing {

jmx-scraper/src/integrationTest/java/io/opentelemetry/contrib/jmxscraper/JmxScraperContainer.java

+1
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ public void start() {
7676
// for now only configure through JVM args
7777
List<String> arguments = new ArrayList<>();
7878
arguments.add("java");
79+
arguments.add("-Dotel.metrics.exporter=otlp");
7980
arguments.add("-Dotel.exporter.otlp.endpoint=" + endpoint);
8081

8182
if (!targetSystems.isEmpty()) {

jmx-scraper/src/integrationTest/java/io/opentelemetry/contrib/jmxscraper/target_systems/JvmIntegrationTest.java

+55-3
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,13 @@
55

66
package io.opentelemetry.contrib.jmxscraper.target_systems;
77

8+
import static io.opentelemetry.contrib.jmxscraper.target_systems.MetricAssertions.assertGauge;
9+
import static io.opentelemetry.contrib.jmxscraper.target_systems.MetricAssertions.assertTypedGauge;
10+
import static io.opentelemetry.contrib.jmxscraper.target_systems.MetricAssertions.assertTypedSum;
11+
812
import io.opentelemetry.contrib.jmxscraper.JmxScraperContainer;
913
import io.opentelemetry.contrib.jmxscraper.TestAppContainer;
10-
import io.opentelemetry.proto.collector.metrics.v1.ExportMetricsServiceRequest;
14+
import java.util.Arrays;
1115
import java.util.List;
1216
import org.testcontainers.containers.GenericContainer;
1317

@@ -25,7 +29,55 @@ protected JmxScraperContainer customizeScraperContainer(JmxScraperContainer scra
2529
}
2630

2731
@Override
28-
protected void verifyMetrics(List<ExportMetricsServiceRequest> metrics) {
29-
// TODO: Verify gathered metrics
32+
protected void verifyMetrics() {
33+
// those values depend on the JVM GC configured
34+
List<String> gcLabels =
35+
Arrays.asList(
36+
"Code Cache",
37+
"PS Eden Space",
38+
"PS Old Gen",
39+
"Metaspace",
40+
"Compressed Class Space",
41+
"PS Survivor Space");
42+
List<String> gcCollectionLabels = Arrays.asList("PS MarkSweep", "PS Scavenge");
43+
44+
waitAndAssertMetrics(
45+
metric -> assertGauge(metric, "jvm.classes.loaded", "number of loaded classes", "1"),
46+
metric ->
47+
assertTypedSum(
48+
metric,
49+
"jvm.gc.collections.count",
50+
"total number of collections that have occurred",
51+
"1",
52+
gcCollectionLabels),
53+
metric ->
54+
assertTypedSum(
55+
metric,
56+
"jvm.gc.collections.elapsed",
57+
"the approximate accumulated collection elapsed time in milliseconds",
58+
"ms",
59+
gcCollectionLabels),
60+
metric -> assertGauge(metric, "jvm.memory.heap.committed", "current heap usage", "by"),
61+
metric -> assertGauge(metric, "jvm.memory.heap.init", "current heap usage", "by"),
62+
metric -> assertGauge(metric, "jvm.memory.heap.max", "current heap usage", "by"),
63+
metric -> assertGauge(metric, "jvm.memory.heap.used", "current heap usage", "by"),
64+
metric ->
65+
assertGauge(metric, "jvm.memory.nonheap.committed", "current non-heap usage", "by"),
66+
metric -> assertGauge(metric, "jvm.memory.nonheap.init", "current non-heap usage", "by"),
67+
metric -> assertGauge(metric, "jvm.memory.nonheap.max", "current non-heap usage", "by"),
68+
metric -> assertGauge(metric, "jvm.memory.nonheap.used", "current non-heap usage", "by"),
69+
metric ->
70+
assertTypedGauge(
71+
metric, "jvm.memory.pool.committed", "current memory pool usage", "by", gcLabels),
72+
metric ->
73+
assertTypedGauge(
74+
metric, "jvm.memory.pool.init", "current memory pool usage", "by", gcLabels),
75+
metric ->
76+
assertTypedGauge(
77+
metric, "jvm.memory.pool.max", "current memory pool usage", "by", gcLabels),
78+
metric ->
79+
assertTypedGauge(
80+
metric, "jvm.memory.pool.used", "current memory pool usage", "by", gcLabels),
81+
metric -> assertGauge(metric, "jvm.threads.count", "number of threads", "1"));
3082
}
3183
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.contrib.jmxscraper.target_systems;
7+
8+
import static org.assertj.core.api.Assertions.assertThat;
9+
import static org.assertj.core.api.Assertions.entry;
10+
11+
import io.opentelemetry.proto.common.v1.KeyValue;
12+
import io.opentelemetry.proto.metrics.v1.Metric;
13+
import io.opentelemetry.proto.metrics.v1.NumberDataPoint;
14+
import java.util.Arrays;
15+
import java.util.List;
16+
import java.util.Map;
17+
import java.util.function.Consumer;
18+
import java.util.stream.Collectors;
19+
import org.assertj.core.api.MapAssert;
20+
21+
/** Metrics assertions */
22+
class MetricAssertions {
23+
24+
private MetricAssertions() {}
25+
26+
static void assertGauge(Metric metric, String name, String description, String unit) {
27+
assertThat(metric.getName()).isEqualTo(name);
28+
assertThat(metric.getDescription()).isEqualTo(description);
29+
assertThat(metric.getUnit()).isEqualTo(unit);
30+
assertThat(metric.hasGauge()).isTrue();
31+
assertThat(metric.getGauge().getDataPointsList())
32+
.satisfiesExactly(point -> assertThat(point.getAttributesList()).isEmpty());
33+
}
34+
35+
static void assertSum(Metric metric, String name, String description, String unit) {
36+
assertSum(metric, name, description, unit, /* isMonotonic= */ true);
37+
}
38+
39+
static void assertSum(
40+
Metric metric, String name, String description, String unit, boolean isMonotonic) {
41+
assertThat(metric.getName()).isEqualTo(name);
42+
assertThat(metric.getDescription()).isEqualTo(description);
43+
assertThat(metric.getUnit()).isEqualTo(unit);
44+
assertThat(metric.hasSum()).isTrue();
45+
assertThat(metric.getSum().getDataPointsList())
46+
.satisfiesExactly(point -> assertThat(point.getAttributesList()).isEmpty());
47+
assertThat(metric.getSum().getIsMonotonic()).isEqualTo(isMonotonic);
48+
}
49+
50+
static void assertTypedGauge(
51+
Metric metric, String name, String description, String unit, List<String> types) {
52+
assertThat(metric.getName()).isEqualTo(name);
53+
assertThat(metric.getDescription()).isEqualTo(description);
54+
assertThat(metric.getUnit()).isEqualTo(unit);
55+
assertThat(metric.hasGauge()).isTrue();
56+
assertTypedPoints(metric.getGauge().getDataPointsList(), types);
57+
}
58+
59+
static void assertTypedSum(
60+
Metric metric, String name, String description, String unit, List<String> types) {
61+
assertThat(metric.getName()).isEqualTo(name);
62+
assertThat(metric.getDescription()).isEqualTo(description);
63+
assertThat(metric.getUnit()).isEqualTo(unit);
64+
assertThat(metric.hasSum()).isTrue();
65+
assertTypedPoints(metric.getSum().getDataPointsList(), types);
66+
}
67+
68+
@SafeVarargs
69+
static void assertSumWithAttributes(
70+
Metric metric,
71+
String name,
72+
String description,
73+
String unit,
74+
Consumer<MapAssert<String, String>>... attributeGroupAssertions) {
75+
assertThat(metric.getName()).isEqualTo(name);
76+
assertThat(metric.getDescription()).isEqualTo(description);
77+
assertThat(metric.getUnit()).isEqualTo(unit);
78+
assertThat(metric.hasSum()).isTrue();
79+
assertAttributedPoints(metric.getSum().getDataPointsList(), attributeGroupAssertions);
80+
}
81+
82+
@SafeVarargs
83+
static void assertGaugeWithAttributes(
84+
Metric metric,
85+
String name,
86+
String description,
87+
String unit,
88+
Consumer<MapAssert<String, String>>... attributeGroupAssertions) {
89+
assertThat(metric.getName()).isEqualTo(name);
90+
assertThat(metric.getDescription()).isEqualTo(description);
91+
assertThat(metric.getUnit()).isEqualTo(unit);
92+
assertThat(metric.hasGauge()).isTrue();
93+
assertAttributedPoints(metric.getGauge().getDataPointsList(), attributeGroupAssertions);
94+
}
95+
96+
@SuppressWarnings("unchecked")
97+
private static void assertTypedPoints(List<NumberDataPoint> points, List<String> types) {
98+
Consumer<MapAssert<String, String>>[] assertions =
99+
types.stream()
100+
.map(
101+
type ->
102+
(Consumer<MapAssert<String, String>>)
103+
attrs -> attrs.containsOnly(entry("name", type)))
104+
.toArray(Consumer[]::new);
105+
106+
assertAttributedPoints(points, assertions);
107+
}
108+
109+
@SuppressWarnings("unchecked")
110+
private static void assertAttributedPoints(
111+
List<NumberDataPoint> points,
112+
Consumer<MapAssert<String, String>>... attributeGroupAssertions) {
113+
Consumer<Map<String, String>>[] assertions =
114+
Arrays.stream(attributeGroupAssertions)
115+
.map(assertion -> (Consumer<Map<String, String>>) m -> assertion.accept(assertThat(m)))
116+
.toArray(Consumer[]::new);
117+
assertThat(points)
118+
.extracting(
119+
numberDataPoint ->
120+
numberDataPoint.getAttributesList().stream()
121+
.collect(
122+
Collectors.toMap(
123+
KeyValue::getKey, keyValue -> keyValue.getValue().getStringValue())))
124+
.satisfiesExactlyInAnyOrder(assertions);
125+
}
126+
}

jmx-scraper/src/integrationTest/java/io/opentelemetry/contrib/jmxscraper/target_systems/TargetSystemIntegrationTest.java

+52-2
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@
55

66
package io.opentelemetry.contrib.jmxscraper.target_systems;
77

8+
import static org.assertj.core.api.Assertions.assertThat;
9+
import static org.awaitility.Awaitility.await;
10+
811
import com.linecorp.armeria.server.ServerBuilder;
912
import com.linecorp.armeria.server.grpc.GrpcService;
1013
import com.linecorp.armeria.testing.junit5.server.ServerExtension;
@@ -13,11 +16,18 @@
1316
import io.opentelemetry.proto.collector.metrics.v1.ExportMetricsServiceRequest;
1417
import io.opentelemetry.proto.collector.metrics.v1.ExportMetricsServiceResponse;
1518
import io.opentelemetry.proto.collector.metrics.v1.MetricsServiceGrpc;
19+
import io.opentelemetry.proto.metrics.v1.Metric;
20+
import io.opentelemetry.proto.metrics.v1.ResourceMetrics;
21+
import java.time.Duration;
1622
import java.util.ArrayList;
23+
import java.util.Arrays;
24+
import java.util.Collection;
1725
import java.util.List;
1826
import java.util.concurrent.BlockingQueue;
1927
import java.util.concurrent.ExecutionException;
2028
import java.util.concurrent.LinkedBlockingDeque;
29+
import java.util.function.Consumer;
30+
import java.util.stream.Collectors;
2131
import org.junit.jupiter.api.AfterAll;
2232
import org.junit.jupiter.api.AfterEach;
2333
import org.junit.jupiter.api.BeforeAll;
@@ -30,6 +40,7 @@
3040
import org.testcontainers.containers.output.Slf4jLogConsumer;
3141

3242
public abstract class TargetSystemIntegrationTest {
43+
private static final Logger logger = LoggerFactory.getLogger(TargetSystemIntegrationTest.class);
3344
private static final Logger targetSystemLogger = LoggerFactory.getLogger("TargetSystemContainer");
3445
private static final Logger jmxScraperLogger = LoggerFactory.getLogger("JmxScraperContainer");
3546
private static final String TARGET_SYSTEM_NETWORK_ALIAS = "targetsystem";
@@ -105,10 +116,45 @@ void endToEndTest() {
105116
scraper = customizeScraperContainer(scraper);
106117
scraper.start();
107118

108-
verifyMetrics(otlpServer.getMetrics());
119+
verifyMetrics();
120+
}
121+
122+
protected void waitAndAssertMetrics(Iterable<Consumer<Metric>> assertions) {
123+
await()
124+
.atMost(Duration.ofSeconds(30))
125+
.untilAsserted(
126+
() -> {
127+
List<ExportMetricsServiceRequest> receivedMetrics = otlpServer.getMetrics();
128+
assertThat(receivedMetrics).describedAs("no metric received").isNotEmpty();
129+
130+
List<Metric> metrics =
131+
receivedMetrics.stream()
132+
.map(ExportMetricsServiceRequest::getResourceMetricsList)
133+
.flatMap(rm -> rm.stream().map(ResourceMetrics::getScopeMetricsList))
134+
.flatMap(Collection::stream)
135+
.filter(
136+
// TODO: disabling batch span exporter might help remove unwanted metrics
137+
sm -> sm.getScope().getName().equals("io.opentelemetry.jmx"))
138+
.flatMap(sm -> sm.getMetricsList().stream())
139+
.collect(Collectors.toList());
140+
141+
assertThat(metrics)
142+
.describedAs("metrics reported but none from JMX scraper")
143+
.isNotEmpty();
144+
145+
for (Consumer<Metric> assertion : assertions) {
146+
assertThat(metrics).anySatisfy(assertion);
147+
}
148+
});
109149
}
110150

111-
protected abstract void verifyMetrics(List<ExportMetricsServiceRequest> metrics);
151+
@SafeVarargs
152+
@SuppressWarnings("varargs")
153+
protected final void waitAndAssertMetrics(Consumer<Metric>... assertions) {
154+
waitAndAssertMetrics(Arrays.asList(assertions));
155+
}
156+
157+
protected abstract void verifyMetrics();
112158

113159
protected JmxScraperContainer customizeScraperContainer(JmxScraperContainer scraper) {
114160
return scraper;
@@ -137,6 +183,10 @@ protected void configure(ServerBuilder sb) {
137183
public void export(
138184
ExportMetricsServiceRequest request,
139185
StreamObserver<ExportMetricsServiceResponse> responseObserver) {
186+
187+
// verbose but helpful to diagnose what is received
188+
logger.debug("receiving metrics {}", request);
189+
140190
metricRequests.add(request);
141191
responseObserver.onNext(ExportMetricsServiceResponse.getDefaultInstance());
142192
responseObserver.onCompleted();

0 commit comments

Comments
 (0)