Skip to content

Commit

Permalink
Merge pull request #2026 from DataDog/ganeshnj/feat/view-loading-api
Browse files Browse the repository at this point in the history
RUM-5841 feat(view-loading-api): implement addViewLoadingTime API
  • Loading branch information
ganeshnj authored Sep 2, 2024
2 parents c93151b + a6f034a commit 654aba5
Show file tree
Hide file tree
Showing 7 changed files with 107 additions and 3 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# Unreleased

- [FEATURE] Add support for view loading API (addViewLoadingTime). See [#2026][]
- [IMPROVEMENT] Drop support for deprecated cocoapod specs. See [#1998][]
- [FIX] Propagate global Tracer tags to OpenTelemetry span attributes. See [#2000][]
- [FEATURE] Add Logs event mapper to ObjC API. See [#2008][]
Expand Down Expand Up @@ -758,6 +759,7 @@ Release `2.0` introduces breaking changes. Follow the [Migration Guide](MIGRATIO
[#2005]: https://github.com/DataDog/dd-sdk-ios/pull/2005
[#1998]: https://github.com/DataDog/dd-sdk-ios/pull/1998
[#1998]: https://github.com/DataDog/dd-sdk-ios/pull/1966
[#2026]: https://github.com/DataDog/dd-sdk-ios/pull/2026
[@00fa9a]: https://github.com/00FA9A
[@britton-earnin]: https://github.com/Britton-Earnin
[@hengyu]: https://github.com/Hengyu
Expand Down
9 changes: 9 additions & 0 deletions DatadogRUM/Sources/RUMMonitor/Monitor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,15 @@ extension Monitor: RUMMonitorProtocol {
)
}

func addViewLoadingTime() {
process(
command: RUMAddViewLoadingTime(
time: dateProvider.now,
attributes: [:]
)
)
}

// MARK: - custom timings

func addTiming(name: String) {
Expand Down
9 changes: 9 additions & 0 deletions DatadogRUM/Sources/RUMMonitor/RUMCommand.swift
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,15 @@ internal struct RUMAddCurrentViewMemoryWarningCommand: RUMErrorCommand {
let missedEventType: SessionEndedMetric.MissedEventType? = .error
}

internal struct RUMAddViewLoadingTime: RUMCommand, RUMViewScopePropagatableAttributes {
var time: Date
var attributes: [AttributeKey: AttributeValue]
let canStartBackgroundView = false // no, it doesn't make sense to start "Background" view on receiving custom timing, as it will be `0ns` timing
let isUserInteraction = false // a custom view timing is not an interactive event

let missedEventType: SessionEndedMetric.MissedEventType? = .viewLoadingTime
}

internal struct RUMAddViewTimingCommand: RUMCommand, RUMViewScopePropagatableAttributes {
var time: Date
var attributes: [AttributeKey: AttributeValue]
Expand Down
7 changes: 6 additions & 1 deletion DatadogRUM/Sources/RUMMonitor/Scopes/RUMViewScope.swift
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ internal class RUMViewScope: RUMScope, RUMContextProvider {
let viewName: String
/// The start time of this View.
let viewStartTime: Date
/// The load time of this View.
private(set) var viewLoadingTime: TimeInterval?

/// Server time offset for date correction.
///
Expand Down Expand Up @@ -198,6 +200,9 @@ internal class RUMViewScope: RUMScope, RUMContextProvider {
case let command as RUMStopViewCommand where identity == command.identity:
isActiveView = false
needsViewUpdate = true
case let command as RUMAddViewLoadingTime where isActiveView:
viewLoadingTime = command.time.timeIntervalSince(viewStartTime)
needsViewUpdate = true
case let command as RUMAddViewTimingCommand where isActiveView:
customTimings[command.timingName] = command.time.timeIntervalSince(viewStartTime).toInt64Nanoseconds
needsViewUpdate = true
Expand Down Expand Up @@ -534,7 +539,7 @@ internal class RUMViewScope: RUMScope, RUMContextProvider {
largestContentfulPaint: nil,
largestContentfulPaintTargetSelector: nil,
loadEvent: nil,
loadingTime: nil,
loadingTime: viewLoadingTime?.toInt64Nanoseconds,
loadingType: nil,
longTask: .init(count: longTasksCount),
memoryAverage: memoryInfo?.meanValue,
Expand Down
19 changes: 17 additions & 2 deletions DatadogRUM/Sources/RUMMonitorProtocol.swift
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,13 @@ public protocol RUMMonitorProtocol: AnyObject {
attributes: [AttributeKey: AttributeValue]
)

/// Adds view loading time to current RUM view based on the time elapsed since the view was started.
/// This method should be called only once per view.
/// If the view is not started, this method does nothing.
/// If the view is not active, this method does nothing.
@_spi(Experimental)
func addViewLoadingTime()

// MARK: - custom timings

/// Records a specific timing within the current RUM view.
Expand Down Expand Up @@ -187,7 +194,7 @@ public protocol RUMMonitorProtocol: AnyObject {
)

/// Adds temporal metrics to given RUM resource.
///
///
/// It must be called before the resource is stopped.
/// - Parameters:
/// - resourceKey: the key representing the resource. It must match the one used to start the resource.
Expand Down Expand Up @@ -282,7 +289,7 @@ public protocol RUMMonitorProtocol: AnyObject {
)

/// Stops RUM action.
///
///
/// The action must be first started with `startAction(type:)`.
/// - Parameters:
/// - type: the type of the action. It should match type passed when starting this action.
Expand Down Expand Up @@ -318,6 +325,13 @@ public protocol RUMMonitorProtocol: AnyObject {
var debug: Bool { set get }
}

extension RUMMonitorProtocol {
/// It cannot be declared '@_spi' without a default implementation in a protocol extension
func addViewLoadingTime() {
// no-op
}
}

// MARK: - NOP moniotor

internal class NOPMonitor: RUMMonitorProtocol {
Expand All @@ -338,6 +352,7 @@ internal class NOPMonitor: RUMMonitorProtocol {
func stopView(viewController: UIViewController, attributes: [AttributeKey: AttributeValue]) { warn() }
func startView(key: String, name: String?, attributes: [AttributeKey: AttributeValue]) { warn() }
func stopView(key: String, attributes: [AttributeKey: AttributeValue]) { warn() }
func addViewLoadingTime() { warn() }
func addTiming(name: String) { warn() }
func addError(message: String, type: String?, stack: String?, source: RUMErrorSource, attributes: [AttributeKey: AttributeValue], file: StaticString?, line: UInt?) { warn() }
func addError(error: Error, source: RUMErrorSource, attributes: [AttributeKey: AttributeValue]) { warn() }
Expand Down
1 change: 1 addition & 0 deletions DatadogRUM/Sources/SDKMetrics/SessionEndedMetric.swift
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ internal class SessionEndedMetric {
case resource
case error
case longTask
case viewLoadingTime
}

/// Tracks the number of RUM events missed due to absence of an active RUM view.
Expand Down
63 changes: 63 additions & 0 deletions DatadogRUM/Tests/RUMMonitor/Monitor+GlobalAttributesTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import XCTest
import TestUtilities
import DatadogInternal
@_spi(Experimental)
@testable import DatadogRUM

class Monitor_GlobalAttributesTests: XCTestCase {
Expand Down Expand Up @@ -484,6 +485,68 @@ class Monitor_GlobalAttributesTests: XCTestCase {
XCTAssertEqual(viewAfterSecondTiming.attribute(forKey: "attribute2"), "value2")
}

// MARK: - View Loading Time

func testAddViewLoadingTimeToActiveView_thenLoadingTimeUpdated() throws {
// Given
monitor.notifySDKInit()
monitor.startView(key: "ActiveView")

// When
monitor.addViewLoadingTime()

// Then
let viewEvents = featureScope.eventsWritten(ofType: RUMViewEvent.self).filter { $0.view.name == "ActiveView" }
let lastView = try XCTUnwrap(viewEvents.last)

XCTAssertNotNil(lastView.view.loadingTime)
XCTAssertTrue(lastView.view.loadingTime! > 0)
}

func testAddViewLoadingTimeNoActiveView_thenNoEvent() throws {
// Given
monitor.notifySDKInit()
monitor.startView(key: "InactiveView")
monitor.stopView(key: "InactiveView")

// When
monitor.addViewLoadingTime()

// Then
let viewEvents = featureScope.eventsWritten(ofType: RUMViewEvent.self).filter { $0.view.name == "InactiveView" }
XCTAssertNil(viewEvents.last?.view.loadingTime)
}

func testAddViewLoadingTimeMultipleTimes_thenLoadingTimeOverwritten() throws {
// Given
monitor.notifySDKInit()
monitor.startView(key: "ActiveView")

// When
monitor.addViewLoadingTime()

// Then
let viewEvents = featureScope.eventsWritten(ofType: RUMViewEvent.self).filter { $0.view.name == "ActiveView" }
let lastView = try XCTUnwrap(viewEvents.last)

XCTAssertNotNil(lastView.view.loadingTime)
XCTAssertTrue(lastView.view.loadingTime! > 0)

let old = lastView.view.loadingTime!

// When
monitor.addViewLoadingTime()

// Then
let viewEvents2 = featureScope.eventsWritten(ofType: RUMViewEvent.self).filter { $0.view.name == "ActiveView" }
let lastView2 = try XCTUnwrap(viewEvents2.last)

XCTAssertNotNil(lastView2.view.loadingTime)
XCTAssertTrue(lastView2.view.loadingTime! > 0)

XCTAssertTrue(lastView2.view.loadingTime! > old)
}

// MARK: - Updating Fatal Error Context With Global Attributes

func testGivenSDKInitialized_whenGlobalAttributeIsAdded_thenFatalErrorContextIsUpdatedWithNewAttributes() throws {
Expand Down

0 comments on commit 654aba5

Please sign in to comment.