Skip to content

Commit

Permalink
RUMM-1064 PR comments addressed
Browse files Browse the repository at this point in the history
  • Loading branch information
buranmert committed Mar 24, 2021
1 parent 857edc6 commit 3808aca
Show file tree
Hide file tree
Showing 13 changed files with 68 additions and 118 deletions.
32 changes: 11 additions & 21 deletions Sources/Datadog/Core/System/AppStateListener.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,13 @@ import class UIKit.UIApplication
/// A data structure to represent recorded app states in a given period of time
internal struct AppStateHistory: Equatable {
/// Snapshot of the app state at `date`
struct Snapshot: Equatable, Comparable {
struct Snapshot: Equatable {
let isActive: Bool
let date: Date

static func < (lhs: Snapshot, rhs: Snapshot) -> Bool {
return lhs.date < rhs.date
}
}

var initialState: Snapshot
private(set) var changes = [Snapshot]()
// NOTE: RUMM-1064 changes.last.date > finalDate case isn't handled as not realistic
var changes = [Snapshot]()
var finalDate: Date
var finalState: Snapshot {
return Snapshot(
Expand All @@ -30,11 +25,6 @@ internal struct AppStateHistory: Equatable {
)
}

mutating func add(change: Snapshot) {
changes.append(change)
changes.sort()
}

/// Limits or extrapolates app state history to the given range
/// This is useful when you record between 0...3t but you are concerned of t...2t only
/// - Parameter range: if outside of `initialState` and `finalState`, it extrapolates; otherwise it limits
Expand Down Expand Up @@ -82,14 +72,14 @@ internal struct AppStateHistory: Equatable {
// and no change after final state
return finalState.isActive
}
var active = initialState.isActive
var active = initialState
for change in changes {
if date < change.date {
break
}
active = change.isActive
active = change
}
return active
return active.isActive
}
}

Expand Down Expand Up @@ -134,15 +124,15 @@ internal class AppStateListener: AppStateListening {
@objc
private func appWillResignActive() {
let now = dateProvider.currentDate()
publisher.mutateAsync {
$0.add(change: Snapshot(isActive: false, date: now))
}
var value = publisher.currentValue
value.changes.append(Snapshot(isActive: false, date: now))
publisher.publishAsync(value)
}
@objc
private func appDidBecomeActive() {
let now = dateProvider.currentDate()
publisher.mutateAsync {
$0.add(change: Snapshot(isActive: true, date: now))
}
var value = publisher.currentValue
value.changes.append(Snapshot(isActive: true, date: now))
publisher.publishAsync(value)
}
}
5 changes: 0 additions & 5 deletions Sources/Datadog/Core/Utils/ValuePublisher.swift
Original file line number Diff line number Diff line change
Expand Up @@ -66,11 +66,6 @@ internal class ValuePublisher<Value> {
concurrentQueue.async(flags: .barrier) { self.unsafeValue = newValue }
}

/// Mutates the `currentValue` asynchronously, without blocking the caller thread.
func mutateAsync(_ block: @escaping (inout Value) -> Void) {
concurrentQueue.async(flags: .barrier) { block(&self.unsafeValue) }
}

/// Registers an observer that will be notified on all value changes.
/// All calls to the `observer` will be synchronised using internal concurrent queue.
func subscribe<Observer: ValueObserver>(_ observer: Observer) where Observer.ObservedValue == Value {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,13 @@
import Foundation

internal class URLSessionTracingHandler: URLSessionInterceptionHandler {
/// Listening to app state changes and use it to report `foreground_duration`
let appStateListener: AppStateListening

init(appStateListener: AppStateListening) {
self.appStateListener = appStateListener
}

// MARK: - URLSessionInterceptionHandler

func notify_taskInterceptionStarted(interception: TaskInterception) {
Expand Down Expand Up @@ -68,12 +75,12 @@ internal class URLSessionTracingHandler: URLSessionInterceptionHandler {
}
}
}
let appStateHistory = interception.appStateListener.history.take(
let appStateHistory = appStateListener.history.take(
between: resourceMetrics.fetch.start...resourceMetrics.fetch.end
)
// TODO: RUMM-1064 tag doesn't seem like the best fit but can't find anything better
span.setTag(key: "foreground_duration", value: "\(appStateHistory.foregroundDuration)")
span.setTag(key: "is_background", value: "\(appStateHistory.didRunInBackground)")
span.setTag(key: "foreground_duration", value: appStateHistory.foregroundDuration.toNanoseconds)
span.setTag(key: "is_background", value: appStateHistory.didRunInBackground)

span.finish(at: resourceMetrics.fetch.end)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,18 +21,14 @@ internal class TaskInterception {
/// Trace information propagated with the task. Not available when Tracing is disabled
/// or when the task was created through `URLSession.dataTask(with:url)` on some iOS13+.
private(set) var spanContext: DDSpanContext?
/// Listening to app state changes and use it to report `foreground_duration`
let appStateListener: AppStateListening

init(
request: URLRequest,
isFirstParty: Bool,
appStateListener: AppStateListening
isFirstParty: Bool
) {
self.identifier = UUID()
self.request = request
self.isFirstPartyRequest = isFirstParty
self.appStateListener = appStateListener
}

func register(metrics: ResourceMetrics) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,6 @@ public class URLSessionInterceptor: URLSessionInterceptorType {
private let defaultFirstPartyURLsFilter: FirstPartyURLsFilter
/// Filters internal `URLs` used by the SDK.
private let internalURLsFilter: InternalURLsFilter
/// Listening to app state changes and use it to report `foreground_duration`
internal let appStateListener: AppStateListening

/// Handles resources interception.
/// Depending on which instrumentation is enabled, this can be either RUM or Tracing handler sending respectively: RUM Resource or tracing Span.
internal let handler: URLSessionInterceptionHandler
Expand All @@ -48,7 +45,7 @@ public class URLSessionInterceptor: URLSessionInterceptorType {
if configuration.instrumentRUM {
handler = URLSessionRUMResourcesHandler(dateProvider: dateProvider)
} else {
handler = URLSessionTracingHandler()
handler = URLSessionTracingHandler(appStateListener: appStateListener)
}

self.init(configuration: configuration, handler: handler, appStateListener: appStateListener)
Expand All @@ -62,7 +59,6 @@ public class URLSessionInterceptor: URLSessionInterceptorType {
self.defaultFirstPartyURLsFilter = FirstPartyURLsFilter(hosts: configuration.userDefinedFirstPartyHosts)
self.internalURLsFilter = InternalURLsFilter(urls: configuration.sdkInternalURLs)
self.handler = handler
self.appStateListener = appStateListener

if configuration.instrumentTracing {
self.injectTracingHeadersToFirstPartyRequests = true
Expand Down Expand Up @@ -118,8 +114,7 @@ public class URLSessionInterceptor: URLSessionInterceptorType {
let isFirstPartyRequest = self.isFirstParty(request: request, for: session)
let interception = TaskInterception(
request: request,
isFirstParty: isFirstPartyRequest,
appStateListener: self.appStateListener
isFirstParty: isFirstPartyRequest
)
self.interceptionByTask[task] = interception

Expand Down
26 changes: 0 additions & 26 deletions Tests/DatadogTests/Datadog/Core/Utils/ValuePublisherTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -55,32 +55,6 @@ class ValuePublisherTests: XCTestCase {
waitForExpectations(timeout: 1, handler: nil)
}

func testWhenValueMutates_itNotifiesObservers() {
let numberOfObservers = 10
let initialValue: Int = .mockRandom()
let newValue: Int = .mockRandom()

let expectation = self.expectation(description: "All observers received new value")
expectation.expectedFulfillmentCount = numberOfObservers

let observers: [ValueObserverMock<Int>] = (0..<numberOfObservers).map { _ in
ValueObserverMock<Int> { old, new in
XCTAssertEqual(old, initialValue)
XCTAssertEqual(new, newValue)
expectation.fulfill()
}
}

let publisher = ValuePublisher<Int>(initialValue: initialValue)
observers.forEach { publisher.subscribe($0) }

// When
publisher.mutateAsync { $0 = newValue }

// Then
waitForExpectations(timeout: 1, handler: nil)
}

func testWhenNonEquatableValueChanges_itNotifiesObserversOnAllChanges() {
struct NonEquatableValue {
let value: Int
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ class URLSessionRUMResourcesHandlerTests: XCTestCase {
// Given
var request = URLRequest(url: .mockRandom())
request.httpMethod = ["GET", "POST", "PUT", "DELETE"].randomElement()!
let taskInterception = TaskInterception(request: request, isFirstParty: .random(), appStateListener: AppStateListener.mockAny())
let taskInterception = TaskInterception(request: request, isFirstParty: .random())
XCTAssertNil(taskInterception.spanContext)

// When
Expand All @@ -49,7 +49,7 @@ class URLSessionRUMResourcesHandlerTests: XCTestCase {
commandSubscriber.onCommandReceived = { _ in receiveCommand.fulfill() }

// Given
let taskInterception = TaskInterception(request: .mockAny(), isFirstParty: true, appStateListener: AppStateListener.mockAny())
let taskInterception = TaskInterception(request: .mockAny(), isFirstParty: true)

// When
handler.notify_taskInterceptionStarted(interception: taskInterception)
Expand All @@ -66,7 +66,7 @@ class URLSessionRUMResourcesHandlerTests: XCTestCase {
commandSubscriber.onCommandReceived = { _ in receiveCommand.fulfill() }

// Given
let taskInterception = TaskInterception(request: .mockAny(), isFirstParty: false, appStateListener: AppStateListener.mockAny())
let taskInterception = TaskInterception(request: .mockAny(), isFirstParty: false)

// When
handler.notify_taskInterceptionStarted(interception: taskInterception)
Expand All @@ -83,7 +83,7 @@ class URLSessionRUMResourcesHandlerTests: XCTestCase {
commandSubscriber.onCommandReceived = { _ in receiveCommand.fulfill() }

// Given
let taskInterception = TaskInterception(request: .mockAny(), isFirstParty: .random(), appStateListener: AppStateListener.mockAny())
let taskInterception = TaskInterception(request: .mockAny(), isFirstParty: .random())
taskInterception.register(spanContext: .mockWith(traceID: 1, spanID: 2))
XCTAssertNotNil(taskInterception.spanContext)

Expand All @@ -108,7 +108,7 @@ class URLSessionRUMResourcesHandlerTests: XCTestCase {
}

// Given
let taskInterception = TaskInterception(request: .mockAny(), isFirstParty: .random(), appStateListener: AppStateListener.mockAny())
let taskInterception = TaskInterception(request: .mockAny(), isFirstParty: .random())
let resourceMetrics: ResourceMetrics = .mockAny()
let resourceCompletion: ResourceCompletion = .mockWith(response: .mockResponseWith(statusCode: 200), error: nil)
taskInterception.register(metrics: resourceMetrics)
Expand Down Expand Up @@ -145,7 +145,7 @@ class URLSessionRUMResourcesHandlerTests: XCTestCase {
}

// Given
let taskInterception = TaskInterception(request: .mockAny(), isFirstParty: .random(), appStateListener: AppStateListener.mockAny())
let taskInterception = TaskInterception(request: .mockAny(), isFirstParty: .random())
let taskError = NSError(domain: "domain", code: 123, userInfo: [NSLocalizedDescriptionKey: "network error"])
let resourceMetrics: ResourceMetrics = .mockAny()
let resourceCompletion: ResourceCompletion = .mockWith(response: nil, error: taskError)
Expand Down
2 changes: 1 addition & 1 deletion Tests/DatadogTests/Datadog/RUMMonitorTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1136,7 +1136,7 @@ class RUMMonitorTests: XCTestCase {
XCTAssertTrue(Global.rum is DDNoopRUMMonitor)

// Then
resourcesHandler.notify_taskInterceptionCompleted(interception: TaskInterception(request: .mockAny(), isFirstParty: .mockAny(), appStateListener: AppStateListener.mockAny()))
resourcesHandler.notify_taskInterceptionCompleted(interception: TaskInterception(request: .mockAny(), isFirstParty: .mockAny()))
XCTAssertEqual(output.recordedLog?.status, .warn)
XCTAssertEqual(
output.recordedLog?.message,
Expand Down
2 changes: 1 addition & 1 deletion Tests/DatadogTests/Datadog/TracerTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1100,7 +1100,7 @@ class TracerTests: XCTestCase {
XCTAssertTrue(Global.sharedTracer is DDNoopTracer)

// Then
tracingHandler.notify_taskInterceptionCompleted(interception: TaskInterception(request: .mockAny(), isFirstParty: true, appStateListener: AppStateListener.mockAny()))
tracingHandler.notify_taskInterceptionCompleted(interception: TaskInterception(request: .mockAny(), isFirstParty: true))
XCTAssertEqual(output.recordedLog?.status, .warn)
XCTAssertEqual(
output.recordedLog?.message,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,17 @@
import XCTest
@testable import Datadog

private class MockAppStateListener: AppStateListening {
let history = AppStateHistory(
initialState: .init(isActive: true, date: .mockDecember15th2019At10AMUTC()),
finalDate: .mockDecember15th2019At10AMUTC() + 10
)
}

class URLSessionTracingHandlerTests: XCTestCase {
private let spanOutput = SpanOutputMock()
private let logOutput = LogOutputMock()
private let handler = URLSessionTracingHandler()
private let handler = URLSessionTracingHandler(appStateListener: MockAppStateListener())

override func setUp() {
Global.sharedTracer = Tracer.mockWith(
Expand All @@ -33,7 +40,7 @@ class URLSessionTracingHandlerTests: XCTestCase {
spanOutput.onSpanRecorded = { _ in spanSentExpectation.fulfill() }

// Given
let interception = TaskInterception(request: .mockAny(), isFirstParty: true, appStateListener: AppStateListener.mockAny())
let interception = TaskInterception(request: .mockAny(), isFirstParty: true)
interception.register(completion: .mockAny())
interception.register(
metrics: .mockWith(
Expand Down Expand Up @@ -70,7 +77,7 @@ class URLSessionTracingHandlerTests: XCTestCase {

// Given
let request: URLRequest = .mockWith(httpMethod: "POST")
let interception = TaskInterception(request: request, isFirstParty: true, appStateListener: AppStateListener.mockAny())
let interception = TaskInterception(request: request, isFirstParty: true)
interception.register(completion: .mockWith(response: .mockResponseWith(statusCode: 200), error: nil))
interception.register(
metrics: .mockWith(
Expand Down Expand Up @@ -108,7 +115,7 @@ class URLSessionTracingHandlerTests: XCTestCase {
// Given
let request: URLRequest = .mockWith(httpMethod: "GET")
let error = NSError(domain: "domain", code: 123, userInfo: [NSLocalizedDescriptionKey: "network error"])
let interception = TaskInterception(request: request, isFirstParty: true, appStateListener: AppStateListener.mockAny())
let interception = TaskInterception(request: request, isFirstParty: true)
interception.register(completion: .mockWith(response: nil, error: error))
interception.register(
metrics: .mockWith(
Expand Down Expand Up @@ -170,7 +177,7 @@ class URLSessionTracingHandlerTests: XCTestCase {

// Given
let request: URLRequest = .mockWith(httpMethod: "GET")
let interception = TaskInterception(request: request, isFirstParty: true, appStateListener: AppStateListener.mockAny())
let interception = TaskInterception(request: request, isFirstParty: true)
interception.register(completion: .mockWith(response: .mockResponseWith(statusCode: 404), error: nil))
interception.register(
metrics: .mockWith(
Expand Down Expand Up @@ -233,7 +240,7 @@ class URLSessionTracingHandlerTests: XCTestCase {
spanOutput.onSpanRecorded = { _ in spanNotSentExpectation.fulfill() }

// Given
let incompleteInterception = TaskInterception(request: .mockAny(), isFirstParty: true, appStateListener: AppStateListener.mockAny())
let incompleteInterception = TaskInterception(request: .mockAny(), isFirstParty: true)
// `incompleteInterception` has no metrics and no completion

// When
Expand All @@ -250,7 +257,7 @@ class URLSessionTracingHandlerTests: XCTestCase {
spanOutput.onSpanRecorded = { _ in spanNotSentExpectation.fulfill() }

// Given
let interception = TaskInterception(request: .mockAny(), isFirstParty: false, appStateListener: AppStateListener.mockAny())
let interception = TaskInterception(request: .mockAny(), isFirstParty: false)
interception.register(completion: .mockAny())
interception.register(
metrics: .mockWith(
Expand All @@ -271,19 +278,8 @@ class URLSessionTracingHandlerTests: XCTestCase {
}

func testGivenAnyInterception_itAddsAppStateInformationToSpan() throws {
class MockAppStateListener: AppStateListening {
let history = AppStateHistory(
initialState: .init(isActive: true, date: .mockDecember15th2019At10AMUTC()),
finalDate: .mockDecember15th2019At10AMUTC() + 10
)
}

// Given
let interception = TaskInterception(
request: .mockAny(),
isFirstParty: true,
appStateListener: MockAppStateListener()
)
let interception = TaskInterception(request: .mockAny(), isFirstParty: true)
interception.register(completion: .mockAny())
interception.register(
metrics: .mockWith(
Expand All @@ -299,7 +295,7 @@ class URLSessionTracingHandlerTests: XCTestCase {

// Then
let recordedSpan = try XCTUnwrap(spanOutput.recordedSpan)
XCTAssertEqual(recordedSpan.tags["foreground_duration"]?.encodable.value as? String, "10.0")
XCTAssertEqual(recordedSpan.tags["is_background"]?.encodable.value as? String, "false")
XCTAssertEqual(recordedSpan.tags["foreground_duration"]?.encodable.value as? UInt64, 10_000_000_000)
XCTAssertEqual(recordedSpan.tags["is_background"]?.encodable.value as? Bool, false)
}
}
Loading

0 comments on commit 3808aca

Please sign in to comment.