Skip to content

Commit 0a7740d

Browse files
committed
RUM-9335 Fix missing Application ID in Session Ended metric
1 parent 88289c7 commit 0a7740d

File tree

11 files changed

+82
-31
lines changed

11 files changed

+82
-31
lines changed

DatadogInternal/Sources/SDKMetrics/SDKMetricFields.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,9 @@ public enum SDKMetricFields {
1717
/// When attached to metric attributes, the value of this key (session ID) will be used to replace
1818
/// the ID of session that the metric was collected in. The key itself is dropped before the metric is sent.
1919
public static let sessionIDOverrideKey = "session_id_override"
20+
/// Key referencing the application ID (`String`) that the metric should be sent with. It expects `String` value.
21+
///
22+
/// When attached to metric attributes, the value of this key (application ID) will be used to replace
23+
/// the ID of application that the metric was collected in. The key itself is dropped before the metric is sent.
24+
public static let applicationIDOverrideKey = "application_id_override"
2025
}

DatadogRUM/Sources/Feature/RUMFeature.swift

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,11 @@ internal final class RUMFeature: DatadogRemoteFeature {
4343

4444
let featureScope = core.scope(for: RUMFeature.self)
4545
let sessionEndedMetric = SessionEndedMetricController(
46-
telemetry: core.telemetry,
47-
sampleRate: configuration.debugSDK ? 100 : configuration.sessionEndedSampleRate
46+
dependencies: .init(
47+
telemetry: core.telemetry,
48+
applicationID: configuration.applicationID,
49+
sampleRate: configuration.debugSDK ? 100 : configuration.sessionEndedSampleRate
50+
)
4851
)
4952
let tnsPredicateType = configuration.networkSettledResourcePredicate.metricPredicateType
5053
let invPredicateType = configuration.nextViewActionPredicate?.metricPredicateType ?? .disabled

DatadogRUM/Sources/Integrations/TelemetryReceiver.swift

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -259,6 +259,10 @@ internal final class TelemetryReceiver: FeatureMessageReceiver {
259259
let sessionIDOverride: String? = attributes.removeValue(forKey: SDKMetricFields.sessionIDOverrideKey)?.dd.decode()
260260
let sessionID = sessionIDOverride ?? rum?.sessionID
261261

262+
// Override applicationID using standard `SDKMetricFields`, otherwise use current RUM application ID:
263+
let applicationIDOverride: String? = attributes.removeValue(forKey: SDKMetricFields.applicationIDOverrideKey)?.dd.decode()
264+
let applicationID = applicationIDOverride ?? rum?.applicationID
265+
262266
// Calculates the composition of sample rates. The metric can have up to 3 layers of sampling.
263267
var effectiveSampleRate = metric.sampleRate.composed(with: self.sampler.samplingRate)
264268
if let headSampleRate = attributes.removeValue(forKey: SDKMetricFields.headSampleRate) as? SampleRate {
@@ -268,7 +272,7 @@ internal final class TelemetryReceiver: FeatureMessageReceiver {
268272
let event = TelemetryDebugEvent(
269273
dd: .init(),
270274
action: rum?.userActionID.map { .init(id: $0) },
271-
application: rum.map { .init(id: $0.applicationID) },
275+
application: applicationID.map { .init(id: $0) },
272276
date: date.addingTimeInterval(context.serverTimeOffset).timeIntervalSince1970.toInt64Milliseconds,
273277
effectiveSampleRate: Double(effectiveSampleRate),
274278
experimentalFeatures: nil,

DatadogRUM/Sources/SDKMetrics/SessionEndedMetric.swift

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,12 @@ internal class SessionEndedMetric {
3939
static let rseKey = "rse"
4040
}
4141

42-
/// An ID of the session being tracked through this metric object.
42+
/// An ID of the session being tracked through this metric object.
4343
let sessionID: RUMUUID
4444

45+
/// An ID of the session being tracked through this metric object.
46+
let applicationID: String
47+
4548
/// The type of OS component where the session was tracked.
4649
private let bundleType: BundleType
4750

@@ -121,16 +124,19 @@ internal class SessionEndedMetric {
121124
/// Initializer.
122125
/// - Parameters:
123126
/// - sessionID: An ID of the session that is being tracked with this metric.
127+
/// - applicationID: The RUM application ID for this session.
124128
/// - precondition: The precondition that led to starting this session.
125129
/// - context: The SDK context at the moment of starting this session.
126130
/// - tracksBackgroundEvents: If background events tracking is enabled for this session.
127131
init(
128132
sessionID: RUMUUID,
133+
applicationID: String,
129134
precondition: RUMSessionPrecondition?,
130135
context: DatadogContext,
131136
tracksBackgroundEvents: Bool
132137
) {
133138
self.sessionID = sessionID
139+
self.applicationID = applicationID
134140
self.bundleType = context.applicationBundleType
135141
self.precondition = precondition
136142
self.tracksBackgroundEvents = tracksBackgroundEvents
@@ -413,6 +419,7 @@ internal class SessionEndedMetric {
413419
return [
414420
SDKMetricFields.typeKey: Constants.typeValue,
415421
SDKMetricFields.sessionIDOverrideKey: sessionID.toRUMDataFormat,
422+
SDKMetricFields.applicationIDOverrideKey: applicationID,
416423
Constants.rseKey: Attributes(
417424
processType: {
418425
switch bundleType {

DatadogRUM/Sources/SDKMetrics/SessionEndedMetricController.swift

Lines changed: 31 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -12,26 +12,35 @@ internal final class SessionEndedMetricController {
1212
/// The default sample rate for "session ended" metric (15%), applied in addition to the telemetry sample rate (20% by default).
1313
static let defaultSampleRate: SampleRate = 15
1414

15+
/// Dependencies required by the Session Ended metric.
16+
internal struct SessionEndedMetricDependencies {
17+
/// The telemetry endpoint for sending metrics.
18+
let telemetry: Telemetry
19+
/// The RUM application ID.
20+
let applicationID: String
21+
/// The sample rate for "RUM Session Ended" metric.
22+
let sampleRate: SampleRate
23+
24+
init(telemetry: Telemetry, applicationID: String, sampleRate: SampleRate = defaultSampleRate) {
25+
self.telemetry = telemetry
26+
self.applicationID = applicationID
27+
self.sampleRate = sampleRate
28+
}
29+
}
30+
1531
/// Dictionary to keep track of pending metrics, keyed by session ID.
1632
@ReadWriteLock
1733
private var metricsBySessionID: [RUMUUID: SessionEndedMetric] = [:]
1834
/// Array to keep track of pending session IDs in their start order.
1935
private var pendingSessionIDs: [RUMUUID] = []
2036

21-
/// Telemetry endpoint for sending metrics.
22-
private let telemetry: Telemetry
23-
24-
/// The sample rate for "RUM Session Ended" metric.
25-
internal var sampleRate: SampleRate
37+
/// Dependencies for this controller.
38+
internal let dependencies: SessionEndedMetricDependencies
2639

2740
/// Initializes a new instance of the metric controller.
28-
/// - Parameters:
29-
/// - telemetry: The telemetry endpoint used for sending metrics.
30-
/// - sampleRate: The sample rate for "RUM Session Ended" metric.
31-
32-
init(telemetry: Telemetry, sampleRate: SampleRate) {
33-
self.telemetry = telemetry
34-
self.sampleRate = sampleRate
41+
/// - Parameter dependencies: The dependencies required by this controller.
42+
init(dependencies: SessionEndedMetricDependencies) {
43+
self.dependencies = dependencies
3544
}
3645

3746
/// Starts a new metric for a given session.
@@ -46,7 +55,13 @@ internal final class SessionEndedMetricController {
4655
return // do not track metric when session is not sampled
4756
}
4857
_metricsBySessionID.mutate { metrics in
49-
metrics[sessionID] = SessionEndedMetric(sessionID: sessionID, precondition: precondition, context: context, tracksBackgroundEvents: tracksBackgroundEvents)
58+
metrics[sessionID] = SessionEndedMetric(
59+
sessionID: sessionID,
60+
applicationID: dependencies.applicationID,
61+
precondition: precondition,
62+
context: context,
63+
tracksBackgroundEvents: tracksBackgroundEvents
64+
)
5065
pendingSessionIDs.append(sessionID)
5166
}
5267
}
@@ -107,10 +122,10 @@ internal final class SessionEndedMetricController {
107122
guard let metric = metrics[sessionID] else {
108123
return
109124
}
110-
telemetry.metric(
125+
dependencies.telemetry.metric(
111126
name: SessionEndedMetric.Constants.name,
112127
attributes: metric.asMetricAttributes(with: context),
113-
sampleRate: sampleRate
128+
sampleRate: dependencies.sampleRate
114129
)
115130
metrics[sessionID] = nil
116131
pendingSessionIDs.removeAll(where: { $0 == sessionID }) // O(n), but "ending the metric" is very rare event
@@ -133,7 +148,7 @@ internal final class SessionEndedMetricController {
133148
do {
134149
try mutation(&metrics[sessionID])
135150
} catch let error {
136-
telemetry.error(error)
151+
dependencies.telemetry.error(error)
137152
}
138153
}
139154
}

DatadogRUM/Tests/Integrations/TelemetryInterceptorTests.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ class TelemetryInterceptorTests: XCTestCase {
1616
let sessionID: RUMUUID = .mockRandom()
1717

1818
// Given
19-
let metricController = SessionEndedMetricController(telemetry: telemetry, sampleRate: 100)
19+
let metricController = SessionEndedMetricController(dependencies: .init(telemetry: telemetry, applicationID: .mockRandom(), sampleRate: 100))
2020
let interceptor = TelemetryInterceptor(sessionEndedMetric: metricController)
2121

2222
// When
@@ -36,7 +36,7 @@ class TelemetryInterceptorTests: XCTestCase {
3636
let sessionID: RUMUUID = .mockRandom()
3737

3838
// Given
39-
let metricController = SessionEndedMetricController(telemetry: telemetry, sampleRate: 100)
39+
let metricController = SessionEndedMetricController(dependencies: .init(telemetry: telemetry, applicationID: .mockRandom(), sampleRate: 100))
4040
let interceptor = TelemetryInterceptor(sessionEndedMetric: metricController)
4141

4242
// When

DatadogRUM/Tests/Integrations/TelemetryReceiverTests.swift

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -456,21 +456,23 @@ class TelemetryReceiverTests: XCTestCase {
456456
XCTAssertEqual(os.build, deviceMock.osBuildNumber)
457457
}
458458

459-
func testSendTelemetryMetricWithRUMContextAndSessionIDOverride() {
459+
func testSendTelemetryMetricWithRUMContextAndOverrides() {
460460
// Given
461461
let rumContext: RUMCoreContext = .mockRandom()
462462
featureScope.contextMock.set(additionalContext: rumContext)
463463
let receiver = TelemetryReceiver.mockWith(featureScope: featureScope)
464464
let sessionIDOverride = "session-id-override"
465+
let applicationIDOverride = "application-id-override"
465466

466467
// When
467468
var attributes = mockRandomAttributes()
468469
attributes[SDKMetricFields.sessionIDOverrideKey] = sessionIDOverride
470+
attributes[SDKMetricFields.applicationIDOverrideKey] = applicationIDOverride
469471
TelemetryMock(with: receiver).metric(name: .mockRandom(), attributes: attributes, sampleRate: 100)
470472

471473
// Then
472474
let event = featureScope.eventsWritten(ofType: TelemetryDebugEvent.self).first
473-
XCTAssertEqual(event?.application?.id, rumContext.applicationID)
475+
XCTAssertEqual(event?.application?.id, applicationIDOverride)
474476
XCTAssertEqual(event?.session?.id, sessionIDOverride)
475477
XCTAssertEqual(event?.view?.id, rumContext.viewID)
476478
XCTAssertEqual(event?.action?.id, rumContext.userActionID)

DatadogRUM/Tests/RUMTests.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ class RUMTests: XCTestCase {
9292
XCTAssertEqual(rum.performanceOverride.maxFileAgeForRead, 24.hours)
9393
XCTAssertEqual(monitor.scopes.dependencies.rumApplicationID, applicationID)
9494
XCTAssertEqual(monitor.scopes.dependencies.sessionSampler.samplingRate, 100)
95-
XCTAssertEqual(monitor.scopes.dependencies.sessionEndedMetric.sampleRate, 15)
95+
XCTAssertEqual(monitor.scopes.dependencies.sessionEndedMetric.dependencies.sampleRate, 15)
9696
XCTAssertEqual(telemetryReceiver?.configurationExtraSampler.samplingRate, 20)
9797
XCTAssertEqual(crashReportReceiver?.sessionSampler.samplingRate, 100)
9898
}

DatadogRUM/Tests/SDKMetrics/SessionEndedMetricControllerTests.swift

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ class SessionEndedMetricControllerTests: XCTestCase {
1818
let errorKinds: [String] = .mockRandom(count: 5)
1919

2020
// Given
21-
let controller = SessionEndedMetricController(telemetry: telemetry, sampleRate: 4.2)
21+
let controller = SessionEndedMetricController(dependencies: .init(telemetry: telemetry, applicationID: .mockRandom(), sampleRate: 4.2))
2222
controller.startMetric(sessionID: sessionID, precondition: .mockRandom(), context: .mockRandom(), tracksBackgroundEvents: .mockRandom())
2323

2424
// When
@@ -43,7 +43,7 @@ class SessionEndedMetricControllerTests: XCTestCase {
4343
let sessionID2: RUMUUID = .mockRandom()
4444

4545
// When
46-
let controller = SessionEndedMetricController(telemetry: telemetry, sampleRate: 100)
46+
let controller = SessionEndedMetricController(dependencies: .init(telemetry: telemetry, applicationID: .mockRandom(), sampleRate: 100))
4747
controller.startMetric(sessionID: sessionID1, precondition: .mockRandom(), context: .mockRandom(), tracksBackgroundEvents: .mockRandom())
4848
controller.startMetric(sessionID: sessionID2, precondition: .mockRandom(), context: .mockRandom(), tracksBackgroundEvents: .mockRandom())
4949
// Session 1:
@@ -75,7 +75,7 @@ class SessionEndedMetricControllerTests: XCTestCase {
7575
let sessionID2: RUMUUID = .mockRandom()
7676

7777
// When
78-
let controller = SessionEndedMetricController(telemetry: telemetry, sampleRate: 100)
78+
let controller = SessionEndedMetricController(dependencies: .init(telemetry: telemetry, applicationID: .mockRandom(), sampleRate: 100))
7979
controller.startMetric(sessionID: sessionID1, precondition: .mockRandom(), context: .mockRandom(), tracksBackgroundEvents: .mockRandom())
8080
controller.startMetric(sessionID: sessionID2, precondition: .mockRandom(), context: .mockRandom(), tracksBackgroundEvents: .mockRandom())
8181
// Track latest session (`sessionID: nil`)
@@ -98,7 +98,7 @@ class SessionEndedMetricControllerTests: XCTestCase {
9898

9999
func testTrackingSessionEndedMetricIsThreadSafe() {
100100
let sessionIDs: [RUMUUID] = .mockRandom(count: 10)
101-
let controller = SessionEndedMetricController(telemetry: telemetry, sampleRate: 100)
101+
let controller = SessionEndedMetricController(dependencies: .init(telemetry: telemetry, applicationID: .mockRandom(), sampleRate: 100))
102102

103103
// swiftlint:disable opening_brace
104104
callConcurrently(

DatadogRUM/Tests/SDKMetrics/SessionEndedMetricTests.swift

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,20 @@ class SessionEndedMetricTests: XCTestCase {
5858
XCTAssertEqual(attributes[SDKMetricFields.sessionIDOverrideKey] as? String, sessionID.toRUMDataFormat)
5959
}
6060

61+
// MARK: - Application ID
62+
63+
func testReportingApplicationID() throws {
64+
// Given
65+
let expectedApplicationID = "test-app-id"
66+
let metric = SessionEndedMetric.with(sessionID: sessionID, applicationID: expectedApplicationID)
67+
68+
// When
69+
let attributes = metric.asMetricAttributes()
70+
71+
// Then
72+
XCTAssertEqual(attributes[SDKMetricFields.applicationIDOverrideKey] as? String, expectedApplicationID)
73+
}
74+
6175
// MARK: - Process Type
6276

6377
func testReportingAppProcessType() throws {
@@ -678,11 +692,12 @@ private extension Int {
678692
private extension SessionEndedMetric {
679693
static func with(
680694
sessionID: RUMUUID,
695+
applicationID: String = .mockRandom(),
681696
precondition: RUMSessionPrecondition? = .mockRandom(),
682697
context: DatadogContext = .mockRandom(),
683698
tracksBackgroundEvents: Bool = .mockRandom()
684699
) -> SessionEndedMetric {
685-
SessionEndedMetric(sessionID: sessionID, precondition: precondition, context: context, tracksBackgroundEvents: tracksBackgroundEvents)
700+
SessionEndedMetric(sessionID: sessionID, applicationID: applicationID, precondition: precondition, context: context, tracksBackgroundEvents: tracksBackgroundEvents)
686701
}
687702

688703
func asMetricAttributes() -> [String: Encodable] {

TestUtilities/Sources/Mocks/DatadogRUM/RUMFeatureMocks.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -785,7 +785,7 @@ extension RUMScopeDependencies {
785785
onSessionStart: @escaping RUM.SessionListener = mockNoOpSessionListener(),
786786
viewCache: ViewCache = ViewCache(dateProvider: SystemDateProvider()),
787787
fatalErrorContext: FatalErrorContextNotifying = FatalErrorContextNotifierMock(),
788-
sessionEndedMetric: SessionEndedMetricController = SessionEndedMetricController(telemetry: NOPTelemetry(), sampleRate: 0),
788+
sessionEndedMetric: SessionEndedMetricController = SessionEndedMetricController(dependencies: .init(telemetry: NOPTelemetry(), applicationID: .mockAny(), sampleRate: 0)),
789789
viewEndedMetricFactory: @escaping () -> ViewEndedController = {
790790
ViewEndedController(telemetry: NOPTelemetry(), sampleRate: 0)
791791
},

0 commit comments

Comments
 (0)