Skip to content

Commit ad6d5fe

Browse files
authored
JMXFetch support for GraalVM Native (#8569)
* Add JMXFetch.run to TracerActivation * Replace org.datadog.jmxfetch.Status.generateJson to fix the GraalVM native build error * Replace org.datadog.jmxfetch.reporter.JsonReporter.doSendServiceCheck to fix the GraalVM native build error * Add jmxfetch config resources needed at runtime * Remove JsonProcessingException stub that won't be needed for com.datadoghq:jmxfetch:0.49.7+ * Adjust GraalVM Native substitutions for JMXFetch * GraalVM Native JMXFetch smoke test * Bump jmxfetch to 0.49.7 for GraalVM Native to resolve GraalVM native build errors related to json and yaml parsing libraries * Add jmxfetch integrations metricconfigs for native builds * Add more extensive error reporting related to metricconfigs for debugging purposes
1 parent d8111df commit ad6d5fe

File tree

13 files changed

+168
-24
lines changed

13 files changed

+168
-24
lines changed

dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/bootstrap/Agent.java

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -184,13 +184,11 @@ public static void start(
184184

185185
if (Platform.isNativeImageBuilder()) {
186186
// these default services are not used during native-image builds
187-
jmxFetchEnabled = false;
188187
remoteConfigEnabled = false;
189188
telemetryEnabled = false;
190-
// apply trace instrumentation, but skip starting other services
189+
// apply trace instrumentation, but skip other products at native-image build time
191190
startDatadogAgent(initTelemetry, inst);
192191
StaticEventLogger.end("Agent.start");
193-
194192
return;
195193
}
196194

dd-java-agent/agent-jmxfetch/build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ plugins {
1111
apply from: "$rootDir/gradle/java.gradle"
1212

1313
dependencies {
14-
api('com.datadoghq:jmxfetch:0.49.6') {
14+
api('com.datadoghq:jmxfetch:0.49.7') {
1515
exclude group: 'org.slf4j', module: 'slf4j-api'
1616
exclude group: 'org.slf4j', module: 'slf4j-jdk14'
1717
exclude group: 'com.beust', module: 'jcommander'

dd-java-agent/agent-jmxfetch/src/main/java/com/fasterxml/jackson/core/JsonProcessingException.java

Lines changed: 0 additions & 4 deletions
This file was deleted.

dd-java-agent/agent-jmxfetch/src/main/java/datadog/trace/agent/jmxfetch/JMXFetch.java

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import datadog.trace.api.StatsDClient;
1010
import datadog.trace.api.StatsDClientManager;
1111
import datadog.trace.api.flare.TracerFlare;
12+
import datadog.trace.api.telemetry.LogCollector;
1213
import de.thetaphi.forbiddenapis.SuppressForbidden;
1314
import java.io.IOException;
1415
import java.io.InputStream;
@@ -174,6 +175,7 @@ private static List<String> getInternalMetricFiles() {
174175
log.debug("metricconfigs not found. returning empty set");
175176
return Collections.emptyList();
176177
}
178+
log.debug("reading found metricconfigs");
177179
Scanner scanner = new Scanner(metricConfigsStream);
178180
scanner.useDelimiter("\n");
179181
final List<String> result = new ArrayList<>();
@@ -183,8 +185,19 @@ private static List<String> getInternalMetricFiles() {
183185
integrationName.clear();
184186
integrationName.add(config.replace(".yaml", ""));
185187

186-
if (Config.get().isJmxFetchIntegrationEnabled(integrationName, false)) {
188+
if (!Config.get().isJmxFetchIntegrationEnabled(integrationName, false)) {
189+
log.debug(
190+
"skipping metric config `{}` because integration {} is disabled",
191+
config,
192+
integrationName);
193+
} else {
187194
final URL resource = JMXFetch.class.getResource("metricconfigs/" + config);
195+
if (resource == null) {
196+
log.debug(
197+
LogCollector.SEND_TELEMETRY, "metric config `{}` not found. skipping", config);
198+
continue;
199+
}
200+
log.debug("adding metric config `{}`", config);
188201

189202
// jar!/ means a file internal to a jar, only add the part after if it exists
190203
final String path = resource.getPath();

dd-java-agent/agent-tooling/build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ dependencies {
4141
api(project(':dd-java-agent:agent-bootstrap')) {
4242
exclude group: 'com.datadoghq', module: 'agent-logging'
4343
}
44+
compileOnly project(':dd-java-agent:agent-jmxfetch')
4445
compileOnly project(':dd-java-agent:agent-profiling')
4546
api group: 'com.blogspot.mydailyjava', name: 'weak-lock-free', version: '0.17'
4647
api libs.bytebuddy

dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/nativeimage/TracerActivation.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,11 @@
22

33
import com.datadog.profiling.controller.openjdk.JFREventContextIntegration;
44
import datadog.communication.ddagent.SharedCommunicationObjects;
5+
import datadog.communication.monitor.DDAgentStatsDClientManager;
6+
import datadog.trace.agent.jmxfetch.JMXFetch;
57
import datadog.trace.agent.tooling.ProfilerInstaller;
68
import datadog.trace.agent.tooling.TracerInstaller;
9+
import datadog.trace.api.StatsDClientManager;
710
import datadog.trace.bootstrap.instrumentation.api.ProfilingContextIntegration;
811
import org.slf4j.Logger;
912
import org.slf4j.LoggerFactory;
@@ -20,6 +23,9 @@ public static void activate() {
2023
withProfiler
2124
? new JFREventContextIntegration()
2225
: ProfilingContextIntegration.NoOp.INSTANCE);
26+
27+
StatsDClientManager statsDClientManager = DDAgentStatsDClientManager.statsDClientManager();
28+
JMXFetch.run(statsDClientManager);
2329
} catch (Throwable e) {
2430
log.warn("Problem activating datadog tracer", e);
2531
}

dd-java-agent/instrumentation/graal/native-image/src/main/java/datadog/trace/instrumentation/graal/nativeimage/AnnotationSubstitutionProcessorInstrumentation.java

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,10 @@ public void methodAdvice(MethodTransformer transformer) {
3737
public String[] helperClassNames() {
3838
return new String[] {
3939
packageName + ".Target_datadog_jctools_counters_FixedSizeStripedLongCounterFields",
40-
packageName + ".Target_datadog_jctools_util_UnsafeRefArrayAccess"
40+
packageName + ".Target_datadog_jctools_util_UnsafeRefArrayAccess",
41+
packageName + ".Target_org_datadog_jmxfetch_App",
42+
packageName + ".Target_org_datadog_jmxfetch_Status",
43+
packageName + ".Target_org_datadog_jmxfetch_reporter_JsonReporter",
4144
};
4245
}
4346

@@ -49,7 +52,10 @@ public String[] muzzleIgnoredClassNames() {
4952
"jdk.vm.ci.meta.ResolvedJavaField",
5053
// ignore helper class names as usual
5154
packageName + ".Target_datadog_jctools_counters_FixedSizeStripedLongCounterFields",
52-
packageName + ".Target_datadog_jctools_util_UnsafeRefArrayAccess"
55+
packageName + ".Target_datadog_jctools_util_UnsafeRefArrayAccess",
56+
packageName + ".Target_org_datadog_jmxfetch_App",
57+
packageName + ".Target_org_datadog_jmxfetch_Status",
58+
packageName + ".Target_org_datadog_jmxfetch_reporter_JsonReporter",
5359
};
5460
}
5561

@@ -58,6 +64,9 @@ public static class FindTargetClassesAdvice {
5864
public static void onExit(@Advice.Return(readOnly = false) List<Class<?>> result) {
5965
result.add(Target_datadog_jctools_counters_FixedSizeStripedLongCounterFields.class);
6066
result.add(Target_datadog_jctools_util_UnsafeRefArrayAccess.class);
67+
result.add(Target_org_datadog_jmxfetch_App.class);
68+
result.add(Target_org_datadog_jmxfetch_Status.class);
69+
result.add(Target_org_datadog_jmxfetch_reporter_JsonReporter.class);
6170
}
6271
}
6372
}

dd-java-agent/instrumentation/graal/native-image/src/main/java/datadog/trace/instrumentation/graal/nativeimage/ResourcesFeatureInstrumentation.java

Lines changed: 42 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,11 @@
77
import com.oracle.svm.core.jdk.Resources;
88
import datadog.trace.agent.tooling.Instrumenter;
99
import datadog.trace.agent.tooling.InstrumenterModule;
10+
import java.io.BufferedReader;
1011
import java.io.InputStream;
12+
import java.io.InputStreamReader;
13+
import java.util.ArrayList;
14+
import java.util.List;
1115
import net.bytebuddy.asm.Advice;
1216

1317
@AutoService(InstrumenterModule.class)
@@ -33,19 +37,45 @@ public static void onExit() {
3337
// (drop trace/shared prefixes from embedded resources, so we can find them in native-image
3438
// as the final executable won't have our isolating class-loader to map these resources)
3539

36-
String[] tracerResources = {
37-
"dd-java-agent.version",
38-
"dd-trace-api.version",
39-
"trace/dd-trace-core.version",
40-
"shared/dogstatsd/version.properties",
41-
"shared/version-utils.version",
42-
"shared/datadog/okhttp3/internal/publicsuffix/publicsuffixes.gz",
43-
"profiling/jfr/dd.jfp",
44-
"profiling/jfr/safepoints.jfp",
45-
"profiling/jfr/overrides/comprehensive.jfp",
46-
"profiling/jfr/overrides/minimal.jfp"
47-
};
40+
List<String> tracerResources = new ArrayList<>();
41+
tracerResources.add("dd-java-agent.version");
42+
tracerResources.add("dd-trace-api.version");
43+
tracerResources.add("trace/dd-trace-core.version");
44+
tracerResources.add("shared/dogstatsd/version.properties");
45+
tracerResources.add("shared/version-utils.version");
46+
tracerResources.add("shared/datadog/okhttp3/internal/publicsuffix/publicsuffixes.gz");
47+
tracerResources.add("profiling/jfr/dd.jfp");
48+
tracerResources.add("profiling/jfr/safepoints.jfp");
49+
tracerResources.add("profiling/jfr/overrides/comprehensive.jfp");
50+
tracerResources.add("profiling/jfr/overrides/minimal.jfp");
4851

52+
// jmxfetch configs
53+
tracerResources.add(
54+
"metrics/project.properties"); // org.datadog.jmxfetch.AppConfig reads its version
55+
tracerResources.add("metrics/org/datadog/jmxfetch/default-jmx-metrics.yaml");
56+
tracerResources.add("metrics/org/datadog/jmxfetch/new-gc-default-jmx-metrics.yaml");
57+
tracerResources.add("metrics/org/datadog/jmxfetch/old-gc-default-jmx-metrics.yaml");
58+
59+
// tracer's jmxfetch configs
60+
tracerResources.add("metrics/jmxfetch-config.yaml");
61+
tracerResources.add("metrics/jmxfetch-websphere-config.yaml");
62+
63+
// jmxfetch integrations metricconfigs
64+
String metricConfigsPath = "metrics/datadog/trace/agent/jmxfetch/";
65+
String metricConfigs = metricConfigsPath + "metricconfigs.txt";
66+
tracerResources.add(metricConfigs);
67+
try (InputStream is = ClassLoader.getSystemResourceAsStream(metricConfigs);
68+
BufferedReader reader = new BufferedReader(new InputStreamReader(is))) {
69+
String metricConfig;
70+
while ((metricConfig = reader.readLine()) != null) {
71+
if (!metricConfig.trim().isEmpty()) {
72+
tracerResources.add(metricConfigsPath + "metricconfigs/" + metricConfig);
73+
}
74+
}
75+
} catch (Throwable ignore) {
76+
}
77+
78+
// registering tracer resources to include in the native build
4979
for (String original : tracerResources) {
5080
String flattened = original.substring(original.indexOf('/') + 1);
5181
try (InputStream is = ClassLoader.getSystemResourceAsStream(original)) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package datadog.trace.instrumentation.graal.nativeimage;
2+
3+
import com.oracle.svm.core.annotate.Substitute;
4+
import com.oracle.svm.core.annotate.TargetClass;
5+
6+
@TargetClass(className = "org.datadog.jmxfetch.App")
7+
public final class Target_org_datadog_jmxfetch_App {
8+
@Substitute
9+
private boolean getJsonConfigs() {
10+
// This method has a reference to the excluded transitive dependency jackson-jr-objects.
11+
// GraalVM Native detects it during the reachability analysis and results in
12+
// "Discovered unresolved method during parsing:
13+
// org.datadog.jmxfetch.App.<init>(org.datadog.jmxfetch.AppConfig)."
14+
// because of the missing classes that belong to the excluded dependencies.
15+
throw new IllegalStateException("Unreachable");
16+
}
17+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package datadog.trace.instrumentation.graal.nativeimage;
2+
3+
import com.oracle.svm.core.annotate.Substitute;
4+
import com.oracle.svm.core.annotate.TargetClass;
5+
import java.io.IOException;
6+
7+
@TargetClass(className = "org.datadog.jmxfetch.Status")
8+
public final class Target_org_datadog_jmxfetch_Status {
9+
@Substitute
10+
private String generateJson() throws IOException {
11+
// This method has a reference to the excluded transitive dependency jackson-jr-objects.
12+
// GraalVM Native detects it during the reachability analysis and results in
13+
// "Discovered unresolved type during parsing: com.fasterxml.jackson.jr.ob.JSON."
14+
// because of the missing classes that belong to the excluded dependencies.
15+
throw new IllegalStateException("Unreachable");
16+
}
17+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package datadog.trace.instrumentation.graal.nativeimage;
2+
3+
import com.oracle.svm.core.annotate.Substitute;
4+
import com.oracle.svm.core.annotate.TargetClass;
5+
6+
@TargetClass(className = "org.datadog.jmxfetch.reporter.JsonReporter")
7+
public final class Target_org_datadog_jmxfetch_reporter_JsonReporter {
8+
@Substitute
9+
public void doSendServiceCheck(
10+
String serviceCheckName, String status, String message, String[] tags) {
11+
// This method has a reference to the excluded transitive dependency jackson-jr-objects.
12+
// GraalVM Native detects it during the reachability analysis and results in
13+
// "Discovered unresolved type during parsing: com.fasterxml.jackson.jr.ob.JSON."
14+
// because of the missing classes that belong to the excluded dependencies.
15+
throw new IllegalStateException("Unreachable");
16+
}
17+
}

dd-smoke-tests/spring-boot-3.0-native/application/build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ if (hasProperty('agentPath')) {
3939
if (withProfiler && property('profiler') == 'true') {
4040
buildArgs.add("-J-Ddd.profiling.enabled=true")
4141
}
42+
buildArgs.add("--enable-monitoring=jmxserver")
4243
}
4344
}
4445
}

dd-smoke-tests/spring-boot-3.0-native/src/test/groovy/SpringBootNativeInstrumentationTest.groovy

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import datadog.smoketest.AbstractServerSmokeTest
2+
import datadog.trace.agent.test.utils.PortUtils
23
import okhttp3.Request
34
import org.openjdk.jmc.common.item.IItemCollection
45
import org.openjdk.jmc.common.item.ItemFilters
@@ -13,6 +14,10 @@ import java.nio.file.Files
1314
import java.nio.file.Path
1415
import java.nio.file.SimpleFileVisitor
1516
import java.nio.file.attribute.BasicFileAttributes
17+
import java.util.concurrent.CompletableFuture
18+
import java.util.concurrent.Executors
19+
import java.util.concurrent.TimeUnit
20+
import java.util.concurrent.TimeoutException
1621
import java.util.concurrent.atomic.AtomicInteger
1722
import java.util.concurrent.locks.LockSupport
1823

@@ -21,6 +26,9 @@ class SpringBootNativeInstrumentationTest extends AbstractServerSmokeTest {
2126
@TempDir
2227
def testJfrDir
2328

29+
@Shared
30+
def statsdPort = PortUtils.randomOpenPort()
31+
2432
@Override
2533
ProcessBuilder createProcessBuilder() {
2634
String springNativeExecutable = System.getProperty('datadog.smoketest.spring.native.executable')
@@ -39,7 +47,10 @@ class SpringBootNativeInstrumentationTest extends AbstractServerSmokeTest {
3947
'-Ddd.profiling.upload.period=1',
4048
'-Ddd.profiling.start-force-first=true',
4149
"-Ddd.profiling.debug.dump_path=${testJfrDir}",
42-
"-Ddd.integration.spring-boot.enabled=true"
50+
"-Ddd.integration.spring-boot.enabled=true",
51+
"-Ddd.trace.debug=true",
52+
"-Ddd.jmxfetch.statsd.port=${statsdPort}",
53+
"-Ddd.jmxfetch.start-delay=0",
4354
])
4455
ProcessBuilder processBuilder = new ProcessBuilder(command)
4556
processBuilder.directory(new File(buildDirectory))
@@ -66,8 +77,18 @@ class SpringBootNativeInstrumentationTest extends AbstractServerSmokeTest {
6677
super.isErrorLog(log) || log.contains("ClassNotFoundException")
6778
}
6879

80+
def setupSpec() {
81+
try {
82+
processTestLogLines { it.contains("JMXFetch config: ") }
83+
} catch (TimeoutException toe) {
84+
throw new AssertionError("'JMXFetch config: ' not found in logs. Make sure it's enabled.", toe)
85+
}
86+
}
87+
6988
def "check native instrumentation"() {
7089
setup:
90+
CompletableFuture<String> udpMessage = receiveUdpMessage(statsdPort, 1000)
91+
7192
String url = "http://localhost:${httpPort}/hello"
7293

7394
when:
@@ -87,6 +108,8 @@ class SpringBootNativeInstrumentationTest extends AbstractServerSmokeTest {
87108
LockSupport.parkNanos(1_000_000)
88109
}
89110
countJfrs() > 0
111+
112+
udpMessage.get(1, TimeUnit.SECONDS) contains "service:smoke-test-java-app,version:99,env:smoketest"
90113
}
91114

92115
int countJfrs() {
@@ -115,4 +138,20 @@ class SpringBootNativeInstrumentationTest extends AbstractServerSmokeTest {
115138
})
116139
return jfrCount.get()
117140
}
141+
142+
CompletableFuture<String> receiveUdpMessage(int port, int bufferSize) {
143+
def future = new CompletableFuture<String>()
144+
Executors.newSingleThreadExecutor().submit {
145+
try (DatagramSocket socket = new DatagramSocket(port)) {
146+
byte[] buffer = new byte[bufferSize]
147+
DatagramPacket packet = new DatagramPacket(buffer, buffer.length)
148+
socket.receive(packet)
149+
def received = new String(packet.data, 0, packet.length)
150+
future.complete(received)
151+
} catch (Exception e) {
152+
future.completeExceptionally(e)
153+
}
154+
}
155+
return future
156+
}
118157
}

0 commit comments

Comments
 (0)