Skip to content

Commit d1794fd

Browse files
Merge pull request #2226 from DataDog/simaoseica/RUM-8656/update-rum-schema-and-add-app-hitches-to-view-events
RUM-8656: Update RUM schema and add App Hitches to View events
2 parents aec2c17 + f25fe57 commit d1794fd

File tree

17 files changed

+558
-27
lines changed

17 files changed

+558
-27
lines changed

Datadog/Datadog.xcodeproj/project.pbxproj

+7-1
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@
2929
116200352D786ED0001C2A51 /* RUMFeatureMocks.swift in Sources */ = {isa = PBXBuildFile; fileRef = 116200312D786ED0001C2A51 /* RUMFeatureMocks.swift */; };
3030
116F84062CFDD06700705755 /* SampleRateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 116F84052CFDD06700705755 /* SampleRateTests.swift */; };
3131
116F84072CFDD06700705755 /* SampleRateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 116F84052CFDD06700705755 /* SampleRateTests.swift */; };
32+
11BA765D2D7F0EF30058C21C /* RUMViewHitchesIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11BA765C2D7F0EE40058C21C /* RUMViewHitchesIntegrationTests.swift */; };
33+
11BA765E2D7F0EF30058C21C /* RUMViewHitchesIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11BA765C2D7F0EE40058C21C /* RUMViewHitchesIntegrationTests.swift */; };
3234
1434A4612B7F73110072E3BB /* OpenTelemetryApi.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3C1F88222B767CE200821579 /* OpenTelemetryApi.xcframework */; };
3335
1434A4622B7F73110072E3BB /* OpenTelemetryApi.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3C1F88222B767CE200821579 /* OpenTelemetryApi.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
3436
1434A4632B7F73170072E3BB /* OpenTelemetryApi.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3C1F88222B767CE200821579 /* OpenTelemetryApi.xcframework */; };
@@ -2139,6 +2141,7 @@
21392141
116200302D786ED0001C2A51 /* RUMDataModelMocks.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RUMDataModelMocks.swift; sourceTree = "<group>"; };
21402142
116200312D786ED0001C2A51 /* RUMFeatureMocks.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RUMFeatureMocks.swift; sourceTree = "<group>"; };
21412143
116F84052CFDD06700705755 /* SampleRateTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SampleRateTests.swift; sourceTree = "<group>"; };
2144+
11BA765C2D7F0EE40058C21C /* RUMViewHitchesIntegrationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RUMViewHitchesIntegrationTests.swift; sourceTree = "<group>"; };
21422145
1434A4652B7F8D880072E3BB /* DebugOTelTracingViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DebugOTelTracingViewController.swift; sourceTree = "<group>"; };
21432146
3C08F9CF2C2D652D002B0FF2 /* Storage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Storage.swift; sourceTree = "<group>"; };
21442147
3C0CB3442C19A1ED003B0E9B /* WatchdogTerminationReporter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WatchdogTerminationReporter.swift; sourceTree = "<group>"; };
@@ -5430,10 +5433,11 @@
54305433
A757AE8F2D3815AC00C772FA /* AnonymousIdentifierTests.swift */,
54315434
6167E6DC2B811A8300C3CA2D /* AppHangsMonitoringTests.swift */,
54325435
1117AA052D09A39400F86B29 /* RUMAttributesIntegrationTests.swift */,
5436+
11BA765C2D7F0EE40058C21C /* RUMViewHitchesIntegrationTests.swift */,
54335437
61E8C5072B28898800E709B4 /* StartingRUMSessionTests.swift */,
5438+
6105C5022CF8D5AB00C4C5EE /* ViewLoadingMetricsTests.swift */,
54345439
3CA00B062C2AE52400E6FE01 /* WatchdogTerminationsMonitoringTests.swift */,
54355440
D2552AF42BBC47D900A45725 /* WebEventIntegrationTests.swift */,
5436-
6105C5022CF8D5AB00C4C5EE /* ViewLoadingMetricsTests.swift */,
54375441
61DCC84C2C05D4E500CB59E5 /* SDKMetrics */,
54385442
);
54395443
path = RUM;
@@ -8131,6 +8135,7 @@
81318135
D244B3A3271EDACD003E1B29 /* SwiftUIExtensionsTests.swift in Sources */,
81328136
D24C9C6429A7CB7B002057CF /* CrashLogReceiverTests.swift in Sources */,
81338137
61B5E42126DF85C7000B0A5F /* DDRUMMonitor+apiTests.m in Sources */,
8138+
11BA765D2D7F0EF30058C21C /* RUMViewHitchesIntegrationTests.swift in Sources */,
81348139
61133C4E2423990D00786299 /* UIKitMocks.swift in Sources */,
81358140
3CF673362B4807490016CE17 /* OTelSpanTests.swift in Sources */,
81368141
61F3E36D2BC7D66700C7881E /* HeadBasedSamplingTests.swift in Sources */,
@@ -9383,6 +9388,7 @@
93839388
D2CB6F2027C520D400A62B57 /* DatadogConfigurationTests.swift in Sources */,
93849389
D2CB6F2127C520D400A62B57 /* URLSessionClientTests.swift in Sources */,
93859390
D2CB6F2227C520D400A62B57 /* DatadogTests.swift in Sources */,
9391+
11BA765E2D7F0EF30058C21C /* RUMViewHitchesIntegrationTests.swift in Sources */,
93869392
61DA8CAD2861C3720074A606 /* DirectoriesTests.swift in Sources */,
93879393
614798972A459AA80095CB02 /* DDTraceTests.swift in Sources */,
93889394
D2CB6F2627C520D400A62B57 /* DataUploadDelayTests.swift in Sources */,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
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 XCTest
8+
import TestUtilities
9+
@testable import DatadogRUM
10+
@testable import DatadogInternal
11+
12+
final class RUMViewHitchesIntegrationTests: XCTestCase {
13+
private var core: DatadogCoreProxy! // swiftlint:disable:this implicitly_unwrapped_optional
14+
15+
override func setUp() {
16+
super.setUp()
17+
core = DatadogCoreProxy()
18+
}
19+
20+
override func tearDown() {
21+
core.flushAndTearDown()
22+
core = nil
23+
super.tearDown()
24+
}
25+
26+
func testViewHitchesNotCollected_whenFeatureFlagIsDisabled() throws {
27+
// Given
28+
let viewName = "MyView"
29+
let rumConfig = RUM.Configuration(applicationID: .mockAny(), featureFlags: [.viewHitches: false])
30+
RUM.enable(with: rumConfig, in: core)
31+
32+
let monitor = RUMMonitor.shared(in: core)
33+
34+
// When
35+
monitor.startView(key: "key", name: viewName)
36+
monitor.stopView(key: "key")
37+
38+
// Then
39+
let session = try RUMSessionMatcher
40+
.groupMatchersBySessions(try core.waitAndReturnRUMEventMatchers())
41+
.takeSingle()
42+
43+
let customView = try XCTUnwrap(session.views.first(where: { $0.name == viewName }))
44+
let stopViewEvent = try XCTUnwrap(customView.viewEvents.last) // stopView event
45+
XCTAssertNil(stopViewEvent.view.slowFrames)
46+
}
47+
48+
func testViewHitchesCollected_whenFeatureFlagIsEnabled() throws {
49+
// Given
50+
let viewName = "MyView"
51+
let rumConfig = RUM.Configuration(applicationID: .mockAny(), featureFlags: [.viewHitches: true])
52+
RUM.enable(with: rumConfig, in: core)
53+
54+
let monitor = RUMMonitor.shared(in: core)
55+
56+
// When
57+
monitor.startView(key: "key", name: viewName)
58+
59+
let completion = expectation(description: "Wait for some slow frames")
60+
61+
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
62+
// sleep main thread to have some slow frames
63+
Thread.sleep(forTimeInterval: 0.1)
64+
65+
// schedule completion to the next runloop
66+
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { completion.fulfill() }
67+
}
68+
69+
wait(for: [completion], timeout: 2)
70+
71+
monitor.stopView(key: "key")
72+
73+
// Then
74+
let session = try RUMSessionMatcher
75+
.groupMatchersBySessions(try core.waitAndReturnRUMEventMatchers())
76+
.takeSingle()
77+
78+
let customView = try XCTUnwrap(session.views.first(where: { $0.name == viewName }))
79+
let stopViewEvent = try XCTUnwrap(customView.viewEvents.last)
80+
81+
XCTAssertGreaterThan(stopViewEvent.view.slowFrames?.count ?? 0, 0)
82+
}
83+
}

