Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -581,7 +581,8 @@ private RequestBody makeErrorTrackingRequestBody(@Nonnull CrashLog payload, bool
if (payload.experimental != null
&& (payload.experimental.ucontext != null
|| payload.experimental.registerToMemoryMapping != null
|| payload.experimental.runtimeArgs != null)) {
|| payload.experimental.runtimeArgs != null
|| payload.experimental.runtimeInfo != null)) {
writer.name("experimental");
writer.beginObject();
if (payload.experimental.ucontext != null) {
Expand Down Expand Up @@ -611,6 +612,20 @@ private RequestBody makeErrorTrackingRequestBody(@Nonnull CrashLog payload, bool
}
writer.endArray();
}
if (payload.experimental.runtimeInfo != null) {
writer.name("runtime_info");
writer.beginObject();
if (payload.experimental.runtimeInfo.jreVersion != null) {
writer.name("jre_version").value(payload.experimental.runtimeInfo.jreVersion);
}
if (payload.experimental.runtimeInfo.javaVm != null) {
writer.name("java_vm").value(payload.experimental.runtimeInfo.javaVm);
}
if (payload.experimental.runtimeInfo.vmInfo != null) {
writer.name("vm_info").value(payload.experimental.runtimeInfo.vmInfo);
}
writer.endObject();
}
writer.endObject();
}
// files (e.g. /proc/self/maps or dynamic_libraries)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,21 +14,33 @@ public final class Experimental {
@Json(name = "runtime_args")
public final List<String> runtimeArgs;

@Json(name = "runtime_info")
public final RuntimeInfo runtimeInfo;

public Experimental(Map<String, String> ucontext) {
this(ucontext, null, null);
this(ucontext, null, null, null);
}

public Experimental(Map<String, String> ucontext, List<String> runtimeArgs) {
this(ucontext, null, runtimeArgs);
this(ucontext, null, runtimeArgs, null);
}

public Experimental(
Map<String, String> ucontext,
Map<String, String> registerToMemoryMapping,
List<String> runtimeArgs) {
this(ucontext, registerToMemoryMapping, runtimeArgs, null);
}

public Experimental(
Map<String, String> ucontext,
Map<String, String> registerToMemoryMapping,
List<String> runtimeArgs,
RuntimeInfo runtimeInfo) {
this.ucontext = ucontext;
this.registerToMemoryMapping = registerToMemoryMapping;
this.runtimeArgs = runtimeArgs;
this.runtimeInfo = runtimeInfo;
}

@Override
Expand All @@ -37,11 +49,12 @@ public boolean equals(Object o) {
Experimental that = (Experimental) o;
return Objects.equals(ucontext, that.ucontext)
&& Objects.equals(registerToMemoryMapping, that.registerToMemoryMapping)
&& Objects.equals(runtimeArgs, that.runtimeArgs);
&& Objects.equals(runtimeArgs, that.runtimeArgs)
&& Objects.equals(runtimeInfo, that.runtimeInfo);
}

