Skip to content

Commit

Permalink
Merge pull request #1685 from DataDog/ncreated/RUM-2925/report-non-fa…
Browse files Browse the repository at this point in the history
…tal-app-hangs-in-RUM

RUM-2925 feat: Report non-fatal App Hangs as RUM errors
  • Loading branch information
ncreated authored Feb 23, 2024
2 parents 76fc32d + 97f4286 commit c85902b
Show file tree
Hide file tree
Showing 12 changed files with 445 additions and 11 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] App Hangs are tracked as RUM errors. See [#1685][]
- [FIX] Propagate parent span in distributing tracing. See [#1627][]

# 2.7.1 / 12-02-2024
Expand Down Expand Up @@ -597,6 +598,7 @@ Release `2.0` introduces breaking changes. Follow the [Migration Guide](MIGRATIO
[#1597]: https://github.com/DataDog/dd-sdk-ios/pull/1597
[#1627]: https://github.com/DataDog/dd-sdk-ios/pull/1627
[#1644]: https://github.com/DataDog/dd-sdk-ios/pull/1644
[#1685]: https://github.com/DataDog/dd-sdk-ios/pull/1685
[#1656]: https://github.com/DataDog/dd-sdk-ios/pull/1656
[#1666]: https://github.com/DataDog/dd-sdk-ios/pull/1666
[@00fa9a]: https://github.com/00FA9A
Expand Down
40 changes: 40 additions & 0 deletions Datadog/Datadog.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,14 @@
615CC4132695957C0005F08C /* CrashReportTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 615CC4122695957C0005F08C /* CrashReportTests.swift */; };
6167C79326665D6900D4CF07 /* E2EUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6167C79226665D6900D4CF07 /* E2EUtils.swift */; };
6167C7952666622800D4CF07 /* LoggingE2EHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6167C7942666622800D4CF07 /* LoggingE2EHelpers.swift */; };
6167E6D32B7F8B3300C3CA2D /* AppHangsObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6167E6D22B7F8B3300C3CA2D /* AppHangsObserver.swift */; };
6167E6D42B7F8B3300C3CA2D /* AppHangsObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6167E6D22B7F8B3300C3CA2D /* AppHangsObserver.swift */; };
6167E6D62B7F8C3400C3CA2D /* AppHangsWatchdogThread.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6167E6D52B7F8C3400C3CA2D /* AppHangsWatchdogThread.swift */; };
6167E6D72B7F8C3400C3CA2D /* AppHangsWatchdogThread.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6167E6D52B7F8C3400C3CA2D /* AppHangsWatchdogThread.swift */; };
6167E6DA2B8004A500C3CA2D /* AppHangsWatchdogThreadTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6167E6D92B8004A500C3CA2D /* AppHangsWatchdogThreadTests.swift */; };
6167E6DB2B8004A500C3CA2D /* AppHangsWatchdogThreadTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6167E6D92B8004A500C3CA2D /* AppHangsWatchdogThreadTests.swift */; };
6167E6DD2B811A8300C3CA2D /* AppHangsMonitoringTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6167E6DC2B811A8300C3CA2D /* AppHangsMonitoringTests.swift */; };
6167E6DE2B811A8300C3CA2D /* AppHangsMonitoringTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6167E6DC2B811A8300C3CA2D /* AppHangsMonitoringTests.swift */; };
616B668E259CC28E00968EE8 /* DDRUMMonitorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 616B668D259CC28E00968EE8 /* DDRUMMonitorTests.swift */; };
6170DC1C25C18729003AED5C /* PLCrashReporterPlugin.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6170DC1B25C18729003AED5C /* PLCrashReporterPlugin.swift */; };
6172472725D673D7007085B3 /* CrashContextTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6172472625D673D7007085B3 /* CrashContextTests.swift */; };
Expand Down Expand Up @@ -2144,6 +2152,10 @@
6167ACBD251A0B410012B4D0 /* Example-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Example-Bridging-Header.h"; sourceTree = "<group>"; };
6167C79226665D6900D4CF07 /* E2EUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = E2EUtils.swift; sourceTree = "<group>"; };
6167C7942666622800D4CF07 /* LoggingE2EHelpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoggingE2EHelpers.swift; sourceTree = "<group>"; };
6167E6D22B7F8B3300C3CA2D /* AppHangsObserver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppHangsObserver.swift; sourceTree = "<group>"; };
6167E6D52B7F8C3400C3CA2D /* AppHangsWatchdogThread.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppHangsWatchdogThread.swift; sourceTree = "<group>"; };
6167E6D92B8004A500C3CA2D /* AppHangsWatchdogThreadTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppHangsWatchdogThreadTests.swift; sourceTree = "<group>"; };
6167E6DC2B811A8300C3CA2D /* AppHangsMonitoringTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppHangsMonitoringTests.swift; sourceTree = "<group>"; };
616B668D259CC28E00968EE8 /* DDRUMMonitorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DDRUMMonitorTests.swift; sourceTree = "<group>"; };
616C0A9D28573DFF00C13264 /* RUMOperatingSystemInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RUMOperatingSystemInfo.swift; sourceTree = "<group>"; };
616C0AA028573F6300C13264 /* RUMOperatingSystemInfoTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RUMOperatingSystemInfoTests.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -4280,6 +4292,23 @@
path = CrashContext;
sourceTree = "<group>";
};
6167E6D12B7F8B1300C3CA2D /* AppHangs */ = {
isa = PBXGroup;
children = (
6167E6D22B7F8B3300C3CA2D /* AppHangsObserver.swift */,
6167E6D52B7F8C3400C3CA2D /* AppHangsWatchdogThread.swift */,
);
path = AppHangs;
sourceTree = "<group>";
};
6167E6D82B80047900C3CA2D /* AppHangs */ = {
isa = PBXGroup;
children = (
6167E6D92B8004A500C3CA2D /* AppHangsWatchdogThreadTests.swift */,
);
path = AppHangs;
sourceTree = "<group>";
};
616CCE11250A181C009FED46 /* Instrumentation */ = {
isa = PBXGroup;
children = (
Expand All @@ -4289,6 +4318,7 @@
6141014D251A578D00E3C2D9 /* Actions */,
6157FA5C252767B3009A8A3B /* Resources */,
9E06058F26EF904200F5F935 /* LongTasks */,
6167E6D12B7F8B1300C3CA2D /* AppHangs */,
);
path = Instrumentation;
sourceTree = "<group>";
Expand Down Expand Up @@ -4791,6 +4821,7 @@
isa = PBXGroup;
children = (
61E8C5072B28898800E709B4 /* StartingRUMSessionTests.swift */,
6167E6DC2B811A8300C3CA2D /* AppHangsMonitoringTests.swift */,
);
path = RUM;
sourceTree = "<group>";
Expand Down Expand Up @@ -4873,6 +4904,7 @@
61F3CDA925121FA100C816E5 /* Views */,
6141014C251A577D00E3C2D9 /* Actions */,
613F23EF252B1287006CD2D7 /* Resources */,
6167E6D82B80047900C3CA2D /* AppHangs */,
61C713BB2A3C95AD00FA735A /* RUMInstrumentationTests.swift */,
);
path = Instrumentation;
Expand Down Expand Up @@ -7333,6 +7365,7 @@
614798962A459AA80095CB02 /* DDTraceTests.swift in Sources */,
D25085102976E30000E931C3 /* DatadogRemoteFeatureMock.swift in Sources */,
A7C816AB2A98CEBA00BF097B /* UIKitBackgroundTaskCoordinatorTests.swift in Sources */,
6167E6DD2B811A8300C3CA2D /* AppHangsMonitoringTests.swift in Sources */,
612C13D02AA772FA0086B5D1 /* SRRequestMatcher.swift in Sources */,
61A1A44929643254007909E7 /* DatadogCoreProxy.swift in Sources */,
D2A1EE3B287EECC000D28DFB /* CarrierInfoPublisherTests.swift in Sources */,
Expand Down Expand Up @@ -7908,6 +7941,7 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
6167E6D42B7F8B3300C3CA2D /* AppHangsObserver.swift in Sources */,
D23F8E5229DDCD28001CFAE8 /* UIViewControllerHandler.swift in Sources */,
D23F8E5329DDCD28001CFAE8 /* RUMCommand.swift in Sources */,
D23F8E5429DDCD28001CFAE8 /* ValuePublisher.swift in Sources */,
Expand Down Expand Up @@ -7945,6 +7979,7 @@
D23F8E7329DDCD28001CFAE8 /* SwiftUIActionModifier.swift in Sources */,
D23F8E7429DDCD28001CFAE8 /* RUMCommandSubscriber.swift in Sources */,
D23F8E7529DDCD28001CFAE8 /* RUMUserActionScope.swift in Sources */,
6167E6D72B7F8C3400C3CA2D /* AppHangsWatchdogThread.swift in Sources */,
61C713A42A3B78F900FA735A /* RUMMonitorProtocol.swift in Sources */,
3C0D5DED2A54405A00446CF9 /* RUMViewEventsFilter.swift in Sources */,
D23F8E7629DDCD28001CFAE8 /* RUMConnectivityInfoProvider.swift in Sources */,
Expand Down Expand Up @@ -8014,6 +8049,7 @@
D23F8EC029DDCD38001CFAE8 /* RUMEventSanitizerTests.swift in Sources */,
6176C1732ABDBA2E00131A70 /* MonitorTests.swift in Sources */,
D23F8EC129DDCD38001CFAE8 /* RUMEventsMapperTests.swift in Sources */,
6167E6DB2B8004A500C3CA2D /* AppHangsWatchdogThreadTests.swift in Sources */,
3C0D5DEA2A543EA300446CF9 /* RUMViewEventsFilterTests.swift in Sources */,
D23F8EC429DDCD38001CFAE8 /* RUMCommandTests.swift in Sources */,
);
Expand Down Expand Up @@ -8168,6 +8204,7 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
6167E6D32B7F8B3300C3CA2D /* AppHangsObserver.swift in Sources */,
D29A9F8029DD85BB005C54A4 /* UIViewControllerHandler.swift in Sources */,
D29A9F5929DD85BB005C54A4 /* RUMCommand.swift in Sources */,
D29A9F8C29DD861C005C54A4 /* ValuePublisher.swift in Sources */,
Expand Down Expand Up @@ -8205,6 +8242,7 @@
D29A9F8729DD85BB005C54A4 /* SwiftUIActionModifier.swift in Sources */,
D29A9F5D29DD85BB005C54A4 /* RUMCommandSubscriber.swift in Sources */,
D29A9F6529DD85BB005C54A4 /* RUMUserActionScope.swift in Sources */,
6167E6D62B7F8C3400C3CA2D /* AppHangsWatchdogThread.swift in Sources */,
61C713A32A3B78F900FA735A /* RUMMonitorProtocol.swift in Sources */,
3C0D5DEC2A54405A00446CF9 /* RUMViewEventsFilter.swift in Sources */,
D29A9F5829DD85BB005C54A4 /* RUMConnectivityInfoProvider.swift in Sources */,
Expand Down Expand Up @@ -8274,6 +8312,7 @@
D29A9FA229DDB483005C54A4 /* RUMEventSanitizerTests.swift in Sources */,
6176C1722ABDBA2E00131A70 /* MonitorTests.swift in Sources */,
D29A9FB929DDB483005C54A4 /* RUMEventsMapperTests.swift in Sources */,
6167E6DA2B8004A500C3CA2D /* AppHangsWatchdogThreadTests.swift in Sources */,
3C0D5DE92A543EA200446CF9 /* RUMViewEventsFilterTests.swift in Sources */,
D29A9FA729DDB483005C54A4 /* RUMCommandTests.swift in Sources */,
);
Expand Down Expand Up @@ -8454,6 +8493,7 @@
D2CB6F0027C520D400A62B57 /* RUMSessionMatcher.swift in Sources */,
A728ADB12934EB0C00397996 /* DDW3CHTTPHeadersWriter+apiTests.m in Sources */,
D2CB6F0127C520D400A62B57 /* DatadogPrivateMocks.swift in Sources */,
6167E6DE2B811A8300C3CA2D /* AppHangsMonitoringTests.swift in Sources */,
D26C49B02886DC7B00802B2D /* ApplicationStatePublisherTests.swift in Sources */,
D24C9C7229A7D57A002057CF /* DirectoriesMock.swift in Sources */,
61DA8CB3286215DE0074A606 /* CryptographyTests.swift in Sources */,
Expand Down
50 changes: 50 additions & 0 deletions Datadog/IntegrationUnitTests/RUM/AppHangsMonitoringTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/*
* Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0.
* This product includes software developed at Datadog (https://www.datadoghq.com/).
* Copyright 2019-Present Datadog, Inc.
*/

