Skip to content

Commit

Permalink
Run JS monitors on bridge for iOS
Browse files Browse the repository at this point in the history
  • Loading branch information
louiszawadzki committed Sep 14, 2023
1 parent 96f4b40 commit a9dba5e
Show file tree
Hide file tree
Showing 3 changed files with 47 additions and 21 deletions.
4 changes: 3 additions & 1 deletion packages/core/ios/Sources/DdSdk.mm
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,11 @@
#import <DatadogSDKReactNative/DatadogSDKReactNative-Swift.h>
#endif
#import "DdSdk.h"
#import <React/RCTBridge+Private.h>

@implementation DdSdk

@synthesize bridge = _bridge;
RCT_EXPORT_MODULE()

RCT_REMAP_METHOD(initialize, withConfiguration:(NSDictionary*)configuration
Expand Down Expand Up @@ -79,7 +81,7 @@ @implementation DdSdk
- (DdSdkImplementation*)ddSdkImplementation
{
if (_ddSdkImplementation == nil) {
_ddSdkImplementation = [[DdSdkImplementation alloc] init];
_ddSdkImplementation = [[DdSdkImplementation alloc] initWithBridge:_bridge];
}
return _ddSdkImplementation;
}
Expand Down
17 changes: 11 additions & 6 deletions packages/core/ios/Sources/DdSdkImplementation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,24 +17,29 @@ func getDefaultAppVersion() -> String {

@objc
public class DdSdkImplementation: NSObject {
@objc var bridge: RCTBridge!

let jsDispatchQueue: DispatchQueueType
let jsRefreshRateMonitor: RefreshRateMonitor
let mainDispatchQueue: DispatchQueueType

private let jsLongTaskThresholdInSeconds: TimeInterval = 0.1;

@objc
public convenience override init() {
self.init(mainDispatchQueue: DispatchQueue.main, jsRefreshRateMonitor: JSRefreshRateMonitor.init())
public convenience init(bridge: RCTBridge) {
self.init(mainDispatchQueue: DispatchQueue.main, bridge: bridge, jsRefreshRateMonitor: JSRefreshRateMonitor.init())
}

init(mainDispatchQueue: DispatchQueueType, jsRefreshRateMonitor: RefreshRateMonitor) {
init(mainDispatchQueue: DispatchQueueType, bridge: DispatchQueueType, jsRefreshRateMonitor: RefreshRateMonitor) {
self.mainDispatchQueue = mainDispatchQueue
self.jsDispatchQueue = bridge
self.jsRefreshRateMonitor = jsRefreshRateMonitor
super.init()
}

// Used only for tests
internal override convenience init() {
self.init(mainDispatchQueue: DispatchQueue.main, bridge: DispatchQueue.main, jsRefreshRateMonitor: JSRefreshRateMonitor.init())
}

// Using @escaping RCTPromiseResolveBlock type will result in an issue when compiling the Swift header file.
@objc
public func initialize(configuration: NSDictionary, resolve:@escaping ((Any?) -> Void), reject:RCTPromiseRejectBlock) -> Void {
Expand Down Expand Up @@ -377,7 +382,7 @@ public class DdSdkImplementation: NSObject {
func startJSRefreshRateMonitoring(sdkConfiguration: DdSdkConfiguration) {
if let frameTimeCallback = buildFrameTimeCallback(sdkConfiguration: sdkConfiguration) {
// Falling back to mainDispatchQueue if bridge is nil is only useful for tests
self.jsRefreshRateMonitor.startMonitoring(jsQueue: bridge ?? mainDispatchQueue, frameTimeCallback: frameTimeCallback)
self.jsRefreshRateMonitor.startMonitoring(jsQueue: jsDispatchQueue ?? mainDispatchQueue, frameTimeCallback: frameTimeCallback)
}
}

Expand Down
47 changes: 33 additions & 14 deletions packages/core/ios/Tests/DdSdkTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,13 @@ final class DispatchQueueMock: DispatchQueueType {
func async(execute work: @escaping @convention(block) () -> Void) {
work()
}

func isSameQueue(queue: DispatchQueueType) -> Bool {
guard let queueAsMock = queue as? DispatchQueueMock else {
return false
}
return self === queueAsMock
}
}

internal class DdSdkTests: XCTestCase {
Expand All @@ -25,11 +32,11 @@ internal class DdSdkTests: XCTestCase {
var printedMessage = ""
consolePrint = { msg in printedMessage += msg }

DdSdkImplementation(mainDispatchQueue: DispatchQueueMock(), jsRefreshRateMonitor: JSRefreshRateMonitor()).initialize(configuration: .mockAny(), resolve: mockResolve, reject: mockReject)
DdSdkImplementation(mainDispatchQueue: DispatchQueueMock(), bridge: DispatchQueueMock(), jsRefreshRateMonitor: JSRefreshRateMonitor()).initialize(configuration: .mockAny(), resolve: mockResolve, reject: mockReject)

XCTAssertEqual(printedMessage, "")

DdSdkImplementation(mainDispatchQueue: DispatchQueueMock(), jsRefreshRateMonitor: JSRefreshRateMonitor()).initialize(configuration: .mockAny(), resolve: mockResolve, reject: mockReject)
DdSdkImplementation(mainDispatchQueue: DispatchQueueMock(), bridge: DispatchQueueMock(), jsRefreshRateMonitor: JSRefreshRateMonitor()).initialize(configuration: .mockAny(), resolve: mockResolve, reject: mockReject)

XCTAssertEqual(printedMessage, "Datadog SDK is already initialized, skipping initialization.")

Expand Down Expand Up @@ -87,7 +94,7 @@ internal class DdSdkTests: XCTestCase {
func testSDKInitializationWithVerbosityDebug() {
let validConfiguration: NSDictionary = .mockAny(additionalConfig: ["_dd.sdk_verbosity": "debug"])

DdSdkImplementation(mainDispatchQueue: DispatchQueueMock(), jsRefreshRateMonitor: JSRefreshRateMonitor()).initialize(configuration: validConfiguration, resolve: mockResolve, reject: mockReject)
DdSdkImplementation(mainDispatchQueue: DispatchQueueMock(), bridge: DispatchQueueMock(), jsRefreshRateMonitor: JSRefreshRateMonitor()).initialize(configuration: validConfiguration, resolve: mockResolve, reject: mockReject)

XCTAssertEqual(Datadog.verbosityLevel, LogLevel.debug)

Expand All @@ -97,7 +104,7 @@ internal class DdSdkTests: XCTestCase {
func testSDKInitializationWithVerbosityInfo() {
let validConfiguration: NSDictionary = .mockAny(additionalConfig: ["_dd.sdk_verbosity": "info"])

DdSdkImplementation(mainDispatchQueue: DispatchQueueMock(), jsRefreshRateMonitor: JSRefreshRateMonitor()).initialize(configuration: validConfiguration, resolve: mockResolve, reject: mockReject)
DdSdkImplementation(mainDispatchQueue: DispatchQueueMock(), bridge: DispatchQueueMock(), jsRefreshRateMonitor: JSRefreshRateMonitor()).initialize(configuration: validConfiguration, resolve: mockResolve, reject: mockReject)

XCTAssertEqual(Datadog.verbosityLevel, LogLevel.info)

Expand All @@ -107,7 +114,7 @@ internal class DdSdkTests: XCTestCase {
func testSDKInitializationWithVerbosityWarn() {
let validConfiguration: NSDictionary = .mockAny(additionalConfig: ["_dd.sdk_verbosity": "warn"])

DdSdkImplementation(mainDispatchQueue: DispatchQueueMock(), jsRefreshRateMonitor: JSRefreshRateMonitor()).initialize(configuration: validConfiguration, resolve: mockResolve, reject: mockReject)
DdSdkImplementation(mainDispatchQueue: DispatchQueueMock(), bridge: DispatchQueueMock(), jsRefreshRateMonitor: JSRefreshRateMonitor()).initialize(configuration: validConfiguration, resolve: mockResolve, reject: mockReject)

XCTAssertEqual(Datadog.verbosityLevel, LogLevel.warn)

Expand All @@ -117,7 +124,7 @@ internal class DdSdkTests: XCTestCase {
func testSDKInitializationWithVerbosityError() {
let validConfiguration: NSDictionary = .mockAny(additionalConfig: ["_dd.sdk_verbosity": "error"])

DdSdkImplementation(mainDispatchQueue: DispatchQueueMock(), jsRefreshRateMonitor: JSRefreshRateMonitor()).initialize(configuration: validConfiguration, resolve: mockResolve, reject: mockReject)
DdSdkImplementation(mainDispatchQueue: DispatchQueueMock(), bridge: DispatchQueueMock(), jsRefreshRateMonitor: JSRefreshRateMonitor()).initialize(configuration: validConfiguration, resolve: mockResolve, reject: mockReject)

XCTAssertEqual(Datadog.verbosityLevel, LogLevel.error)

Expand All @@ -127,7 +134,7 @@ internal class DdSdkTests: XCTestCase {
func testSDKInitializationWithVerbosityNil() {
let validConfiguration: NSDictionary = .mockAny(additionalConfig: nil)

DdSdkImplementation(mainDispatchQueue: DispatchQueueMock(), jsRefreshRateMonitor: JSRefreshRateMonitor()).initialize(configuration: validConfiguration, resolve: mockResolve, reject: mockReject)
DdSdkImplementation(mainDispatchQueue: DispatchQueueMock(), bridge: DispatchQueueMock(), jsRefreshRateMonitor: JSRefreshRateMonitor()).initialize(configuration: validConfiguration, resolve: mockResolve, reject: mockReject)

XCTAssertNil(Datadog.verbosityLevel)

Expand All @@ -137,7 +144,7 @@ internal class DdSdkTests: XCTestCase {
func testSDKInitializationWithVerbosityUnknown() {
let validConfiguration: NSDictionary = .mockAny(additionalConfig: ["_dd.sdk_verbosity": "foo"])

DdSdkImplementation(mainDispatchQueue: DispatchQueueMock(), jsRefreshRateMonitor: JSRefreshRateMonitor()).initialize(configuration: validConfiguration, resolve: mockResolve, reject: mockReject)
DdSdkImplementation(mainDispatchQueue: DispatchQueueMock(), bridge: DispatchQueueMock(), jsRefreshRateMonitor: JSRefreshRateMonitor()).initialize(configuration: validConfiguration, resolve: mockResolve, reject: mockReject)

XCTAssertNil(Datadog.verbosityLevel)

Expand Down Expand Up @@ -310,7 +317,7 @@ internal class DdSdkTests: XCTestCase {
}

func testSettingUserInfo() throws {
let bridge = DdSdkImplementation(mainDispatchQueue: DispatchQueueMock(), jsRefreshRateMonitor: JSRefreshRateMonitor())
let bridge = DdSdkImplementation(mainDispatchQueue: DispatchQueueMock(), bridge: DispatchQueueMock(), jsRefreshRateMonitor: JSRefreshRateMonitor())
bridge.initialize(configuration: .mockAny(), resolve: mockResolve, reject: mockReject)

bridge.setUser(
Expand Down Expand Up @@ -340,7 +347,7 @@ internal class DdSdkTests: XCTestCase {
}

func testSettingAttributes() {
let bridge = DdSdkImplementation(mainDispatchQueue: DispatchQueueMock(), jsRefreshRateMonitor: JSRefreshRateMonitor())
let bridge = DdSdkImplementation(mainDispatchQueue: DispatchQueueMock(), bridge: DispatchQueueMock(), jsRefreshRateMonitor: JSRefreshRateMonitor())
bridge.initialize(configuration: .mockAny(), resolve: mockResolve, reject: mockReject)

let rumMonitorMock = MockRUMMonitor()
Expand Down Expand Up @@ -590,7 +597,7 @@ internal class DdSdkTests: XCTestCase {
let mockRefreshRateMonitor = MockJSRefreshRateMonitor()
let rumMonitorMock = MockRUMMonitor()

DdSdkImplementation(mainDispatchQueue: DispatchQueueMock(), jsRefreshRateMonitor: mockRefreshRateMonitor).initialize(configuration: .mockAny(longTaskThresholdMs: 0.0), resolve: mockResolve, reject: mockReject)
DdSdkImplementation(mainDispatchQueue: DispatchQueueMock(), bridge: DispatchQueueMock(), jsRefreshRateMonitor: mockRefreshRateMonitor).initialize(configuration: .mockAny(longTaskThresholdMs: 0.0), resolve: mockResolve, reject: mockReject)
Global.rum = rumMonitorMock

XCTAssertTrue(mockRefreshRateMonitor.isStarted)
Expand All @@ -606,7 +613,7 @@ internal class DdSdkTests: XCTestCase {
let mockRefreshRateMonitor = MockJSRefreshRateMonitor()
let rumMonitorMock = MockRUMMonitor()

DdSdkImplementation(mainDispatchQueue: DispatchQueueMock(), jsRefreshRateMonitor: mockRefreshRateMonitor).initialize(configuration: .mockAny(longTaskThresholdMs: 0.0, vitalsUpdateFrequency: "never"), resolve: mockResolve, reject: mockReject)
DdSdkImplementation(mainDispatchQueue: DispatchQueueMock(), bridge: DispatchQueueMock(), jsRefreshRateMonitor: mockRefreshRateMonitor).initialize(configuration: .mockAny(longTaskThresholdMs: 0.0, vitalsUpdateFrequency: "never"), resolve: mockResolve, reject: mockReject)
Global.rum = rumMonitorMock

XCTAssertFalse(mockRefreshRateMonitor.isStarted)
Expand All @@ -622,7 +629,7 @@ internal class DdSdkTests: XCTestCase {
let mockRefreshRateMonitor = MockJSRefreshRateMonitor()
let rumMonitorMock = MockRUMMonitor()

DdSdkImplementation(mainDispatchQueue: DispatchQueueMock(), jsRefreshRateMonitor: mockRefreshRateMonitor).initialize(configuration: .mockAny(longTaskThresholdMs: 0.2, vitalsUpdateFrequency: "never"), resolve: mockResolve, reject: mockReject)
DdSdkImplementation(mainDispatchQueue: DispatchQueueMock(), bridge: DispatchQueueMock(), jsRefreshRateMonitor: mockRefreshRateMonitor).initialize(configuration: .mockAny(longTaskThresholdMs: 0.2, vitalsUpdateFrequency: "never"), resolve: mockResolve, reject: mockReject)
Global.rum = rumMonitorMock

XCTAssertTrue(mockRefreshRateMonitor.isStarted)
Expand All @@ -639,7 +646,7 @@ internal class DdSdkTests: XCTestCase {
let mockRefreshRateMonitor = MockJSRefreshRateMonitor()
let rumMonitorMock = MockRUMMonitor()

DdSdkImplementation(mainDispatchQueue: DispatchQueueMock(), jsRefreshRateMonitor: mockRefreshRateMonitor).initialize(configuration: .mockAny(longTaskThresholdMs: 200, vitalsUpdateFrequency: "average"), resolve: mockResolve, reject: mockReject)
DdSdkImplementation(mainDispatchQueue: DispatchQueueMock(), bridge: DispatchQueueMock(), jsRefreshRateMonitor: mockRefreshRateMonitor).initialize(configuration: .mockAny(longTaskThresholdMs: 200, vitalsUpdateFrequency: "average"), resolve: mockResolve, reject: mockReject)
Global.rum = rumMonitorMock

XCTAssertTrue(mockRefreshRateMonitor.isStarted)
Expand Down Expand Up @@ -682,6 +689,7 @@ internal class DdSdkTests: XCTestCase {
func testConfigurationTelemetryEventMapper() throws {
DdSdkImplementation(
mainDispatchQueue: DispatchQueueMock(),
bridge: DispatchQueueMock(),
jsRefreshRateMonitor: JSRefreshRateMonitor())
.initialize(
configuration: .mockAny(
Expand Down Expand Up @@ -784,6 +792,15 @@ internal class DdSdkTests: XCTestCase {
let mappedEvent = actionEventMapper(mockActionEvent)
XCTAssertNotNil(mappedEvent)
}

func testReactNativeThreadMonitorsRunOnBridge() throws {
let bridge = DispatchQueueMock()
let mockJSRefreshRateMonitor = MockJSRefreshRateMonitor()

DdSdkImplementation(mainDispatchQueue: DispatchQueueMock(), bridge: bridge, jsRefreshRateMonitor: mockJSRefreshRateMonitor).initialize(configuration: .mockAny(longTaskThresholdMs: 0.2), resolve: mockResolve, reject: mockReject)

XCTAssertTrue(bridge.isSameQueue(queue: mockJSRefreshRateMonitor.jsQueue!))
}
}

private class MockRUMMonitor: DDRUMMonitor, RUMCommandSubscriber {
Expand All @@ -810,11 +827,13 @@ private final class MockJSRefreshRateMonitor: RefreshRateMonitor {
private var refreshRateListener: RefreshRateListener?
private var frameTimeCallback: frame_time_callback?
var isStarted: Bool = false
private(set) var jsQueue: DispatchQueueType?

init() {}

public func startMonitoring(jsQueue: DispatchQueueType, frameTimeCallback: @escaping frame_time_callback) {
self.frameTimeCallback = frameTimeCallback
self.jsQueue = jsQueue
isStarted = true
}

Expand Down

0 comments on commit a9dba5e

Please sign in to comment.