Skip to content

Commit 83f658a

Browse files
authored
Add J9/OpenJ9 crash tracking support (#10496)
1 parent 000d631 commit 83f658a

File tree

12 files changed

+1310
-21
lines changed

12 files changed

+1310
-21
lines changed

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

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1278,10 +1278,6 @@ private static boolean isCrashTrackingAutoconfigEnabled() {
12781278
}
12791279

12801280
private static void initializeCrashTracking(boolean delayed, boolean checkNative) {
1281-
if (JavaVirtualMachine.isJ9()) {
1282-
// TODO currently crash tracking is supported only for HotSpot based JVMs
1283-
return;
1284-
}
12851281
log.debug("Initializing crashtracking");
12861282
try {
12871283
Class<?> clz = AGENT_CLASSLOADER.loadClass("datadog.crashtracking.Initializer");

dd-java-agent/agent-crashtracking/src/main/java/datadog/crashtracking/CrashLogParser.java

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,49 @@
22

33
import datadog.crashtracking.dto.CrashLog;
44
import datadog.crashtracking.parsers.HotspotCrashLogParser;
5+
import datadog.crashtracking.parsers.J9JavacoreParser;
56

67
public final class CrashLogParser {
8+
9+
/** J9 javacore files start with section markers like "0SECTION" */
10+
private static final String J9_SECTION_MARKER = "0SECTION";
11+
12+
/** J9 javacore TITLE section identifier */
13+
private static final String J9_TITLE_MARKER = "TITLE";
14+
15+
/** Parse a HotSpot crash log (hs_err_pidXXX.log format). */
716
public static CrashLog fromHotspotCrashLog(String uuid, String logText) {
817
return new HotspotCrashLogParser().parse(uuid, logText);
918
}
19+
20+
/** Parse a J9/OpenJ9 javacore dump file. */
21+
public static CrashLog fromJ9Javacore(String uuid, String javacoreContent) {
22+
return new J9JavacoreParser().parse(uuid, javacoreContent);
23+
}
24+
25+
/**
26+
* Auto-detect crash log format and parse accordingly.
27+
*
28+
* <p>Detection is based on format-specific markers:
29+
*
30+
* <ul>
31+
* <li>J9 javacore: Contains "0SECTION" and "TITLE" markers
32+
* <li>HotSpot hs_err: Default fallback
33+
* </ul>
34+
*/
35+
public static CrashLog parse(String uuid, String content) {
36+
if (isJ9Javacore(content)) {
37+
return fromJ9Javacore(uuid, content);
38+
}
39+
return fromHotspotCrashLog(uuid, content);
40+
}
41+
42+
/** Check if the content appears to be a J9 javacore file. */
43+
static boolean isJ9Javacore(String content) {
44+
if (content == null || content.isEmpty()) {
45+
return false;
46+
}
47+
// J9 javacores have a distinctive format with 0SECTION markers
48+
return content.contains(J9_SECTION_MARKER) && content.contains(J9_TITLE_MARKER);
49+
}
1050
}

dd-java-agent/agent-crashtracking/src/main/java/datadog/crashtracking/CrashUploader.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -256,7 +256,8 @@ void remoteUpload(
256256
@Nonnull String fileContent, boolean sendToTelemetry, boolean sendToErrorTracking) {
257257
final String uuid = storedConfig.reportUUID;
258258
try {
259-
CrashLog crashLog = CrashLogParser.fromHotspotCrashLog(uuid, fileContent);
259+
// Auto-detect crash log format (HotSpot hs_err or J9 javacore)
260+
CrashLog crashLog = CrashLogParser.parse(uuid, fileContent);
260261
if (sendToTelemetry) {
261262
uploadToTelemetry(crashLog);
262263
}

dd-java-agent/agent-crashtracking/src/main/java/datadog/crashtracking/CrashUploaderScriptInitializer.java

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,11 @@ private CrashUploaderScriptInitializer() {}
3131

3232
// @VisibleForTests
3333
static void initialize(String onErrorVal, String onErrorFile) {
34+
initialize(onErrorVal, onErrorFile, null);
35+
}
36+
37+
// @VisibleForTests
38+
static void initialize(String onErrorVal, String onErrorFile, String javacorePath) {
3439
if (onErrorVal == null || onErrorVal.isEmpty()) {
3540
LOG.debug(
3641
SEND_TELEMETRY, "'-XX:OnError' argument was not provided. Crash tracking is disabled.");
@@ -56,7 +61,11 @@ static void initialize(String onErrorVal, String onErrorFile) {
5661
return;
5762
}
5863

59-
writeConfigToPath(scriptPath, "agent", agentJar, "hs_err", onErrorFile);
64+
if (javacorePath != null && !javacorePath.isEmpty()) {
65+
writeConfigToPath(scriptPath, "agent", agentJar, "javacore_path", javacorePath);
66+
} else {
67+
writeConfigToPath(scriptPath, "agent", agentJar, "hs_err", onErrorFile);
68+
}
6069
}
6170

6271
private static boolean copyCrashUploaderScript(

dd-java-agent/agent-crashtracking/src/main/java/datadog/crashtracking/Initializer.java

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
import com.datadoghq.profiler.JVMAccess;
88
import com.sun.management.HotSpotDiagnosticMXBean;
9+
import datadog.environment.JavaVirtualMachine;
910
import datadog.environment.OperatingSystem;
1011
import datadog.libs.ddprof.DdprofLibraryLoader;
1112
import datadog.trace.api.Platform;
@@ -17,6 +18,7 @@
1718
import java.nio.file.Files;
1819
import java.nio.file.Path;
1920
import java.nio.file.Paths;
21+
import java.util.List;
2022
import java.util.StringTokenizer;
2123
import java.util.function.Predicate;
2224
import java.util.stream.Stream;
@@ -75,6 +77,11 @@ public boolean setValue(String flagName, String value) {
7577
}
7678

7779
public static boolean initialize(boolean forceJmx) {
80+
// J9/OpenJ9 requires different initialization path
81+
if (JavaVirtualMachine.isJ9()) {
82+
return initializeJ9();
83+
}
84+
7885
try {
7986
FlagAccess access = null;
8087
// Native images don't support the native ddprof library, use JMX instead
@@ -105,6 +112,105 @@ public static boolean initialize(boolean forceJmx) {
105112
return false;
106113
}
107114

115+
/**
116+
* Initialize crash tracking for J9/OpenJ9 JVMs.
117+
*
118+
* <p>Unlike HotSpot, J9's -Xdump option cannot be modified at runtime. This method:
119+
*
120+
* <ol>
121+
* <li>Checks if -Xdump:tool is already configured
122+
* <li>Deploys the crash uploader script if configured
123+
* <li>Logs instructions for manual configuration if not configured
124+
* </ol>
125+
*
126+
* @return true if -Xdump is properly configured, false otherwise
127+
*/
128+
private static boolean initializeJ9() {
129+
try {
130+
String scriptPath = getJ9CrashUploaderScriptPath();
131+
132+
// Check if -Xdump:tool is already configured via JVM arguments
133+
boolean xdumpConfigured = isXdumpToolConfigured();
134+
// Get custom javacore path if configured
135+
String javacorePath = getJ9JavacorePath();
136+
137+
if (xdumpConfigured) {
138+
LOG.debug("J9 crash tracking: -Xdump:tool already configured, crash uploads enabled");
139+
// Initialize the crash uploader script and config manager
140+
CrashUploaderScriptInitializer.initialize(scriptPath, null, javacorePath);
141+
// Also set up OOME notifier script
142+
String oomeScript = getScript("dd_oome_notifier");
143+
OOMENotifierScriptInitializer.initialize(oomeScript);
144+
return true;
145+
} else {
146+
// Log instructions for manual configuration
147+
LOG.info("J9 JVM detected. To enable crash tracking, add this JVM argument at startup:");
148+
LOG.info(" -Xdump:tool:events=gpf+abort,exec={}\\ %pid", scriptPath);
149+
LOG.info(
150+
"Crash tracking will not be active until this argument is added and JVM is restarted.");
151+
return false;
152+
}
153+
} catch (Throwable t) {
154+
logInitializationError(
155+
"Unexpected exception while initializing J9 crash tracking. Crash tracking will not work.",
156+
t);
157+
}
158+
return false;
159+
}
160+
161+
/**
162+
* Get the custom javacore file path from -Xdump:java:file=... JVM argument.
163+
*
164+
* @return the custom javacore path, or null if not configured
165+
*/
166+
private static String getJ9JavacorePath() {
167+
List<String> vmArgs = JavaVirtualMachine.getVmOptions();
168+
for (String arg : vmArgs) {
169+
if (arg.startsWith("-Xdump:java:file=") || arg.startsWith("-Xdump:java+heap:file=")) {
170+
int fileIdx = arg.indexOf("file=");
171+
if (fileIdx >= 0) {
172+
String path = arg.substring(fileIdx + 5);
173+
// Handle comma-separated options: -Xdump:java:file=/path,request=exclusive
174+
int commaIdx = path.indexOf(',');
175+
if (commaIdx > 0) {
176+
path = path.substring(0, commaIdx);
177+
}
178+
return path;
179+
}
180+
}
181+
}
182+
return null;
183+
}
184+
185+
/**
186+
* Check if -Xdump:tool is configured with our crash uploader script.
187+
*
188+
* <p>Looks for JVM arguments matching: -Xdump:tool:events=...,exec=...dd_crash_uploader...
189+
*/
190+
private static boolean isXdumpToolConfigured() {
191+
List<String> vmArgs = JavaVirtualMachine.getVmOptions();
192+
for (String arg : vmArgs) {
193+
if (arg.startsWith("-Xdump:tool") && arg.contains("dd_crash_uploader")) {
194+
return true;
195+
}
196+
}
197+
return false;
198+
}
199+
200+
/**
201+
* Get the path where the crash uploader script should be deployed for J9.
202+
*
203+
* <p>Note: The actual script deployment is handled by {@link CrashUploaderScriptInitializer} when
204+
* initialize() is called with this path.
205+
*
206+
* @return the full path for the crash uploader script
207+
*/
208+
private static String getJ9CrashUploaderScriptPath() {
209+
String scriptFileName = getScriptFileName("dd_crash_uploader");
210+
Path scriptPath = TempLocationManager.getInstance().getTempDir().resolve(scriptFileName);
211+
return scriptPath.toString();
212+
}
213+
108214
static InputStream getCrashUploaderTemplate() {
109215
String name = OperatingSystem.isWindows() ? "upload_crash.bat" : "upload_crash.sh";
110216
return CrashUploader.class.getResourceAsStream(name);

0 commit comments

Comments
 (0)