Skip to content

Commit

Permalink
Merge pull request #590 from DataDog/maxep/RUMM-1455/publish-session-id
Browse files Browse the repository at this point in the history
RUMM-1455 Add Session Start Callback
  • Loading branch information
maxep authored Sep 7, 2021
2 parents f571f04 + 4dc2ecc commit 88b9e66
Show file tree
Hide file tree
Showing 15 changed files with 203 additions and 25 deletions.
4 changes: 3 additions & 1 deletion Sources/Datadog/Core/FeaturesConfiguration.swift
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ internal struct FeaturesConfiguration {
/// RUM auto instrumentation configuration, `nil` if not enabled.
let autoInstrumentation: AutoInstrumentation?
let backgroundEventTrackingEnabled: Bool
let onSessionStart: RUMSessionListener?
}

struct URLSessionAutoInstrumentation {
Expand Down Expand Up @@ -200,7 +201,8 @@ extension FeaturesConfiguration {
actionEventMapper: configuration.rumActionEventMapper,
errorEventMapper: configuration.rumErrorEventMapper,
autoInstrumentation: autoInstrumentation,
backgroundEventTrackingEnabled: configuration.rumBackgroundEventTrackingEnabled
backgroundEventTrackingEnabled: configuration.rumBackgroundEventTrackingEnabled,
onSessionStart: configuration.rumSessionsListener
)
} else {
let error = ProgrammerError(
Expand Down
13 changes: 13 additions & 0 deletions Sources/Datadog/DatadogConfiguration.swift
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,7 @@ extension Datadog {
private(set) var firstPartyHosts: Set<String>?
private(set) var spanEventMapper: SpanEventMapper?
private(set) var rumSessionsSamplingRate: Float
private(set) var rumSessionsListener: RUMSessionListener?
private(set) var rumUIKitViewsPredicate: UIKitRUMViewsPredicate?
private(set) var rumUIKitUserActionsPredicate: UIKitRUMUserActionsPredicate?
private(set) var rumViewEventMapper: RUMViewEventMapper?
Expand Down Expand Up @@ -306,6 +307,7 @@ extension Datadog {
firstPartyHosts: nil,
spanEventMapper: nil,
rumSessionsSamplingRate: 100.0,
rumSessionsListener: nil,
rumUIKitViewsPredicate: nil,
rumUIKitUserActionsPredicate: nil,
rumViewEventMapper: nil,
Expand Down Expand Up @@ -505,6 +507,17 @@ extension Datadog {
return self
}

/// Sets the RUM Session start callback.
///
/// The callback takes 2 arguments: the newly started Session ID and a boolean indicating whether or not the session is discarded by the sampling rate
/// (when `true` it means no event in this session will be kept).
///
/// - Parameter handler: the callback handler to notify whenever a new Session starts.
public func onRUMSessionStart(_ handler: @escaping (String, Bool) -> Void) -> Builder {
configuration.rumSessionsListener = handler
return self
}

/// Sets the predicate for automatically tracking `UIViewControllers` as RUM Views.
///
/// When the app is running, the SDK will ask provided `predicate` if any noticed `UIViewController` should be considered
Expand Down
10 changes: 8 additions & 2 deletions Sources/Datadog/RUM/RUMFeature.swift
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ internal final class RUMFeature {
let vitalMemoryReader: SamplingBasedVitalReader // VitalMemoryReader
let vitalRefreshRateReader: ContinuousVitalReader // VitalRefreshRateReader

let onSessionStart: RUMSessionListener?

// MARK: - Components

static let featureName = "rum"
Expand Down Expand Up @@ -143,7 +145,8 @@ internal final class RUMFeature {
commonDependencies: commonDependencies,
vitalCPUReader: VitalCPUReader(),
vitalMemoryReader: VitalMemoryReader(),
vitalRefreshRateReader: VitalRefreshRateReader()
vitalRefreshRateReader: VitalRefreshRateReader(),
onSessionStart: configuration.onSessionStart
)
}

Expand All @@ -155,7 +158,8 @@ internal final class RUMFeature {
commonDependencies: FeaturesCommonDependencies,
vitalCPUReader: SamplingBasedVitalReader,
vitalMemoryReader: SamplingBasedVitalReader,
vitalRefreshRateReader: ContinuousVitalReader
vitalRefreshRateReader: ContinuousVitalReader,
onSessionStart: RUMSessionListener? = nil
) {
// Configuration
self.configuration = configuration
Expand All @@ -176,6 +180,8 @@ internal final class RUMFeature {
self.vitalCPUReader = vitalCPUReader
self.vitalMemoryReader = vitalMemoryReader
self.vitalRefreshRateReader = vitalRefreshRateReader

self.onSessionStart = onSessionStart
}

#if DD_SDK_COMPILED_FOR_TESTING
Expand Down
12 changes: 12 additions & 0 deletions Sources/Datadog/RUM/RUMMonitor/Scopes/RUMApplicationScope.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@

import Foundation

internal typealias RUMSessionListener = (String, Bool) -> Void

/// Injection container for common dependencies used by all `RUMScopes`.
internal struct RUMScopeDependencies {
let userInfoProvider: RUMUserInfoProvider
Expand All @@ -20,6 +22,8 @@ internal struct RUMScopeDependencies {
let vitalCPUReader: SamplingBasedVitalReader
let vitalMemoryReader: SamplingBasedVitalReader
let vitalRefreshRateReader: ContinuousVitalReader

let onSessionStart: RUMSessionListener?
}

internal class RUMApplicationScope: RUMScope, RUMContextProvider {
Expand All @@ -28,6 +32,7 @@ internal class RUMApplicationScope: RUMScope, RUMContextProvider {
/// Session scope. It gets created with the first `.startView` event.
/// Might be re-created later according to session duration constraints.
private(set) var sessionScope: RUMSessionScope?

/// RUM Sessions sampling rate.
internal let samplingRate: Float

Expand Down Expand Up @@ -87,6 +92,7 @@ internal class RUMApplicationScope: RUMScope, RUMContextProvider {
private func refresh(expiredSession: RUMSessionScope, on command: RUMCommand) {
let refreshedSession = RUMSessionScope(from: expiredSession, startTime: command.time)
sessionScope = refreshedSession
sessionScopeDidUpdate(refreshedSession)
_ = refreshedSession.process(command: command)
}

Expand All @@ -103,6 +109,12 @@ internal class RUMApplicationScope: RUMScope, RUMContextProvider {
)

sessionScope = initialSession
sessionScopeDidUpdate(initialSession)
_ = initialSession.process(command: startInitialViewCommand)
}

private func sessionScopeDidUpdate(_ sessionScope: RUMSessionScope) {
let sessionID = sessionScope.sessionUUID.rawValue.uuidString
dependencies.onSessionStart?(sessionID, sessionScope.shouldBeSampledOut)
}
}
4 changes: 2 additions & 2 deletions Sources/Datadog/RUM/RUMMonitor/Scopes/RUMSessionScope.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,10 @@ internal class RUMSessionScope: RUMScope, RUMContextProvider {

/// This Session UUID. Equals `.nullUUID` if the Session is sampled.
let sessionUUID: RUMUUID
/// Tells if events from this Session should be sampled-out (not send).
let shouldBeSampledOut: Bool
/// RUM Session sampling rate.
private let samplingRate: Float
/// Tells if events from this Session should be sampled-out (not send).
private let shouldBeSampledOut: Bool
/// The start time of this Session.
private let sessionStartTime: Date
/// Time of the last RUM interaction noticed by this Session.
Expand Down
3 changes: 2 additions & 1 deletion Sources/Datadog/RUMMonitor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,8 @@ public class RUMMonitor: DDRUMMonitor, RUMCommandSubscriber {
dateCorrector: rumFeature.dateCorrector,
vitalCPUReader: rumFeature.vitalCPUReader,
vitalMemoryReader: rumFeature.vitalMemoryReader,
vitalRefreshRateReader: rumFeature.vitalRefreshRateReader
vitalRefreshRateReader: rumFeature.vitalRefreshRateReader,
onSessionStart: rumFeature.onSessionStart
),
samplingRate: rumFeature.configuration.sessionSamplingRate,
backgroundEventTrackingEnabled: rumFeature.configuration.backgroundEventTrackingEnabled
Expand Down
5 changes: 5 additions & 0 deletions Sources/DatadogObjc/DatadogConfiguration+objc.swift
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,11 @@ public class DDConfigurationBuilder: NSObject {
_ = sdkBuilder.set(rumSessionsSamplingRate: rumSessionsSamplingRate)
}

@objc
public func set(onRUMSessionStart handler: @escaping (String, Bool) -> Void) {
_ = sdkBuilder.onRUMSessionStart(handler)
}

@objc
public func trackUIKitRUMViews() {
let defaultPredicate = DefaultUIKitRUMViewsPredicate()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ class DatadogConfigurationBuilderTests: XCTestCase {
XCTAssertNil(configuration.firstPartyHosts)
XCTAssertNil(configuration.spanEventMapper)
XCTAssertEqual(configuration.rumSessionsSamplingRate, 100.0)
XCTAssertNil(configuration.rumSessionsListener)
XCTAssertNil(configuration.rumUIKitViewsPredicate)
XCTAssertNil(configuration.rumUIKitUserActionsPredicate)
XCTAssertNil(configuration.rumViewEventMapper)
Expand Down Expand Up @@ -78,6 +79,7 @@ class DatadogConfigurationBuilderTests: XCTestCase {
.set(customTracesEndpoint: URL(string: "https://api.custom.traces/")!)
.set(customRUMEndpoint: URL(string: "https://api.custom.rum/")!)
.set(rumSessionsSamplingRate: 42.5)
.onRUMSessionStart { _, _ in }
.setSpanEventMapper { _ in mockSpanEvent }
.trackURLSession(firstPartyHosts: ["example.com"])
.trackUIKitRUMViews(using: UIKitRUMViewsPredicateMock())
Expand Down Expand Up @@ -133,6 +135,7 @@ class DatadogConfigurationBuilderTests: XCTestCase {
XCTAssertEqual(configuration.customRUMEndpoint, URL(string: "https://api.custom.rum/")!)
XCTAssertEqual(configuration.firstPartyHosts, ["example.com"])
XCTAssertEqual(configuration.rumSessionsSamplingRate, 42.5)
XCTAssertNotNil(configuration.rumSessionsListener)
XCTAssertTrue(configuration.rumUIKitViewsPredicate is UIKitRUMViewsPredicateMock)
XCTAssertTrue(configuration.rumUIKitUserActionsPredicate is UIKitRUMUserActionsPredicateMock)
XCTAssertEqual(configuration.spanEventMapper?(.mockRandom()), mockSpanEvent)
Expand Down
6 changes: 4 additions & 2 deletions Tests/DatadogTests/Datadog/Mocks/CoreMocks.swift
Original file line number Diff line number Diff line change
Expand Up @@ -233,7 +233,8 @@ extension FeaturesConfiguration.RUM {
actionEventMapper: RUMActionEventMapper? = nil,
errorEventMapper: RUMErrorEventMapper? = nil,
autoInstrumentation: FeaturesConfiguration.RUM.AutoInstrumentation? = nil,
backgroundEventTrackingEnabled: Bool = false
backgroundEventTrackingEnabled: Bool = false,
onSessionStart: @escaping RUMSessionListener = mockNoOpSessionListerner()
) -> Self {
return .init(
common: common,
Expand All @@ -246,7 +247,8 @@ extension FeaturesConfiguration.RUM {
actionEventMapper: actionEventMapper,
errorEventMapper: errorEventMapper,
autoInstrumentation: autoInstrumentation,
backgroundEventTrackingEnabled: backgroundEventTrackingEnabled
backgroundEventTrackingEnabled: backgroundEventTrackingEnabled,
onSessionStart: onSessionStart
)
}
}
Expand Down
16 changes: 12 additions & 4 deletions Tests/DatadogTests/Datadog/Mocks/RUMFeatureMocks.swift
Original file line number Diff line number Diff line change
Expand Up @@ -381,6 +381,10 @@ extension RUMContext {

// MARK: - RUMScope Mocks

func mockNoOpSessionListerner() -> RUMSessionListener {
return { _, _ in }
}

extension RUMScopeDependencies {
static func mockAny() -> RUMScopeDependencies {
return mockWith()
Expand All @@ -396,7 +400,8 @@ extension RUMScopeDependencies {
eventBuilder: RUMEventBuilder = RUMEventBuilder(eventsMapper: .mockNoOp()),
eventOutput: RUMEventOutput = RUMEventOutputMock(),
rumUUIDGenerator: RUMUUIDGenerator = DefaultRUMUUIDGenerator(),
dateCorrector: DateCorrectorType = DateCorrectorMock()
dateCorrector: DateCorrectorType = DateCorrectorMock(),
onSessionStart: @escaping RUMSessionListener = mockNoOpSessionListerner()
) -> RUMScopeDependencies {
return RUMScopeDependencies(
userInfoProvider: userInfoProvider,
Expand All @@ -408,7 +413,8 @@ extension RUMScopeDependencies {
dateCorrector: dateCorrector,
vitalCPUReader: SamplingBasedVitalReaderMock(),
vitalMemoryReader: SamplingBasedVitalReaderMock(),
vitalRefreshRateReader: ContinuousVitalReaderMock()
vitalRefreshRateReader: ContinuousVitalReaderMock(),
onSessionStart: onSessionStart
)
}

Expand All @@ -420,7 +426,8 @@ extension RUMScopeDependencies {
eventBuilder: RUMEventBuilder? = nil,
eventOutput: RUMEventOutput? = nil,
rumUUIDGenerator: RUMUUIDGenerator? = nil,
dateCorrector: DateCorrectorType? = nil
dateCorrector: DateCorrectorType? = nil,
onSessionStart: @escaping RUMSessionListener = mockNoOpSessionListerner()
) -> RUMScopeDependencies {
return RUMScopeDependencies(
userInfoProvider: userInfoProvider ?? self.userInfoProvider,
Expand All @@ -432,7 +439,8 @@ extension RUMScopeDependencies {
dateCorrector: dateCorrector ?? self.dateCorrector,
vitalCPUReader: SamplingBasedVitalReaderMock(),
vitalMemoryReader: SamplingBasedVitalReaderMock(),
vitalRefreshRateReader: ContinuousVitalReaderMock()
vitalRefreshRateReader: ContinuousVitalReaderMock(),
onSessionStart: onSessionStart
)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,16 +24,48 @@ class RUMApplicationScopeTests: XCTestCase {
}

func testWhenFirstViewIsStarted_itStartsNewSession() {
let scope = RUMApplicationScope(rumApplicationID: .mockAny(), dependencies: .mockAny(), samplingRate: 100, backgroundEventTrackingEnabled: .mockAny())
let expectation = self.expectation(description: "onSessionStart is called")
let onSessionStart: RUMSessionListener = { sessionId, isDiscarded in
XCTAssertTrue(sessionId.matches(regex: .uuidRegex))
XCTAssertTrue(isDiscarded)
expectation.fulfill()
}

let scope = RUMApplicationScope(
rumApplicationID: .mockAny(),
dependencies: .mockWith(
onSessionStart: onSessionStart
),
samplingRate: 0,
backgroundEventTrackingEnabled: .mockAny()
)

XCTAssertNil(scope.sessionScope)
XCTAssertTrue(scope.process(command: RUMStartViewCommand.mockAny()))
waitForExpectations(timeout: 0.5)
XCTAssertNotNil(scope.sessionScope)
XCTAssertEqual(scope.sessionScope?.backgroundEventTrackingEnabled, scope.backgroundEventTrackingEnabled)
}

func testWhenSessionExpires_itStartsANewOneAndTransfersActiveViews() throws {
let scope = RUMApplicationScope(rumApplicationID: .mockAny(), dependencies: .mockAny(), samplingRate: 100, backgroundEventTrackingEnabled: .mockAny())
let expectation = self.expectation(description: "onSessionStart is called twice")
expectation.expectedFulfillmentCount = 2

let onSessionStart: RUMSessionListener = { sessionId, isDiscarded in
XCTAssertTrue(sessionId.matches(regex: .uuidRegex))
XCTAssertFalse(isDiscarded)
expectation.fulfill()
}

let scope = RUMApplicationScope(
rumApplicationID: .mockAny(),
dependencies: .mockWith(
onSessionStart: onSessionStart
),
samplingRate: 100,
backgroundEventTrackingEnabled: .mockAny()
)

var currentTime = Date()

let view = createMockViewInWindow()
Expand All @@ -49,6 +81,7 @@ class RUMApplicationScopeTests: XCTestCase {
let secondSessionViewScopes = try XCTUnwrap(scope.sessionScope?.viewScopes)
let secondSessionViewScope = try XCTUnwrap(secondSessionViewScopes.first)

waitForExpectations(timeout: 0.5)
XCTAssertNotEqual(firstSessionUUID, secondSessionUUID)
XCTAssertEqual(firstsSessionViewScopes.count, secondSessionViewScopes.count)
XCTAssertTrue(secondSessionViewScope.identity.equals(view))
Expand Down
27 changes: 27 additions & 0 deletions Tests/DatadogTests/Datadog/RUMMonitorTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -789,6 +789,33 @@ class RUMMonitorTests: XCTestCase {
XCTAssertEqual(try lastViewUpdate.timing(named: "timing3_.@$-______"), 3_000_000_000)
}

// MARK: - RUM New Session

func testStartingViewCreatesNewSession() {
let keepAllSessions: Bool = .random()

let expectation = self.expectation(description: "onSessionStart is called")
let onSessionStart: RUMSessionListener = { sessionId, isDiscarded in
XCTAssertTrue(sessionId.matches(regex: .uuidRegex))
XCTAssertEqual(isDiscarded, !keepAllSessions)
expectation.fulfill()
}

RUMFeature.instance = .mockWith(
directories: temporaryFeatureDirectories,
configuration: .mockWith(
sessionSamplingRate: keepAllSessions ? 100 : 0,
onSessionStart: onSessionStart
)
)

defer { RUMFeature.instance?.deinitialize() }

let monitor = RUMMonitor.initialize()
monitor.startView(viewController: mockView)
waitForExpectations(timeout: 0.5)
}

// MARK: - RUM Events Dates Correction

func testGivenTimeDifferenceBetweenDeviceAndServer_whenCollectingRUMEvents_thenEventsDateUseServerTime() throws {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ - (void)testDDConfigurationBuilderAPI {
[builder trackURLSessionWithFirstPartyHosts:[NSSet setWithArray:@[]]];
[builder setWithServiceName:@""];
[builder setWithRumSessionsSamplingRate:50];
[builder setOnRUMSessionStart:^(NSString * _Nonnull sessionId, BOOL isDiscarded) {}];
[builder trackUIKitRUMViews];
[builder trackUIKitRUMViewsUsing:[CustomDDUIKitRUMViewsPredicate new]];
[builder trackUIKitRUMActions];
Expand Down
Loading

0 comments on commit 88b9e66

Please sign in to comment.