Skip to content

Commit 42ae556

Browse files
gyuheon0hdevflow.devflow-routing-intake
andauthored
feat(crashtracking): include JVM distribution details in crash report (#11715)
Include JVM distribution details in crash report Add runtime info to experimental instead Co-authored-by: devflow.devflow-routing-intake <devflow.devflow-routing-intake@kubernetes.us1.ddbuild.io>
1 parent 7e083fd commit 42ae556

7 files changed

Lines changed: 155 additions & 7 deletions

File tree

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

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -581,7 +581,8 @@ private RequestBody makeErrorTrackingRequestBody(@Nonnull CrashLog payload, bool
581581
if (payload.experimental != null
582582
&& (payload.experimental.ucontext != null
583583
|| payload.experimental.registerToMemoryMapping != null
584-
|| payload.experimental.runtimeArgs != null)) {
584+
|| payload.experimental.runtimeArgs != null
585+
|| payload.experimental.runtimeInfo != null)) {
585586
writer.name("experimental");
586587
writer.beginObject();
587588
if (payload.experimental.ucontext != null) {
@@ -611,6 +612,20 @@ private RequestBody makeErrorTrackingRequestBody(@Nonnull CrashLog payload, bool
611612
}
612613
writer.endArray();
613614
}
615+
if (payload.experimental.runtimeInfo != null) {
616+
writer.name("runtime_info");
617+
writer.beginObject();
618+
if (payload.experimental.runtimeInfo.jreVersion != null) {
619+
writer.name("jre_version").value(payload.experimental.runtimeInfo.jreVersion);
620+
}
621+
if (payload.experimental.runtimeInfo.javaVm != null) {
622+
writer.name("java_vm").value(payload.experimental.runtimeInfo.javaVm);
623+
}
624+
if (payload.experimental.runtimeInfo.vmInfo != null) {
625+
writer.name("vm_info").value(payload.experimental.runtimeInfo.vmInfo);
626+
}
627+
writer.endObject();
628+
}
614629
writer.endObject();
615630
}
616631
// files (e.g. /proc/self/maps or dynamic_libraries)

dd-java-agent/agent-crashtracking/src/main/java/datadog/crashtracking/dto/Experimental.java

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,21 +14,33 @@ public final class Experimental {
1414
@Json(name = "runtime_args")
1515
public final List<String> runtimeArgs;
1616

17+
@Json(name = "runtime_info")
18+
public final RuntimeInfo runtimeInfo;
19+
1720
public Experimental(Map<String, String> ucontext) {
18-
this(ucontext, null, null);
21+
this(ucontext, null, null, null);
1922
}
2023

2124
public Experimental(Map<String, String> ucontext, List<String> runtimeArgs) {
22-
this(ucontext, null, runtimeArgs);
25+
this(ucontext, null, runtimeArgs, null);
2326
}
2427

2528
public Experimental(
2629
Map<String, String> ucontext,
2730
Map<String, String> registerToMemoryMapping,
2831
List<String> runtimeArgs) {
32+
this(ucontext, registerToMemoryMapping, runtimeArgs, null);
33+
}
34+
35+
public Experimental(
36+
Map<String, String> ucontext,
37+
Map<String, String> registerToMemoryMapping,
38+
List<String> runtimeArgs,
39+
RuntimeInfo runtimeInfo) {
2940
this.ucontext = ucontext;
3041
this.registerToMemoryMapping = registerToMemoryMapping;
3142
this.runtimeArgs = runtimeArgs;
43+
this.runtimeInfo = runtimeInfo;
3244
}
3345

3446
@Override
@@ -37,11 +49,12 @@ public boolean equals(Object o) {
3749
Experimental that = (Experimental) o;
3850
return Objects.equals(ucontext, that.ucontext)
3951
&& Objects.equals(registerToMemoryMapping, that.registerToMemoryMapping)
40-
&& Objects.equals(runtimeArgs, that.runtimeArgs);
52+
&& Objects.equals(runtimeArgs, that.runtimeArgs)
53+
&& Objects.equals(runtimeInfo, that.runtimeInfo);
4154
}
4255

4356
@Override
4457
public int hashCode() {
45-
return Objects.hash(ucontext, registerToMemoryMapping, runtimeArgs);
58+
return Objects.hash(ucontext, registerToMemoryMapping, runtimeArgs, runtimeInfo);
4659
}
4760
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package datadog.crashtracking.dto;
2+
3+
import com.squareup.moshi.Json;
4+
import java.util.Objects;
5+
6+
/**
7+
* JDK runtime information extracted from the hs_err crash log header and vm_info line. This
8+
* captures the exact JDK vendor and build so crash reports can be correlated with the specific
9+
* binaries in use.
10+
*/
11+
public final class RuntimeInfo {
12+
@Json(name = "jre_version")
13+
public final String jreVersion;
14+
15+
@Json(name = "java_vm")
16+
public final String javaVm;
17+
18+
@Json(name = "vm_info")
19+
public final String vmInfo;
20+
21+
public RuntimeInfo(String jreVersion, String javaVm, String vmInfo) {
22+
this.jreVersion = jreVersion;
23+
this.javaVm = javaVm;
24+
this.vmInfo = vmInfo;
25+
}
26+
27+
@Override
28+
public boolean equals(Object o) {
29+
if (this == o) {
30+
return true;
31+
}
32+
if (o == null || getClass() != o.getClass()) {
33+
return false;
34+
}
35+
RuntimeInfo that = (RuntimeInfo) o;
36+
return Objects.equals(jreVersion, that.jreVersion)
37+
&& Objects.equals(javaVm, that.javaVm)
38+
&& Objects.equals(vmInfo, that.vmInfo);
39+
}
40+
41+
@Override
42+
public int hashCode() {
43+
return Objects.hash(jreVersion, javaVm, vmInfo);
44+
}
45+
}

dd-java-agent/agent-crashtracking/src/main/java/datadog/crashtracking/parsers/HotspotCrashLogParser.java

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import datadog.crashtracking.dto.Metadata;
1313
import datadog.crashtracking.dto.OSInfo;
1414
import datadog.crashtracking.dto.ProcInfo;
15+
import datadog.crashtracking.dto.RuntimeInfo;
1516
import datadog.crashtracking.dto.SigInfo;
1617
import datadog.crashtracking.dto.StackFrame;
1718
import datadog.crashtracking.dto.StackTrace;
@@ -44,6 +45,9 @@
4445
*/
4546
public final class HotspotCrashLogParser {
4647
private static final String HOTSPOT_JVM_ARGS_PREFIX = "jvm_args:";
48+
private static final String JRE_VERSION_PREFIX = "# JRE version: ";
49+
private static final String JAVA_VM_PREFIX = "# Java VM: ";
50+
private static final String VM_INFO_PREFIX = "vm_info: ";
4751
private static final DateTimeFormatter ZONED_DATE_TIME_FORMATTER =
4852
DateTimeFormatter.ofPattern("EEE MMM ppd HH:mm:ss yyyy zzz", Locale.getDefault());
4953
private static final DateTimeFormatter OFFSET_DATE_TIME_FORMATTER =
@@ -362,6 +366,9 @@ public CrashLog parse(String uuid, String crashLog) {
362366
String dynamicLibraryKey = null;
363367
boolean previousLineBlank = false;
364368
State nextThreadSectionState = null;
369+
String jreVersion = null;
370+
String javaVm = null;
371+
String vmInfo = null;
365372

366373
String[] lines = NEWLINE_SPLITTER.split(crashLog);
367374
outer:
@@ -392,6 +399,11 @@ public CrashLog parse(String uuid, String crashLog) {
392399
}
393400
}
394401
}
402+
if (jreVersion == null && line.startsWith(JRE_VERSION_PREFIX)) {
403+
jreVersion = line.substring(JRE_VERSION_PREFIX.length()).trim();
404+
} else if (javaVm == null && line.startsWith(JAVA_VM_PREFIX)) {
405+
javaVm = line.substring(JAVA_VM_PREFIX.length()).trim();
406+
}
395407
break;
396408
case HEADER:
397409
if (line.contains("S U M M A R Y")) {
@@ -486,6 +498,8 @@ public CrashLog parse(String uuid, String crashLog) {
486498
state = State.DYNAMIC_LIBRARIES;
487499
} else if (line.contains("S Y S T E M")) {
488500
state = State.SYSTEM;
501+
} else if (vmInfo == null && line.startsWith(VM_INFO_PREFIX)) {
502+
vmInfo = line.substring(VM_INFO_PREFIX.length()).trim();
489503
} else if (line.equals("END.")) {
490504
state = State.DONE;
491505
}
@@ -527,6 +541,8 @@ public CrashLog parse(String uuid, String crashLog) {
527541
datetimeRaw = line.substring(6).trim();
528542
} else if (datetime == null && datetimeRaw != null && line.startsWith("timezone: ")) {
529543
datetime = dateTimeToISO(datetimeRaw + " " + line.substring(10).trim());
544+
} else if (vmInfo == null && line.startsWith(VM_INFO_PREFIX)) {
545+
vmInfo = line.substring(VM_INFO_PREFIX.length()).trim();
530546
}
531547
break;
532548
case DONE:
@@ -609,11 +625,16 @@ public CrashLog parse(String uuid, String crashLog) {
609625
registerToMemoryMapping.replaceAll((k, v) -> RedactUtils.redactRegisterToMemoryMapping(v));
610626
resolvedMapping = registerToMemoryMapping;
611627
}
628+
RuntimeInfo runtimeInfo =
629+
(jreVersion != null || javaVm != null || vmInfo != null)
630+
? new RuntimeInfo(jreVersion, javaVm, vmInfo)
631+
: null;
612632
Experimental experimental =
613633
!registers.isEmpty()
614634
|| resolvedMapping != null
615635
|| (runtimeArgs != null && !runtimeArgs.isEmpty())
616-
? new Experimental(registers, resolvedMapping, runtimeArgs)
636+
|| runtimeInfo != null
637+
? new Experimental(registers, resolvedMapping, runtimeArgs, runtimeInfo)
617638
: null;
618639
DynamicLibs files =
619640
(dynamicLibraryLines != null && !dynamicLibraryLines.isEmpty())

dd-java-agent/agent-crashtracking/src/main/java/datadog/crashtracking/parsers/J9JavacoreParser.java

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import datadog.crashtracking.dto.Metadata;
1212
import datadog.crashtracking.dto.OSInfo;
1313
import datadog.crashtracking.dto.ProcInfo;
14+
import datadog.crashtracking.dto.RuntimeInfo;
1415
import datadog.crashtracking.dto.SigInfo;
1516
import datadog.crashtracking.dto.StackFrame;
1617
import datadog.crashtracking.dto.StackTrace;
@@ -46,6 +47,8 @@
4647
*/
4748
public final class J9JavacoreParser {
4849
private static final String J9_USER_ARG_PREFIX = "2CIUSERARG";
50+
private static final String J9_JAVA_VERSION_PREFIX = "1CIJAVAVERSION ";
51+
private static final String J9_VM_VERSION_PREFIX = "1CIVMVERSION";
4952

5053
private final BuildIdCollector buildIdCollector;
5154

@@ -119,6 +122,8 @@ public CrashLog parse(String uuid, String javacoreContent) {
119122

120123
Map<String, String> registers = null;
121124
RuntimeArgs j9UserArgs = new RuntimeArgs();
125+
String j9JavaVersion = null;
126+
String j9VmVersion = null;
122127

123128
String[] lines = NEWLINE_SPLITTER.split(javacoreContent);
124129

@@ -179,6 +184,11 @@ public CrashLog parse(String uuid, String javacoreContent) {
179184
if (pidMatcher.matches()) {
180185
pid = pidMatcher.group(1);
181186
}
187+
if (j9JavaVersion == null && line.startsWith(J9_JAVA_VERSION_PREFIX)) {
188+
j9JavaVersion = line.substring(J9_JAVA_VERSION_PREFIX.length()).trim();
189+
} else if (j9VmVersion == null && line.startsWith(J9_VM_VERSION_PREFIX)) {
190+
j9VmVersion = line.substring(J9_VM_VERSION_PREFIX.length()).trim();
191+
}
182192
break;
183193

184194
case THREADS:
@@ -299,10 +309,15 @@ public CrashLog parse(String uuid, String javacoreContent) {
299309
Integer parsedPid = safelyParseInt(pid);
300310
ProcInfo procInfo = parsedPid != null ? new ProcInfo(parsedPid) : null;
301311
List<String> runtimeArgs = j9UserArgs.build();
312+
RuntimeInfo runtimeInfo =
313+
(j9JavaVersion != null || j9VmVersion != null)
314+
? new RuntimeInfo(j9JavaVersion, null, j9VmVersion)
315+
: null;
302316
Experimental experimental =
303317
(registers != null && !registers.isEmpty())
304318
|| (runtimeArgs != null && !runtimeArgs.isEmpty())
305-
? new Experimental(registers, runtimeArgs)
319+
|| runtimeInfo != null
320+
? new Experimental(registers, null, runtimeArgs, runtimeInfo)
306321
: null;
307322

308323
return new CrashLog(

dd-java-agent/agent-crashtracking/src/test/java/datadog/crashtracking/parsers/HotspotCrashLogParserTest.java

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -337,6 +337,28 @@ public void testParseCurrentThreadName(String line, String expected) {
337337
HotspotCrashLogParser.parseCurrentThreadName(line));
338338
}
339339

340+
@TableTest({
341+
"scenario | filename | expectedJreVersion | expectedVmInfo ",
342+
"Zulu 17 | sample-crash-for-telemetry.txt | OpenJDK Runtime Environment Zulu17.42+20-SA (17.0.7+7) (build 17.0.7+7-LTS) | OpenJDK 64-Bit Server VM (17.0.7+7-LTS) for linux-amd64 JRE (17.0.7+7-LTS) (Zulu17.42+20-SA), built on Apr 11 2023 11:39:51 by \"zulu_re\" with gcc 8.3.0 ",
343+
"Temurin 22 | sample-crash-for-telemetry-2.txt | OpenJDK Runtime Environment Temurin-22.0.1+8 (22.0.1+8) (build 22.0.1+8) | OpenJDK 64-Bit Server VM (22.0.1+8) for linux-amd64 JRE (22.0.1+8), built on 2024-04-16T00:00:00Z by \"admin\" with gcc 11.3.0 ",
344+
"Zulu 8 | sample-crash-for-telemetry-3.txt | OpenJDK Runtime Environment (Zulu 8.70.0.23-CA-macos-aarch64) (8.0_372-b07) (build 1.8.0_372-b07) | OpenJDK 64-Bit Server VM (25.372-b07) for bsd-aarch64 JRE (Zulu 8.70.0.23-CA-macos-aarch64) (1.8.0_372-b07), built on Apr 18 2023 01:36:20 by \"zulu_re\" with gcc Apple LLVM 12.0.0 (clang-1200.0.32.28)",
345+
"Corretto 21 | sample-crash-linux-aarch64.txt | OpenJDK Runtime Environment Corretto-21.0.7.6.1 (21.0.7+6) (build 21.0.7+6-LTS) | OpenJDK 64-Bit Server VM (21.0.7+6-LTS) for linux-aarch64-musl JRE (21.0.7+6-LTS), built on 2025-04-09T23:34:45Z by \"jenkins\" with gcc 12.2.1 20220924 ",
346+
"OpenJDK 25 | sample-crash-macos-aarch64.txt | OpenJDK Runtime Environment (25.0.2+10) (build 25.0.2+10-69) | OpenJDK 64-Bit Server VM (25.0.2+10-69) for bsd-aarch64 JRE (25.0.2+10-69), built on 2025-12-18T11:36:35Z with clang Apple LLVM 15.0.0 (clang-1500.3.9.4) "
347+
})
348+
public void testRuntimeInfoParsing(
349+
String filename, String expectedJreVersion, String expectedVmInfo) throws Exception {
350+
CrashLog crashLog =
351+
new HotspotCrashLogParser().parse(UUID.randomUUID().toString(), readFileAsString(filename));
352+
353+
assertNotNull(crashLog.experimental, "experimental should be populated");
354+
assertNotNull(crashLog.experimental.runtimeInfo, "runtimeInfo should be populated");
355+
assertNotNull(crashLog.experimental.runtimeInfo.jreVersion, "jreVersion should be populated");
356+
assertNotNull(crashLog.experimental.runtimeInfo.javaVm, "javaVm should be populated");
357+
assertNotNull(crashLog.experimental.runtimeInfo.vmInfo, "vmInfo should be populated");
358+
assertEquals(expectedJreVersion, crashLog.experimental.runtimeInfo.jreVersion);
359+
assertEquals(expectedVmInfo, crashLog.experimental.runtimeInfo.vmInfo);
360+
}
361+
340362
@Test
341363
public void testNoSignalProducesInternalError() throws Exception {
342364
// A crash log that reaches the PROCESS section but has no siginfo line

dd-java-agent/agent-crashtracking/src/test/java/datadog/crashtracking/parsers/J9JavacoreParserTest.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,23 @@ public void testDateTimeParsing() throws Exception {
212212
"Expected ISO-8601 format, got: " + crashLog.timestamp);
213213
}
214214

215+
@TableTest({
216+
"scenario | filename | expectedJreVersion ",
217+
"OpenJ9 11 GPF | sample-j9-javacore-gpf.txt | JRE 11.0.12 Linux amd64-64 ",
218+
"OpenJ9 17 OOM | sample-j9-javacore-oom.txt | JRE 17.0.6 Linux amd64-64 ",
219+
"OpenJ9 11 aarch | sample-openj9-11-javacore-gpf.txt | JRE 11 Linux aarch64-64 (build 11.0.28+6) ",
220+
"IBM J9 8 | sample-ibmj9-8-javacore-gpf.txt | JRE 1.8.0 Linux amd64-64 (build 8.0.8.51 - pxa6480sr8fp51-20250819_01(SR8 FP51))"
221+
})
222+
public void testRuntimeInfoParsing(String filename, String expectedJreVersion) throws Exception {
223+
CrashLog crashLog =
224+
new J9JavacoreParser().parse(UUID.randomUUID().toString(), readFileAsString(filename));
225+
226+
assertNotNull(crashLog.experimental, "experimental should be populated");
227+
assertNotNull(crashLog.experimental.runtimeInfo, "runtimeInfo should be populated");
228+
assertNotNull(crashLog.experimental.runtimeInfo.jreVersion, "jreVersion should be populated");
229+
assertEquals(expectedJreVersion, crashLog.experimental.runtimeInfo.jreVersion);
230+
}
231+
215232
@Test
216233
public void testNoSignalProducesInternalError() throws Exception {
217234
// A javacore with a THREADS section but no 1TISIGINFO line

0 commit comments

Comments
 (0)