@Override
public int hashCode() {
return Objects.hash(ucontext, registerToMemoryMapping, runtimeArgs);
return Objects.hash(ucontext, registerToMemoryMapping, runtimeArgs, runtimeInfo);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package datadog.crashtracking.dto;

import com.squareup.moshi.Json;
import java.util.Objects;

/**
* JDK runtime information extracted from the hs_err crash log header and vm_info line. This
* captures the exact JDK vendor and build so crash reports can be correlated with the specific
* binaries in use.
*/
public final class RuntimeInfo {
@Json(name = "jre_version")
public final String jreVersion;

@Json(name = "java_vm")
public final String javaVm;

@Json(name = "vm_info")
public final String vmInfo;

public RuntimeInfo(String jreVersion, String javaVm, String vmInfo) {
this.jreVersion = jreVersion;
this.javaVm = javaVm;
this.vmInfo = vmInfo;
}

@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
RuntimeInfo that = (RuntimeInfo) o;
return Objects.equals(jreVersion, that.jreVersion)
&& Objects.equals(javaVm, that.javaVm)
&& Objects.equals(vmInfo, that.vmInfo);
}

@Override
public int hashCode() {
return Objects.hash(jreVersion, javaVm, vmInfo);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import datadog.crashtracking.dto.Metadata;
import datadog.crashtracking.dto.OSInfo;
import datadog.crashtracking.dto.ProcInfo;
import datadog.crashtracking.dto.RuntimeInfo;
import datadog.crashtracking.dto.SigInfo;
import datadog.crashtracking.dto.StackFrame;
import datadog.crashtracking.dto.StackTrace;
Expand Down Expand Up @@ -44,6 +45,9 @@
*/
public final class HotspotCrashLogParser {
private static final String HOTSPOT_JVM_ARGS_PREFIX = "jvm_args:";
private static final String JRE_VERSION_PREFIX = "# JRE version: ";
private static final String JAVA_VM_PREFIX = "# Java VM: ";
private static final String VM_INFO_PREFIX = "vm_info: ";
private static final DateTimeFormatter ZONED_DATE_TIME_FORMATTER =
DateTimeFormatter.ofPattern("EEE MMM ppd HH:mm:ss yyyy zzz", Locale.getDefault());
private static final DateTimeFormatter OFFSET_DATE_TIME_FORMATTER =
Expand Down Expand Up @@ -362,6 +366,9 @@ public CrashLog parse(String uuid, String crashLog) {
String dynamicLibraryKey = null;
boolean previousLineBlank = false;
State nextThreadSectionState = null;
String jreVersion = null;
String javaVm = null;
String vmInfo = null;

String[] lines = NEWLINE_SPLITTER.split(crashLog);
outer:
Expand Down Expand Up @@ -392,6 +399,11 @@ public CrashLog parse(String uuid, String crashLog) {
}
}
}
if (jreVersion == null && line.startsWith(JRE_VERSION_PREFIX)) {
jreVersion = line.substring(JRE_VERSION_PREFIX.length()).trim();
} else if (javaVm == null && line.startsWith(JAVA_VM_PREFIX)) {
javaVm = line.substring(JAVA_VM_PREFIX.length()).trim();
}
break;
case HEADER:
if (line.contains("S U M M A R Y")) {
Expand Down Expand Up @@ -486,6 +498,8 @@ public CrashLog parse(String uuid, String crashLog) {
state = State.DYNAMIC_LIBRARIES;
} else if (line.contains("S Y S T E M")) {
state = State.SYSTEM;
} else if (vmInfo == null && line.startsWith(VM_INFO_PREFIX)) {
vmInfo = line.substring(VM_INFO_PREFIX.length()).trim();
} else if (line.equals("END.")) {
state = State.DONE;
}
Expand Down Expand Up @@ -527,6 +541,8 @@ public CrashLog parse(String uuid, String crashLog) {
datetimeRaw = line.substring(6).trim();
} else if (datetime == null && datetimeRaw != null && line.startsWith("timezone: ")) {
datetime = dateTimeToISO(datetimeRaw + " " + line.substring(10).trim());
} else if (vmInfo == null && line.startsWith(VM_INFO_PREFIX)) {
vmInfo = line.substring(VM_INFO_PREFIX.length()).trim();
}
break;
case DONE:
Expand Down Expand Up @@ -609,11 +625,16 @@ public CrashLog parse(String uuid, String crashLog) {
registerToMemoryMapping.replaceAll((k, v) -> RedactUtils.redactRegisterToMemoryMapping(v));
resolvedMapping = registerToMemoryMapping;
}
RuntimeInfo runtimeInfo =
(jreVersion != null || javaVm != null || vmInfo != null)
? new RuntimeInfo(jreVersion, javaVm, vmInfo)
: null;
Experimental experimental =
!registers.isEmpty()
|| resolvedMapping != null
|| (runtimeArgs != null && !runtimeArgs.isEmpty())
? new Experimental(registers, resolvedMapping, runtimeArgs)
|| runtimeInfo != null
? new Experimental(registers, resolvedMapping, runtimeArgs, runtimeInfo)
: null;
DynamicLibs files =
(dynamicLibraryLines != null && !dynamicLibraryLines.isEmpty())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import datadog.crashtracking.dto.Metadata;
import datadog.crashtracking.dto.OSInfo;
import datadog.crashtracking.dto.ProcInfo;
import datadog.crashtracking.dto.RuntimeInfo;
import datadog.crashtracking.dto.SigInfo;
import datadog.crashtracking.dto.StackFrame;
import datadog.crashtracking.dto.StackTrace;
Expand Down Expand Up @@ -46,6 +47,8 @@
*/
public final class J9JavacoreParser {
private static final String J9_USER_ARG_PREFIX = "2CIUSERARG";
private static final String J9_JAVA_VERSION_PREFIX = "1CIJAVAVERSION ";
private static final String J9_VM_VERSION_PREFIX = "1CIVMVERSION";

private final BuildIdCollector buildIdCollector;

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

Map<String, String> registers = null;
RuntimeArgs j9UserArgs = new RuntimeArgs();
String j9JavaVersion = null;
String j9VmVersion = null;

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

Expand Down Expand Up @@ -179,6 +184,11 @@ public CrashLog parse(String uuid, String javacoreContent) {
if (pidMatcher.matches()) {
pid = pidMatcher.group(1);
}
if (j9JavaVersion == null && line.startsWith(J9_JAVA_VERSION_PREFIX)) {
j9JavaVersion = line.substring(J9_JAVA_VERSION_PREFIX.length()).trim();
} else if (j9VmVersion == null && line.startsWith(J9_VM_VERSION_PREFIX)) {
j9VmVersion = line.substring(J9_VM_VERSION_PREFIX.length()).trim();
}
break;

case THREADS:
Expand Down Expand Up @@ -299,10 +309,15 @@ public CrashLog parse(String uuid, String javacoreContent) {
Integer parsedPid = safelyParseInt(pid);
ProcInfo procInfo = parsedPid != null ? new ProcInfo(parsedPid) : null;
List<String> runtimeArgs = j9UserArgs.build();
RuntimeInfo runtimeInfo =
(j9JavaVersion != null || j9VmVersion != null)
? new RuntimeInfo(j9JavaVersion, null, j9VmVersion)
: null;
Experimental experimental =
(registers != null && !registers.isEmpty())
|| (runtimeArgs != null && !runtimeArgs.isEmpty())
? new Experimental(registers, runtimeArgs)
|| runtimeInfo != null
? new Experimental(registers, null, runtimeArgs, runtimeInfo)
: null;

return new CrashLog(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -337,6 +337,28 @@ public void testParseCurrentThreadName(String line, String expected) {
HotspotCrashLogParser.parseCurrentThreadName(line));
}

@TableTest({
"scenario | filename | expectedJreVersion | expectedVmInfo ",
"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 ",
"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 ",
"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)",
"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 ",
"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) "
})
public void testRuntimeInfoParsing(
String filename, String expectedJreVersion, String expectedVmInfo) throws Exception {
CrashLog crashLog =
new HotspotCrashLogParser().parse(UUID.randomUUID().toString(), readFileAsString(filename));

assertNotNull(crashLog.experimental, "experimental should be populated");
assertNotNull(crashLog.experimental.runtimeInfo, "runtimeInfo should be populated");
assertNotNull(crashLog.experimental.runtimeInfo.jreVersion, "jreVersion should be populated");
assertNotNull(crashLog.experimental.runtimeInfo.javaVm, "javaVm should be populated");
assertNotNull(crashLog.experimental.runtimeInfo.vmInfo, "vmInfo should be populated");
assertEquals(expectedJreVersion, crashLog.experimental.runtimeInfo.jreVersion);
assertEquals(expectedVmInfo, crashLog.experimental.runtimeInfo.vmInfo);
}

@Test
public void testNoSignalProducesInternalError() throws Exception {
// A crash log that reaches the PROCESS section but has no siginfo line
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,23 @@ public void testDateTimeParsing() throws Exception {
"Expected ISO-8601 format, got: " + crashLog.timestamp);
}

@TableTest({
"scenario | filename | expectedJreVersion ",
"OpenJ9 11 GPF | sample-j9-javacore-gpf.txt | JRE 11.0.12 Linux amd64-64 ",
"OpenJ9 17 OOM | sample-j9-javacore-oom.txt | JRE 17.0.6 Linux amd64-64 ",
"OpenJ9 11 aarch | sample-openj9-11-javacore-gpf.txt | JRE 11 Linux aarch64-64 (build 11.0.28+6) ",
"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))"
})
public void testRuntimeInfoParsing(String filename, String expectedJreVersion) throws Exception {
CrashLog crashLog =
new J9JavacoreParser().parse(UUID.randomUUID().toString(), readFileAsString(filename));

assertNotNull(crashLog.experimental, "experimental should be populated");
assertNotNull(crashLog.experimental.runtimeInfo, "runtimeInfo should be populated");
assertNotNull(crashLog.experimental.runtimeInfo.jreVersion, "jreVersion should be populated");
assertEquals(expectedJreVersion, crashLog.experimental.runtimeInfo.jreVersion);
}

@Test
public void testNoSignalProducesInternalError() throws Exception {
// A javacore with a THREADS section but no 1TISIGINFO line
Expand Down