import XCTest
import DatadogInternal
@testable import DatadogRUM

/// Test case covering scenarios of App Hangs monitoring in RUM.
class AppHangsMonitoringTests: XCTestCase {
private var core: DatadogCoreProxy! // swiftlint:disable:this implicitly_unwrapped_optional
private var rumConfig = RUM.Configuration(applicationID: .mockAny())

override func setUp() {
core = DatadogCoreProxy()
}

override func tearDown() {
core.flushAndTearDown()
core = nil
}

func testWhenMainThreadHangsAboveThreshold_itTracksAppHang() throws {
let mainQueue = DispatchQueue(label: "main-queue", qos: .userInteractive)
rumConfig.mainQueue = mainQueue

// Given
RUM.enable(with: rumConfig, in: core)

// When
let beforeHang = Date()
mainQueue.sync {
Thread.sleep(forTimeInterval: self.rumConfig.defaultAppHangThreshold * 1.5)
}

// Then
Thread.sleep(forTimeInterval: 0.5) // wait to make sure watchdog thread completes hang tracking
RUMMonitor.shared(in: core).dd.flush() // flush RUM monitor to await hang processing

let errors = core.waitAndReturnEvents(ofFeature: RUMFeature.name, ofType: RUMErrorEvent.self)
let appHangError = try XCTUnwrap(errors.first)

XCTAssertEqual(appHangError.error.message, "App Hang")
XCTAssertEqual(appHangError.error.type, "AppHang")
XCTAssertEqual(appHangError.error.source, .source)
XCTAssertGreaterThanOrEqual(appHangError.date, beforeHang.timeIntervalSince1970.toInt64Milliseconds)
}
}
5 changes: 4 additions & 1 deletion DatadogRUM/Sources/Feature/RUMFeature.swift
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,10 @@ internal final class RUMFeature: DatadogRemoteFeature {
uiKitRUMViewsPredicate: configuration.uiKitViewsPredicate,
uiKitRUMActionsPredicate: configuration.uiKitActionsPredicate,
longTaskThreshold: configuration.longTaskThreshold,
dateProvider: configuration.dateProvider
appHangThreshold: configuration.defaultAppHangThreshold,
mainQueue: configuration.mainQueue,
dateProvider: configuration.dateProvider,
telemetry: core.telemetry
)
self.requestBuilder = RequestBuilder(
customIntakeURL: configuration.customEndpoint,
Expand Down
59 changes: 59 additions & 0 deletions DatadogRUM/Sources/Instrumentation/AppHangs/AppHangsObserver.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/*
* Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0.
* This product includes software developed at Datadog (https://www.datadoghq.com/).
* Copyright 2019-Present Datadog, Inc.
*/

import Foundation
import DatadogInternal

internal class AppHangsObserver: RUMCommandPublisher {
/// Watchdog thread that monitors the main queue for App Hangs.
private let watchdogThread: AppHangsWatchdogThread
/// Weak reference to RUM monitor for sending App Hang events.
private(set) weak var subscriber: RUMCommandSubscriber?

init(
appHangThreshold: TimeInterval,
observedQueue: DispatchQueue,
dateProvider: DateProvider,
telemetry: Telemetry
) {
watchdogThread = AppHangsWatchdogThread(
appHangThreshold: appHangThreshold,
queue: observedQueue,
dateProvider: dateProvider,
telemetry: telemetry
)
watchdogThread.onHangEnded = { [weak self] appHang in
// called on watchdog thread
self?.report(appHang: appHang)
}
}

func start() {
watchdogThread.start()
}

func stop() {
watchdogThread.cancel()
}

func publish(to subscriber: RUMCommandSubscriber) {
self.subscriber = subscriber
}

private func report(appHang: AppHang) {
let addHangCommand = RUMAddCurrentViewErrorCommand(
time: appHang.date,
message: "App Hang",
type: "AppHang",
stack: nil, // TODO: RUM-2925 Add hang stack trace
source: .source,
attributes: [
"hang_duration": appHang.duration
]
)
subscriber?.process(command: addHangCommand)
}
}
Loading

0 comments on commit c85902b

Please sign in to comment.