DatadogCore/Tests/Datadog/Mocks/RUM/RUMDataModelMocks.swift

+10
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,12 @@ extension RUMViewEvent.DD.Configuration: RandomMockable {
112112
}
113113
}
114114

115+
extension RUMViewEvent.View.SlowFrames: RandomMockable {
116+
public static func mockRandom() -> RUMViewEvent.View.SlowFrames {
117+
.init(duration: .mockRandom(), start: .mockRandom())
118+
}
119+
}
120+
115121
extension RUMViewEvent: RandomMockable {
116122
public static func mockRandom() -> RUMViewEvent {
117123
return mockRandomWith()
@@ -126,6 +132,7 @@ extension RUMViewEvent: RandomMockable {
126132
return RUMViewEvent(
127133
dd: .init(
128134
browserSdkVersion: nil,
135+
cls: nil,
129136
configuration: .mockRandom(),
130137
documentVersion: .mockRandom(),
131138
pageStates: nil,
@@ -179,6 +186,7 @@ extension RUMViewEvent: RandomMockable {
179186
firstInputTime: .mockRandom(),
180187
flutterBuildTime: nil,
181188
flutterRasterTime: nil,
189+
freezeRate: .mockRandom(),
182190
frozenFrame: .init(count: .mockRandom()),
183191
frustration: nil,
184192
id: .mockRandom(),
@@ -209,6 +217,8 @@ extension RUMViewEvent: RandomMockable {
209217
refreshRateAverage: .mockRandom(),
210218
refreshRateMin: .mockRandom(),
211219
resource: .init(count: .mockRandom()),
220+
slowFrames: .mockRandom(),
221+
slowFramesRate: .mockRandom(),
212222
timeSpent: viewTimeSpent,
213223
url: .mockRandom()
214224
)

DatadogObjc/Sources/RUM/RUMDataModels+objc.swift

+48-1
Original file line numberDiff line numberDiff line change
@@ -4891,6 +4891,10 @@ public class DDRUMViewEventDD: NSObject {
48914891
root.swiftModel.dd.browserSdkVersion
48924892
}
48934893

4894+
@objc public var cls: DDRUMViewEventDDCLS? {
4895+
root.swiftModel.dd.cls != nil ? DDRUMViewEventDDCLS(root: root) : nil
4896+
}
4897+
48944898
@objc public var configuration: DDRUMViewEventDDConfiguration? {
48954899
root.swiftModel.dd.configuration != nil ? DDRUMViewEventDDConfiguration(root: root) : nil
48964900
}
@@ -4916,6 +4920,19 @@ public class DDRUMViewEventDD: NSObject {
49164920
}
49174921
}
49184922

4923+
@objc
4924+
public class DDRUMViewEventDDCLS: NSObject {
4925+
internal let root: DDRUMViewEvent
4926+
4927+
internal init(root: DDRUMViewEvent) {
4928+
self.root = root
4929+
}
4930+
4931+
@objc public var devicePixelRatio: NSNumber? {
4932+
root.swiftModel.dd.cls!.devicePixelRatio as NSNumber?
4933+
}
4934+
}
4935+
49194936
@objc
49204937
public class DDRUMViewEventDDConfiguration: NSObject {
49214938
internal let root: DDRUMViewEvent
@@ -5777,6 +5794,10 @@ public class DDRUMViewEventView: NSObject {
57775794
root.swiftModel.view.flutterRasterTime != nil ? DDRUMViewEventViewFlutterRasterTime(root: root) : nil
57785795
}
57795796

5797+
@objc public var freezeRate: NSNumber? {
5798+
root.swiftModel.view.freezeRate as NSNumber?
5799+
}
5800+
57805801
@objc public var frozenFrame: DDRUMViewEventViewFrozenFrame? {
57815802
root.swiftModel.view.frozenFrame != nil ? DDRUMViewEventViewFrozenFrame(root: root) : nil
57825803
}
@@ -5883,6 +5904,14 @@ public class DDRUMViewEventView: NSObject {
58835904
DDRUMViewEventViewResource(root: root)
58845905
}
58855906

5907+
@objc public var slowFrames: [DDRUMViewEventViewSlowFrames]? {
5908+
root.swiftModel.view.slowFrames?.map { DDRUMViewEventViewSlowFrames(swiftModel: $0) }
5909+
}
5910+
5911+
@objc public var slowFramesRate: NSNumber? {
5912+
root.swiftModel.view.slowFramesRate as NSNumber?
5913+
}
5914+
58865915
@objc public var timeSpent: NSNumber {
58875916
root.swiftModel.view.timeSpent as NSNumber
58885917
}
@@ -6320,6 +6349,24 @@ public class DDRUMViewEventViewResource: NSObject {
63206349
}
63216350
}
63226351

6352+
@objc
6353+
public class DDRUMViewEventViewSlowFrames: NSObject {
6354+
internal var swiftModel: RUMViewEvent.View.SlowFrames
6355+
internal var root: DDRUMViewEventViewSlowFrames { self }
6356+
6357+
internal init(swiftModel: RUMViewEvent.View.SlowFrames) {
6358+
self.swiftModel = swiftModel
6359+
}
6360+
6361+
@objc public var duration: NSNumber {
6362+
root.swiftModel.duration as NSNumber
6363+
}
6364+
6365+
@objc public var start: NSNumber {
6366+
root.swiftModel.start as NSNumber
6367+
}
6368+
}
6369+
63236370
@objc
63246371
public class DDRUMVitalEvent: NSObject {
63256372
internal var swiftModel: RUMVitalEvent
@@ -8467,4 +8514,4 @@ public class DDTelemetryConfigurationEventView: NSObject {
84678514

84688515
// swiftlint:enable force_unwrapping
84698516

8470-
// Generated from https://github.com/DataDog/rum-events-format/tree/69147431d689b3e59bff87e15bb0088a9bb319a9
8517+
// Generated from https://github.com/DataDog/rum-events-format/tree/45a80c1390b8ec886534f5f1b43763a6d9d0a643

DatadogRUM/Sources/DataModels/RUMDataModels.swift

+41-1
Original file line numberDiff line numberDiff line change
@@ -2418,6 +2418,9 @@ public struct RUMViewEvent: RUMDataModel {
24182418
/// Browser SDK version
24192419
public let browserSdkVersion: String?
24202420

2421+
/// Additional information of the reported Cumulative Layout Shift
2422+
public let cls: CLS?
2423+
24212424
/// Subset of the SDK configuration options in use during its execution
24222425
public let configuration: Configuration?
24232426

@@ -2438,6 +2441,7 @@ public struct RUMViewEvent: RUMDataModel {
24382441

24392442
enum CodingKeys: String, CodingKey {
24402443
case browserSdkVersion = "browser_sdk_version"
2444+
case cls = "cls"
24412445
case configuration = "configuration"
24422446
case documentVersion = "document_version"
24432447
case formatVersion = "format_version"
@@ -2446,6 +2450,16 @@ public struct RUMViewEvent: RUMDataModel {
24462450
case session = "session"
24472451
}
24482452

2453+
/// Additional information of the reported Cumulative Layout Shift
2454+
public struct CLS: Codable {
2455+
/// Pixel ratio of the device where the layout shift was reported
2456+
public let devicePixelRatio: Double?
2457+
2458+
enum CodingKeys: String, CodingKey {
2459+
case devicePixelRatio = "device_pixel_ratio"
2460+
}
2461+
}
2462+
24492463
/// Subset of the SDK configuration options in use during its execution
24502464
public struct Configuration: Codable {
24512465
/// The percentage of sessions with RUM & Session Replay pricing tracked
@@ -2757,6 +2771,9 @@ public struct RUMViewEvent: RUMDataModel {
27572771
/// Time taken for Flutter to rasterize the view.
27582772
public let flutterRasterTime: FlutterRasterTime?
27592773

2774+
/// Rate of freezes during the view’s lifetime (in seconds per hour)
2775+
public let freezeRate: Double?
2776+
27602777
/// Properties of the frozen frames of the view
27612778
public let frozenFrame: FrozenFrame?
27622779

@@ -2835,6 +2852,12 @@ public struct RUMViewEvent: RUMDataModel {
28352852
/// Properties of the resources of the view
28362853
public let resource: Resource
28372854

2855+
/// List of slow frames during the view’s lifetime
2856+
public let slowFrames: [SlowFrames]?
2857+
2858+
/// Rate of slow frames during the view’s lifetime (in milliseconds per second)
2859+
public let slowFramesRate: Double?
2860+
28382861
/// Time spent on the view in ns
28392862
public let timeSpent: Int64
28402863

@@ -2861,6 +2884,7 @@ public struct RUMViewEvent: RUMDataModel {
28612884
case firstInputTime = "first_input_time"
28622885
case flutterBuildTime = "flutter_build_time"
28632886
case flutterRasterTime = "flutter_raster_time"
2887+
case freezeRate = "freeze_rate"
28642888
case frozenFrame = "frozen_frame"
28652889
case frustration = "frustration"
28662890
case id = "id"
@@ -2887,6 +2911,8 @@ public struct RUMViewEvent: RUMDataModel {
28872911
case refreshRateAverage = "refresh_rate_average"
28882912
case refreshRateMin = "refresh_rate_min"
28892913
case resource = "resource"
2914+
case slowFrames = "slow_frames"
2915+
case slowFramesRate = "slow_frames_rate"
28902916
case timeSpent = "time_spent"
28912917
case url = "url"
28922918
}
@@ -3226,6 +3252,20 @@ public struct RUMViewEvent: RUMDataModel {
32263252
case count = "count"
32273253
}
32283254
}
3255+
3256+
/// Properties of the slow frames
3257+
public struct SlowFrames: Codable {
3258+
/// Duration in ns of the slow frame
3259+
public let duration: Int64
3260+
3261+
/// Duration in ns between start of the view and the start of the slow frame
3262+
public let start: Int64
3263+
3264+
enum CodingKeys: String, CodingKey {
3265+
case duration = "duration"
3266+
case start = "start"
3267+
}
3268+
}
32293269
}
32303270
}
32313271

@@ -5591,4 +5631,4 @@ public struct RUMTelemetryOperatingSystem: Codable {
55915631
}
55925632
}
55935633

5594-
// Generated from https://github.com/DataDog/rum-events-format/tree/69147431d689b3e59bff87e15bb0088a9bb319a9
5634+
// Generated from https://github.com/DataDog/rum-events-format/tree/45a80c1390b8ec886534f5f1b43763a6d9d0a643

DatadogRUM/Sources/FatalErrorBuilder.swift

+4
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,7 @@ internal struct FatalErrorBuilder {
144144
return RUMViewEvent(
145145
dd: .init(
146146
browserSdkVersion: original.dd.browserSdkVersion,
147+
cls: original.dd.cls,
147148
configuration: original.dd.configuration,
148149
documentVersion: original.dd.documentVersion + 1,
149150
pageStates: original.dd.pageStates,
@@ -198,6 +199,7 @@ internal struct FatalErrorBuilder {
198199
firstInputTime: original.view.firstInputTime,
199200
flutterBuildTime: original.view.flutterBuildTime,
200201
flutterRasterTime: original.view.flutterRasterTime,
202+
freezeRate: original.view.freezeRate,
201203
frozenFrame: original.view.frozenFrame,
202204
frustration: original.view.frustration,
203205
id: original.view.id,
@@ -223,6 +225,8 @@ internal struct FatalErrorBuilder {
223225
refreshRateAverage: original.view.refreshRateAverage,
224226
refreshRateMin: original.view.refreshRateMin,
225227
resource: original.view.resource,
228+
slowFrames: original.view.slowFrames,
229+
slowFramesRate: original.view.slowFramesRate,
226230
timeSpent: original.view.timeSpent,
227231
url: original.view.url
228232
)

DatadogRUM/Sources/Feature/RUMFeature.swift

+5-1
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,11 @@ internal final class RUMFeature: DatadogRemoteFeature {
9898
}
9999
}(),
100100
renderLoopObserver: DisplayLinker(notificationCenter: configuration.notificationCenter),
101-
viewHitchesMetricFactory: { ViewHitchesReader(hangThreshold: configuration.appHangThreshold) },
101+
viewHitchesMetricFactory: {
102+
configuration.featureFlags[.viewHitches]
103+
? ViewHitchesReader(hangThreshold: configuration.appHangThreshold)
104+
: nil
105+
},
102106
vitalsReaders: configuration.vitalsUpdateFrequency.map {
103107
VitalsReaders(
104108
frequency: $0.timeInterval,

0 commit comments

Comments
 (0)