Skip to content

Commit 9e5ca81

Browse files
committed
Send ping message before initiating crashtracking
1 parent 6b33361 commit 9e5ca81

File tree

8 files changed

+98
-58
lines changed

8 files changed

+98
-58
lines changed

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

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ public static class StoredConfig {
3535
final String reportUUID;
3636

3737
StoredConfig(
38+
String reportUUID,
3839
String service,
3940
String env,
4041
String version,
@@ -47,7 +48,7 @@ public static class StoredConfig {
4748
this.tags = tags;
4849
this.processTags = processTags;
4950
this.runtimeId = runtimeId;
50-
this.reportUUID = RandomUtils.randomUUID().toString();
51+
this.reportUUID = reportUUID;
5152
}
5253

5354
public static class Builder {
@@ -57,13 +58,15 @@ public static class Builder {
5758
String tags;
5859
String processTags;
5960
String runtimeId;
61+
String reportUUID;
6062

6163
public Builder(Config config) {
6264
// get sane defaults
6365
this.service = config.getServiceName();
6466
this.env = config.getEnv();
6567
this.version = config.getVersion();
6668
this.runtimeId = config.getRuntimeId();
69+
this.reportUUID = RandomUtils.randomUUID().toString();
6770
}
6871

6972
public Builder service(String service) {
@@ -96,8 +99,14 @@ public Builder runtimeId(String runtimeId) {
9699
return this;
97100
}
98101

102+
// @VisibleForTesting
103+
Builder reportUUID(String reportUUID) {
104+
this.reportUUID = reportUUID;
105+
return this;
106+
}
107+
99108
public StoredConfig build() {
100-
return new StoredConfig(service, env, version, tags, processTags, runtimeId);
109+
return new StoredConfig(reportUUID, service, env, version, tags, processTags, runtimeId);
101110
}
102111
}
103112
}

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

Lines changed: 26 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -118,15 +118,17 @@ public void notifyCrashStarted(String error) {
118118
JsonWriter writer = JsonWriter.of(buf)) {
119119
writer.beginObject();
120120
writer.name("crash_uuid").value(storedConfig.reportUUID);
121+
writer.name("kind").value("Crash ping");
122+
writer.name("current_schema_version").value("1.0");
121123
writer
122124
.name("message")
123125
.value(
124126
"Crashtracker crash ping: " + (error != null ? error : "crash processing started"));
125127
writer.endObject();
126-
handleCall(
127-
makeTelemetryRequest(RequestBody.create(APPLICATION_JSON, buf.toString())), "ping");
128+
handleCall(makeTelemetryRequest(makeTelemetryRequestBody(buf.readUtf8(), true)), "ping");
129+
128130
} catch (Throwable t) {
129-
log.error("Failed to send c crash ping", t);
131+
log.error("Failed to send crash ping", t);
130132
}
131133
}
132134

@@ -259,9 +261,14 @@ private String extractErrorStackTrace(String fileContent, boolean redact) {
259261
boolean uploadToTelemetry(@Nonnull Path file, String uuid) {
260262
try {
261263
String content = new String(Files.readAllBytes(file), Charset.defaultCharset());
262-
handleCall(makeTelemetryRequest(makeTelemetryRequestBody(content, uuid)), "file");
263-
} catch (IOException e) {
264-
log.error("Failed to upload crash file: {}", file, e);
264+
CrashLog crashLog = CrashLogParser.fromHotspotCrashLog(uuid, content);
265+
if (crashLog == null) {
266+
log.error("Failed to parse crash log");
267+
return false;
268+
}
269+
handleCall(makeTelemetryRequest(makeTelemetryRequestBody(crashLog.toJson(), false)), "crash");
270+
} catch (Throwable t) {
271+
log.error("Failed to upload crash file: {}", file, t);
265272
return false;
266273
}
267274
return true;
@@ -287,12 +294,9 @@ private Call makeTelemetryRequest(@Nonnull RequestBody requestBody) throws IOExc
287294
.build());
288295
}
289296

290-
private RequestBody makeTelemetryRequestBody(@Nonnull String content, String uuid)
297+
private RequestBody makeTelemetryRequestBody(@Nonnull String payload, boolean isPing)
291298
throws IOException {
292-
CrashLog crashLog = CrashLogParser.fromHotspotCrashLog(content, uuid);
293-
if (crashLog == null) {
294-
throw new IOException("Failed to parse crash log");
295-
}
299+
296300
try (Buffer buf = new Buffer()) {
297301
try (JsonWriter writer = JsonWriter.of(buf)) {
298302
writer.beginObject();
@@ -306,11 +310,17 @@ private RequestBody makeTelemetryRequestBody(@Nonnull String content, String uui
306310
writer.name("payload");
307311
writer.beginArray();
308312
writer.beginObject();
309-
writer.name("message").value(crashLog.toJson());
310-
writer.name("level").value("ERROR");
311-
writer.name("tags").value("severity:crash");
312-
writer.name("is_sensitive").value(true);
313-
writer.name("is_crash").value(true);
313+
writer.name("message").value(payload);
314+
if (isPing) {
315+
writer.name("level").value("DEBUG");
316+
writer.name("is_sensitive").value(false);
317+
writer.name("is_crash_ping").value(true);
318+
} else {
319+
writer.name("level").value("ERROR");
320+
writer.name("tags").value("severity:crash");
321+
writer.name("is_sensitive").value(true);
322+
writer.name("is_crash").value(true);
323+
}
314324
writer.endObject();
315325
writer.endArray();
316326
writer.name("application");

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

Lines changed: 0 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -87,21 +87,4 @@ public int hashCode() {
8787
return Objects.hash(
8888
uuid, timestamp, incomplete, error, metadata, osInfo, procInfo, version, dataSchemaVersion);
8989
}
90-
91-
public boolean equalsForTest(Object o) {
92-
// for tests, we need to ignore UUID, OSInfo and Metadata part
93-
if (this == o) {
94-
return true;
95-
}
96-
if (o == null || getClass() != o.getClass()) {
97-
return false;
98-
}
99-
CrashLog crashLog = (CrashLog) o;
100-
return incomplete == crashLog.incomplete
101-
&& version == crashLog.version
102-
&& Objects.equals(timestamp, crashLog.timestamp)
103-
&& Objects.equals(error, crashLog.error)
104-
&& Objects.equals(procInfo, crashLog.procInfo)
105-
&& Objects.equals(dataSchemaVersion, crashLog.dataSchemaVersion);
106-
}
10790
}

dd-java-agent/agent-crashtracking/src/test/java/datadog/crashtracking/CrashUploaderTest.java

Lines changed: 57 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -46,17 +46,6 @@ public class CrashUploaderTest {
4646
private static final String SERVICE = "crash-service";
4747
private static final String VERSION = "crash-version";
4848
private static final String SAMPLE_UUID = "a4194cd6-8cb3-45fd-9bd9-3af83e0a3ad3";
49-
// private static final Map<String, String> TAGS = Map.of("foo", "bar", "baz", "123", "null",
50-
// null, "empty", "");
51-
52-
// // We sort tags to have expected parameters to have expected result
53-
// private static final Map<String, String> EXPECTED_TAGS =
54-
// ImmutableMap.of(
55-
// "baz", "123",
56-
// "foo", "bar",
57-
// PidHelper.PID_TAG, PidHelper.PID.toString(),
58-
// VersionInfo.PROFILER_VERSION_TAG, VersionInfo.VERSION,
59-
// VersionInfo.LIBRARY_VERSION_TAG, VersionInfo.VERSION);
6049

6150
// TODO: Add a test to verify overall request timeout rather than IO timeout
6251
private final Duration REQUEST_TIMEOUT = Duration.ofSeconds(10);
@@ -145,6 +134,42 @@ private Path getResourcePath(String resourceName) throws Exception {
145134
return Paths.get(getClass().getClassLoader().getResource(resourceName).toURI());
146135
}
147136

137+
@Test
138+
public void testCrashPing() throws Exception {
139+
// Given
140+
final String expected = readFileAsString("golden/sample-ping-for-telemetry.txt");
141+
ConfigManager.StoredConfig crashConfig =
142+
new ConfigManager.StoredConfig.Builder(config)
143+
.reportUUID(SAMPLE_UUID)
144+
.processTags("a:b")
145+
.runtimeId("1234")
146+
.build();
147+
// When
148+
uploader = new CrashUploader(config, crashConfig);
149+
server.enqueue(new MockResponse().setResponseCode(200));
150+
uploader.notifyCrashStarted(null);
151+
152+
final RecordedRequest recordedRequest = server.takeRequest(5, TimeUnit.SECONDS);
153+
154+
// Then
155+
assertEquals(url, recordedRequest.getRequestUrl());
156+
157+
final ObjectMapper mapper = new ObjectMapper();
158+
final JsonNode event = mapper.readTree(recordedRequest.getBody().readUtf8());
159+
160+
// header
161+
assertCommonHeader(event);
162+
163+
// payload:
164+
assertEquals("DEBUG", event.get("payload").get(0).get("level").asText());
165+
166+
assertFalse(event.get("payload").get(0).get("is_sensitive").asBoolean());
167+
assertTrue(event.get("payload").get(0).get("is_crash_ping").asBoolean());
168+
169+
assertEquals(expected, event.get("payload").get(0).get("message").asText());
170+
assertCommonPayload(event);
171+
}
172+
148173
@ParameterizedTest
149174
@ValueSource(
150175
strings = {
@@ -156,7 +181,11 @@ public void testTelemetryHappyPath(String log) throws Exception {
156181
// Given
157182
CrashLog expected = CrashLog.fromJson(readFileAsString("golden/" + log));
158183
ConfigManager.StoredConfig crashConfig =
159-
new ConfigManager.StoredConfig.Builder(config).processTags("a:b").runtimeId("1234").build();
184+
new ConfigManager.StoredConfig.Builder(config)
185+
.reportUUID(SAMPLE_UUID)
186+
.processTags("a:b")
187+
.runtimeId("1234")
188+
.build();
160189
// When
161190
uploader = new CrashUploader(config, crashConfig);
162191
server.enqueue(new MockResponse().setResponseCode(200));
@@ -170,25 +199,33 @@ public void testTelemetryHappyPath(String log) throws Exception {
170199
final ObjectMapper mapper = new ObjectMapper();
171200
final JsonNode event = mapper.readTree(recordedRequest.getBody().readUtf8());
172201

173-
assertEquals(CrashUploader.TELEMETRY_API_VERSION, event.get("api_version").asText());
174-
assertEquals(SAMPLE_UUID, event.get("uuid").asText());
175-
assertEquals("logs", event.get("request_type").asText());
176-
assertEquals("crashtracker", event.get("origin").asText());
177-
assertEquals("1234", event.get("runtime_id").asText());
202+
// header
203+
assertCommonHeader(event);
178204

179205
// payload:
180206
assertEquals("ERROR", event.get("payload").get(0).get("level").asText());
181207

182208
assertTrue(event.get("payload").get(0).get("is_sensitive").asBoolean());
183209
assertTrue(event.get("payload").get(0).get("is_crash").asBoolean());
184-
// we need to sanitize the UIID which keeps on changing
185210
String message = event.get("payload").get(0).get("message").asText();
186211
CrashLog extracted = CrashLog.fromJson(message);
187212

188-
assertTrue(
189-
expected.equalsForTest(extracted),
213+
assertEquals(
214+
expected,
215+
extracted,
190216
() -> "Expected: " + expected.toJson() + "\nbut got: " + extracted.toJson());
191217
assertEquals("severity:crash", event.get("payload").get(0).get("tags").asText());
218+
assertCommonPayload(event);
219+
}
220+
221+
private void assertCommonHeader(JsonNode event) {
222+
assertEquals(CrashUploader.TELEMETRY_API_VERSION, event.get("api_version").asText());
223+
assertEquals("logs", event.get("request_type").asText());
224+
assertEquals("crashtracker", event.get("origin").asText());
225+
assertEquals("1234", event.get("runtime_id").asText());
226+
}
227+
228+
private void assertCommonPayload(JsonNode event) {
192229
// application:
193230
assertEquals(ENV, event.get("application").get("env").asText());
194231
assertEquals("jvm", event.get("application").get("language_name").asText());
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
{"data_schema_version":"1.0","error":{"is_crash":true,"kind":"SIGSEGV","message":"\n\nJRE version: OpenJDK Runtime Environment Temurin-22.0.1+8 (22.0.1+8) (build 22.0.1+8)\nJava 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)\nProblematic frame:\nC [libpthread.so.0+0x9cd5] __pthread_clockjoin_ex+0x255\n\nNative frames: (J=compiled Java code, j=interpreted, Vv=VM code, C=native code)\nC [libpthread.so.0+0x9cd5] __pthread_clockjoin_ex+0x255\n","source_type":"crashtracking","stack":{"format":"CrashTrackerV1","frames":[{"file":"libpthread.so.0","function":"__pthread_clockjoin_ex"}]}},"incomplete":false,"metadata":{"family":"java","library_name":"dd-trace-java","library_version":"1.55.0-SNAPSHOT~d506c27e84"},"os_info":{"architecture":"aarch64","bitness":"64","os_type":"Mac OS X","version":{"Semantic":[15,7,1]}},"proc_info":{"pid":"576034"},"timestamp":"2024-09-20T13:19:06Z","uuid":"db6760d5-71db-44bf-9e13-21320dac86da","version_id":0}
1+
{"data_schema_version":"1.0","error":{"is_crash":true,"kind":"SIGSEGV","message":"\n\nJRE version: OpenJDK Runtime Environment Temurin-22.0.1+8 (22.0.1+8) (build 22.0.1+8)\nJava 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)\nProblematic frame:\nC [libpthread.so.0+0x9cd5] __pthread_clockjoin_ex+0x255\n\nNative frames: (J=compiled Java code, j=interpreted, Vv=VM code, C=native code)\nC [libpthread.so.0+0x9cd5] __pthread_clockjoin_ex+0x255\n","source_type":"crashtracking","stack":{"format":"CrashTrackerV1","frames":[{"file":"libpthread.so.0","function":"__pthread_clockjoin_ex"}]}},"incomplete":false,"metadata":{"family":"java","library_name":"dd-trace-java","library_version":"1.55.0-SNAPSHOT~6b33361cb4"},"os_info":{"architecture":"aarch64","bitness":"64","os_type":"Mac OS X","version":{"Semantic":[15,7,1]}},"proc_info":{"pid":"576034"},"timestamp":"2024-09-20T13:19:06Z","uuid":"a4194cd6-8cb3-45fd-9bd9-3af83e0a3ad3","version_id":0}
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
{"data_schema_version":"1.0","error":{"is_crash":true,"kind":"INVALID","message":"\n\n fatal error: OutOfMemory encountered: Java heap space\nJRE version: OpenJDK Runtime Environment (Zulu 8.70.0.23-CA-macos-aarch64) (8.0_372-b07) (build 1.8.0_372-b07)\nJava VM: OpenJDK 64-Bit Server VM (25.372-b07 mixed mode bsd-aarch64 compressed oops)\n\nNative frames: (J=compiled Java code, j=interpreted, Vv=VM code, C=native code)\nV [libjvm.dylib+0x565d30] VMError::report_and_die()+0x468\nV [libjvm.dylib+0x1941a0] report_vm_error(char const*, int, char const*, char const*)+0x5c\nV [libjvm.dylib+0x1943d8] report_java_out_of_memory(char const*)+0xfc\nV [libjvm.dylib+0x70430] CollectedHeap::common_mem_allocate_noinit(KlassHandle, unsigned long, Thread*)+0x128\nV [libjvm.dylib+0x53eba8] TypeArrayKlass::allocate_common(int, bool, Thread*)+0xfc\nV [libjvm.dylib+0x285b6c] InterpreterRuntime::newarray(JavaThread*, BasicType, int)+0x48\nj datadog.smoketest.crashtracking.CrashtrackingTestApplication.main([Ljava/lang/String;)V+105\nv ~StubRoutines::call_stub\nV [libjvm.dylib+0x28f86c] JavaCalls::call_helper(JavaValue*, methodHandle*, JavaCallArguments*, Thread*)+0x840\nV [libjvm.dylib+0x2d3b44] jni_invoke_static(JNIEnv_*, JavaValue*, _jobject*, JNICallType, _jmethodID*, JNI_ArgumentPusher*, Thread*)+0x294\nV [libjvm.dylib+0x2d7160] jni_CallStaticVoidMethod+0x188\nC [java+0x6404] JavaMain+0xa10\nC [libsystem_pthread.dylib+0x6f94] _pthread_start+0x88\nC [libsystem_pthread.dylib+0x1d34] thread_start+0x8\n","source_type":"crashtracking","stack":{"format":"CrashTrackerV1","frames":[{"file":"libjvm.dylib","function":"VMError::report_and_die()"},{"file":"libjvm.dylib","function":"report_vm_error(char const*, int, char const*, char const*)"},{"file":"libjvm.dylib","function":"report_java_out_of_memory(char const*)"},{"file":"libjvm.dylib","function":"CollectedHeap::common_mem_allocate_noinit(KlassHandle, unsigned long, Thread*)"},{"file":"libjvm.dylib","function":"TypeArrayKlass::allocate_common(int, bool, Thread*)"},{"file":"libjvm.dylib","function":"InterpreterRuntime::newarray(JavaThread*, BasicType, int)"},{"function":"datadog.smoketest.crashtracking.CrashtrackingTestApplication.main([Ljava/lang/String;)V","line":105},{"function":" ~StubRoutines::call_stub"},{"file":"libjvm.dylib","function":"JavaCalls::call_helper(JavaValue*, methodHandle*, JavaCallArguments*, Thread*)"},{"file":"libjvm.dylib","function":"jni_invoke_static(JNIEnv_*, JavaValue*, _jobject*, JNICallType, _jmethodID*, JNI_ArgumentPusher*, Thread*)"},{"file":"libjvm.dylib","function":"jni_CallStaticVoidMethod"},{"file":"java","function":"JavaMain"},{"file":"libsystem_pthread.dylib","function":"_pthread_start"},{"file":"libsystem_pthread.dylib","function":"thread_start"}]}},"incomplete":false,"metadata":{"family":"java","library_name":"dd-trace-java","library_version":"1.55.0-SNAPSHOT~d506c27e84"},"os_info":{"architecture":"aarch64","bitness":"64","os_type":"Mac OS X","version":{"Semantic":[15,7,1]}},"proc_info":{"pid":"96267"},"uuid":"556a8c7e-2fe5-4c86-bfb8-297147d22ce4","version_id":0}
1+
{"data_schema_version":"1.0","error":{"is_crash":true,"kind":"INVALID","message":"\n\n fatal error: OutOfMemory encountered: Java heap space\nJRE version: OpenJDK Runtime Environment (Zulu 8.70.0.23-CA-macos-aarch64) (8.0_372-b07) (build 1.8.0_372-b07)\nJava VM: OpenJDK 64-Bit Server VM (25.372-b07 mixed mode bsd-aarch64 compressed oops)\n\nNative frames: (J=compiled Java code, j=interpreted, Vv=VM code, C=native code)\nV [libjvm.dylib+0x565d30] VMError::report_and_die()+0x468\nV [libjvm.dylib+0x1941a0] report_vm_error(char const*, int, char const*, char const*)+0x5c\nV [libjvm.dylib+0x1943d8] report_java_out_of_memory(char const*)+0xfc\nV [libjvm.dylib+0x70430] CollectedHeap::common_mem_allocate_noinit(KlassHandle, unsigned long, Thread*)+0x128\nV [libjvm.dylib+0x53eba8] TypeArrayKlass::allocate_common(int, bool, Thread*)+0xfc\nV [libjvm.dylib+0x285b6c] InterpreterRuntime::newarray(JavaThread*, BasicType, int)+0x48\nj datadog.smoketest.crashtracking.CrashtrackingTestApplication.main([Ljava/lang/String;)V+105\nv ~StubRoutines::call_stub\nV [libjvm.dylib+0x28f86c] JavaCalls::call_helper(JavaValue*, methodHandle*, JavaCallArguments*, Thread*)+0x840\nV [libjvm.dylib+0x2d3b44] jni_invoke_static(JNIEnv_*, JavaValue*, _jobject*, JNICallType, _jmethodID*, JNI_ArgumentPusher*, Thread*)+0x294\nV [libjvm.dylib+0x2d7160] jni_CallStaticVoidMethod+0x188\nC [java+0x6404] JavaMain+0xa10\nC [libsystem_pthread.dylib+0x6f94] _pthread_start+0x88\nC [libsystem_pthread.dylib+0x1d34] thread_start+0x8\n","source_type":"crashtracking","stack":{"format":"CrashTrackerV1","frames":[{"file":"libjvm.dylib","function":"VMError::report_and_die()"},{"file":"libjvm.dylib","function":"report_vm_error(char const*, int, char const*, char const*)"},{"file":"libjvm.dylib","function":"report_java_out_of_memory(char const*)"},{"file":"libjvm.dylib","function":"CollectedHeap::common_mem_allocate_noinit(KlassHandle, unsigned long, Thread*)"},{"file":"libjvm.dylib","function":"TypeArrayKlass::allocate_common(int, bool, Thread*)"},{"file":"libjvm.dylib","function":"InterpreterRuntime::newarray(JavaThread*, BasicType, int)"},{"function":"datadog.smoketest.crashtracking.CrashtrackingTestApplication.main([Ljava/lang/String;)V","line":105},{"function":" ~StubRoutines::call_stub"},{"file":"libjvm.dylib","function":"JavaCalls::call_helper(JavaValue*, methodHandle*, JavaCallArguments*, Thread*)"},{"file":"libjvm.dylib","function":"jni_invoke_static(JNIEnv_*, JavaValue*, _jobject*, JNICallType, _jmethodID*, JNI_ArgumentPusher*, Thread*)"},{"file":"libjvm.dylib","function":"jni_CallStaticVoidMethod"},{"file":"java","function":"JavaMain"},{"file":"libsystem_pthread.dylib","function":"_pthread_start"},{"file":"libsystem_pthread.dylib","function":"thread_start"}]}},"incomplete":false,"metadata":{"family":"java","library_name":"dd-trace-java","library_version":"1.55.0-SNAPSHOT~6b33361cb4"},"os_info":{"architecture":"aarch64","bitness":"64","os_type":"Mac OS X","version":{"Semantic":[15,7,1]}},"proc_info":{"pid":"96267"},"uuid":"a4194cd6-8cb3-45fd-9bd9-3af83e0a3ad3","version_id":0}

0 commit comments

Comments
 (0)