Skip to content

Commit

Permalink
Merge pull request #2139 from DataDog/feature/view-loading-times
Browse files Browse the repository at this point in the history
RUM-6501 feat: RUM View Loading Time metrics
  • Loading branch information
ncreated authored Jan 14, 2025
2 parents 1b0ac46 + 074bc4b commit d41d5dd
Showing 26 changed files with 2,560 additions and 129 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# Unreleased

- [FEATURE] Add Time To Network Setled metric in RUM. See [#2125][]
- [FEATURE] Add Interaction To Next View metric in RUM. See [#2153][]
- [FIX] Fix SwiftUI placeholder in Session Replay when Feature Flag is disabled. See [#2170][]

# 2.22.0 / 02-01-2025
@@ -809,10 +811,12 @@ Release `2.0` introduces breaking changes. Follow the [Migration Guide](MIGRATIO
[#2063]: https://github.com/DataDog/dd-sdk-ios/pull/2063
[#2092]: https://github.com/DataDog/dd-sdk-ios/pull/2092
[#2113]: https://github.com/DataDog/dd-sdk-ios/pull/2113
[#2125]: https://github.com/DataDog/dd-sdk-ios/pull/2125
[#2114]: https://github.com/DataDog/dd-sdk-ios/pull/2114
[#2116]: https://github.com/DataDog/dd-sdk-ios/pull/2116
[#2120]: https://github.com/DataDog/dd-sdk-ios/pull/2120
[#2126]: https://github.com/DataDog/dd-sdk-ios/pull/2126
[#2153]: https://github.com/DataDog/dd-sdk-ios/pull/2153
[#2148]: https://github.com/DataDog/dd-sdk-ios/pull/2148
[#2154]: https://github.com/DataDog/dd-sdk-ios/pull/2154
[@00fa9a]: https://github.com/00FA9A
58 changes: 58 additions & 0 deletions Datadog/Datadog.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

482 changes: 482 additions & 0 deletions Datadog/IntegrationUnitTests/RUM/ViewLoadingMetricsTests.swift

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions DatadogCore/Tests/Datadog/Mocks/RUMDataModelMocks.swift
Original file line number Diff line number Diff line change
@@ -191,7 +191,7 @@ extension RUMViewEvent: RandomMockable {
interactionToNextPaint: nil,
interactionToNextPaintTargetSelector: nil,
interactionToNextPaintTime: .mockRandom(),
interactionToNextViewTime: nil,
interactionToNextViewTime: .mockRandom(),
isActive: viewIsActive,
isSlowRendered: .mockRandom(),
jsRefreshRate: nil,
@@ -204,7 +204,7 @@ extension RUMViewEvent: RandomMockable {
memoryAverage: .mockRandom(),
memoryMax: .mockRandom(),
name: .mockRandom(),
networkSettledTime: nil,
networkSettledTime: .mockRandom(),
referrer: .mockRandom(),
refreshRateAverage: .mockRandom(),
refreshRateMin: .mockRandom(),
38 changes: 28 additions & 10 deletions DatadogCore/Tests/Datadog/Mocks/RUMFeatureMocks.swift
Original file line number Diff line number Diff line change
@@ -786,7 +786,13 @@ extension RUMScopeDependencies {
viewCache: ViewCache = ViewCache(dateProvider: SystemDateProvider()),
fatalErrorContext: FatalErrorContextNotifying = FatalErrorContextNotifierMock(),
sessionEndedMetric: SessionEndedMetricController = SessionEndedMetricController(telemetry: NOPTelemetry(), sampleRate: 0),
watchdogTermination: WatchdogTerminationMonitor? = nil
watchdogTermination: WatchdogTerminationMonitor? = nil,
networkSettledMetricFactory: @escaping (Date, String) -> TTNSMetricTracking = {
TTNSMetric(viewName: $1, viewStartDate: $0, resourcePredicate: TimeBasedTTNSResourcePredicate())
},
interactionToNextViewMetricFactory: @escaping () -> ITNVMetricTracking = {
ITNVMetric(predicate: TimeBasedITNVActionPredicate())
}
) -> RUMScopeDependencies {
return RUMScopeDependencies(
featureScope: featureScope,
@@ -805,7 +811,9 @@ extension RUMScopeDependencies {
viewCache: viewCache,
fatalErrorContext: fatalErrorContext,
sessionEndedMetric: sessionEndedMetric,
watchdogTermination: watchdogTermination
watchdogTermination: watchdogTermination,
networkSettledMetricFactory: networkSettledMetricFactory,
interactionToNextViewMetricFactory: interactionToNextViewMetricFactory
)
}

@@ -826,7 +834,9 @@ extension RUMScopeDependencies {
viewCache: ViewCache? = nil,
fatalErrorContext: FatalErrorContextNotifying? = nil,
sessionEndedMetric: SessionEndedMetricController? = nil,
watchdogTermination: WatchdogTerminationMonitor? = nil
watchdogTermination: WatchdogTerminationMonitor? = nil,
networkSettledMetricFactory: ((Date, String) -> TTNSMetricTracking)? = nil,
interactionToNextViewMetricFactory: (() -> ITNVMetricTracking)? = nil
) -> RUMScopeDependencies {
return RUMScopeDependencies(
featureScope: self.featureScope,
@@ -845,7 +855,9 @@ extension RUMScopeDependencies {
viewCache: viewCache ?? self.viewCache,
fatalErrorContext: fatalErrorContext ?? self.fatalErrorContext,
sessionEndedMetric: sessionEndedMetric ?? self.sessionEndedMetric,
watchdogTermination: watchdogTermination
watchdogTermination: watchdogTermination ?? self.watchdogTermination,
networkSettledMetricFactory: networkSettledMetricFactory ?? self.networkSettledMetricFactory,
interactionToNextViewMetricFactory: interactionToNextViewMetricFactory ?? self.interactionToNextViewMetricFactory
)
}
}
@@ -932,7 +944,8 @@ extension RUMViewScope {
attributes: [AttributeKey: AttributeValue] = [:],
customTimings: [String: Int64] = randomTimings(),
startTime: Date = .mockAny(),
serverTimeOffset: TimeInterval = .zero
serverTimeOffset: TimeInterval = .zero,
interactionToNextViewMetric: ITNVMetricTracking = ITNVMetric(predicate: TimeBasedITNVActionPredicate())
) -> RUMViewScope {
return RUMViewScope(
isInitialView: isInitialView,
@@ -943,7 +956,8 @@ extension RUMViewScope {
name: name,
customTimings: customTimings,
startTime: startTime,
serverTimeOffset: serverTimeOffset
serverTimeOffset: serverTimeOffset,
interactionToNextViewMetric: interactionToNextViewMetric
)
}
}
@@ -962,8 +976,9 @@ extension RUMResourceScope {
isFirstPartyResource: Bool? = nil,
resourceKindBasedOnRequest: RUMResourceType? = nil,
spanContext: RUMSpanContext? = .mockAny(),
onResourceEventSent: @escaping () -> Void = {},
onErrorEventSent: @escaping () -> Void = {}
networkSettledMetric: TTNSMetricTracking = TTNSMetric(viewName: .mockAny(), viewStartDate: .mockAny(), resourcePredicate: TimeBasedTTNSResourcePredicate()),
onResourceEvent: @escaping (Bool) -> Void = { _ in },
onErrorEvent: @escaping (Bool) -> Void = { _ in }
) -> RUMResourceScope {
return RUMResourceScope(
context: context,
@@ -975,8 +990,9 @@ extension RUMResourceScope {
httpMethod: httpMethod,
resourceKindBasedOnRequest: resourceKindBasedOnRequest,
spanContext: spanContext,
onResourceEventSent: onResourceEventSent,
onErrorEventSent: onErrorEventSent
networkSettledMetric: networkSettledMetric,
onResourceEvent: onResourceEvent,
onErrorEvent: onErrorEvent
)
}
}
@@ -993,6 +1009,7 @@ extension RUMUserActionScope {
serverTimeOffset: TimeInterval = .zero,
isContinuous: Bool = .mockAny(),
instrumentation: InstrumentationType = .manual,
interactionToNextViewMetric: ITNVMetricTracking = ITNVMetric(predicate: TimeBasedITNVActionPredicate()),
onActionEventSent: @escaping (RUMActionEvent) -> Void = { _ in }
) -> RUMUserActionScope {
return RUMUserActionScope(
@@ -1005,6 +1022,7 @@ extension RUMUserActionScope {
serverTimeOffset: serverTimeOffset,
isContinuous: isContinuous,
instrumentation: instrumentation,
interactionToNextViewMetric: interactionToNextViewMetric,
onActionEventSent: onActionEventSent
)
}
14 changes: 13 additions & 1 deletion DatadogRUM/Sources/Feature/RUMFeature.swift
Original file line number Diff line number Diff line change
@@ -100,7 +100,19 @@ internal final class RUMFeature: DatadogRemoteFeature {
viewCache: ViewCache(dateProvider: configuration.dateProvider),
fatalErrorContext: FatalErrorContextNotifier(messageBus: featureScope),
sessionEndedMetric: sessionEndedMetric,
watchdogTermination: watchdogTermination
watchdogTermination: watchdogTermination,
networkSettledMetricFactory: { viewStartDate, viewName in
return TTNSMetric(
viewName: viewName,
viewStartDate: viewStartDate,
resourcePredicate: configuration.networkSettledResourcePredicate
)
},
interactionToNextViewMetricFactory: {
return ITNVMetric(
predicate: configuration.nextViewActionPredicate
)
}
)

self.monitor = Monitor(
31 changes: 31 additions & 0 deletions DatadogRUM/Sources/RUMConfiguration.swift
Original file line number Diff line number Diff line change
@@ -147,6 +147,29 @@ extension RUM {
/// Default: `.average`.
public var vitalsUpdateFrequency: VitalsFrequency?

/// The predicate used to classify resources for the Time-To-Network-Settled (TTNS) view metric calculation.
///
/// **Time-To-Network-Settled (TTNS)** is a metric that measures the time from when a view becomes visible until all resources considered part of the view loading process
/// are fully loaded. This metric helps to understand how long it takes for a view to be fully ready with all required resources loaded.
///
/// The `NetworkSettledResourcePredicate` defines which resources are included in the TTNS calculation based on their properties (e.g., start time, resource URL, etc.).
///
/// Default: The default predicate, `TimeBasedTTNSResourcePredicate`, calculates TTNS using all resources that start within **100ms** of the view start.
/// This time threshold can be customized by providing a custom predicate or adjusting the threshold in the default predicate.
public var networkSettledResourcePredicate: NetworkSettledResourcePredicate

/// The predicate used to classify the "last interaction" for the Interaction-To-Next-View (ITNV) metric.
///
/// **Interaction-To-Next-View (ITNV)** is a metric that measures how long it takes from the last user interaction in a previous view
/// until the next view starts. It provides insight into how quickly a new view is displayed after a user’s action.
///
/// The `NextViewActionPredicate` determines which action in the previous view should be considered the "last interaction" for ITNV,
/// based on properties such as action type, name, or timing relative to the next view’s start.
///
/// Default: The default predicate, `TimeBasedITNVActionPredicate`, classifies actions as the last interaction if they occur within a
/// 3-second threshold before the next view starts. You can customize this time threshold or provide your own predicate.
public var nextViewActionPredicate: NextViewActionPredicate

/// Custom mapper for RUM view events.
///
/// It can be used to modify view events before they are sent. The implementation of the mapper should
@@ -345,6 +368,10 @@ extension RUM.Configuration {
/// - appHangThreshold: The threshold for App Hangs monitoring (in seconds). Default: `nil`.
/// - trackWatchdogTerminations: Determines whether the SDK should track application termination by the watchdog. Default: `false`.
/// - vitalsUpdateFrequency: The preferred frequency for collecting RUM vitals. Default: `.average`.
/// - networkSettledResourcePredicate: Predicate used to classify resources for the Time-To-Network-Settled (TTNS) metric calculation.
/// Default: `TimeBasedTTNSResourcePredicate()`.
/// - nextViewActionPredicate: The predicate used to classify which action in the previous view is considered the "last interaction"
/// for the Interaction-To-Next-View (ITNV) metric. Default: `TimeBasedITNVActionPredicate()`.
/// - viewEventMapper: Custom mapper for RUM view events. Default: `nil`.
/// - resourceEventMapper: Custom mapper for RUM resource events. Default: `nil`.
/// - actionEventMapper: Custom mapper for RUM action events. Default: `nil`.
@@ -365,6 +392,8 @@ extension RUM.Configuration {
appHangThreshold: TimeInterval? = nil,
trackWatchdogTerminations: Bool = false,
vitalsUpdateFrequency: VitalsFrequency? = .average,
networkSettledResourcePredicate: NetworkSettledResourcePredicate = TimeBasedTTNSResourcePredicate(),
nextViewActionPredicate: NextViewActionPredicate = TimeBasedITNVActionPredicate(),
viewEventMapper: RUM.ViewEventMapper? = nil,
resourceEventMapper: RUM.ResourceEventMapper? = nil,
actionEventMapper: RUM.ActionEventMapper? = nil,
@@ -384,6 +413,8 @@ extension RUM.Configuration {
self.longTaskThreshold = longTaskThreshold
self.appHangThreshold = appHangThreshold
self.vitalsUpdateFrequency = vitalsUpdateFrequency
self.networkSettledResourcePredicate = networkSettledResourcePredicate
self.nextViewActionPredicate = nextViewActionPredicate
self.viewEventMapper = viewEventMapper
self.resourceEventMapper = resourceEventMapper
self.actionEventMapper = actionEventMapper
Loading

0 comments on commit d41d5dd

Please sign in to comment.