Skip to content

Commit 7293d46

Browse files
authored
Include JVM distribution details in crash report (#11715)
1 parent 8206fbc commit 7293d46

15 files changed

Lines changed: 209 additions & 0 deletions

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

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -577,6 +577,21 @@ private RequestBody makeErrorTrackingRequestBody(@Nonnull CrashLog payload, bool
577577
"os.version")); // this has been restructured under OsInfo so taking raw here
578578
writer.endObject();
579579
}
580+
// runtime info (JDK vendor and build details from the crash log)
581+
if (payload.runtimeInfo != null) {
582+
writer.name("runtime_info");
583+
writer.beginObject();
584+
if (payload.runtimeInfo.jreVersion != null) {
585+
writer.name("jre_version").value(payload.runtimeInfo.jreVersion);
586+
}
587+
if (payload.runtimeInfo.javaVm != null) {
588+
writer.name("java_vm").value(payload.runtimeInfo.javaVm);
589+
}
590+
if (payload.runtimeInfo.vmInfo != null) {
591+
writer.name("vm_info").value(payload.runtimeInfo.vmInfo);
592+
}
593+
writer.endObject();
594+
}
580595
// experimental
581596
if (payload.experimental != null
582597
&& (payload.experimental.ucontext != null

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

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,9 @@ public final class CrashLog {
4141

4242
public final Experimental experimental;
4343

44+
@Json(name = "runtime_info")
45+
public final RuntimeInfo runtimeInfo;
46+
4447
/**
4548
* Useful files for triage and debugging (e.g. {@code /proc/self/maps}, {@code
4649
* dynamic_libraries}).
@@ -68,6 +71,7 @@ public CrashLog(
6871
sigInfo,
6972
dataSchemaVersion,
7073
null,
74+
null,
7175
null);
7276
}
7377

@@ -83,6 +87,34 @@ public CrashLog(
8387
String dataSchemaVersion,
8488
Experimental experimental,
8589
DynamicLibs files) {
90+
this(
91+
uuid,
92+
incomplete,
93+
timestamp,
94+
error,
95+
metadata,
96+
osInfo,
97+
procInfo,
98+
sigInfo,
99+
dataSchemaVersion,
100+
experimental,
101+
null,
102+
files);
103+
}
104+
105+
public CrashLog(
106+
String uuid,
107+
boolean incomplete,
108+
String timestamp,
109+
ErrorData error,
110+
Metadata metadata,
111+
OSInfo osInfo,
112+
ProcInfo procInfo,
113+
SigInfo sigInfo,
114+
String dataSchemaVersion,
115+
Experimental experimental,
116+
RuntimeInfo runtimeInfo,
117+
DynamicLibs files) {
86118
this.uuid = uuid != null ? uuid : RandomUtils.randomUUID().toString();
87119
this.incomplete = incomplete;
88120
this.timestamp = timestamp;
@@ -93,6 +125,7 @@ public CrashLog(
93125
this.sigInfo = sigInfo;
94126
this.dataSchemaVersion = dataSchemaVersion;
95127
this.experimental = experimental;
128+
this.runtimeInfo = runtimeInfo;
96129
this.files = files;
97130
}
98131

@@ -123,6 +156,7 @@ public boolean equals(Object o) {
123156
&& Objects.equals(sigInfo, crashLog.sigInfo)
124157
&& Objects.equals(dataSchemaVersion, crashLog.dataSchemaVersion)
125158
&& Objects.equals(experimental, crashLog.experimental)
159+
&& Objects.equals(runtimeInfo, crashLog.runtimeInfo)
126160
&& Objects.equals(files, crashLog.files);
127161
}
128162

@@ -140,6 +174,7 @@ public int hashCode() {
140174
version,
141175
dataSchemaVersion,
142176
experimental,
177+
runtimeInfo,
143178
files);
144179
}
145180

@@ -161,6 +196,7 @@ public boolean equalsForTest(Object o) {
161196
&& Objects.equals(sigInfo, crashLog.sigInfo)
162197
&& Objects.equals(dataSchemaVersion, crashLog.dataSchemaVersion)
163198
&& Objects.equals(experimental, crashLog.experimental)
199+
&& Objects.equals(runtimeInfo, crashLog.runtimeInfo)
164200
&& Objects.equals(files, crashLog.files);
165201
}
166202
}
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: 21 additions & 0 deletions
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:
@@ -615,6 +631,10 @@ public CrashLog parse(String uuid, String crashLog) {
615631
|| (runtimeArgs != null && !runtimeArgs.isEmpty())
616632
? new Experimental(registers, resolvedMapping, runtimeArgs)
617633
: null;
634+
RuntimeInfo runtimeInfo =
635+
(jreVersion != null || javaVm != null || vmInfo != null)
636+
? new RuntimeInfo(jreVersion, javaVm, vmInfo)
637+
: null;
618638
DynamicLibs files =
619639
(dynamicLibraryLines != null && !dynamicLibraryLines.isEmpty())
620640
? new DynamicLibs(dynamicLibraryKey, dynamicLibraryLines)
@@ -630,6 +650,7 @@ public CrashLog parse(String uuid, String crashLog) {
630650
sigInfo,
631651
"1.0",
632652
experimental,
653+
runtimeInfo,
633654
files);
634655
}
635656

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

Lines changed: 15 additions & 0 deletions
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:
@@ -304,6 +314,10 @@ public CrashLog parse(String uuid, String javacoreContent) {
304314
|| (runtimeArgs != null && !runtimeArgs.isEmpty())
305315
? new Experimental(registers, runtimeArgs)
306316
: null;
317+
RuntimeInfo runtimeInfo =
318+
(j9JavaVersion != null || j9VmVersion != null)
319+
? new RuntimeInfo(j9JavaVersion, null, j9VmVersion)
320+
: null;
307321

308322
return new CrashLog(
309323
uuid,
@@ -316,6 +330,7 @@ public CrashLog parse(String uuid, String javacoreContent) {
316330
sigInfo,
317331
"1.0",
318332
experimental,
333+
runtimeInfo,
319334
null);
320335
}
321336

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

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -337,6 +337,27 @@ 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.runtimeInfo, "runtimeInfo should be populated");
354+
assertNotNull(crashLog.runtimeInfo.jreVersion, "jreVersion should be populated");
355+
assertNotNull(crashLog.runtimeInfo.javaVm, "javaVm should be populated");
356+
assertNotNull(crashLog.runtimeInfo.vmInfo, "vmInfo should be populated");
357+
assertEquals(expectedJreVersion, crashLog.runtimeInfo.jreVersion);
358+
assertEquals(expectedVmInfo, crashLog.runtimeInfo.vmInfo);
359+
}
360+
340361
@Test
341362
public void testNoSignalProducesInternalError() throws Exception {
342363
// 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: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,22 @@ 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.runtimeInfo, "runtimeInfo should be populated");
227+
assertNotNull(crashLog.runtimeInfo.jreVersion, "jreVersion should be populated");
228+
assertEquals(expectedJreVersion, crashLog.runtimeInfo.jreVersion);
229+
}
230+
215231
@Test
216232
public void testNoSignalProducesInternalError() throws Exception {
217233
// A javacore with a THREADS section but no 1TISIGINFO line

dd-java-agent/agent-crashtracking/src/test/resources/golden/errortracking/sample-crash-for-telemetry-2.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,11 @@
2626
"si_pid": 554848,
2727
"si_uid": 1000
2828
},
29+
"runtime_info": {
30+
"java_vm": "OpenJDK 64-Bit Server VM Temurin-22.0.1+8 (22.0.1+8, mixed mode, sharing, tiered, compressed oops, compressed class ptrs, g1 gc, linux-amd64)",
31+
"jre_version": "OpenJDK Runtime Environment Temurin-22.0.1+8 (22.0.1+8) (build 22.0.1+8)",
32+
"vm_info": "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"
33+
},
2934
"experimental": {
3035
"ucontext": {
3136
"RAX": "0x00000000000000ca",

dd-java-agent/agent-crashtracking/src/test/resources/golden/errortracking/sample-crash-for-telemetry-3.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,11 @@
103103
]
104104
}
105105
},
106+
"runtime_info": {
107+
"java_vm": "OpenJDK 64-Bit Server VM (25.372-b07 mixed mode bsd-aarch64 compressed oops)",
108+
"jre_version": "OpenJDK Runtime Environment (Zulu 8.70.0.23-CA-macos-aarch64) (8.0_372-b07) (build 1.8.0_372-b07)",
109+
"vm_info": "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)"
110+
},
106111
"files": {
107112
"dynamic_libraries": [
108113
"0x00000001ae54a000 \t/System/Library/Frameworks/Cocoa.framework/Versions/A/Cocoa",

dd-java-agent/agent-crashtracking/src/test/resources/golden/errortracking/sample-crash-for-telemetry.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -472,6 +472,11 @@
472472
"si_code": 1,
473473
"si_code_human_readable": "SEGV_MAPERR"
474474
},
475+
"runtime_info": {
476+
"java_vm": "OpenJDK 64-Bit Server VM Zulu17.42+20-SA (17.0.7+7-LTS, mixed mode, tiered, compressed oops, compressed class ptrs, g1 gc, linux-amd64)",
477+
"jre_version": "OpenJDK Runtime Environment Zulu17.42+20-SA (17.0.7+7) (build 17.0.7+7-LTS)",
478+
"vm_info": "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"
479+
},
475480
"experimental": {
476481
"ucontext": {
477482
"RAX": "0x00007f36ccfbf170",

0 commit comments

Comments
 (0)