Skip to content

Commit 562d076

Browse files
committed
RUM-15735: Add profiling aggregation diagnostics telemetry
1 parent 473ba7c commit 562d076

7 files changed

Lines changed: 117 additions & 27 deletions

File tree

Datadog/Datadog.xcodeproj/project.pbxproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
1121FD002EE319F000297156 /* ProfilingTelemetryController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1121FCFE2EE319E600297156 /* ProfilingTelemetryController.swift */; };
3535
1121FD022EE33B3400297156 /* ConfigurationMetric.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1121FD012EE33B2E00297156 /* ConfigurationMetric.swift */; };
3636
1121FD052F00000000297156 /* ProfilingSessionMetric.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1121FD042F00000000297156 /* ProfilingSessionMetric.swift */; };
37+
1121FD072F00000100297156 /* AggregationDiagnosticsMetric.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1121FD062F00000100297156 /* AggregationDiagnosticsMetric.swift */; };
3738
1124D5302EA6D23C0002E053 /* StartupTypeHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1124D52E2EA6D2370002E053 /* StartupTypeHandler.swift */; };
3839
1124D5322EA6D4050002E053 /* RUMAppLaunchManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1124D5312EA6D4050002E053 /* RUMAppLaunchManagerTests.swift */; };
3940
1124D5382EA6D4390002E053 /* RUMAppLaunchManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1124D5342EA6D4390002E053 /* RUMAppLaunchManager.swift */; };
@@ -1803,6 +1804,7 @@
18031804
1121FCFE2EE319E600297156 /* ProfilingTelemetryController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfilingTelemetryController.swift; sourceTree = "<group>"; };
18041805
1121FD012EE33B2E00297156 /* ConfigurationMetric.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigurationMetric.swift; sourceTree = "<group>"; };
18051806
1121FD042F00000000297156 /* ProfilingSessionMetric.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfilingSessionMetric.swift; sourceTree = "<group>"; };
1807+
1121FD062F00000100297156 /* AggregationDiagnosticsMetric.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AggregationDiagnosticsMetric.swift; sourceTree = "<group>"; };
18061808
1124D52E2EA6D2370002E053 /* StartupTypeHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StartupTypeHandler.swift; sourceTree = "<group>"; };
18071809
1124D5312EA6D4050002E053 /* RUMAppLaunchManagerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RUMAppLaunchManagerTests.swift; sourceTree = "<group>"; };
18081810
1124D5342EA6D4390002E053 /* RUMAppLaunchManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RUMAppLaunchManager.swift; sourceTree = "<group>"; };
@@ -3262,6 +3264,7 @@
32623264
1121FCFA2EE305D900297156 /* SDKMetrics */ = {
32633265
isa = PBXGroup;
32643266
children = (
3267+
1121FD062F00000100297156 /* AggregationDiagnosticsMetric.swift */,
32653268
1121FD012EE33B2E00297156 /* ConfigurationMetric.swift */,
32663269
1121FD042F00000000297156 /* ProfilingSessionMetric.swift */,
32673270
1121FCFE2EE319E600297156 /* ProfilingTelemetryController.swift */,
@@ -8459,6 +8462,7 @@
84598462
110DD1B22EEB6BBD002F3B8B /* ProfilingConfiguration.swift in Sources */,
84608463
D233E7C42E4625D400E7CFDE /* RequestBuilder.swift in Sources */,
84618464
D233E7C52E4625D400E7CFDE /* ProfileEvent.swift in Sources */,
8465+
1121FD072F00000100297156 /* AggregationDiagnosticsMetric.swift in Sources */,
84628466
1121FD002EE319F000297156 /* ProfilingTelemetryController.swift in Sources */,
84638467
1121FD052F00000000297156 /* ProfilingSessionMetric.swift in Sources */,
84648468
D233E8022E4B653F00E7CFDE /* dd_pprof.cpp in Sources */,

DatadogProfiling/Mach/dd_profiler.cpp

Lines changed: 12 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,10 @@ static constexpr double DD_PROFILER_MAX_SAMPLE_RATE = 100.0;
2727
// Maximum queued aggregation batch memory before new batches are dropped.
2828
static constexpr uint64_t DD_PROFILER_DEFAULT_HARD_LIMIT_BYTES = 64ULL * 1024ULL * 1024ULL;
2929

30+
static dd_profiler_diagnostics_t empty_diagnostics() {
31+
return {0, 0, 0};
32+
}
33+
3034
namespace dd::profiler { class dd_profiler; }
3135

3236
static dd::profiler::dd_profiler* g_dd_profiler = nullptr;
@@ -281,18 +285,14 @@ class dd_profiler {
281285
return swap_context.flushed_profile;
282286
}
283287

284-
void consume_diagnostics(dd_profiler_diagnostics_t* out) {
285-
if (!out) {
286-
return;
287-
}
288-
289-
out->dropped_batch_count = 0;
290-
out->dropped_sample_count = 0;
291-
out->max_pending_bytes = 0;
288+
dd_profiler_diagnostics_t diagnostics() {
289+
dd_profiler_diagnostics_t result = empty_diagnostics();
292290

293291
if (profiler) {
294-
profiler->consume_diagnostics(out);
292+
profiler->consume_diagnostics(&result);
295293
}
294+
295+
return result;
296296
}
297297

298298
private:
@@ -466,16 +466,12 @@ dd_profiler_status_t dd_profiler_get_status(void) {
466466
return g_dd_profiler ? g_dd_profiler->status : DD_PROFILER_STATUS_NOT_CREATED;
467467
}
468468

469-
void dd_profiler_consume_diagnostics(dd_profiler_diagnostics_t* out) {
469+
dd_profiler_diagnostics_t dd_profiler_diagnostics(void) {
470470
std::lock_guard<std::mutex> lock(g_dd_profiler_mutex);
471-
if (out) {
472-
out->dropped_batch_count = 0;
473-
out->dropped_sample_count = 0;
474-
out->max_pending_bytes = 0;
475-
}
476471
if (g_dd_profiler) {
477-
g_dd_profiler->consume_diagnostics(out);
472+
return g_dd_profiler->diagnostics();
478473
}
474+
return empty_diagnostics();
479475
}
480476

481477
bool dd_profiler_is_running() {

DatadogProfiling/Mach/include/dd_profiler.h

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -213,13 +213,11 @@ typedef struct profile dd_profile_t;
213213
dd_profiler_status_t dd_profiler_get_status(void);
214214

215215
/**
216-
* @brief Returns and resets profiling diagnostics accumulated since the last consume.
216+
* @brief Returns and resets profiling diagnostics accumulated since the last call.
217217
*
218-
* If `out` is null, this function is a no-op.
219-
*
220-
* @param out Output diagnostics structure
218+
* @return Profiling diagnostics, or zeroed diagnostics if the profiler does not exist.
221219
*/
222-
void dd_profiler_consume_diagnostics(dd_profiler_diagnostics_t* out);
220+
dd_profiler_diagnostics_t dd_profiler_diagnostics(void);
223221

224222
/**
225223
* @brief Stops profiling if it's currently running
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
/*
2+
* Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0.
3+
* This product includes software developed at Datadog (https://www.datadoghq.com/).
4+
* Copyright 2019-Present Datadog, Inc.
5+
*/
6+
7+
import Foundation
8+
9+
#if !os(watchOS)
10+
11+
// swiftlint:disable duplicate_imports
12+
#if swift(>=6.0)
13+
internal import DatadogMachProfiler
14+
#else
15+
@_implementationOnly import DatadogMachProfiler
16+
#endif
17+
// swiftlint:enable duplicate_imports
18+
19+
/// Tracks profiling aggregation diagnostics to be added to telemetry metrics.
20+
internal final class AggregationDiagnosticsMetric {
21+
internal enum Constants {
22+
/// Namespace for bundling profiling diagnostics.
23+
static let diagnosticsKey = "profiling_diagnostics"
24+
}
25+
26+
/// Number of sampled batches dropped during aggregation.
27+
let droppedBatchCount: UInt64
28+
/// Number of sampled stack traces contained in dropped batches.
29+
let droppedSampleCount: UInt64
30+
/// Maximum queued aggregation memory observed, in bytes.
31+
let maxPendingBytes: UInt64
32+
33+
init(
34+
droppedBatchCount: UInt64 = 0,
35+
droppedSampleCount: UInt64 = 0,
36+
maxPendingBytes: UInt64 = 0
37+
) {
38+
self.droppedBatchCount = droppedBatchCount
39+
self.droppedSampleCount = droppedSampleCount
40+
self.maxPendingBytes = maxPendingBytes
41+
}
42+
43+
func asMetricAttributes() -> [String: Encodable]? {
44+
[Constants.diagnosticsKey: Attributes(aggregation: self)]
45+
}
46+
}
47+
48+
extension AggregationDiagnosticsMetric: Encodable {
49+
enum CodingKeys: String, CodingKey {
50+
case droppedBatchCount = "dropped_batch_count"
51+
case droppedSampleCount = "dropped_sample_count"
52+
case maxPendingBytes = "max_pending_bytes"
53+
}
54+
}
55+
56+
extension AggregationDiagnosticsMetric {
57+
static func consumeDiagnostics() -> AggregationDiagnosticsMetric {
58+
let diagnostics = dd_profiler_diagnostics()
59+
60+
return AggregationDiagnosticsMetric(
61+
droppedBatchCount: diagnostics.dropped_batch_count,
62+
droppedSampleCount: diagnostics.dropped_sample_count,
63+
maxPendingBytes: diagnostics.max_pending_bytes
64+
)
65+
}
66+
67+
/// Container to encode Profiling diagnostics according to the spec.
68+
internal struct Attributes: Encodable {
69+
/// Diagnostics for the serialized aggregation worker.
70+
let aggregation: AggregationDiagnosticsMetric
71+
}
72+
}
73+
74+
#endif

DatadogProfiling/Sources/SDKMetrics/ProfilingTelemetryController.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@ internal final class ProfilingTelemetryController {
107107
telemetry.debug("Failed to compute attributes for '\(metric.metricName)'")
108108
return
109109
}
110+
metricAttributes.merge(AggregationDiagnosticsMetric.consumeDiagnostics().asMetricAttributes() ?? [:]) { $1 }
110111
metricAttributes.merge(configMetric.asMetricAttributes() ?? [:]) { $1 }
111112

112113
telemetry.metric(name: metric.metricName, attributes: metricAttributes, sampleRate: sampleRate)

DatadogProfiling/Tests/DDProfilerTests.swift

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -89,17 +89,15 @@ final class DDProfilerTests: XCTestCase {
8989
dd_pprof_destroy(profile)
9090
}
9191

92-
var diagnostics = dd_profiler_diagnostics_t()
93-
dd_profiler_consume_diagnostics(&diagnostics)
92+
let diagnostics = dd_profiler_diagnostics()
9493

9594
XCTAssertGreaterThan(diagnostics.dropped_batch_count, 0, "Hard-limit drops should be reported")
9695
XCTAssertGreaterThan(diagnostics.dropped_sample_count, 0, "Dropped samples should be counted")
9796
XCTAssertGreaterThan(diagnostics.max_pending_bytes, 0, "Pending-byte high-water mark should be reported")
9897

99-
var secondRead = dd_profiler_diagnostics_t()
100-
dd_profiler_consume_diagnostics(&secondRead)
101-
XCTAssertEqual(secondRead.dropped_batch_count, 0, "Consuming diagnostics should reset batch drops")
102-
XCTAssertEqual(secondRead.dropped_sample_count, 0, "Consuming diagnostics should reset sample drops")
98+
let secondRead = dd_profiler_diagnostics()
99+
XCTAssertEqual(secondRead.dropped_batch_count, 0, "Reading diagnostics should reset batch drops")
100+
XCTAssertEqual(secondRead.dropped_sample_count, 0, "Reading diagnostics should reset sample drops")
103101
}
104102

105103
func testDDProfiler_doesNotIncludeProfilerInternalThreadsInProfile() throws {

DatadogProfiling/Tests/SDKMetrics/ProfilingTelemetryControllerTests.swift

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,25 @@ final class ProfilingTelemetryControllerTests: XCTestCase {
182182
XCTAssertEqual(metricTelemetry.sampleRate, 20.0)
183183
}
184184

185+
func testTrackingProfilingSessionMetric_attachesAggregationDiagnostics() throws {
186+
// Given
187+
let controller = ProfilingTelemetryController(telemetry: telemetry)
188+
let diagnosticsKey = AggregationDiagnosticsMetric.Constants.diagnosticsKey
189+
190+
// When
191+
controller.sendProfile(durationNs: 1, fileSize: 2, for: .continuousProfiling)
192+
controller.sendProfileNotWritten(for: .customProfiling)
193+
194+
// Then
195+
let diagnostics = telemetry.messages.compactMap { message in
196+
message.asMetric?.attributes[diagnosticsKey] as? AggregationDiagnosticsMetric.Attributes
197+
}
198+
XCTAssertEqual(diagnostics.count, 2)
199+
XCTAssertEqual(diagnostics.first?.aggregation.droppedBatchCount, 0)
200+
XCTAssertEqual(diagnostics.first?.aggregation.droppedSampleCount, 0)
201+
XCTAssertEqual(diagnostics.first?.aggregation.maxPendingBytes, 0)
202+
}
203+
185204
func testSendProfile_usesContinuousStartReason_forContinuousOperation() throws {
186205
// Given
187206
let controller = ProfilingTelemetryController(telemetry: telemetry)

0 commit comments

Comments
 (0)