Skip to content

Commit

Permalink
Merge pull request #1029 from DataDog/ncreated/RUMM-2290-support-rule…
Browse files Browse the repository at this point in the history
…-PSR-for-APM-ingestion-control

RUMM-2290 Support `rulePsr` for APM traffic ingestion
  • Loading branch information
ncreated authored Oct 24, 2022
2 parents 5ab760d + d099988 commit 639e6e1
Show file tree
Hide file tree
Showing 13 changed files with 143 additions and 34 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# Unreleased

- [IMPROVEMENT] Send trace sample rate (`dd.rulePsr`) for APM's traffic ingestion control page. See [#1029][]

# 1.12.1 / 18-10-2022

- [IMPROVEMENT] Upgrade to PLCrashReporter 1.11.0 to fix Xcode 14 support.
Expand Down Expand Up @@ -396,6 +398,7 @@
[#964]: https://github.com/DataDog/dd-sdk-ios/issues/964
[#973]: https://github.com/DataDog/dd-sdk-ios/issues/973
[#997]: https://github.com/DataDog/dd-sdk-ios/issues/997
[#1029]: https://github.com/DataDog/dd-sdk-ios/issues/1029
[@00fa9a]: https://github.com/00FA9A
[@britton-earnin]: https://github.com/Britton-Earnin
[@hengyu]: https://github.com/Hengyu
Expand Down
6 changes: 6 additions & 0 deletions Sources/Datadog/Core/Attributes/Attributes.swift
Original file line number Diff line number Diff line change
Expand Up @@ -113,4 +113,10 @@ internal struct CrossPlatformAttributes {
/// and send it within the RUM resource, so the RUM backend can issue corresponding APM span on behalf of the mobile app.
/// Expects `String` value.
static let spanID = "_dd.span_id"

/// Trace sample rate applied to RUM resources created by cross platform SDK.
/// We send cross-platform SDK's sample rate within RUM resource in order to provide accurate visibility into what settings are
/// configured at the SDK level. This gets displayed on APM's traffic ingestion control page.
/// Expects `Double` value between `0.0` and `1.0`.
static let rulePSR = "_dd.rule_psr"
}
4 changes: 3 additions & 1 deletion Sources/Datadog/Core/FeaturesConfiguration.swift
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,9 @@ internal struct FeaturesConfiguration {
let instrumentTracing: Bool
/// If the RUM instrumentation should be enabled.
let instrumentRUM: Bool
// Tracing sampler
/// Tracing sampler to decide if trace should be generated for certain network request:
/// - if RUM instrumentation is enabled, it is used to sample traces generated by RUM BE,
/// - if RUM is disabled, it is used to sample traces generated by the SDK.
let tracingSampler: Sampler
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,17 @@ internal typealias URLSessionRUMAttributesProvider = (URLRequest, URLResponse?,

internal class URLSessionRUMResourcesHandler: URLSessionInterceptionHandler, RUMCommandPublisher {
private let dateProvider: DateProvider
/// Tracing sampler used to sample traces generated by RUM BE.
let tracingSampler: Sampler
/// Attributes-providing callback.
/// It is configured by the user and should be used to associate additional RUM attributes with intercepted RUM Resource.
let rumAttributesProvider: (URLSessionRUMAttributesProvider)?
let rumAttributesProvider: URLSessionRUMAttributesProvider?

// MARK: - Initialization

init(dateProvider: DateProvider, rumAttributesProvider: (URLSessionRUMAttributesProvider)?) {
init(dateProvider: DateProvider, tracingSampler: Sampler, rumAttributesProvider: URLSessionRUMAttributesProvider?) {
self.dateProvider = dateProvider
self.tracingSampler = tracingSampler
self.rumAttributesProvider = rumAttributesProvider
}

Expand Down Expand Up @@ -45,7 +48,8 @@ internal class URLSessionRUMResourcesHandler: URLSessionInterceptionHandler, RUM
spanContext: interception.spanContext.map {
.init(
traceID: String($0.traceID.rawValue),
spanID: String($0.spanID.rawValue)
spanID: String($0.spanID.rawValue),
samplingRate: Double(tracingSampler.samplingRate) / 100.0
)
}
)
Expand Down
4 changes: 4 additions & 0 deletions Sources/Datadog/RUM/RUMMonitor/RUMCommand.swift
Original file line number Diff line number Diff line change
Expand Up @@ -146,8 +146,12 @@ internal protocol RUMResourceCommand: RUMCommand {
/// Tracing information propagated by Tracing to the underlying `URLRequest`. It is passed to the RUM backend
/// in order to create the APM span. The actual `Span` is not send by the SDK.
internal struct RUMSpanContext {
/// The trace ID injected to `URLRequest` that issues RUM resource.
let traceID: String
/// The span ID injected to `URLRequest` that issues RUM resource.
let spanID: String
/// The sampling rate applied to the trace (a value between `0.0` and `1.0`).
let samplingRate: Double
}

internal struct RUMStartResourceCommand: RUMResourceCommand {
Expand Down
3 changes: 2 additions & 1 deletion Sources/Datadog/RUM/RUMMonitor/Scopes/RUMResourceScope.swift
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ internal class RUMResourceScope: RUMScope {
// Check trace attributes
let traceId = (attributes.removeValue(forKey: CrossPlatformAttributes.traceID) as? String) ?? spanContext?.traceID
let spanId = (attributes.removeValue(forKey: CrossPlatformAttributes.spanID) as? String) ?? spanContext?.spanID
let traceSamplingRate = (attributes.removeValue(forKey: CrossPlatformAttributes.rulePSR) as? Double) ?? spanContext?.samplingRate

/// Metrics values take precedence over other values.
if let metrics = resourceMetrics {
Expand All @@ -135,7 +136,7 @@ internal class RUMResourceScope: RUMScope {
dd: .init(
browserSdkVersion: nil,
discarded: nil,
rulePsr: nil,
rulePsr: traceSamplingRate,
session: .init(plan: .plan1),
spanId: spanId,
traceId: traceId
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import Foundation
internal class URLSessionTracingHandler: URLSessionInterceptionHandler {
/// Listening to app state changes and use it to report `foreground_duration`
let appStateListener: AppStateListening
/// The Tracing sampler.
/// Tracing sampler used to sample traces generated by the SDK.
let tracingSampler: Sampler

init(appStateListener: AppStateListening, tracingSampler: Sampler) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ public class URLSessionInterceptor: URLSessionInterceptorType {
/// Additional header injected to intercepted 1st party requests.
/// Set to `x-datadog-origin: rum` if both RUM and Tracing instrumentations are enabled and `nil` in all other cases.
internal let additionalHeadersForFirstPartyRequests: [String: String]?
/// Tracing sampler
/// Tracing sampler used to sample traces generated by the SDK or RUM BE.
internal let tracingSampler: Sampler

// MARK: - Initialization
Expand All @@ -60,6 +60,7 @@ public class URLSessionInterceptor: URLSessionInterceptorType {
if configuration.instrumentRUM {
handler = URLSessionRUMResourcesHandler(
dateProvider: dateProvider,
tracingSampler: configuration.tracingSampler,
rumAttributesProvider: configuration.rumAttributesProvider
)
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@ class RUMResourcesScenarioTests: IntegrationTests, RUMCommonAsserts {

XCTAssertNil(firstPartyResource1.dd.traceId, "`firstPartyGETResourceURL` should not be traced")
XCTAssertNil(firstPartyResource1.dd.spanId, "`firstPartyGETResourceURL` should not be traced")
XCTAssertNil(firstPartyResource1.dd.rulePsr, "Not traced resource should not send sample rate")

let firstPartyResource2 = try XCTUnwrap(
session.viewVisits[0].resourceEvents.first { $0.resource.url == firstPartyPOSTResourceURL.absoluteString },
Expand All @@ -151,6 +152,8 @@ class RUMResourcesScenarioTests: IntegrationTests, RUMCommonAsserts {
firstPartyPOSTRequestSpanID,
"Tracing information should be propagated to `firstPartyPOSTResourceURL`"
)
let firstPartyResource2SampleRate = try XCTUnwrap(firstPartyResource2.dd.rulePsr, "Traced resource should send sample rate")
XCTAssertTrue(isValid(sampleRate: firstPartyResource2SampleRate), "\(firstPartyResource2SampleRate) is not valid sample rate")

let firstPartyResourceError1 = try XCTUnwrap(
session.viewVisits[0].errorEvents.first { $0.error.resource?.url == firstPartyBadResourceURL.absoluteString },
Expand All @@ -172,6 +175,7 @@ class RUMResourcesScenarioTests: IntegrationTests, RUMCommonAsserts {
XCTAssertGreaterThan(thirdPartyResource1.resource.duration, 0)
XCTAssertNil(thirdPartyResource1.dd.traceId, "3rd party RUM Resources should not be traced")
XCTAssertNil(thirdPartyResource1.dd.spanId, "3rd party RUM Resources should not be traced")
XCTAssertNil(thirdPartyResource1.dd.rulePsr, "Not traced resource should not send sample rate")

let thirdPartyResource2 = try XCTUnwrap(
session.viewVisits[1].resourceEvents.first { $0.resource.url == thirdPartyPOSTResourceURL.absoluteString },
Expand All @@ -181,6 +185,7 @@ class RUMResourcesScenarioTests: IntegrationTests, RUMCommonAsserts {
XCTAssertGreaterThan(thirdPartyResource2.resource.duration, 0)
XCTAssertNil(thirdPartyResource2.dd.traceId, "3rd party RUM Resources should not be traced")
XCTAssertNil(thirdPartyResource2.dd.spanId, "3rd party RUM Resources should not be traced")
XCTAssertNil(thirdPartyResource2.dd.rulePsr, "Not traced resource should not send sample rate")

XCTAssertTrue(
thirdPartyResource1.resource.dns != nil || thirdPartyResource2.resource.dns != nil,
Expand Down Expand Up @@ -237,4 +242,8 @@ class RUMResourcesScenarioTests: IntegrationTests, RUMCommonAsserts {
header?.removeFirst(prefix.count)
return header
}

private func isValid(sampleRate: Double) -> Bool {
return sampleRate >= 0 && sampleRate <= 1
}
}
32 changes: 29 additions & 3 deletions Tests/DatadogTests/Datadog/Mocks/RUMFeatureMocks.swift
Original file line number Diff line number Diff line change
Expand Up @@ -289,6 +289,32 @@ extension RUMAddViewTimingCommand: AnyMockable, RandomMockable {
}
}

extension RUMSpanContext: AnyMockable, RandomMockable {
static func mockAny() -> RUMSpanContext {
return .mockWith()
}

static func mockRandom() -> RUMSpanContext {
return RUMSpanContext(
traceID: .mockRandom(),
spanID: .mockRandom(),
samplingRate: .mockRandom()
)
}

static func mockWith(
traceID: String = .mockAny(),
spanID: String = .mockAny(),
samplingRate: Double = .mockAny()
) -> RUMSpanContext {
return RUMSpanContext(
traceID: traceID,
spanID: spanID,
samplingRate: samplingRate
)
}
}

extension RUMStartResourceCommand: AnyMockable, RandomMockable {
static func mockAny() -> RUMStartResourceCommand { mockWith() }

Expand All @@ -301,7 +327,7 @@ extension RUMStartResourceCommand: AnyMockable, RandomMockable {
httpMethod: .mockRandom(),
kind: .mockAny(),
isFirstPartyRequest: .mockRandom(),
spanContext: .init(traceID: .mockRandom(), spanID: .mockRandom())
spanContext: .init(traceID: .mockRandom(), spanID: .mockRandom(), samplingRate: .mockAny())
)
}

Expand All @@ -313,7 +339,7 @@ extension RUMStartResourceCommand: AnyMockable, RandomMockable {
httpMethod: RUMMethod = .mockAny(),
kind: RUMResourceType = .mockAny(),
isFirstPartyRequest: Bool = .mockAny(),
spanContext: RUMSpanContext? = nil
spanContext: RUMSpanContext? = .mockAny()
) -> RUMStartResourceCommand {
return RUMStartResourceCommand(
resourceKey: resourceKey,
Expand Down Expand Up @@ -784,7 +810,7 @@ extension RUMResourceScope {
httpMethod: RUMMethod = .mockAny(),
isFirstPartyResource: Bool? = nil,
resourceKindBasedOnRequest: RUMResourceType? = nil,
spanContext: RUMSpanContext? = nil,
spanContext: RUMSpanContext? = .mockAny(),
onResourceEventSent: @escaping () -> Void = {},
onErrorEventSent: @escaping () -> Void = {}
) -> RUMResourceScope {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -370,10 +370,14 @@ extension Bool: AnyMockable {
}
}

extension Float: AnyMockable {
extension Float: AnyMockable, RandomMockable {
static func mockAny() -> Float {
return 0
}

static func mockRandom() -> Float {
return .random(in: -Float(Int.min)...Float(Int.max))
}
}

extension Double: AnyMockable, RandomMockable {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,15 @@ extension ResourceMetrics: EquatableInTests {}

class URLSessionRUMResourcesHandlerTests: XCTestCase {
private let dateProvider = RelativeDateProvider(using: .mockDecember15th2019At10AMUTC())
private let traceSamplingRate: Double = .mockRandom(min: 0, max: 1)
private let commandSubscriber = RUMCommandSubscriberMock()

private func createHandler(rumAttributesProvider: URLSessionRUMAttributesProvider? = nil) -> URLSessionRUMResourcesHandler {
let handler = URLSessionRUMResourcesHandler(dateProvider: dateProvider, rumAttributesProvider: rumAttributesProvider)
let handler = URLSessionRUMResourcesHandler(
dateProvider: dateProvider,
tracingSampler: Sampler(samplingRate: Float(traceSamplingRate * 100)),
rumAttributesProvider: rumAttributesProvider
)
handler.publish(to: commandSubscriber)
return handler
}
Expand Down Expand Up @@ -61,8 +66,10 @@ class URLSessionRUMResourcesHandlerTests: XCTestCase {
waitForExpectations(timeout: 0.5, handler: nil)

let resourceStartCommand = try XCTUnwrap(commandSubscriber.lastReceivedCommand as? RUMStartResourceCommand)
XCTAssertEqual(resourceStartCommand.spanContext?.traceID, "1")
XCTAssertEqual(resourceStartCommand.spanContext?.spanID, "2")
let spanContext = try XCTUnwrap(resourceStartCommand.spanContext)
XCTAssertEqual(spanContext.traceID, "1")
XCTAssertEqual(spanContext.spanID, "2")
XCTAssertEqual(spanContext.samplingRate, traceSamplingRate, accuracy: 0.01)
}

func testGivenTaskInterceptionWithMetricsAndResponse_whenInterceptionCompletes_itStopsRUMResourceWithMetrics() throws {
Expand Down
Loading

0 comments on commit 639e6e1

Please sign in to comment.