From a3538999ec5ccb7acf2ac380070c0646bdb347cd Mon Sep 17 00:00:00 2001 From: Philipp Hofmann Date: Thu, 3 Oct 2024 13:46:57 +0200 Subject: [PATCH 01/36] fix: Linking ongoing trace to crash event (#4393) Fix linking a crash event to an ongoing trace by skipping setting the trace context in Scope.applyToEvent for crash events. Fixes GH-4375 --- CHANGELOG.md | 1 + Sources/Sentry/SentryScope.m | 9 ++++++++- Tests/SentryTests/SentryScopeSwiftTests.swift | 11 +++++++++++ 3 files changed, 20 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 74c4f42213b..0ed7cd68b49 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ ### Fixes - Fix the versioning to support app release with Beta versions (#4368) +- Linking ongoing trace to crash event (#4393) ## 8.37.0-beta.1 diff --git a/Sources/Sentry/SentryScope.m b/Sources/Sentry/SentryScope.m index dee7df38cec..21fb2e873bf 100644 --- a/Sources/Sentry/SentryScope.m +++ b/Sources/Sentry/SentryScope.m @@ -2,7 +2,7 @@ #import "SentryAttachment+Private.h" #import "SentryBreadcrumb.h" #import "SentryEnvelopeItemType.h" -#import "SentryEvent.h" +#import "SentryEvent+Private.h" #import "SentryGlobalEventProcessor.h" #import "SentryLevelMapper.h" #import "SentryLog.h" @@ -564,6 +564,13 @@ - (SentryEvent *__nullable)applyToEvent:(SentryEvent *)event [SentryDictionary mergeEntriesFromDictionary:event.context intoDictionary:newContext]; } + // Don't add the trace context of a current trace to a crash event because crash events are from + // a previous run. + if (event.isCrashEvent) { + event.context = newContext; + return event; + } + if (self.span != nil) { id span; @synchronized(_spanLock) { diff --git a/Tests/SentryTests/SentryScopeSwiftTests.swift b/Tests/SentryTests/SentryScopeSwiftTests.swift index 9031bd6eb3f..f6ec28907be 100644 --- a/Tests/SentryTests/SentryScopeSwiftTests.swift +++ b/Tests/SentryTests/SentryScopeSwiftTests.swift @@ -225,6 +225,17 @@ class SentryScopeSwiftTests: XCTestCase { XCTAssertEqual(trace?["span_id"] as? String, fixture.transaction.spanId.sentrySpanIdString) } + func testApplyToEvent_ScopeWithSpan_NotAppliedToCrashEvent() { + let scope = fixture.scope + scope.span = fixture.transaction + let event = fixture.event + event.isCrashEvent = true + + let actual = scope.applyTo(event: event, maxBreadcrumbs: 10) + XCTAssertNil(fixture.event.context?["trace"]) + XCTAssertNil(actual?.transaction) + } + func testApplyToEvent_EventWithDist() { let event = fixture.event event.dist = "myDist" From 46970f1c66a297cf2a2acd3ae7e3f557a02a148f Mon Sep 17 00:00:00 2001 From: Dhiogo Brustolin Date: Thu, 3 Oct 2024 15:49:39 +0200 Subject: [PATCH 02/36] chore: Bumping Xcode and SauceLab CLI for benchmarking (#4397) Our benchmarking workflows still uses Xcode 14 that does not work with mergeable-libraries. --- .github/workflows/benchmarking.yml | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/.github/workflows/benchmarking.yml b/.github/workflows/benchmarking.yml index 7290089557c..7398a8e833c 100644 --- a/.github/workflows/benchmarking.yml +++ b/.github/workflows/benchmarking.yml @@ -28,7 +28,7 @@ jobs: runs-on: macos-13 steps: - uses: actions/checkout@v4 - - run: ./scripts/ci-select-xcode.sh + - run: ./scripts/ci-select-xcode.sh 15.2 - uses: ruby/setup-ruby@v1 with: bundler-cache: true @@ -91,7 +91,7 @@ jobs: - uses: actions/download-artifact@v4 with: name: DerivedData-Xcode - - run: npm install -g saucectl@0.173.2 + - run: npm install -g saucectl@0.186.0 - name: Run Benchmarks in SauceLab env: SAUCE_USERNAME: ${{ secrets.SAUCE_USERNAME }} @@ -100,11 +100,11 @@ jobs: app-metrics: name: Collect app metrics - runs-on: macos-13 + runs-on: macos-13-xlarge steps: - name: Git checkout uses: actions/checkout@v4 - - run: ./scripts/ci-select-xcode.sh + - run: ./scripts/ci-select-xcode.sh 15.2 - uses: ruby/setup-ruby@v1 with: bundler-cache: true @@ -126,6 +126,15 @@ jobs: MATCH_USERNAME: ${{ secrets.MATCH_USERNAME }} - name: Build Framework run: make build-xcframework + + - name: Archive build log if failed + uses: actions/upload-artifact@v4 + if: ${{ failure() || cancelled() }} + with: + name: raw-build-output-build-xcframework + path: | + build-xcframework.log + - name: Build test app with sentry run: bundle exec fastlane build_perf_test_app_sentry env: From c7aec2a3de072601d9125ccfe3a6f25b0e3ffe9d Mon Sep 17 00:00:00 2001 From: Philipp Hofmann Date: Thu, 3 Oct 2024 15:52:15 +0200 Subject: [PATCH 03/36] ref: Remove upload dsyms xcode build script (#4398) We remove this script because there is a risk of accidentially committing tokens to GH, and we're not using this script anymore. Fixes GH-4328 --- .../upload-dsyms-with-xcode-build-phase.patch | 40 ------------------- .../upload-dsyms-with-xcode-build-phase.sh | 14 ------- 2 files changed, 54 deletions(-) delete mode 100644 scripts/upload-dsyms-with-xcode-build-phase.patch delete mode 100755 scripts/upload-dsyms-with-xcode-build-phase.sh diff --git a/scripts/upload-dsyms-with-xcode-build-phase.patch b/scripts/upload-dsyms-with-xcode-build-phase.patch deleted file mode 100644 index d1626032d69..00000000000 --- a/scripts/upload-dsyms-with-xcode-build-phase.patch +++ /dev/null @@ -1,40 +0,0 @@ -diff --git a/Samples/iOS-Swift/iOS-Swift.xcodeproj/project.pbxproj b/Samples/iOS-Swift/iOS-Swift.xcodeproj/project.pbxproj -index 9adac264..8ef3a3bb 100644 ---- a/Samples/iOS-Swift/iOS-Swift.xcodeproj/project.pbxproj -+++ b/Samples/iOS-Swift/iOS-Swift.xcodeproj/project.pbxproj -@@ -561,6 +561,7 @@ - 637AFDA4243B02760034958B /* Resources */, - 630853552440C60F00DDE4CE /* Embed Frameworks */, - D840D535273A07F600CDF142 /* Embed App Clips */, -+ 62F226AA29A35FAE0038080D /* ShellScript */, - ); - buildRules = ( - ); -@@ -821,6 +822,27 @@ - }; - /* End PBXResourcesBuildPhase section */ - -+/* Begin PBXShellScriptBuildPhase section */ -+ 62F226AA29A35FAE0038080D /* ShellScript */ = { -+ isa = PBXShellScriptBuildPhase; -+ buildActionMask = 2147483647; -+ files = ( -+ ); -+ inputFileListPaths = ( -+ ); -+ inputPaths = ( -+ "${DWARF_DSYM_FOLDER_PATH}/${DWARF_DSYM_FILE_NAME}/Contents/Resources/DWARF/${TARGET_NAME}", -+ ); -+ outputFileListPaths = ( -+ ); -+ outputPaths = ( -+ ); -+ runOnlyForDeploymentPostprocessing = 0; -+ shellPath = /bin/sh; -+ shellScript = "if which sentry-cli >/dev/null; then\nexport SENTRY_ORG=sentry-sdks\nexport SENTRY_PROJECT=sentry-cocoa\nexport SENTRY_AUTH_TOKEN=YOUR_AUTH_TOKEN\nERROR=$(sentry-cli upload-dif \"$DWARF_DSYM_FOLDER_PATH\" 2>&1 >/dev/null)\nif [ ! $? -eq 0 ]; then\necho \"warning: sentry-cli - $ERROR\"\nfi\nelse\necho \"warning: sentry-cli not installed, download from https://github.com/getsentry/sentry-cli/releases\"\nfi\n"; -+ }; -+/* End PBXShellScriptBuildPhase section */ -+ - /* Begin PBXSourcesBuildPhase section */ - 637AFDA2243B02760034958B /* Sources */ = { - isa = PBXSourcesBuildPhase; diff --git a/scripts/upload-dsyms-with-xcode-build-phase.sh b/scripts/upload-dsyms-with-xcode-build-phase.sh deleted file mode 100755 index ed33ba6fa21..00000000000 --- a/scripts/upload-dsyms-with-xcode-build-phase.sh +++ /dev/null @@ -1,14 +0,0 @@ -#!/bin/bash -set -euxo pipefail - -# Use this script to apply a patch so Xcode uploads the iOS-Swift's dSYMs to -# Sentry during Xcode's build phase. -# Ensure to not commit the patch file after running this script, which then contains -# your auth token. - -SENTRY_AUTH_TOKEN="${1}" - -REPLACE="s/YOUR_AUTH_TOKEN/${SENTRY_AUTH_TOKEN}/g" -sed -i '' "$REPLACE" ./scripts/upload-dsyms-with-xcode-build-phase.patch - -git apply ./scripts/upload-dsyms-with-xcode-build-phase.patch From a930c80f5310438517f9139b19ef531b72349e17 Mon Sep 17 00:00:00 2001 From: Dhiogo Brustolin Date: Fri, 4 Oct 2024 10:00:41 +0200 Subject: [PATCH 04/36] feat: Added support for arm64e (#3398) A developer may choose the arm64e architecture for their iOS app to benefit from enhanced security features. Sentry was not compiling for this architecture due to issues with accessing machine context properties --- CHANGELOG.md | 1 + .../iOS-Swift.xcodeproj/project.pbxproj | 42 +++++++++++++++++-- .../iOS-Swift/iOS13-Swift/AppDelegate.swift | 13 ++++-- .../iOS13-SwiftTests/LaunchUITest.swift | 5 +++ Sentry.xcodeproj/project.pbxproj | 7 ---- Sources/Configuration/Sentry.xcconfig | 2 + Sources/Configuration/SentrySwiftUI.xcconfig | 2 + Sources/Sentry/SentryBacktrace.cpp | 4 +- Sources/Sentry/SentryProfiler.mm | 4 +- Sources/Sentry/SentrySamplingProfiler.cpp | 8 ++-- Sources/Sentry/include/SentryBacktrace.hpp | 4 +- .../Sentry/include/SentrySamplingProfiler.hpp | 4 +- Sources/Sentry/include/SentryThreadState.hpp | 6 +-- .../Recording/Tools/SentryCrashCPU_arm64.c | 16 +++---- .../SentryBacktraceTests.mm | 4 +- 15 files changed, 83 insertions(+), 39 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0ed7cd68b49..91da52f495e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ - Added breadcrumb.origin private field (#4358) - Custom redact modifier for SwiftUI (#4362) +- Add support for arm64e (#3398) - Add mergeable libraries support to dynamic libraries (#4381) ### Improvements diff --git a/Samples/iOS-Swift/iOS-Swift.xcodeproj/project.pbxproj b/Samples/iOS-Swift/iOS-Swift.xcodeproj/project.pbxproj index 6b3bc884795..531e1489808 100644 --- a/Samples/iOS-Swift/iOS-Swift.xcodeproj/project.pbxproj +++ b/Samples/iOS-Swift/iOS-Swift.xcodeproj/project.pbxproj @@ -101,6 +101,7 @@ D8AE48CF2C57E0BD0092A2A6 /* WebViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8AE48C82C57DC2F0092A2A6 /* WebViewController.swift */; }; D8B56CF0273A8D97004DF238 /* Sentry.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 630853322440C44F00DDE4CE /* Sentry.framework */; }; D8B56CF1273A8D97004DF238 /* Sentry.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 630853322440C44F00DDE4CE /* Sentry.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + D8BC843E2B021C5200A662B7 /* SwiftUIView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B4F33F7271EBD2500C8591E /* SwiftUIView.swift */; }; D8C33E1F29FBB1F70071B75A /* UIEventBreadcrumbsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8C33E1E29FBB1F70071B75A /* UIEventBreadcrumbsController.swift */; }; D8C33E2629FBB8D90071B75A /* UIEventBreadcrumbTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8C33E2529FBB8D90071B75A /* UIEventBreadcrumbTests.swift */; }; D8D7BB4A2750067900044146 /* UIAssert.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8D7BB492750067900044146 /* UIAssert.swift */; }; @@ -1146,6 +1147,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + D8BC843E2B021C5200A662B7 /* SwiftUIView.swift in Sources */, D8832B1A2AF5000F00C522B0 /* TopViewControllerInspector.swift in Sources */, D8AE48CF2C57E0BD0092A2A6 /* WebViewController.swift in Sources */, 84BA71F22C8F73FD0045B828 /* DSNDisplayViewController.swift in Sources */, @@ -1417,7 +1419,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 8.37.0-beta.1; + MARKETING_VERSION = "8.37.0-beta.1"; PRODUCT_BUNDLE_IDENTIFIER = "io.sentry.sample.iOS-Swift"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = "match Development io.sentry.sample.iOS-Swift"; @@ -1446,7 +1448,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 8.37.0-beta.1; + MARKETING_VERSION = "8.37.0-beta.1"; PRODUCT_BUNDLE_IDENTIFIER = "io.sentry.sample.iOS-Swift"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = "match AppStore io.sentry.sample.iOS-Swift"; @@ -1730,11 +1732,16 @@ 84D4FE8528ECD1EA00EDAAFE /* Test */ = { isa = XCBuildConfiguration; buildSettings = { + ARCHS = ( + "$(ARCHS_STANDARD)", + arm64e, + ); ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_ENTITLEMENTS = "iOS13-Swift/iOS13-Swift.entitlements"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = 97JCY7859U; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = "iOS13-Swift/Info.plist"; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; @@ -1762,8 +1769,10 @@ 84D4FE8628ECD1EA00EDAAFE /* Test */ = { isa = XCBuildConfiguration; buildSettings = { + CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = 97JCY7859U; GENERATE_INFOPLIST_FILE = YES; IPHONEOS_DEPLOYMENT_TARGET = 13.0; LD_RUNPATH_SEARCH_PATHS = ( @@ -1774,6 +1783,7 @@ MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = "io.sentry.Sentry.iOS13-SwiftTests"; PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_EMIT_LOC_STRINGS = NO; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; @@ -1955,11 +1965,16 @@ 84D4FE8D28ECD1ED00EDAAFE /* TestCI */ = { isa = XCBuildConfiguration; buildSettings = { + ARCHS = ( + "$(ARCHS_STANDARD)", + arm64e, + ); ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_ENTITLEMENTS = "iOS13-Swift/iOS13-Swift.entitlements"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = 97JCY7859U; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = "iOS13-Swift/Info.plist"; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; @@ -1987,8 +2002,10 @@ 84D4FE8E28ECD1ED00EDAAFE /* TestCI */ = { isa = XCBuildConfiguration; buildSettings = { + CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = 97JCY7859U; GENERATE_INFOPLIST_FILE = YES; IPHONEOS_DEPLOYMENT_TARGET = 13.0; LD_RUNPATH_SEARCH_PATHS = ( @@ -1999,6 +2016,7 @@ MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = "io.sentry.Sentry.iOS13-SwiftTests"; PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_EMIT_LOC_STRINGS = NO; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; @@ -2009,11 +2027,16 @@ D8269A4A274C096000BD5BD5 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { + ARCHS = ( + "$(ARCHS_STANDARD)", + arm64e, + ); ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_ENTITLEMENTS = "iOS13-Swift/iOS13-Swift.entitlements"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = 97JCY7859U; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = "iOS13-Swift/Info.plist"; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; @@ -2041,11 +2064,16 @@ D8269A4B274C096000BD5BD5 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { + ARCHS = ( + "$(ARCHS_STANDARD)", + arm64e, + ); ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_ENTITLEMENTS = "iOS13-Swift/iOS13-Swift.entitlements"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = 97JCY7859U; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = "iOS13-Swift/Info.plist"; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; @@ -2095,7 +2123,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 8.37.0-beta.1; + MARKETING_VERSION = "8.37.0-beta.1"; PRODUCT_BUNDLE_IDENTIFIER = "io.sentry.sample.iOS-Swift.Clip"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = "match Development io.sentry.sample.iOS-Swift.Clip"; @@ -2130,7 +2158,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 8.37.0-beta.1; + MARKETING_VERSION = "8.37.0-beta.1"; PRODUCT_BUNDLE_IDENTIFIER = "io.sentry.sample.iOS-Swift.Clip"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = "match AppStore io.sentry.sample.iOS-Swift.Clip"; @@ -2143,8 +2171,10 @@ D85DAA52274C244F004DF43C /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { + CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = 97JCY7859U; GENERATE_INFOPLIST_FILE = YES; IPHONEOS_DEPLOYMENT_TARGET = 13.0; LD_RUNPATH_SEARCH_PATHS = ( @@ -2155,6 +2185,7 @@ MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = "io.sentry.Sentry.iOS13-SwiftTests"; PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_EMIT_LOC_STRINGS = NO; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; @@ -2165,8 +2196,10 @@ D85DAA53274C244F004DF43C /* Release */ = { isa = XCBuildConfiguration; buildSettings = { + CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = 97JCY7859U; GENERATE_INFOPLIST_FILE = YES; IPHONEOS_DEPLOYMENT_TARGET = 13.0; LD_RUNPATH_SEARCH_PATHS = ( @@ -2177,6 +2210,7 @@ MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = "io.sentry.Sentry.iOS13-SwiftTests"; PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_EMIT_LOC_STRINGS = NO; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; diff --git a/Samples/iOS-Swift/iOS13-Swift/AppDelegate.swift b/Samples/iOS-Swift/iOS13-Swift/AppDelegate.swift index dac028ce399..2032de1ea5a 100644 --- a/Samples/iOS-Swift/iOS13-Swift/AppDelegate.swift +++ b/Samples/iOS-Swift/iOS13-Swift/AppDelegate.swift @@ -21,10 +21,17 @@ class AppDelegate: UIResponder, UIApplicationDelegate { SentrySDK.start { options in options.dsn = dsn options.debug = true - options.tracesSampleRate = 1.0 - if ProcessInfo.processInfo.arguments.contains("--io.sentry.profiling.enable") { - options.profilesSampleRate = 1 + if #available(iOS 15.0, *) { + options.enableMetricKit = true } + // Sampling 100% - In Production you probably want to adjust this + options.tracesSampleRate = 1.0 + options.sessionTrackingIntervalMillis = 5_000 + options.profilesSampleRate = 1.0 + options.attachScreenshot = true + options.attachViewHierarchy = true + options.environment = "test-app" + options.enableTimeToFullDisplayTracing = true options.initialScope = { scope in scope.injectGitInformation() return scope diff --git a/Samples/iOS-Swift/iOS13-SwiftTests/LaunchUITest.swift b/Samples/iOS-Swift/iOS13-SwiftTests/LaunchUITest.swift index 7eb93026a9c..2b07ed9904e 100644 --- a/Samples/iOS-Swift/iOS13-SwiftTests/LaunchUITest.swift +++ b/Samples/iOS-Swift/iOS13-SwiftTests/LaunchUITest.swift @@ -25,6 +25,11 @@ class LaunchUITests: XCTestCase { XCTAssertTrue(app.staticTexts["SwiftUI!"].waitForExistence(timeout: timeout), "SwiftUI not loaded.") } + func testNavigationTransaction() { + app.buttons["Test Navigation Transaction"].tap() + XCTAssertEqual(app.state, .runningForeground) + } + private func waitForExistenceOfMainScreen() { XCTAssertTrue(app.buttons["captureMessage"].waitForExistence(timeout: timeout), "Home Screen doesn't exist.") } diff --git a/Sentry.xcodeproj/project.pbxproj b/Sentry.xcodeproj/project.pbxproj index 70964f92086..9a77b69818e 100644 --- a/Sentry.xcodeproj/project.pbxproj +++ b/Sentry.xcodeproj/project.pbxproj @@ -5402,7 +5402,6 @@ GCC_WARN_SHADOW = YES; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_DYLIB_INSTALL_NAME = "$(DYLIB_INSTALL_NAME_BASE:standardizepath)/$(EXECUTABLE_PATH)"; - ONLY_ACTIVE_ARCH = YES; OTHER_CFLAGS = "-DCARTHAGE_$(CARTHAGE)"; PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; @@ -5440,7 +5439,6 @@ "@executable_path/Frameworks", "@loader_path/Frameworks", ); - ONLY_ACTIVE_ARCH = NO; OTHER_CFLAGS = "-DCARTHAGE_$(CARTHAGE)"; PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_VERSION = 5.0; @@ -5600,7 +5598,6 @@ "@executable_path/Frameworks", "@loader_path/Frameworks", ); - ONLY_ACTIVE_ARCH = YES; OTHER_CFLAGS = "-DCARTHAGE_$(CARTHAGE)"; PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; @@ -5727,7 +5724,6 @@ "@executable_path/Frameworks", "@loader_path/Frameworks", ); - ONLY_ACTIVE_ARCH = YES; OTHER_CFLAGS = "-DCARTHAGE_$(CARTHAGE)"; PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; @@ -5776,7 +5772,6 @@ baseConfigurationReference = D8199DCF29376FF40074249E /* SentrySwiftUI.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; - BUILD_LIBRARY_FOR_DISTRIBUTION = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CLANG_ENABLE_MODULES = YES; @@ -6207,7 +6202,6 @@ "@executable_path/Frameworks", "@loader_path/Frameworks", ); - ONLY_ACTIVE_ARCH = NO; OTHER_CFLAGS = "-DCARTHAGE_$(CARTHAGE)"; PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_VERSION = 5.0; @@ -6437,7 +6431,6 @@ "@executable_path/Frameworks", "@loader_path/Frameworks", ); - ONLY_ACTIVE_ARCH = YES; OTHER_CFLAGS = "-DCARTHAGE_$(CARTHAGE)"; PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; diff --git a/Sources/Configuration/Sentry.xcconfig b/Sources/Configuration/Sentry.xcconfig index 894aa15beeb..5bda006b0a1 100644 --- a/Sources/Configuration/Sentry.xcconfig +++ b/Sources/Configuration/Sentry.xcconfig @@ -19,3 +19,5 @@ PRODUCT_MODULE_NAME = $(PRODUCT_MODULE_NAME_$(CONFIGURATION)) PRODUCT_NAME = $(PRODUCT_MODULE_NAME) PRODUCT_BUNDLE_IDENTIFIER = io.sentry.$(PRODUCT_MODULE_NAME) MODULEMAP_FILE = $(SRCROOT)/Sources/Resources/$(PRODUCT_MODULE_NAME_$(CONFIGURATION)).modulemap +ARCHS = $(ARCHS_STANDARD) arm64e +ARCHS[sdk=*simulator] = $(ARCHS_STANDARD) diff --git a/Sources/Configuration/SentrySwiftUI.xcconfig b/Sources/Configuration/SentrySwiftUI.xcconfig index 6c76b66635e..1c0ebcc9373 100644 --- a/Sources/Configuration/SentrySwiftUI.xcconfig +++ b/Sources/Configuration/SentrySwiftUI.xcconfig @@ -14,3 +14,5 @@ OTHER_LDFLAGS_Test = -framework Sentry OTHER_LDFLAGS_TestCI = -framework Sentry OTHER_LDFLAGS_Release = -framework Sentry OTHER_LDFLAGS = $(OTHER_LDFLAGS_$(CONFIGURATION)) +ARCHS = $(ARCHS_STANDARD) arm64e +ARCHS[sdk=*simulator] = $(ARCHS_STANDARD) diff --git a/Sources/Sentry/SentryBacktrace.cpp b/Sources/Sentry/SentryBacktrace.cpp index 32cb54e27d2..1b82dacb11b 100644 --- a/Sources/Sentry/SentryBacktrace.cpp +++ b/Sources/Sentry/SentryBacktrace.cpp @@ -110,8 +110,8 @@ namespace profiling { } void - enumerateBacktracesForAllThreads(const std::function &f, - const std::shared_ptr &cache) + enumerateBacktracesForAllThreads( + const std::function &f, ThreadMetadataCache *cache) { const auto pair = ThreadHandle::allExcludingCurrent(); for (const auto &thread : pair.first) { diff --git a/Sources/Sentry/SentryProfiler.mm b/Sources/Sentry/SentryProfiler.mm index a46f1a73373..77c4e52c85a 100644 --- a/Sources/Sentry/SentryProfiler.mm +++ b/Sources/Sentry/SentryProfiler.mm @@ -56,7 +56,7 @@ } @implementation SentryProfiler { - std::shared_ptr _samplingProfiler; + std::unique_ptr _samplingProfiler; } + (void)load @@ -165,7 +165,7 @@ - (void)start SentryProfilerState *const state = [[SentryProfilerState alloc] init]; self.state = state; - _samplingProfiler = std::make_shared( + _samplingProfiler = std::make_unique( [state](auto &backtrace) { Backtrace backtraceCopy = backtrace; backtraceCopy.absoluteTimestamp diff --git a/Sources/Sentry/SentrySamplingProfiler.cpp b/Sources/Sentry/SentrySamplingProfiler.cpp index ecba14ae855..1f006e12f04 100644 --- a/Sources/Sentry/SentrySamplingProfiler.cpp +++ b/Sources/Sentry/SentrySamplingProfiler.cpp @@ -21,7 +21,7 @@ namespace profiling { mach_port_t port; clock_serv_t clock; mach_timespec_t delaySpec; - std::shared_ptr cache; + ThreadMetadataCache *cache; std::function callback; std::atomic_uint64_t &numSamples; std::function onThreadStart; @@ -77,7 +77,7 @@ namespace profiling { SamplingProfiler::SamplingProfiler( std::function callback, std::uint32_t samplingRateHz) : callback_(std::move(callback)) - , cache_(std::make_shared()) + , cache_(std::make_unique()) , isInitialized_(false) , isSampling_(false) , port_(0) @@ -139,8 +139,8 @@ namespace profiling { SENTRY_ASYNC_SAFE_LOG_ERRNO_RETURN(pthread_attr_setschedparam(&attr, ¶m)); } - const auto params = new SamplingThreadParams { port_, clock_, delaySpec_, cache_, callback_, - std::ref(numSamples_), std::move(onThreadStart) }; + const auto params = new SamplingThreadParams { port_, clock_, delaySpec_, cache_.get(), + callback_, std::ref(numSamples_), std::move(onThreadStart) }; if (SENTRY_ASYNC_SAFE_LOG_ERRNO_RETURN( pthread_create(&thread_, &attr, samplingThreadMain, params)) != 0) { diff --git a/Sources/Sentry/include/SentryBacktrace.hpp b/Sources/Sentry/include/SentryBacktrace.hpp index 211a213d1ff..11fc51129f2 100644 --- a/Sources/Sentry/include/SentryBacktrace.hpp +++ b/Sources/Sentry/include/SentryBacktrace.hpp @@ -57,8 +57,8 @@ namespace profiling { * @param f The function to call for each entry. * @param cache The cache used to look up thread metadata. */ - void enumerateBacktracesForAllThreads(const std::function &f, - const std::shared_ptr &cache); + void enumerateBacktracesForAllThreads( + const std::function &f, ThreadMetadataCache *cache); } // namespace profiling } // namespace sentry diff --git a/Sources/Sentry/include/SentrySamplingProfiler.hpp b/Sources/Sentry/include/SentrySamplingProfiler.hpp index 4d56ded6b78..62a1f2f2509 100644 --- a/Sources/Sentry/include/SentrySamplingProfiler.hpp +++ b/Sources/Sentry/include/SentrySamplingProfiler.hpp @@ -21,7 +21,7 @@ namespace profiling { * Samples the stacks on all threads at a specified interval, using the mach clock * alarm API for scheduling. */ - class SamplingProfiler : public std::enable_shared_from_this { + class SamplingProfiler { public: /** * Creates a new sampling profiler that samples at the specified rate. @@ -56,7 +56,7 @@ namespace profiling { private: mach_timespec_t delaySpec_; std::function callback_; - std::shared_ptr cache_; + std::unique_ptr cache_; bool isInitialized_; std::mutex isSamplingLock_; bool isSampling_; diff --git a/Sources/Sentry/include/SentryThreadState.hpp b/Sources/Sentry/include/SentryThreadState.hpp index f6f8f99634e..19023411cb2 100644 --- a/Sources/Sentry/include/SentryThreadState.hpp +++ b/Sources/Sentry/include/SentryThreadState.hpp @@ -69,7 +69,7 @@ namespace profiling { # elif CPU(ARM64) // fp is an alias for frame pointer register x29: // https://developer.apple.com/library/archive/documentation/Xcode/Conceptual/iPhoneOSABIReference/Articles/ARM64FunctionCallingConventions.html - return context->__ss.__fp; + return arm_thread_state64_get_fp(context->__ss); # elif CPU(ARM) // https://developer.apple.com/library/archive/documentation/Xcode/Conceptual/iPhoneOSABIReference/Articles/ARMv6FunctionCallingConventions.html#//apple_ref/doc/uid/TP40009021-SW1 return context->__ss.__r[7]; @@ -104,7 +104,7 @@ namespace profiling { getLinkRegister(const MachineContext *context) noexcept { // https://stackoverflow.com/a/8236974 - return context->__ss.__lr; + return arm_thread_state64_get_lr(context->__ss); # else ALWAYS_INLINE std::uintptr_t getLinkRegister(__unused const MachineContext *context) noexcept @@ -123,7 +123,7 @@ namespace profiling { getProgramCounter(const MachineContext *context) noexcept { # if CPU(ARM64) || CPU(ARM) - return context->__ss.__pc; + return arm_thread_state64_get_pc(context->__ss); # elif CPU(X86_64) return context->__ss.__rip; # elif CPU(X86) diff --git a/Sources/SentryCrash/Recording/Tools/SentryCrashCPU_arm64.c b/Sources/SentryCrash/Recording/Tools/SentryCrashCPU_arm64.c index d6f5ef4cbd9..8c18a24ccdd 100644 --- a/Sources/SentryCrash/Recording/Tools/SentryCrashCPU_arm64.c +++ b/Sources/SentryCrash/Recording/Tools/SentryCrashCPU_arm64.c @@ -52,7 +52,7 @@ sentrycrashcpu_framePointer(const SentryCrashMachineContext *const context) // We don't want this from stopping us to enable warnings as errors. This needs to be fixed. # pragma clang diagnostic push # pragma GCC diagnostic ignored "-Wshorten-64-to-32" - return context->machineContext.__ss.__fp; + return arm_thread_state64_get_fp(context->machineContext.__ss); # pragma clang diagnostic pop } @@ -62,7 +62,7 @@ sentrycrashcpu_stackPointer(const SentryCrashMachineContext *const context) // We don't want this from stopping us to enable warnings as errors. This needs to be fixed. # pragma clang diagnostic push # pragma GCC diagnostic ignored "-Wshorten-64-to-32" - return context->machineContext.__ss.__sp; + return arm_thread_state64_get_sp(context->machineContext.__ss); # pragma clang diagnostic pop } @@ -72,7 +72,7 @@ sentrycrashcpu_instructionAddress(const SentryCrashMachineContext *const context // We don't want this from stopping us to enable warnings as errors. This needs to be fixed. # pragma clang diagnostic push # pragma GCC diagnostic ignored "-Wshorten-64-to-32" - return context->machineContext.__ss.__pc; + return arm_thread_state64_get_pc(context->machineContext.__ss); # pragma clang diagnostic pop } @@ -82,7 +82,7 @@ sentrycrashcpu_linkRegister(const SentryCrashMachineContext *const context) // We don't want this from stopping us to enable warnings as errors. This needs to be fixed. # pragma clang diagnostic push # pragma GCC diagnostic ignored "-Wshorten-64-to-32" - return context->machineContext.__ss.__lr; + return arm_thread_state64_get_lr(context->machineContext.__ss); # pragma clang diagnostic pop } @@ -122,13 +122,13 @@ sentrycrashcpu_registerValue(const SentryCrashMachineContext *const context, con switch (regNumber) { case 29: - return context->machineContext.__ss.__fp; + return arm_thread_state64_get_fp(context->machineContext.__ss); case 30: - return context->machineContext.__ss.__lr; + return arm_thread_state64_get_lr(context->machineContext.__ss); case 31: - return context->machineContext.__ss.__sp; + return arm_thread_state64_get_sp(context->machineContext.__ss); case 32: - return context->machineContext.__ss.__pc; + return arm_thread_state64_get_pc(context->machineContext.__ss); case 33: return context->machineContext.__ss.__cpsr; } diff --git a/Tests/SentryProfilerTests/SentryBacktraceTests.mm b/Tests/SentryProfilerTests/SentryBacktraceTests.mm index 2246c610f5b..3615f9dbdfa 100644 --- a/Tests/SentryProfilerTests/SentryBacktraceTests.mm +++ b/Tests/SentryProfilerTests/SentryBacktraceTests.mm @@ -199,7 +199,7 @@ - (void)testCollectsMultiThreadBacktrace XCTAssertEqual( pthread_create(&thread2, nullptr, threadEntry, reinterpret_cast(bc_d)), 0); - const auto cache = std::make_shared(); + ThreadMetadataCache cache; bool foundThread1 = false, foundThread2 = false; // Try up to 3 times. for (int i = 0; i < 3; i++) { @@ -230,7 +230,7 @@ - (void)testCollectsMultiThreadBacktrace } } }, - cache); + &cache); if (foundThread1 && foundThread2) { break; } From 4314fa2af61963544f3ad0efd93d85f881309e36 Mon Sep 17 00:00:00 2001 From: Dhiogo Brustolin Date: Fri, 4 Oct 2024 11:25:28 +0200 Subject: [PATCH 05/36] chose: Fix "Build and Upload iOS-Swift to Testflight" workflow (#4404) ix "Build and Upload iOS-Swift to Testflight" --- .../iOS-Swift.xcodeproj/project.pbxproj | 18 ++++++++++-------- Samples/iOS-Swift/iOS-Swift/Sample.xcconfig | 1 + Utils/VersionBump/main.swift | 2 +- 3 files changed, 12 insertions(+), 9 deletions(-) create mode 100644 Samples/iOS-Swift/iOS-Swift/Sample.xcconfig diff --git a/Samples/iOS-Swift/iOS-Swift.xcodeproj/project.pbxproj b/Samples/iOS-Swift/iOS-Swift.xcodeproj/project.pbxproj index 531e1489808..3c89ed2f6ac 100644 --- a/Samples/iOS-Swift/iOS-Swift.xcodeproj/project.pbxproj +++ b/Samples/iOS-Swift/iOS-Swift.xcodeproj/project.pbxproj @@ -102,6 +102,8 @@ D8B56CF0273A8D97004DF238 /* Sentry.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 630853322440C44F00DDE4CE /* Sentry.framework */; }; D8B56CF1273A8D97004DF238 /* Sentry.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 630853322440C44F00DDE4CE /* Sentry.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; D8BC843E2B021C5200A662B7 /* SwiftUIView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B4F33F7271EBD2500C8591E /* SwiftUIView.swift */; }; + D8BCCDE22CAFDE9400E8A030 /* Sample.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = D8BCCDE12CAFDE9400E8A030 /* Sample.xcconfig */; }; + D8BCCDE32CAFDE9400E8A030 /* Sample.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = D8BCCDE12CAFDE9400E8A030 /* Sample.xcconfig */; }; D8C33E1F29FBB1F70071B75A /* UIEventBreadcrumbsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8C33E1E29FBB1F70071B75A /* UIEventBreadcrumbsController.swift */; }; D8C33E2629FBB8D90071B75A /* UIEventBreadcrumbTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8C33E2529FBB8D90071B75A /* UIEventBreadcrumbTests.swift */; }; D8D7BB4A2750067900044146 /* UIAssert.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8D7BB492750067900044146 /* UIAssert.swift */; }; @@ -364,6 +366,7 @@ D890CD3B26CEE2FA001246CF /* NibViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = NibViewController.xib; sourceTree = ""; }; D890CD3E26CEE31B001246CF /* NibViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NibViewController.swift; sourceTree = ""; }; D8AE48C82C57DC2F0092A2A6 /* WebViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebViewController.swift; sourceTree = ""; }; + D8BCCDE12CAFDE9400E8A030 /* Sample.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Sample.xcconfig; sourceTree = ""; }; D8C33E1E29FBB1F70071B75A /* UIEventBreadcrumbsController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIEventBreadcrumbsController.swift; sourceTree = ""; }; D8C33E2529FBB8D90071B75A /* UIEventBreadcrumbTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIEventBreadcrumbTests.swift; sourceTree = ""; }; D8D7BB492750067900044146 /* UIAssert.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIAssert.swift; sourceTree = ""; }; @@ -505,6 +508,7 @@ 637AFDB4243B02770034958B /* LaunchScreen.storyboard */, 637AFDB7243B02770034958B /* Info.plist */, D845F35927BAD4CC00A4D7A2 /* SentryData.xcdatamodeld */, + D8BCCDE12CAFDE9400E8A030 /* Sample.xcconfig */, ); path = "iOS-Swift"; sourceTree = ""; @@ -905,6 +909,7 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + D8BCCDE22CAFDE9400E8A030 /* Sample.xcconfig in Resources */, D890CD3C26CEE2FA001246CF /* NibViewController.xib in Resources */, 7BFC8B0626D4D24B000D3504 /* LoremIpsum.txt in Resources */, 637AFDB6243B02770034958B /* LaunchScreen.storyboard in Resources */, @@ -937,6 +942,7 @@ files = ( D8269A56274C0F9E00BD5BD5 /* NibViewController.xib in Resources */, D8269A51274C0F6C00BD5BD5 /* LaunchScreen.storyboard in Resources */, + D8BCCDE32CAFDE9400E8A030 /* Sample.xcconfig in Resources */, D8269A52274C0F7200BD5BD5 /* Tongariro.jpg in Resources */, D8269A50274C0F6800BD5BD5 /* Assets.xcassets in Resources */, D8269A43274C095F00BD5BD5 /* Main.storyboard in Resources */, @@ -1287,6 +1293,7 @@ /* Begin XCBuildConfiguration section */ 637AFDB8243B02770034958B /* Debug */ = { isa = XCBuildConfiguration; + baseConfigurationReference = D8BCCDE12CAFDE9400E8A030 /* Sample.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; @@ -1348,6 +1355,7 @@ }; 637AFDB9243B02770034958B /* Release */ = { isa = XCBuildConfiguration; + baseConfigurationReference = D8BCCDE12CAFDE9400E8A030 /* Sample.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; @@ -1419,7 +1427,6 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = "8.37.0-beta.1"; PRODUCT_BUNDLE_IDENTIFIER = "io.sentry.sample.iOS-Swift"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = "match Development io.sentry.sample.iOS-Swift"; @@ -1448,7 +1455,6 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = "8.37.0-beta.1"; PRODUCT_BUNDLE_IDENTIFIER = "io.sentry.sample.iOS-Swift"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = "match AppStore io.sentry.sample.iOS-Swift"; @@ -1560,6 +1566,7 @@ }; 84D4FE8028ECD1EA00EDAAFE /* Test */ = { isa = XCBuildConfiguration; + baseConfigurationReference = D8BCCDE12CAFDE9400E8A030 /* Sample.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; @@ -1634,7 +1641,6 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 7.27.0; PRODUCT_BUNDLE_IDENTIFIER = "io.sentry.sample.iOS-Swift"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = "match Development io.sentry.sample.iOS-Swift"; @@ -1719,7 +1725,6 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 7.27.0; PRODUCT_BUNDLE_IDENTIFIER = "io.sentry.sample.iOS-Swift.Clip"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = "match Development io.sentry.sample.iOS-Swift.Clip"; @@ -1793,6 +1798,7 @@ }; 84D4FE8828ECD1ED00EDAAFE /* TestCI */ = { isa = XCBuildConfiguration; + baseConfigurationReference = D8BCCDE12CAFDE9400E8A030 /* Sample.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; @@ -1867,7 +1873,6 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 7.27.0; PRODUCT_BUNDLE_IDENTIFIER = "io.sentry.sample.iOS-Swift"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = "match Development io.sentry.sample.iOS-Swift"; @@ -1952,7 +1957,6 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 7.27.0; PRODUCT_BUNDLE_IDENTIFIER = "io.sentry.sample.iOS-Swift.Clip"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = "match Development io.sentry.sample.iOS-Swift.Clip"; @@ -2123,7 +2127,6 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = "8.37.0-beta.1"; PRODUCT_BUNDLE_IDENTIFIER = "io.sentry.sample.iOS-Swift.Clip"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = "match Development io.sentry.sample.iOS-Swift.Clip"; @@ -2158,7 +2161,6 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = "8.37.0-beta.1"; PRODUCT_BUNDLE_IDENTIFIER = "io.sentry.sample.iOS-Swift.Clip"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = "match AppStore io.sentry.sample.iOS-Swift.Clip"; diff --git a/Samples/iOS-Swift/iOS-Swift/Sample.xcconfig b/Samples/iOS-Swift/iOS-Swift/Sample.xcconfig new file mode 100644 index 00000000000..c78f1428283 --- /dev/null +++ b/Samples/iOS-Swift/iOS-Swift/Sample.xcconfig @@ -0,0 +1 @@ +MARKETING_VERSION = 8.37.0 diff --git a/Utils/VersionBump/main.swift b/Utils/VersionBump/main.swift index e4cda5fc465..04683d2156a 100644 --- a/Utils/VersionBump/main.swift +++ b/Utils/VersionBump/main.swift @@ -10,13 +10,13 @@ let files = [ "./SentryPrivate.podspec", "./SentrySwiftUI.podspec", "./Sources/Sentry/SentryMeta.m", - "./Samples/iOS-Swift/iOS-Swift.xcodeproj/project.pbxproj", "./Tests/HybridSDKTest/HybridPod.podspec" ] // Files that only accept the format x.x.x in order to release an app using the framework. // This will enable publishing apps with SDK beta version. let restrictFiles = [ + "./Samples/iOS-Swift/iOS-Swift/Sample.xcconfig", "./Sources/Configuration/SDK.xcconfig", "./Sources/Configuration/SentrySwiftUI.xcconfig" ] From 2f5e5f70a7b16712c4ec978232d6fe8b010c2d82 Mon Sep 17 00:00:00 2001 From: Dhiogo Brustolin Date: Fri, 4 Oct 2024 11:25:56 +0200 Subject: [PATCH 06/36] Rename session replay `redact` options and APIs to `mask` (#4373) Renaming SR redact to mask and ignoretounmask --- .github/workflows/benchmarking.yml | 2 +- CHANGELOG.md | 1 + .../iOS-ObjectiveC/AppDelegate.m | 4 +-- Samples/iOS-Swift/iOS-Swift/AppDelegate.swift | 2 +- .../SRRedactSampleViewController.swift | 2 +- .../iOS-SwiftUI/iOS-SwiftUI/ContentView.swift | 2 +- .../iOS-SwiftUI/iOS-SwiftUI/SwiftUIApp.swift | 4 +-- Sources/Sentry/Public/SentryReplayApi.h | 9 +++-- Sources/Sentry/SentryReplayApi.m | 8 ++--- Sources/SentrySwiftUI/SentryReplayView.swift | 8 ++--- .../Swift/Extensions/UIViewExtensions.swift | 8 ++--- .../SessionReplay/SentryReplayOptions.swift | 20 +++++------ .../Swift/Protocol/SentryRedactOptions.swift | 8 ++--- Sources/Swift/Tools/UIRedactBuilder.swift | 34 +++++++++---------- .../SentrySessionReplayIntegrationTests.swift | 4 +-- .../SwiftUI/SentryRedactModifierTests.swift | 2 +- Tests/SentryTests/UIRedactBuilderTests.swift | 30 ++++++++-------- 17 files changed, 74 insertions(+), 74 deletions(-) diff --git a/.github/workflows/benchmarking.yml b/.github/workflows/benchmarking.yml index 7398a8e833c..a36fb53525c 100644 --- a/.github/workflows/benchmarking.yml +++ b/.github/workflows/benchmarking.yml @@ -134,7 +134,7 @@ jobs: name: raw-build-output-build-xcframework path: | build-xcframework.log - + - name: Build test app with sentry run: bundle exec fastlane build_perf_test_app_sentry env: diff --git a/CHANGELOG.md b/CHANGELOG.md index 91da52f495e..32c8fd3070a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ - Speed up HTTP tracking for multiple requests in parallel (#4366) - Slightly speed up SentryInAppLogic (#4370) +- Rename session replay `redact` options and APIs to `mask` (#4373) - Stop canceling timer for manual transactions (#4380) ### Fixes diff --git a/Samples/iOS-ObjectiveC/iOS-ObjectiveC/AppDelegate.m b/Samples/iOS-ObjectiveC/iOS-ObjectiveC/AppDelegate.m index a46b3a19b61..1b1ea1dac5c 100644 --- a/Samples/iOS-ObjectiveC/iOS-ObjectiveC/AppDelegate.m +++ b/Samples/iOS-ObjectiveC/iOS-ObjectiveC/AppDelegate.m @@ -29,8 +29,8 @@ - (BOOL)application:(UIApplication *)application options.failedRequestStatusCodes = @[ httpStatusCodeRange ]; options.experimental.sessionReplay.quality = SentryReplayQualityMedium; - options.experimental.sessionReplay.redactAllText = true; - options.experimental.sessionReplay.redactAllImages = true; + options.experimental.sessionReplay.maskAllText = true; + options.experimental.sessionReplay.maskAllImages = true; options.experimental.sessionReplay.sessionSampleRate = 0; options.experimental.sessionReplay.onErrorSampleRate = 1; diff --git a/Samples/iOS-Swift/iOS-Swift/AppDelegate.swift b/Samples/iOS-Swift/iOS-Swift/AppDelegate.swift index 83452933956..ff01f04398e 100644 --- a/Samples/iOS-Swift/iOS-Swift/AppDelegate.swift +++ b/Samples/iOS-Swift/iOS-Swift/AppDelegate.swift @@ -44,7 +44,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate { options.debug = true if #available(iOS 16.0, *), !args.contains("--disable-session-replay") { - options.experimental.sessionReplay = SentryReplayOptions(sessionSampleRate: 1, onErrorSampleRate: 1, redactAllText: true, redactAllImages: true) + options.experimental.sessionReplay = SentryReplayOptions(sessionSampleRate: 1, onErrorSampleRate: 1, maskAllText: true, maskAllImages: true) options.experimental.sessionReplay.quality = .high } diff --git a/Samples/iOS-Swift/iOS-Swift/ViewControllers/SRRedactSampleViewController.swift b/Samples/iOS-Swift/iOS-Swift/ViewControllers/SRRedactSampleViewController.swift index e7a23a5e850..d14416638db 100644 --- a/Samples/iOS-Swift/iOS-Swift/ViewControllers/SRRedactSampleViewController.swift +++ b/Samples/iOS-Swift/iOS-Swift/ViewControllers/SRRedactSampleViewController.swift @@ -12,6 +12,6 @@ class SRRedactSampleViewController: UIViewController { notRedactedView.backgroundColor = .green notRedactedView.transform = CGAffineTransform(rotationAngle: 45 * .pi / 180.0) - SentrySDK.replay.ignoreView(notRedactedView) + SentrySDK.replay.maskView(notRedactedView) } } diff --git a/Samples/iOS-SwiftUI/iOS-SwiftUI/ContentView.swift b/Samples/iOS-SwiftUI/iOS-SwiftUI/ContentView.swift index 280f2c20efb..2767e1574f2 100644 --- a/Samples/iOS-SwiftUI/iOS-SwiftUI/ContentView.swift +++ b/Samples/iOS-SwiftUI/iOS-SwiftUI/ContentView.swift @@ -199,7 +199,7 @@ struct ContentView: View { Text("Form Screen") } } - .sentryReplayRedact() + .sentryReplayMask() } SecondView() } diff --git a/Samples/iOS-SwiftUI/iOS-SwiftUI/SwiftUIApp.swift b/Samples/iOS-SwiftUI/iOS-SwiftUI/SwiftUIApp.swift index 502e719f66e..5a490cd2861 100644 --- a/Samples/iOS-SwiftUI/iOS-SwiftUI/SwiftUIApp.swift +++ b/Samples/iOS-SwiftUI/iOS-SwiftUI/SwiftUIApp.swift @@ -11,8 +11,8 @@ struct SwiftUIApp: App { options.tracesSampleRate = 1.0 options.profilesSampleRate = 1.0 options.experimental.sessionReplay.sessionSampleRate = 1.0 - options.experimental.sessionReplay.redactAllImages = false - options.experimental.sessionReplay.redactAllText = false + options.experimental.sessionReplay.maskAllImages = false + options.experimental.sessionReplay.maskAllText = false options.initialScope = { scope in scope.injectGitInformation() return scope diff --git a/Sources/Sentry/Public/SentryReplayApi.h b/Sources/Sentry/Public/SentryReplayApi.h index ecbb58872a1..800dbb0c976 100644 --- a/Sources/Sentry/Public/SentryReplayApi.h +++ b/Sources/Sentry/Public/SentryReplayApi.h @@ -15,19 +15,18 @@ NS_ASSUME_NONNULL_BEGIN @interface SentryReplayApi : NSObject /** - * Marks this view to be redacted during replays. + * Marks this view to be masked during replays. * * @warning This is an experimental feature and may still have bugs. */ -- (void)redactView:(UIView *)view NS_SWIFT_NAME(redactView(_:)); +- (void)maskView:(UIView *)view NS_SWIFT_NAME(maskView(_:)); /** - * Marks this view to be ignored during redact step of session replay. - * All its content will be visible in the replay. + * Marks this view to not be masked during redact step of session replay. * * @warning This is an experimental feature and may still have bugs. */ -- (void)ignoreView:(UIView *)view NS_SWIFT_NAME(ignoreView(_:)); +- (void)unmaskView:(UIView *)view NS_SWIFT_NAME(unmaskView(_:)); /** * Pauses the replay. diff --git a/Sources/Sentry/SentryReplayApi.m b/Sources/Sentry/SentryReplayApi.m index e9ce39b34a9..9d2e7ba4b10 100644 --- a/Sources/Sentry/SentryReplayApi.m +++ b/Sources/Sentry/SentryReplayApi.m @@ -10,14 +10,14 @@ @implementation SentryReplayApi -- (void)redactView:(UIView *)view +- (void)maskView:(UIView *)view { - [SentryRedactViewHelper redactView:view]; + [SentryRedactViewHelper maskView:view]; } -- (void)ignoreView:(UIView *)view +- (void)unmaskView:(UIView *)view { - [SentryRedactViewHelper ignoreView:view]; + [SentryRedactViewHelper unmaskView:view]; } - (void)pause diff --git a/Sources/SentrySwiftUI/SentryReplayView.swift b/Sources/SentrySwiftUI/SentryReplayView.swift index fa24d3f4e2f..e35db36e512 100644 --- a/Sources/SentrySwiftUI/SentryReplayView.swift +++ b/Sources/SentrySwiftUI/SentryReplayView.swift @@ -10,7 +10,7 @@ struct SentryReplayView: UIViewRepresentable { func makeUIView(context: Context) -> UIView { let result = SentryRedactView() - result.sentryReplayRedact() + result.sentryReplayMask() return result } @@ -29,15 +29,15 @@ struct SentryReplayModifier: ViewModifier { @available(iOS 13, macOS 10.15, tvOS 13, *) public extension View { - /// Marks the view as containing sensitive information that should be redacted during replays. + /// Marks the view as containing sensitive information that should be masked during replays. /// - /// When this modifier is applied, any sensitive content within the view will be hidden or masked + /// When this modifier is applied, any sensitive content within the view will be masked /// during session replays to ensure user privacy. This is useful for views containing personal /// data or confidential information that shouldn't be visible when the replay is reviewed. /// /// - Returns: A modifier that redacts sensitive information during session replays. /// - Experiment: This is an experimental feature and may still have bugs. - func sentryReplayRedact() -> some View { + func sentryReplayMask() -> some View { modifier(SentryReplayModifier()) } } diff --git a/Sources/Swift/Extensions/UIViewExtensions.swift b/Sources/Swift/Extensions/UIViewExtensions.swift index 61ca12e2b2e..83bf0ade79d 100644 --- a/Sources/Swift/Extensions/UIViewExtensions.swift +++ b/Sources/Swift/Extensions/UIViewExtensions.swift @@ -9,8 +9,8 @@ public extension UIView { * Marks this view to be redacted during replays. * - experiment: This is an experimental feature and may still have bugs. */ - func sentryReplayRedact() { - SentryRedactViewHelper.redactView(self) + func sentryReplayMask() { + SentryRedactViewHelper.maskView(self) } /** @@ -18,8 +18,8 @@ public extension UIView { * of session replay. All its content will be visible in the replay. * - experiment: This is an experimental feature and may still have bugs. */ - func sentryReplayIgnore() { - SentryRedactViewHelper.ignoreView(self) + func sentryReplayUnmask() { + SentryRedactViewHelper.unmaskView(self) } } diff --git a/Sources/Swift/Integrations/SessionReplay/SentryReplayOptions.swift b/Sources/Swift/Integrations/SessionReplay/SentryReplayOptions.swift index 35b20553eee..a29b3a050b2 100644 --- a/Sources/Swift/Integrations/SessionReplay/SentryReplayOptions.swift +++ b/Sources/Swift/Integrations/SessionReplay/SentryReplayOptions.swift @@ -51,7 +51,7 @@ public class SentryReplayOptions: NSObject, SentryRedactOptions { * * - note: The default is true */ - public var redactAllText = true + public var maskAllText = true /** * Indicates whether session replay should redact all non-bundled image @@ -59,7 +59,7 @@ public class SentryReplayOptions: NSObject, SentryRedactOptions { * * - note: The default is true */ - public var redactAllImages = true + public var maskAllImages = true /** * Indicates the quality of the replay. @@ -73,7 +73,7 @@ public class SentryReplayOptions: NSObject, SentryRedactOptions { * By default Sentry already mask text and image elements from UIKit * Every child of a view that is redacted will also be redacted. */ - public var redactViewClasses = [AnyClass]() + public var maskedViewClasses = [AnyClass]() /** * A list of custom UIView subclasses to be ignored @@ -81,7 +81,7 @@ public class SentryReplayOptions: NSObject, SentryRedactOptions { * The views of given classes will not be redacted but their children may be. * This property has precedence over `redactViewTypes`. */ - public var ignoreViewClasses = [AnyClass]() + public var unmaskedViewClasses = [AnyClass]() /** * Defines the quality of the session replay. @@ -139,18 +139,18 @@ public class SentryReplayOptions: NSObject, SentryRedactOptions { * - errorSampleRate Indicates the percentage in which a 30 seconds replay will be send with * error events. */ - public init(sessionSampleRate: Float = 0, onErrorSampleRate: Float = 0, redactAllText: Bool = true, redactAllImages: Bool = true) { + public init(sessionSampleRate: Float = 0, onErrorSampleRate: Float = 0, maskAllText: Bool = true, maskAllImages: Bool = true) { self.sessionSampleRate = sessionSampleRate self.onErrorSampleRate = onErrorSampleRate - self.redactAllText = redactAllText - self.redactAllImages = redactAllImages + self.maskAllText = maskAllText + self.maskAllImages = maskAllImages } convenience init(dictionary: [String: Any]) { let sessionSampleRate = (dictionary["sessionSampleRate"] as? NSNumber)?.floatValue ?? 0 let onErrorSampleRate = (dictionary["errorSampleRate"] as? NSNumber)?.floatValue ?? 0 - let redactAllText = (dictionary["redactAllText"] as? NSNumber)?.boolValue ?? true - let redactAllImages = (dictionary["redactAllImages"] as? NSNumber)?.boolValue ?? true - self.init(sessionSampleRate: sessionSampleRate, onErrorSampleRate: onErrorSampleRate, redactAllText: redactAllText, redactAllImages: redactAllImages) + let maskAllText = (dictionary["maskAllText"] as? NSNumber)?.boolValue ?? true + let maskAllImages = (dictionary["maskAllImages"] as? NSNumber)?.boolValue ?? true + self.init(sessionSampleRate: sessionSampleRate, onErrorSampleRate: onErrorSampleRate, maskAllText: maskAllText, maskAllImages: maskAllImages) } } diff --git a/Sources/Swift/Protocol/SentryRedactOptions.swift b/Sources/Swift/Protocol/SentryRedactOptions.swift index dc0e05c9737..24560dddea7 100644 --- a/Sources/Swift/Protocol/SentryRedactOptions.swift +++ b/Sources/Swift/Protocol/SentryRedactOptions.swift @@ -2,8 +2,8 @@ import Foundation @objc protocol SentryRedactOptions { - var redactAllText: Bool { get } - var redactAllImages: Bool { get } - var redactViewClasses: [AnyClass] { get } - var ignoreViewClasses: [AnyClass] { get } + var maskAllText: Bool { get } + var maskAllImages: Bool { get } + var maskedViewClasses: [AnyClass] { get } + var unmaskedViewClasses: [AnyClass] { get } } diff --git a/Sources/Swift/Tools/UIRedactBuilder.swift b/Sources/Swift/Tools/UIRedactBuilder.swift index 2ec380441d8..01b99a3e296 100644 --- a/Sources/Swift/Tools/UIRedactBuilder.swift +++ b/Sources/Swift/Tools/UIRedactBuilder.swift @@ -52,21 +52,21 @@ class UIRedactBuilder { - parameter options: A `SentryRedactOptions` object that specifies the configuration for the redaction process. - - If `options.redactAllText` is `true`, common text-related views such as `UILabel`, `UITextView`, and `UITextField` are redacted. - - If `options.redactAllImages` is `true`, common image-related views such as `UIImageView` and various internal `SwiftUI` image views are redacted. - - The `options.ignoreRedactViewTypes` allows specifying custom view types to be ignored during the redaction process. - - The `options.redactViewTypes` allows specifying additional custom view types to be redacted. + - If `options.maskAllText` is `true`, common text-related views such as `UILabel`, `UITextView`, and `UITextField` are redacted. + - If `options.maskAllImages` is `true`, common image-related views such as `UIImageView` and various internal `SwiftUI` image views are redacted. + - The `options.unmaskViewTypes` allows specifying custom view types to be ignored during the redaction process. + - The `options.maskViewTypes` allows specifying additional custom view types to be redacted. - note: On iOS, views such as `WKWebView` and `UIWebView` are automatically redacted, and controls like `UISlider` and `UISwitch` are ignored. */ init(options: SentryRedactOptions) { var redactClasses = [AnyClass]() - if options.redactAllText { + if options.maskAllText { redactClasses += [ UILabel.self, UITextView.self, UITextField.self ] } - if options.redactAllImages { + if options.maskAllImages { //this classes are used by SwiftUI to display images. redactClasses += ["_TtCOCV7SwiftUI11DisplayList11ViewUpdater8Platform13CGDrawingView", "_TtC7SwiftUIP33_A34643117F00277B93DEBAB70EC0697122_UIShapeHitTestingView", @@ -89,11 +89,11 @@ class UIRedactBuilder { redactClassesIdentifiers = Set(redactClasses.map({ ObjectIdentifier($0) })) - for type in options.ignoreViewClasses { + for type in options.unmaskedViewClasses { self.ignoreClassesIdentifiers.insert(ObjectIdentifier(type)) } - for type in options.redactViewClasses { + for type in options.maskedViewClasses { self.redactClassesIdentifiers.insert(ObjectIdentifier(type)) } } @@ -133,15 +133,15 @@ class UIRedactBuilder { This function identifies and returns the regions within a given UIView that need to be redacted, based on the specified redaction options. - Parameter view: The root UIView for which redaction regions are to be calculated. - - Parameter options: A `SentryRedactOptions` object specifying whether to redact all text (`redactAllText`) or all images (`redactAllImages`). If `options` is nil, defaults are used (redacting all text and images). + - Parameter options: A `SentryRedactOptions` object specifying whether to redact all text (`maskAllText`) or all images (`maskAllImages`). If `options` is nil, defaults are used (redacting all text and images). - Returns: An array of `RedactRegion` objects representing areas of the view (and its subviews) that require redaction, based on the current visibility, opacity, and content (text or images). The method recursively traverses the view hierarchy, collecting redaction areas from the view and all its subviews. Each redaction area is calculated based on the view’s presentation layer, size, transformation matrix, and other attributes. The redaction process considers several key factors: - 1. **Text Redaction**: If `redactAllText` is set to true, regions containing text within the view or its subviews are marked for redaction. - 2. **Image Redaction**: If `redactAllImages` is set to true, image-containing regions are also marked for redaction. + 1. **Text Redaction**: If `maskAllText` is set to true, regions containing text within the view or its subviews are marked for redaction. + 2. **Image Redaction**: If `maskAllImages` is set to true, image-containing regions are also marked for redaction. 3. **Opaque View Handling**: If an opaque view covers the entire area, obfuscating views beneath it, those hidden views are excluded from processing, and we can remove them from the result. 4. **Clip Area Creation**: If a smaller opaque view blocks another view, we create a clip area to avoid drawing a redact mask on top of a view that does not require redaction. @@ -159,11 +159,11 @@ class UIRedactBuilder { } private func shouldIgnore(view: UIView) -> Bool { - return SentryRedactViewHelper.shouldIgnoreView(view) || containsIgnoreClass(type(of: view)) + return SentryRedactViewHelper.shouldUnmask(view) || containsIgnoreClass(type(of: view)) } private func shouldRedact(view: UIView) -> Bool { - if SentryRedactViewHelper.shouldRedactView(view) { + if SentryRedactViewHelper.shouldMaskView(view) { return true } if let imageView = view as? UIImageView, containsRedactClass(UIImageView.self) { @@ -257,19 +257,19 @@ class SentryRedactViewHelper: NSObject { private static var associatedRedactObjectHandle: UInt8 = 0 private static var associatedIgnoreObjectHandle: UInt8 = 0 - static func shouldRedactView(_ view: UIView) -> Bool { + static func shouldMaskView(_ view: UIView) -> Bool { (objc_getAssociatedObject(view, &associatedRedactObjectHandle) as? NSNumber)?.boolValue ?? false } - static func shouldIgnoreView(_ view: UIView) -> Bool { + static func shouldUnmask(_ view: UIView) -> Bool { (objc_getAssociatedObject(view, &associatedIgnoreObjectHandle) as? NSNumber)?.boolValue ?? false } - static func redactView(_ view: UIView) { + static func maskView(_ view: UIView) { objc_setAssociatedObject(view, &associatedRedactObjectHandle, true, .OBJC_ASSOCIATION_ASSIGN) } - static func ignoreView(_ view: UIView) { + static func unmaskView(_ view: UIView) { objc_setAssociatedObject(view, &associatedIgnoreObjectHandle, true, .OBJC_ASSOCIATION_ASSIGN) } } diff --git a/Tests/SentryTests/Integrations/SessionReplay/SentrySessionReplayIntegrationTests.swift b/Tests/SentryTests/Integrations/SessionReplay/SentrySessionReplayIntegrationTests.swift index 2b2b2a8aeb0..a308c8eb2ec 100644 --- a/Tests/SentryTests/Integrations/SessionReplay/SentrySessionReplayIntegrationTests.swift +++ b/Tests/SentryTests/Integrations/SessionReplay/SentrySessionReplayIntegrationTests.swift @@ -288,7 +288,7 @@ class SentrySessionReplayIntegrationTests: XCTestCase { } startSDK(sessionSampleRate: 1, errorSampleRate: 1) { options in - options.experimental.sessionReplay.redactViewClasses = [AnotherLabel.self] + options.experimental.sessionReplay.maskedViewClasses = [AnotherLabel.self] } let sut = try getSut() @@ -301,7 +301,7 @@ class SentrySessionReplayIntegrationTests: XCTestCase { } startSDK(sessionSampleRate: 1, errorSampleRate: 1) { options in - options.experimental.sessionReplay.ignoreViewClasses = [AnotherLabel.self] + options.experimental.sessionReplay.unmaskedViewClasses = [AnotherLabel.self] } let sut = try getSut() diff --git a/Tests/SentryTests/SwiftUI/SentryRedactModifierTests.swift b/Tests/SentryTests/SwiftUI/SentryRedactModifierTests.swift index a6023705e57..d2ae74fc7ed 100644 --- a/Tests/SentryTests/SwiftUI/SentryRedactModifierTests.swift +++ b/Tests/SentryTests/SwiftUI/SentryRedactModifierTests.swift @@ -8,7 +8,7 @@ class SentryRedactModifierTests: XCTestCase { func testViewRedacted() throws { let text = Text("Hello, World!") - let redactedText = text.sentryReplayRedact() + let redactedText = text.sentryReplayMask() XCTAssertTrue(redactedText is ModifiedContent) } diff --git a/Tests/SentryTests/UIRedactBuilderTests.swift b/Tests/SentryTests/UIRedactBuilderTests.swift index 1711973ca9a..0261485d7b1 100644 --- a/Tests/SentryTests/UIRedactBuilderTests.swift +++ b/Tests/SentryTests/UIRedactBuilderTests.swift @@ -6,16 +6,16 @@ import UIKit import XCTest class RedactOptions: SentryRedactOptions { - var redactViewClasses: [AnyClass] - var ignoreViewClasses: [AnyClass] - var redactAllText: Bool - var redactAllImages: Bool + var maskedViewClasses: [AnyClass] + var unmaskedViewClasses: [AnyClass] + var maskAllText: Bool + var maskAllImages: Bool - init(redactAllText: Bool = true, redactAllImages: Bool = true) { - self.redactAllText = redactAllText - self.redactAllImages = redactAllImages - redactViewClasses = [] - ignoreViewClasses = [] + init(maskAllText: Bool = true, maskAllImages: Bool = true) { + self.maskAllText = maskAllText + self.maskAllImages = maskAllImages + maskedViewClasses = [] + unmaskedViewClasses = [] } } @@ -52,7 +52,7 @@ class UIRedactBuilderTests: XCTestCase { } func testDontRedactALabelOptionDisabled() { - let sut = getSut(RedactOptions(redactAllText: false)) + let sut = getSut(RedactOptions(maskAllText: false)) let label = UILabel(frame: CGRect(x: 20, y: 20, width: 40, height: 40)) label.textColor = .purple rootView.addSubview(label) @@ -81,7 +81,7 @@ class UIRedactBuilderTests: XCTestCase { } func testDontRedactAImageOptionDisabled() { - let sut = getSut(RedactOptions(redactAllImages: false)) + let sut = getSut(RedactOptions(maskAllImages: false)) let image = UIGraphicsImageRenderer(size: CGSize(width: 40, height: 40)).image { context in context.fill(CGRect(x: 0, y: 0, width: 40, height: 40)) @@ -197,7 +197,7 @@ class UIRedactBuilderTests: XCTestCase { let sut = getSut() let label = AnotherLabel(frame: CGRect(x: 20, y: 20, width: 40, height: 40)) - SentrySDK.replay.ignoreView(label) + SentrySDK.replay.unmaskView(label) rootView.addSubview(label) let result = sut.redactRegionsFor(view: rootView) @@ -210,7 +210,7 @@ class UIRedactBuilderTests: XCTestCase { let sut = getSut() let view = AnotherView(frame: CGRect(x: 20, y: 20, width: 40, height: 40)) - SentrySDK.replay.redactView(view) + SentrySDK.replay.maskView(view) rootView.addSubview(view) let result = sut.redactRegionsFor(view: rootView) @@ -223,7 +223,7 @@ class UIRedactBuilderTests: XCTestCase { let sut = getSut() let label = AnotherLabel(frame: CGRect(x: 20, y: 20, width: 40, height: 40)) - label.sentryReplayIgnore() + label.sentryReplayUnmask() rootView.addSubview(label) let result = sut.redactRegionsFor(view: rootView) @@ -236,7 +236,7 @@ class UIRedactBuilderTests: XCTestCase { let sut = getSut() let view = AnotherView(frame: CGRect(x: 20, y: 20, width: 40, height: 40)) - view.sentryReplayRedact() + view.sentryReplayMask() rootView.addSubview(view) let result = sut.redactRegionsFor(view: rootView) From 2d2068d4247d91a9143c43c4681805caa06d0443 Mon Sep 17 00:00:00 2001 From: Dhiogo Brustolin Date: Mon, 7 Oct 2024 18:48:12 +0200 Subject: [PATCH 07/36] Update CHANGELOG.md (#4409) --- CHANGELOG.md | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 32c8fd3070a..ac4d46c34c3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,34 @@ - Fix the versioning to support app release with Beta versions (#4368) - Linking ongoing trace to crash event (#4393) +## 8.37.0 + +### Features + +- Added `thermal_state` to device context (#4305) +- Send envelopes that cannot be cached to disk (#4294) + +### Refactoring + +- Moved session replay API to `SentrySDK.replay` (#4326) +- Changed default session replay quality to `medium` (#4326) + +### Fixes + +- Resumes replay when the app becomes active (#4303) +- Session replay redact view with transformation (#4308) +- Correct redact UIView with higher zPosition (#4309) +- Don't redact clipped views (#4325) +- Session replay for crash not created because of a race condition (#4314) +- Double-quoted include, expected angle-bracketed instead (#4298) +- Discontinue use of NSApplicationSupportDirectory in favor of NSCachesDirectory (#4335) +- Safe guard `strncpy` usage (#4336) +- Stop using `redactAllText` as an indicator tha redact is enabled (#4327) + +### Improvements + +- Avoid extra work when storing invalid envelopes (#4337) + ## 8.37.0-beta.1 ### Features From 62ef8dcb79fd24f01c39c8e379a49813a9e27c69 Mon Sep 17 00:00:00 2001 From: getsentry-bot Date: Mon, 7 Oct 2024 17:17:12 +0000 Subject: [PATCH 08/36] release: 8.38.0-beta.1 --- .github/last-release-runid | 2 +- CHANGELOG.md | 2 +- Package.swift | 8 ++++---- Samples/iOS-Swift/iOS-Swift/Sample.xcconfig | 2 +- Sentry.podspec | 2 +- SentryPrivate.podspec | 2 +- SentrySwiftUI.podspec | 4 ++-- Sources/Configuration/SDK.xcconfig | 2 +- Sources/Configuration/SentrySwiftUI.xcconfig | 2 +- Sources/Sentry/SentryMeta.m | 2 +- Tests/HybridSDKTest/HybridPod.podspec | 2 +- 11 files changed, 15 insertions(+), 15 deletions(-) diff --git a/.github/last-release-runid b/.github/last-release-runid index 24d416962f8..643b1e7e02f 100644 --- a/.github/last-release-runid +++ b/.github/last-release-runid @@ -1 +1 @@ -10958820944 +11220184614 diff --git a/CHANGELOG.md b/CHANGELOG.md index ac4d46c34c3..bf1d2e6776f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## Unreleased +## 8.38.0-beta.1 ### Features diff --git a/Package.swift b/Package.swift index 90bcacaa8af..3725e781d54 100644 --- a/Package.swift +++ b/Package.swift @@ -12,13 +12,13 @@ let package = Package( targets: [ .binaryTarget( name: "Sentry", - url: "https://github.com/getsentry/sentry-cocoa/releases/download/8.37.0-beta.1/Sentry.xcframework.zip", - checksum: "ec3883674ff8374d56f936d5d0155ffa01ee7bfadc31e086d768b6126843476d" //Sentry-Static + url: "https://github.com/getsentry/sentry-cocoa/releases/download/8.38.0-beta.1/Sentry.xcframework.zip", + checksum: "ee65620084f83011c541d48dfc07054277366e200fac83cc8ba4b602fdc31fb1" //Sentry-Static ), .binaryTarget( name: "Sentry-Dynamic", - url: "https://github.com/getsentry/sentry-cocoa/releases/download/8.37.0-beta.1/Sentry-Dynamic.xcframework.zip", - checksum: "fdd797fb6aec8830e5aa541f7d7eddabd435a9118853525f0ebd847b40e5c90d" //Sentry-Dynamic + url: "https://github.com/getsentry/sentry-cocoa/releases/download/8.38.0-beta.1/Sentry-Dynamic.xcframework.zip", + checksum: "340558f58cec3bef18d9c40f07210df0cf00d1fe1eefd8ee1238d61276496346" //Sentry-Dynamic ), .target ( name: "SentrySwiftUI", dependencies: ["Sentry", "SentryInternal"], diff --git a/Samples/iOS-Swift/iOS-Swift/Sample.xcconfig b/Samples/iOS-Swift/iOS-Swift/Sample.xcconfig index c78f1428283..971c13504e3 100644 --- a/Samples/iOS-Swift/iOS-Swift/Sample.xcconfig +++ b/Samples/iOS-Swift/iOS-Swift/Sample.xcconfig @@ -1 +1 @@ -MARKETING_VERSION = 8.37.0 +MARKETING_VERSION = 8.38.0 diff --git a/Sentry.podspec b/Sentry.podspec index 0588f2aca8d..878c6ea6aad 100644 --- a/Sentry.podspec +++ b/Sentry.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "Sentry" - s.version = "8.37.0-beta.1" + s.version = "8.38.0-beta.1" s.summary = "Sentry client for cocoa" s.homepage = "https://github.com/getsentry/sentry-cocoa" s.license = "mit" diff --git a/SentryPrivate.podspec b/SentryPrivate.podspec index dd26ccd9676..c14ab713b2c 100644 --- a/SentryPrivate.podspec +++ b/SentryPrivate.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "SentryPrivate" - s.version = "8.37.0-beta.1" + s.version = "8.38.0-beta.1" s.summary = "Sentry Private Library." s.homepage = "https://github.com/getsentry/sentry-cocoa" s.license = "mit" diff --git a/SentrySwiftUI.podspec b/SentrySwiftUI.podspec index 421e21bb995..06bbaf16930 100644 --- a/SentrySwiftUI.podspec +++ b/SentrySwiftUI.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "SentrySwiftUI" - s.version = "8.37.0-beta.1" + s.version = "8.38.0-beta.1" s.summary = "Sentry client for SwiftUI" s.homepage = "https://github.com/getsentry/sentry-cocoa" s.license = "mit" @@ -19,5 +19,5 @@ Pod::Spec.new do |s| s.watchos.framework = 'WatchKit' s.source_files = "Sources/SentrySwiftUI/**/*.{swift,h,m}" - s.dependency 'Sentry', "8.37.0-beta.1" + s.dependency 'Sentry', "8.38.0-beta.1" end diff --git a/Sources/Configuration/SDK.xcconfig b/Sources/Configuration/SDK.xcconfig index e25be24877a..a5de2e1f823 100644 --- a/Sources/Configuration/SDK.xcconfig +++ b/Sources/Configuration/SDK.xcconfig @@ -10,7 +10,7 @@ DYLIB_INSTALL_NAME_BASE = @rpath MACH_O_TYPE = mh_dylib FRAMEWORK_VERSION = A -CURRENT_PROJECT_VERSION = 8.37.0 +CURRENT_PROJECT_VERSION = 8.38.0 ALWAYS_SEARCH_USER_PATHS = NO CLANG_ENABLE_OBJC_ARC = YES diff --git a/Sources/Configuration/SentrySwiftUI.xcconfig b/Sources/Configuration/SentrySwiftUI.xcconfig index 1c0ebcc9373..d8fcf7e2eac 100644 --- a/Sources/Configuration/SentrySwiftUI.xcconfig +++ b/Sources/Configuration/SentrySwiftUI.xcconfig @@ -1,5 +1,5 @@ PRODUCT_NAME = SentrySwiftUI -CURRENT_PROJECT_VERSION = 8.37.0 +CURRENT_PROJECT_VERSION = 8.38.0 MACOSX_DEPLOYMENT_TARGET = 10.15 IPHONEOS_DEPLOYMENT_TARGET = 13.0 diff --git a/Sources/Sentry/SentryMeta.m b/Sources/Sentry/SentryMeta.m index d7946712ea2..c21b602a788 100644 --- a/Sources/Sentry/SentryMeta.m +++ b/Sources/Sentry/SentryMeta.m @@ -5,7 +5,7 @@ @implementation SentryMeta // Don't remove the static keyword. If you do the compiler adds the constant name to the global // symbol table and it might clash with other constants. When keeping the static keyword the // compiler replaces all occurrences with the value. -static NSString *versionString = @"8.37.0-beta.1"; +static NSString *versionString = @"8.38.0-beta.1"; static NSString *sdkName = @"sentry.cocoa"; + (NSString *)versionString diff --git a/Tests/HybridSDKTest/HybridPod.podspec b/Tests/HybridSDKTest/HybridPod.podspec index 48348df598f..12281804213 100644 --- a/Tests/HybridSDKTest/HybridPod.podspec +++ b/Tests/HybridSDKTest/HybridPod.podspec @@ -13,6 +13,6 @@ Pod::Spec.new do |s| s.requires_arc = true s.frameworks = 'Foundation' s.swift_versions = "5.5" - s.dependency "Sentry/HybridSDK", "8.37.0-beta.1" + s.dependency "Sentry/HybridSDK", "8.38.0-beta.1" s.source_files = "HybridTest.swift" end From 187edbf09bf4f0731cad126d4f1e79c6981ff9ad Mon Sep 17 00:00:00 2001 From: Philipp Hofmann Date: Tue, 8 Oct 2024 08:15:14 +0200 Subject: [PATCH 09/36] feat: AppHangsV2 (#4379) Expose the AppHangsV2 algorithm with the options enableAppHangTrackingV2 and enableReportNonFullyBlockingAppHangs. Fixes GH-3492 --- CHANGELOG.md | 1 + Sentry.xcodeproj/project.pbxproj | 4 + Sources/Sentry/Public/SentryOptions.h | 34 ++++++ Sources/Sentry/SentryANRTrackingIntegration.m | 21 +++- Sources/Sentry/SentryBaseIntegration.m | 19 ++- Sources/Sentry/SentryDependencyContainer.m | 71 ++++++----- Sources/Sentry/SentryEvent.m | 3 +- Sources/Sentry/SentryOptions.m | 12 +- ...ryWatchdogTerminationTrackingIntegration.m | 3 +- .../HybridPublic/SentryDependencyContainer.h | 13 +-- .../Sentry/include/SentryBaseIntegration.h | 1 - .../Sentry/include/SentryOptions+Private.h | 2 - .../ANR/SentryANRTrackerV2Delegate.swift | 7 -- .../Integrations/ANR/SentryANRType.swift | 33 ++++++ .../SentryDependencyContainerTests.swift | 50 +++++++- .../SentryANRTrackingIntegrationTests.swift | 110 +++++++++++++++++- Tests/SentryTests/SentryOptionsTest.m | 9 ++ 17 files changed, 317 insertions(+), 76 deletions(-) create mode 100644 Sources/Swift/Integrations/ANR/SentryANRType.swift diff --git a/CHANGELOG.md b/CHANGELOG.md index ac4d46c34c3..a92f48efe01 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ - Added breadcrumb.origin private field (#4358) - Custom redact modifier for SwiftUI (#4362) +- AppHangV2 detection (#4379) Add a new algorithm for detecting app hangs that differentiates between fully blocking and non-fully blocking app hangs. Read more in-depth in our [docs](https://docs.sentry.io/platforms/apple/guides/ios/configuration/app-hangs/#app-hangs-v2). - Add support for arm64e (#3398) - Add mergeable libraries support to dynamic libraries (#4381) diff --git a/Sentry.xcodeproj/project.pbxproj b/Sentry.xcodeproj/project.pbxproj index 9a77b69818e..c64e8f922a8 100644 --- a/Sentry.xcodeproj/project.pbxproj +++ b/Sentry.xcodeproj/project.pbxproj @@ -80,6 +80,7 @@ 621AE74D2C626C510012E730 /* SentryANRTrackerV2.m in Sources */ = {isa = PBXBuildFile; fileRef = 621AE74C2C626C510012E730 /* SentryANRTrackerV2.m */; }; 621D9F2F2B9B0320003D94DE /* SentryCurrentDateProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 621D9F2E2B9B0320003D94DE /* SentryCurrentDateProvider.swift */; }; 621F61F12BEA073A005E654F /* SentryEnabledFeaturesBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 621F61F02BEA073A005E654F /* SentryEnabledFeaturesBuilder.swift */; }; + 6221BBCA2CAA932100C627CA /* SentryANRType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6221BBC92CAA932100C627CA /* SentryANRType.swift */; }; 62262B862BA1C46D004DA3DD /* SentryStatsdClient.h in Headers */ = {isa = PBXBuildFile; fileRef = 62262B852BA1C46D004DA3DD /* SentryStatsdClient.h */; }; 62262B882BA1C490004DA3DD /* SentryStatsdClient.m in Sources */ = {isa = PBXBuildFile; fileRef = 62262B872BA1C490004DA3DD /* SentryStatsdClient.m */; }; 62262B8B2BA1C4C1004DA3DD /* EncodeMetrics.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62262B8A2BA1C4C1004DA3DD /* EncodeMetrics.swift */; }; @@ -1088,6 +1089,7 @@ 621AE74E2C626CF70012E730 /* SentryANRTrackerV2Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryANRTrackerV2Tests.swift; sourceTree = ""; }; 621D9F2E2B9B0320003D94DE /* SentryCurrentDateProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryCurrentDateProvider.swift; sourceTree = ""; }; 621F61F02BEA073A005E654F /* SentryEnabledFeaturesBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryEnabledFeaturesBuilder.swift; sourceTree = ""; }; + 6221BBC92CAA932100C627CA /* SentryANRType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryANRType.swift; sourceTree = ""; }; 62262B852BA1C46D004DA3DD /* SentryStatsdClient.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SentryStatsdClient.h; path = include/SentryStatsdClient.h; sourceTree = ""; }; 62262B872BA1C490004DA3DD /* SentryStatsdClient.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SentryStatsdClient.m; sourceTree = ""; }; 62262B8A2BA1C4C1004DA3DD /* EncodeMetrics.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EncodeMetrics.swift; sourceTree = ""; }; @@ -2199,6 +2201,7 @@ children = ( 6294774B2C6F255F00846CBC /* SentryANRTrackerV2Delegate.swift */, 62FC18AE2C9D5FAC008803CD /* SentryANRTracker.swift */, + 6221BBC92CAA932100C627CA /* SentryANRType.swift */, ); path = ANR; sourceTree = ""; @@ -4634,6 +4637,7 @@ D8ACE3C92762187200F5A213 /* SentryFileIOTrackingIntegration.m in Sources */, 63FE713B20DA4C1100CDBAE8 /* SentryCrashFileUtils.c in Sources */, 63FE716920DA4C1100CDBAE8 /* SentryCrashStackCursor.c in Sources */, + 6221BBCA2CAA932100C627CA /* SentryANRType.swift in Sources */, 7BA61CCA247D128B00C130A8 /* SentryThreadInspector.m in Sources */, D8CA12952C203E71005894F4 /* SentrySessionListener.swift in Sources */, 63FE718D20DA4C1100CDBAE8 /* SentryCrashReportStore.c in Sources */, diff --git a/Sources/Sentry/Public/SentryOptions.h b/Sources/Sentry/Public/SentryOptions.h index a98d8d64cc8..7914b54437e 100644 --- a/Sources/Sentry/Public/SentryOptions.h +++ b/Sources/Sentry/Public/SentryOptions.h @@ -559,6 +559,40 @@ NS_SWIFT_NAME(Options) */ @property (nonatomic, assign) BOOL enableAppHangTracking; +#if SENTRY_UIKIT_AVAILABLE + +/** + * AppHangTrackingV2 can differentiate between fully-blocking and non-fully blocking app hangs. + * fully-blocking app hang is when the main thread is stuck completely, and the app can't render a + * single frame. A non-fully-blocking app hang is when the app appears stuck to the user but can + still + * render a few frames. Fully-blocking app hangs are more actionable because the stacktrace shows + the + * exact blocking location on the main thread. As the main thread isn't completely blocked, + * non-fully-blocking app hangs can have a stacktrace that doesn't highlight the exact blocking + * location. + * + * You can use @c enableReportNonFullyBlockingAppHangs to ignore non-fully-blocking app hangs. + * + * @note This flag wins over enableAppHangTracking. When enabling both enableAppHangTracking and + enableAppHangTrackingV2, the SDK only enables enableAppHangTrackingV2 and disables + enableAppHangTracking. + * + * @warning This is an experimental feature and may still have bugs. + */ +@property (nonatomic, assign) BOOL enableAppHangTrackingV2; + +/** + * When enabled the SDK reports non-fully-blocking app hangs. A non-fully-blocking app hang is when + * the app appears stuck to the user but can still render a few frames. For more information see @c + * enableAppHangTrackingV2. + * + * @note The default is @c YES. This feature only works when @c enableAppHangTrackingV2 is enabled. + */ +@property (nonatomic, assign) BOOL enableReportNonFullyBlockingAppHangs; + +#endif // SENTRY_UIKIT_AVAILABLE + /** * The minimum amount of time an app should be unresponsive to be classified as an App Hanging. * @note The actual amount may be a little longer. diff --git a/Sources/Sentry/SentryANRTrackingIntegration.m b/Sources/Sentry/SentryANRTrackingIntegration.m index 35fa65cb1fd..c1d6793ed35 100644 --- a/Sources/Sentry/SentryANRTrackingIntegration.m +++ b/Sources/Sentry/SentryANRTrackingIntegration.m @@ -29,6 +29,7 @@ @interface SentryANRTrackingIntegration () @property (nonatomic, strong) id tracker; @property (nonatomic, strong) SentryOptions *options; @property (atomic, assign) BOOL reportAppHangs; +@property (atomic, assign) BOOL enableReportNonFullyBlockingAppHangs; @end @@ -40,9 +41,15 @@ - (BOOL)installWithOptions:(SentryOptions *)options return NO; } +#if SENTRY_HAS_UIKIT + self.tracker = + [SentryDependencyContainer.sharedInstance getANRTracker:options.appHangTimeoutInterval + isV2Enabled:options.enableAppHangTrackingV2]; +#else self.tracker = - [SentryDependencyContainer.sharedInstance getANRTrackerV1:options.appHangTimeoutInterval]; + [SentryDependencyContainer.sharedInstance getANRTracker:options.appHangTimeoutInterval]; +#endif // SENTRY_HAS_UIKIT [self.tracker addListener:self]; self.options = options; self.reportAppHangs = YES; @@ -83,6 +90,12 @@ - (void)anrDetectedWithType:(enum SentryANRType)type } #if SENTRY_HAS_UIKIT + if (type == SentryANRTypeNonFullyBlocking + && !self.options.enableReportNonFullyBlockingAppHangs) { + SENTRY_LOG_DEBUG(@"Ignoring non fully blocking app hang.") + return; + } + // If the app is not active, the main thread may be blocked or too busy. // Since there is no UI for the user to interact, there is no need to report app hang. if (SentryDependencyContainer.sharedInstance.application.applicationState @@ -103,8 +116,10 @@ - (void)anrDetectedWithType:(enum SentryANRType)type NSString *message = [NSString stringWithFormat:@"App hanging for at least %li ms.", (long)(self.options.appHangTimeoutInterval * 1000)]; SentryEvent *event = [[SentryEvent alloc] initWithLevel:kSentryLevelError]; - SentryException *sentryException = - [[SentryException alloc] initWithValue:message type:SentryANRExceptionType]; + + NSString *exceptionType = [SentryAppHangTypeMapper getExceptionTypeWithAnrType:type]; + SentryException *sentryException = [[SentryException alloc] initWithValue:message + type:exceptionType]; sentryException.mechanism = [[SentryMechanism alloc] initWithType:@"AppHang"]; sentryException.stacktrace = [threads[0] stacktrace]; diff --git a/Sources/Sentry/SentryBaseIntegration.m b/Sources/Sentry/SentryBaseIntegration.m index f6f7f6698d2..35ea3988672 100644 --- a/Sources/Sentry/SentryBaseIntegration.m +++ b/Sources/Sentry/SentryBaseIntegration.m @@ -78,22 +78,17 @@ - (BOOL)shouldBeEnabledWithOptions:(SentryOptions *)options #endif if (integrationOptions & kIntegrationOptionEnableAppHangTracking) { - if (!options.enableAppHangTracking) { - [self logWithOptionName:@"enableAppHangTracking"]; - return NO; - } - - if (options.appHangTimeoutInterval == 0) { - [self logWithReason:@"because appHangTimeoutInterval is 0"]; +#if SENTRY_HAS_UIKIT + if (!options.enableAppHangTracking && !options.enableAppHangTrackingV2) { + [self logWithOptionName:@"enableAppHangTracking && enableAppHangTrackingV2"]; return NO; } - } - - if (integrationOptions & kIntegrationOptionEnableAppHangTrackingV2) { - if (!options.enableAppHangTrackingV2) { - [self logWithOptionName:@"enableAppHangTrackingV2"]; +#else + if (!options.enableAppHangTracking) { + [self logWithOptionName:@"enableAppHangTracking"]; return NO; } +#endif // SENTRY_HAS_UIKIT if (options.appHangTimeoutInterval == 0) { [self logWithReason:@"because appHangTimeoutInterval is 0"]; diff --git a/Sources/Sentry/SentryDependencyContainer.m b/Sources/Sentry/SentryDependencyContainer.m index 7d32483cad3..19c95805841 100644 --- a/Sources/Sentry/SentryDependencyContainer.m +++ b/Sources/Sentry/SentryDependencyContainer.m @@ -1,5 +1,5 @@ #import "SentryANRTrackerV1.h" -#import "SentryANRTrackerV2.h" + #import "SentryBinaryImageCache.h" #import "SentryDispatchFactory.h" #import "SentryDispatchQueueWrapper.h" @@ -32,6 +32,7 @@ #import #if SENTRY_HAS_UIKIT +# import "SentryANRTrackerV2.h" # import "SentryFramesTracker.h" # import "SentryUIApplication.h" # import @@ -46,6 +47,12 @@ # import "SentryReachability.h" #endif // !TARGET_OS_WATCH +@interface SentryDependencyContainer () + +@property (nonatomic, strong) id anrTracker; + +@end + @implementation SentryDependencyContainer static SentryDependencyContainer *instance; @@ -301,32 +308,6 @@ - (SentryFramesTracker *)framesTracker SENTRY_DISABLE_THREAD_SANITIZER( # endif // SENTRY_HAS_UIKIT } -- (SentryANRTrackerV2 *)getANRTrackerV2:(NSTimeInterval)timeout - SENTRY_DISABLE_THREAD_SANITIZER("double-checked lock produce false alarms") -{ -# if SENTRY_HAS_UIKIT - if (_anrTrackerV2 == nil) { - @synchronized(sentryDependencyContainerLock) { - if (_anrTrackerV2 == nil) { - _anrTrackerV2 = - [[SentryANRTrackerV2 alloc] initWithTimeoutInterval:timeout - crashWrapper:self.crashWrapper - dispatchQueueWrapper:self.dispatchQueueWrapper - threadWrapper:self.threadWrapper - framesTracker:self.framesTracker]; - } - } - } - - return _anrTrackerV2; -# else - SENTRY_LOG_DEBUG( - @"SentryDependencyContainer.getANRTrackerV2 only works with UIKit enabled. Ensure you're " - @"using the right configuration of Sentry that links UIKit."); - return nil; -# endif // SENTRY_HAS_UIKIT -} - - (SentrySwizzleWrapper *)swizzleWrapper SENTRY_DISABLE_THREAD_SANITIZER( "double-checked lock produce false alarms") { @@ -348,13 +329,13 @@ - (SentrySwizzleWrapper *)swizzleWrapper SENTRY_DISABLE_THREAD_SANITIZER( } #endif // SENTRY_UIKIT_AVAILABLE -- (SentryANRTrackerV1 *)getANRTrackerV1:(NSTimeInterval)timeout +- (id)getANRTracker:(NSTimeInterval)timeout SENTRY_DISABLE_THREAD_SANITIZER("double-checked lock produce false alarms") { - if (_anrTrackerV1 == nil) { + if (_anrTracker == nil) { @synchronized(sentryDependencyContainerLock) { - if (_anrTrackerV1 == nil) { - _anrTrackerV1 = + if (_anrTracker == nil) { + _anrTracker = [[SentryANRTrackerV1 alloc] initWithTimeoutInterval:timeout crashWrapper:self.crashWrapper dispatchQueueWrapper:self.dispatchQueueWrapper @@ -363,8 +344,34 @@ - (SentryANRTrackerV1 *)getANRTrackerV1:(NSTimeInterval)timeout } } - return _anrTrackerV1; + return _anrTracker; +} + +#if SENTRY_HAS_UIKIT +- (id)getANRTracker:(NSTimeInterval)timeout + isV2Enabled:(BOOL)isV2Enabled + SENTRY_DISABLE_THREAD_SANITIZER("double-checked lock produce false alarms") +{ + if (isV2Enabled) { + if (_anrTracker == nil) { + @synchronized(sentryDependencyContainerLock) { + if (_anrTracker == nil) { + _anrTracker = [[SentryANRTrackerV2 alloc] + initWithTimeoutInterval:timeout + crashWrapper:self.crashWrapper + dispatchQueueWrapper:self.dispatchQueueWrapper + threadWrapper:self.threadWrapper + framesTracker:self.framesTracker]; + } + } + } + + return _anrTracker; + } else { + return [self getANRTracker:timeout]; + } } +#endif // SENTRY_HAS_UIKIT - (SentryNSProcessInfoWrapper *)processInfoWrapper SENTRY_DISABLE_THREAD_SANITIZER( "double-checked lock produce false alarms") diff --git a/Sources/Sentry/SentryEvent.m b/Sources/Sentry/SentryEvent.m index 2408c73a7e5..c5478226b18 100644 --- a/Sources/Sentry/SentryEvent.m +++ b/Sources/Sentry/SentryEvent.m @@ -204,7 +204,8 @@ - (BOOL)isMetricKitEvent - (BOOL)isAppHangEvent { return self.exceptions.count == 1 && - [self.exceptions.firstObject.type isEqualToString:SentryANRExceptionType]; + [SentryAppHangTypeMapper + isExceptionTypeAppHangWithExceptionType:self.exceptions.firstObject.type]; } @end diff --git a/Sources/Sentry/SentryOptions.m b/Sources/Sentry/SentryOptions.m index b0da7a19088..554eb37b43b 100644 --- a/Sources/Sentry/SentryOptions.m +++ b/Sources/Sentry/SentryOptions.m @@ -118,10 +118,11 @@ - (instancetype)init self.enableUserInteractionTracing = YES; self.idleTimeout = SentryTracerDefaultTimeout; self.enablePreWarmedAppStartTracing = NO; + self.enableAppHangTrackingV2 = NO; + self.enableReportNonFullyBlockingAppHangs = YES; #endif // SENTRY_HAS_UIKIT self.enableAppHangTracking = YES; self.appHangTimeoutInterval = 2.0; - self.enableAppHangTrackingV2 = NO; self.enableAutoBreadcrumbTracking = YES; self.enableNetworkTracking = YES; self.enableFileIOTracing = YES; @@ -435,6 +436,12 @@ - (BOOL)validateOptions:(NSDictionary *)options [self setBool:options[@"enablePreWarmedAppStartTracing"] block:^(BOOL value) { self->_enablePreWarmedAppStartTracing = value; }]; + [self setBool:options[@"enableAppHangTrackingV2"] + block:^(BOOL value) { self->_enableAppHangTrackingV2 = value; }]; + + [self setBool:options[@"enableReportNonFullyBlockingAppHangs"] + block:^(BOOL value) { self->_enableReportNonFullyBlockingAppHangs = value; }]; + #endif // SENTRY_HAS_UIKIT [self setBool:options[@"enableAppHangTracking"] @@ -444,9 +451,6 @@ - (BOOL)validateOptions:(NSDictionary *)options self.appHangTimeoutInterval = [options[@"appHangTimeoutInterval"] doubleValue]; } - [self setBool:options[@"enableAppHangTrackingV2"] - block:^(BOOL value) { self->_enableAppHangTrackingV2 = value; }]; - [self setBool:options[@"enableNetworkTracking"] block:^(BOOL value) { self->_enableNetworkTracking = value; }]; diff --git a/Sources/Sentry/SentryWatchdogTerminationTrackingIntegration.m b/Sources/Sentry/SentryWatchdogTerminationTrackingIntegration.m index 850530b12d2..65a8126cbde 100644 --- a/Sources/Sentry/SentryWatchdogTerminationTrackingIntegration.m +++ b/Sources/Sentry/SentryWatchdogTerminationTrackingIntegration.m @@ -74,7 +74,8 @@ - (BOOL)installWithOptions:(SentryOptions *)options [self.tracker start]; self.anrTracker = - [SentryDependencyContainer.sharedInstance getANRTrackerV1:options.appHangTimeoutInterval]; + [SentryDependencyContainer.sharedInstance getANRTracker:options.appHangTimeoutInterval + isV2Enabled:options.enableAppHangTrackingV2]; [self.anrTracker addListener:self]; self.appStateManager = appStateManager; diff --git a/Sources/Sentry/include/HybridPublic/SentryDependencyContainer.h b/Sources/Sentry/include/HybridPublic/SentryDependencyContainer.h index e9e817f1aa5..2a2cf867c42 100644 --- a/Sources/Sentry/include/HybridPublic/SentryDependencyContainer.h +++ b/Sources/Sentry/include/HybridPublic/SentryDependencyContainer.h @@ -1,7 +1,6 @@ #import "SentryDefines.h" -@class SentryANRTrackerV1; -@class SentryANRTrackerV2; +@protocol SentryANRTracker; @class SentryAppStateManager; @class SentryBinaryImageCache; @class SentryCrash; @@ -63,8 +62,6 @@ SENTRY_NO_INIT @property (nonatomic, strong) SentryDispatchQueueWrapper *dispatchQueueWrapper; @property (nonatomic, strong) SentryNSNotificationCenterWrapper *notificationCenterWrapper; @property (nonatomic, strong) SentryDebugImageProvider *debugImageProvider; -@property (nonatomic, strong) SentryANRTrackerV1 *anrTrackerV1; -@property (nonatomic, strong) SentryANRTrackerV2 *anrTrackerV2; @property (nonatomic, strong) SentryNSProcessInfoWrapper *processInfoWrapper; @property (nonatomic, strong) SentrySystemWrapper *systemWrapper; @property (nonatomic, strong) SentryDispatchFactory *dispatchFactory; @@ -90,10 +87,10 @@ SENTRY_NO_INIT @property (nonatomic, strong) SentryReachability *reachability; #endif // !TARGET_OS_WATCH -- (SentryANRTrackerV1 *)getANRTrackerV1:(NSTimeInterval)timeout; -#if SENTRY_UIKIT_AVAILABLE -- (SentryANRTrackerV2 *)getANRTrackerV2:(NSTimeInterval)timeout; -#endif // SENTRY_UIKIT_AVAILABLE +- (id)getANRTracker:(NSTimeInterval)timeout; +#if SENTRY_HAS_UIKIT +- (id)getANRTracker:(NSTimeInterval)timeout isV2Enabled:(BOOL)isV2Enabled; +#endif // SENTRY_HAS_UIKIT #if SENTRY_HAS_METRIC_KIT @property (nonatomic, strong) SentryMXManager *metricKitManager API_AVAILABLE( diff --git a/Sources/Sentry/include/SentryBaseIntegration.h b/Sources/Sentry/include/SentryBaseIntegration.h index fa803291fc3..c8adad48fc9 100644 --- a/Sources/Sentry/include/SentryBaseIntegration.h +++ b/Sources/Sentry/include/SentryBaseIntegration.h @@ -23,7 +23,6 @@ typedef NS_OPTIONS(NSUInteger, SentryIntegrationOption) { kIntegrationOptionEnableCrashHandler = 1 << 16, kIntegrationOptionEnableMetricKit = 1 << 17, kIntegrationOptionEnableReplay = 1 << 18, - kIntegrationOptionEnableAppHangTrackingV2 = 1 << 19, }; @class SentryOptions; diff --git a/Sources/Sentry/include/SentryOptions+Private.h b/Sources/Sentry/include/SentryOptions+Private.h index 1b6db177db1..b9c74a20e24 100644 --- a/Sources/Sentry/include/SentryOptions+Private.h +++ b/Sources/Sentry/include/SentryOptions+Private.h @@ -32,8 +32,6 @@ FOUNDATION_EXPORT NSString *const kSentryDefaultEnvironment; SENTRY_EXTERN BOOL sentry_isValidSampleRate(NSNumber *sampleRate); -@property (nonatomic, assign) BOOL enableAppHangTrackingV2; - @end NS_ASSUME_NONNULL_END diff --git a/Sources/Swift/Integrations/ANR/SentryANRTrackerV2Delegate.swift b/Sources/Swift/Integrations/ANR/SentryANRTrackerV2Delegate.swift index 670c65df7f7..92849ff581a 100644 --- a/Sources/Swift/Integrations/ANR/SentryANRTrackerV2Delegate.swift +++ b/Sources/Swift/Integrations/ANR/SentryANRTrackerV2Delegate.swift @@ -6,10 +6,3 @@ protocol SentryANRTrackerDelegate { func anrDetected(type: SentryANRType) func anrStopped() } - -@objc -enum SentryANRType: Int { - case fullyBlocking - case nonFullyBlocking - case unknown -} diff --git a/Sources/Swift/Integrations/ANR/SentryANRType.swift b/Sources/Swift/Integrations/ANR/SentryANRType.swift new file mode 100644 index 00000000000..46ee99d65f6 --- /dev/null +++ b/Sources/Swift/Integrations/ANR/SentryANRType.swift @@ -0,0 +1,33 @@ +@objc +enum SentryANRType: Int { + case fullyBlocking + case nonFullyBlocking + case unknown +} + +@objc +class SentryAppHangTypeMapper: NSObject { + + private enum ExceptionType: String { + case fullyBlocking = "App Hanging Fully Blocked" + case nonFullyBlocking = "App Hanging Non Fully Blocked" + case unknown = "App Hanging" + } + + @objc + static func getExceptionType(anrType: SentryANRType) -> String { + switch anrType { + case .fullyBlocking: + return ExceptionType.fullyBlocking.rawValue + case .nonFullyBlocking: + return ExceptionType.nonFullyBlocking.rawValue + default: + return ExceptionType.unknown.rawValue + } + } + + @objc + static func isExceptionTypeAppHang(exceptionType: String) -> Bool { + return ExceptionType(rawValue: exceptionType) != nil + } +} diff --git a/Tests/SentryTests/Helper/SentryDependencyContainerTests.swift b/Tests/SentryTests/Helper/SentryDependencyContainerTests.swift index db16f407935..148c594e940 100644 --- a/Tests/SentryTests/Helper/SentryDependencyContainerTests.swift +++ b/Tests/SentryTests/Helper/SentryDependencyContainerTests.swift @@ -1,8 +1,8 @@ import XCTest -#if os(iOS) || os(tvOS) || targetEnvironment(macCatalyst) final class SentryDependencyContainerTests: XCTestCase { - + +#if os(iOS) || os(tvOS) || targetEnvironment(macCatalyst) func testReset_CallsFramesTrackerStop() throws { let framesTracker = SentryDependencyContainer.sharedInstance().framesTracker framesTracker.start() @@ -10,5 +10,49 @@ final class SentryDependencyContainerTests: XCTestCase { XCTAssertFalse(framesTracker.isRunning) } -} + + func testGetANRTrackerV2() { + let instance = SentryDependencyContainer.sharedInstance().getANRTracker(2.0, isV2Enabled: true) + XCTAssertTrue(instance is SentryANRTrackerV2) + + SentryDependencyContainer.reset() + + } + + func testGetANRTrackerV1() { + let instance = SentryDependencyContainer.sharedInstance().getANRTracker(2.0, isV2Enabled: false) + XCTAssertTrue(instance is SentryANRTrackerV1) + + SentryDependencyContainer.reset() + } + + func testGetANRTrackerV2AndThenV1_FirstCalledVersionStaysTheSame() { + let instance1 = SentryDependencyContainer.sharedInstance().getANRTracker(2.0, isV2Enabled: true) + XCTAssertTrue(instance1 is SentryANRTrackerV2) + + let instance2 = SentryDependencyContainer.sharedInstance().getANRTracker(2.0, isV2Enabled: false) + XCTAssertTrue(instance2 is SentryANRTrackerV2) + + SentryDependencyContainer.reset() + } + + func testGetANRTrackerV1AndThenV2_FirstCalledVersionStaysTheSame() { + let instance1 = SentryDependencyContainer.sharedInstance().getANRTracker(2.0, isV2Enabled: false) + XCTAssertTrue(instance1 is SentryANRTrackerV1) + + let instance2 = SentryDependencyContainer.sharedInstance().getANRTracker(2.0, isV2Enabled: true) + XCTAssertTrue(instance2 is SentryANRTrackerV1) + + SentryDependencyContainer.reset() + } + #endif + + func testGetANRTracker_ReturnsV1() { + + let instance = SentryDependencyContainer.sharedInstance().getANRTracker(2.0) + XCTAssertTrue(instance is SentryANRTrackerV1) + + SentryDependencyContainer.reset() + } +} diff --git a/Tests/SentryTests/Integrations/ANR/SentryANRTrackingIntegrationTests.swift b/Tests/SentryTests/Integrations/ANR/SentryANRTrackingIntegrationTests.swift index 63602ec816c..9c15e6606c5 100644 --- a/Tests/SentryTests/Integrations/ANR/SentryANRTrackingIntegrationTests.swift +++ b/Tests/SentryTests/Integrations/ANR/SentryANRTrackingIntegrationTests.swift @@ -41,7 +41,10 @@ class SentryANRTrackingIntegrationTests: SentrySDKIntegrationTestsBase { func testWhenNoDebuggerAttached_TrackerInitialized() { givenInitializedTracker() - XCTAssertNotNil(Dynamic(sut).tracker.asAnyObject) + + let tracker = Dynamic(sut).tracker.asAnyObject + XCTAssertNotNil(tracker) + XCTAssertTrue(tracker is SentryANRTrackerV1) } func test_enableAppHangsTracking_Disabled() { @@ -65,6 +68,22 @@ class SentryANRTrackingIntegrationTests: SentrySDKIntegrationTestsBase { XCTAssertFalse(result) } +#if os(iOS) || os(tvOS) || targetEnvironment(macCatalyst) + func test_enableAppHangTrackingV2_UsesV2Tracker() { + let options = Options() + options.enableAppHangTracking = true + options.enableAppHangTrackingV2 = true + + sut = SentryANRTrackingIntegration() + let result = sut.install(with: options) + XCTAssertTrue(result) + + let tracker = Dynamic(sut).tracker.asAnyObject + XCTAssertNotNil(tracker) + XCTAssertTrue(tracker is SentryANRTrackerV2) + } +#endif + func testANRDetected_EventCaptured() throws { givenInitializedTracker() setUpThreadInspector() @@ -102,6 +121,92 @@ class SentryANRTrackingIntegrationTests: SentrySDKIntegrationTestsBase { } } + func testANRDetected_FullyBlocking_EventCaptured() throws { + givenInitializedTracker() + setUpThreadInspector() + + Dynamic(sut).anrDetectedWithType(SentryANRType.fullyBlocking) + + try assertEventWithScopeCaptured { event, _, _ in + XCTAssertNotNil(event) + guard let ex = event?.exceptions?.first else { + XCTFail("ANR Exception not found") + return + } + + XCTAssertEqual(ex.mechanism?.type, "AppHang") + XCTAssertEqual(ex.type, "App Hanging Fully Blocked") + XCTAssertEqual(ex.value, "App hanging for at least 4500 ms.") + XCTAssertNotNil(ex.stacktrace) + XCTAssertEqual(ex.stacktrace?.frames.first?.function, "main") + XCTAssertEqual(ex.stacktrace?.snapshot?.boolValue, true) + XCTAssertEqual(try XCTUnwrap(event?.threads?.first).current?.boolValue, true) + XCTAssertEqual(event?.isAppHangEvent, true) + + guard let threads = event?.threads else { + XCTFail("ANR Exception not found") + return + } + + // Sometimes during tests its possible to have one thread without frames + // We just need to make sure we retrieve frame information for at least one other thread than the main thread + let threadsWithFrames = threads.filter { + ($0.stacktrace?.frames.count ?? 0) >= 1 + }.count + + XCTAssertTrue(threadsWithFrames > 1, "Not enough threads with frames") + } + } + + func testANRDetected_NonFullyBlocked_EventCaptured() throws { + givenInitializedTracker() + setUpThreadInspector() + + Dynamic(sut).anrDetectedWithType(SentryANRType.nonFullyBlocking) + + try assertEventWithScopeCaptured { event, _, _ in + XCTAssertNotNil(event) + guard let ex = event?.exceptions?.first else { + XCTFail("ANR Exception not found") + return + } + + XCTAssertEqual(ex.mechanism?.type, "AppHang") + XCTAssertEqual(ex.type, "App Hanging Non Fully Blocked") + XCTAssertEqual(ex.value, "App hanging for at least 4500 ms.") + XCTAssertNotNil(ex.stacktrace) + XCTAssertEqual(ex.stacktrace?.frames.first?.function, "main") + XCTAssertEqual(ex.stacktrace?.snapshot?.boolValue, true) + XCTAssertEqual(try XCTUnwrap(event?.threads?.first).current?.boolValue, true) + XCTAssertEqual(event?.isAppHangEvent, true) + + guard let threads = event?.threads else { + XCTFail("ANR Exception not found") + return + } + + // Sometimes during tests its possible to have one thread without frames + // We just need to make sure we retrieve frame information for at least one other thread than the main thread + let threadsWithFrames = threads.filter { + ($0.stacktrace?.frames.count ?? 0) >= 1 + }.count + + XCTAssertTrue(threadsWithFrames > 1, "Not enough threads with frames") + } + } + +#if os(iOS) || os(tvOS) || targetEnvironment(macCatalyst) + func testANRDetected_NonFullyBlockedDisabled_EventNotCaptured() throws { + fixture.options.enableReportNonFullyBlockingAppHangs = false + givenInitializedTracker() + setUpThreadInspector() + + Dynamic(sut).anrDetectedWithType(SentryANRType.nonFullyBlocking) + + assertNoEventCaptured() + } +#endif + func testANRDetected_DetectingPaused_NoEventCaptured() { givenInitializedTracker() setUpThreadInspector() @@ -151,6 +256,7 @@ class SentryANRTrackingIntegrationTests: SentrySDKIntegrationTestsBase { assertNoEventCaptured() } + #if os(iOS) || os(tvOS) || targetEnvironment(macCatalyst) func testANRDetected_ButBackground_EventNotCaptured() { @@ -180,7 +286,7 @@ class SentryANRTrackingIntegrationTests: SentrySDKIntegrationTestsBase { initIntegration() - let tracker = SentryDependencyContainer.sharedInstance().getANRTrackerV1(self.options.appHangTimeoutInterval) + let tracker = SentryDependencyContainer.sharedInstance().getANRTracker(self.options.appHangTimeoutInterval) let listeners = Dynamic(tracker).listeners.asObject as? NSHashTable diff --git a/Tests/SentryTests/SentryOptionsTest.m b/Tests/SentryTests/SentryOptionsTest.m index 07bc316848c..14925b4a48f 100644 --- a/Tests/SentryTests/SentryOptionsTest.m +++ b/Tests/SentryTests/SentryOptionsTest.m @@ -916,11 +916,20 @@ - (void)testEnableAppHangTracking [self testBooleanField:@"enableAppHangTracking" defaultValue:YES]; } +#if SENTRY_UIKIT_AVAILABLE + - (void)testEnableAppHangTrackingV2 { [self testBooleanField:@"enableAppHangTrackingV2" defaultValue:NO]; } +- (void)testEnableReportNonFullyBlockingAppHangs +{ + [self testBooleanField:@"enableReportNonFullyBlockingAppHangs" defaultValue:YES]; +} + +#endif // SENTRY_UIKIT_AVAILABLE + - (void)testDefaultAppHangsTimeout { SentryOptions *options = [self getValidOptions:@{}]; From 51e8cc09c10404d21f892839a7d870dfde426692 Mon Sep 17 00:00:00 2001 From: Dhiogo Brustolin Date: Tue, 8 Oct 2024 08:35:00 +0200 Subject: [PATCH 10/36] chore: Run benchmark workflow if build script changed (#4400) We broke main CI because of this. Any change in the build script should also run benchmark workflow. And I small improvement in performance --- .github/workflows/benchmarking.yml | 3 +- scripts/build-xcframework.sh | 66 +++++++++++++++++------------- 2 files changed, 40 insertions(+), 29 deletions(-) diff --git a/.github/workflows/benchmarking.yml b/.github/workflows/benchmarking.yml index a36fb53525c..19a800574fd 100644 --- a/.github/workflows/benchmarking.yml +++ b/.github/workflows/benchmarking.yml @@ -16,6 +16,7 @@ on: - '.sauce/benchmarking-config.yml' - 'fastlane/**' - 'scripts/ci-select-xcode.sh' + - 'scripts/build-xcframework.sh' # https://docs.github.com/en/actions/using-jobs/using-concurrency#example-using-a-fallback-value concurrency: @@ -125,7 +126,7 @@ jobs: MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }} MATCH_USERNAME: ${{ secrets.MATCH_USERNAME }} - name: Build Framework - run: make build-xcframework + run: ./scripts/build-xcframework.sh iOSOnly - name: Archive build log if failed uses: actions/upload-artifact@v4 diff --git a/scripts/build-xcframework.sh b/scripts/build-xcframework.sh index 85bab81b972..b108d743baf 100755 --- a/scripts/build-xcframework.sh +++ b/scripts/build-xcframework.sh @@ -2,7 +2,13 @@ set -eou pipefail -sdks=( iphoneos iphonesimulator macosx appletvos appletvsimulator watchos watchsimulator xros xrsimulator ) +args="${1:-}" + +if [ "$args" = "iOSOnly" ]; then + sdks=( iphoneos iphonesimulator ) +else + sdks=( iphoneos iphonesimulator macosx appletvos appletvsimulator watchos watchsimulator xros xrsimulator ) +fi rm -rf Carthage/ mkdir Carthage @@ -81,31 +87,33 @@ generate_xcframework() { OTHER_LDFLAGS="-Wl,-make_mergeable" fi - #Create framework for mac catalyst - xcodebuild \ - -project Sentry.xcodeproj/ \ - -scheme "$scheme" \ - -configuration "$resolved_configuration" \ - -sdk iphoneos \ - -destination 'platform=macOS,variant=Mac Catalyst' \ - -derivedDataPath ./Carthage/DerivedData \ - CODE_SIGNING_REQUIRED=NO \ - CODE_SIGN_IDENTITY= \ - CARTHAGE=YES \ - MACH_O_TYPE="$MACH_O_TYPE" \ - SUPPORTS_MACCATALYST=YES \ - ENABLE_CODE_COVERAGE=NO \ - GCC_GENERATE_DEBUGGING_SYMBOLS="$GCC_GENERATE_DEBUGGING_SYMBOLS" \ - OTHER_LDFLAGS="$OTHER_LDFLAGS" + if [ "$args" != "iOSOnly" ]; then + #Create framework for mac catalyst + xcodebuild \ + -project Sentry.xcodeproj/ \ + -scheme "$scheme" \ + -configuration "$resolved_configuration" \ + -sdk iphoneos \ + -destination 'platform=macOS,variant=Mac Catalyst' \ + -derivedDataPath ./Carthage/DerivedData \ + CODE_SIGNING_REQUIRED=NO \ + CODE_SIGN_IDENTITY= \ + CARTHAGE=YES \ + MACH_O_TYPE="$MACH_O_TYPE" \ + SUPPORTS_MACCATALYST=YES \ + ENABLE_CODE_COVERAGE=NO \ + GCC_GENERATE_DEBUGGING_SYMBOLS="$GCC_GENERATE_DEBUGGING_SYMBOLS" \ + OTHER_LDFLAGS="$OTHER_LDFLAGS" - if [ "$MACH_O_TYPE" = "staticlib" ]; then - local infoPlist="Carthage/DerivedData/Build/Products/$resolved_configuration-maccatalyst/${scheme}.framework/Resources/Info.plist" - plutil -replace "MinimumOSVersion" -string "100.0" "$infoPlist" - fi - - createxcframework+="-framework Carthage/DerivedData/Build/Products/$resolved_configuration-maccatalyst/${resolved_product_name}.framework " - if [ -d "Carthage/DerivedData/Build/Products/$resolved_configuration-maccatalyst/${resolved_product_name}.framework.dSYM" ]; then - createxcframework+="-debug-symbols $(pwd -P)/Carthage/DerivedData/Build/Products/$resolved_configuration-maccatalyst/${resolved_product_name}.framework.dSYM " + if [ "$MACH_O_TYPE" = "staticlib" ]; then + local infoPlist="Carthage/DerivedData/Build/Products/$resolved_configuration-maccatalyst/${scheme}.framework/Resources/Info.plist" + plutil -replace "MinimumOSVersion" -string "100.0" "$infoPlist" + fi + + createxcframework+="-framework Carthage/DerivedData/Build/Products/$resolved_configuration-maccatalyst/${resolved_product_name}.framework " + if [ -d "Carthage/DerivedData/Build/Products/$resolved_configuration-maccatalyst/${resolved_product_name}.framework.dSYM" ]; then + createxcframework+="-debug-symbols $(pwd -P)/Carthage/DerivedData/Build/Products/$resolved_configuration-maccatalyst/${resolved_product_name}.framework.dSYM " + fi fi createxcframework+="-output Carthage/${scheme}${suffix}.xcframework" @@ -114,8 +122,10 @@ generate_xcframework() { generate_xcframework "Sentry" "-Dynamic" -generate_xcframework "Sentry" "" staticlib +if [ "$args" != "iOSOnly" ]; then + generate_xcframework "Sentry" "" staticlib -generate_xcframework "SentrySwiftUI" + generate_xcframework "SentrySwiftUI" -generate_xcframework "Sentry" "-WithoutUIKitOrAppKit" mh_dylib WithoutUIKit + generate_xcframework "Sentry" "-WithoutUIKitOrAppKit" mh_dylib WithoutUIKit +fi From 5b7961d453e86d770e6f0cb0303565b3292a9e7d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 8 Oct 2024 08:44:41 +0200 Subject: [PATCH 11/36] chore(deps): bump fastlane from 2.223.1 to 2.224.0 (#4412) Bumps [fastlane](https://github.com/fastlane/fastlane) from 2.223.1 to 2.224.0. - [Release notes](https://github.com/fastlane/fastlane/releases) - [Changelog](https://github.com/fastlane/fastlane/blob/master/CHANGELOG.latest.md) - [Commits](https://github.com/fastlane/fastlane/compare/fastlane/2.223.1...fastlane/2.224.0) --- updated-dependencies: - dependency-name: fastlane dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Gemfile.lock | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 06189f19ef2..33df5df7291 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -24,7 +24,7 @@ GEM artifactory (3.0.17) atomos (0.1.3) aws-eventstream (1.3.0) - aws-partitions (1.981.0) + aws-partitions (1.986.0) aws-sdk-core (3.209.1) aws-eventstream (~> 1, >= 1.3.0) aws-partitions (~> 1, >= 1.651.0) @@ -33,7 +33,7 @@ GEM aws-sdk-kms (1.94.0) aws-sdk-core (~> 3, >= 3.207.0) aws-sigv4 (~> 1.5) - aws-sdk-s3 (1.166.0) + aws-sdk-s3 (1.167.0) aws-sdk-core (~> 3, >= 3.207.0) aws-sdk-kms (~> 1) aws-sigv4 (~> 1.5) @@ -97,7 +97,7 @@ GEM escape (0.0.4) ethon (0.16.0) ffi (>= 1.15.0) - excon (0.111.0) + excon (0.112.0) faraday (1.10.4) faraday-em_http (~> 1.0) faraday-em_synchrony (~> 1.0) @@ -127,7 +127,7 @@ GEM faraday_middleware (1.2.1) faraday (~> 1.0) fastimage (2.3.1) - fastlane (2.223.1) + fastlane (2.224.0) CFPropertyList (>= 2.3, < 4.0.0) addressable (>= 2.8, < 3.0.0) artifactory (~> 3.0) @@ -219,7 +219,7 @@ GEM concurrent-ruby (~> 1.0) jmespath (1.6.2) json (2.7.2) - jwt (2.9.1) + jwt (2.9.3) base64 logger (1.6.1) mime-types (3.5.1) @@ -292,13 +292,13 @@ GEM uber (0.1.0) unicode-display_width (2.6.0) word_wrap (1.0.0) - xcodeproj (1.25.0) + xcodeproj (1.25.1) CFPropertyList (>= 2.3.3, < 4.0) atomos (~> 0.1.3) claide (>= 1.0.2, < 2.0) colored2 (~> 3.1) nanaimo (~> 0.3.0) - rexml (>= 3.3.2, < 4.0) + rexml (>= 3.3.6, < 4.0) xcpretty (0.3.0) rouge (~> 2.0.7) xcpretty-travis-formatter (1.0.1) From 0f8dc8883f038344b211921f5b59249623f9a752 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 8 Oct 2024 08:44:58 +0200 Subject: [PATCH 12/36] chore(deps): bump codecov/codecov-action from 4.5.0 to 4.6.0 (#4411) Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from 4.5.0 to 4.6.0. - [Release notes](https://github.com/codecov/codecov-action/releases) - [Changelog](https://github.com/codecov/codecov-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/codecov/codecov-action/compare/e28ff129e5465c2c0dcc6f003fc735cb6ae0c673...b9fd7d16f6d7d1b5d2bec1a2887e65ceed900238) --- updated-dependencies: - dependency-name: codecov/codecov-action dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/test.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 455a7137b2c..94284f9592e 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -203,7 +203,7 @@ jobs: # We don't upload codecov for scheduled runs as CodeCov only accepts a limited amount of uploads per commit. - name: Push code coverage to codecov id: codecov_1 - uses: codecov/codecov-action@e28ff129e5465c2c0dcc6f003fc735cb6ae0c673 # pin@v4.5.0 + uses: codecov/codecov-action@b9fd7d16f6d7d1b5d2bec1a2887e65ceed900238 # pin@v4.6.0 if: ${{ contains(matrix.platform, 'iOS') && !contains(github.ref, 'release') && github.event.schedule == '' }} with: # Although public repos should not have to specify a token there seems to be a bug with the Codecov GH action, which can @@ -215,7 +215,7 @@ jobs: # Sometimes codecov uploads etc can fail. Retry one time to rule out e.g. intermittent network failures. - name: Push code coverage to codecov id: codecov_2 - uses: codecov/codecov-action@e28ff129e5465c2c0dcc6f003fc735cb6ae0c673 # pin@v4.5.0 + uses: codecov/codecov-action@b9fd7d16f6d7d1b5d2bec1a2887e65ceed900238 # pin@v4.6.0 if: ${{ steps.codecov_1.outcome == 'failure' && contains(matrix.platform, 'iOS') && !contains(github.ref, 'release') && github.event.schedule == '' }} with: token: ${{ secrets.CODECOV_TOKEN }} From 4c03b87395bab848a0bc3270a502ab0420eaa8c2 Mon Sep 17 00:00:00 2001 From: Karl Heinz Struggl Date: Tue, 8 Oct 2024 02:04:20 -0700 Subject: [PATCH 13/36] chore(readme): Add info about updated release channels (#4410) --- README.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/README.md b/README.md index aa308219978..667f923caba 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,14 @@ _Bad software is everywhere, and we're tired of it. Sentry is on a mission to he This SDK is written in Objective-C but also provides a nice Swift interface. +# Releases + +This repo uses the following ways to release SDK updates: + +- `Pre-release`: We create pre-releases (alpha, beta, RC,…) for larger and potentially more impactful changes, such as new features or major versions. +- `Latest`: We continuously release major/minor/hotfix versions from the `main` branch. These releases go through all our internal quality gates and are very safe to use and intended to be the default for most teams. +- `Stable`: We promote releases from `Latest` when they have been used in the field for some time and in scale, considering time since release, adoption, and other quality and stability metrics. These releases will be indicated on the [releases page](https://github.com/getsentry/sentry-cocoa/releases/) with the `Stable` suffix. + **Where is the master branch?** We renamed the default branch from `master` to `main`. From 913fab76543f3893b2e1f620546aa2e0636c3102 Mon Sep 17 00:00:00 2001 From: Philipp Hofmann Date: Tue, 8 Oct 2024 14:40:56 +0200 Subject: [PATCH 14/36] chore: Remove deprecated experimental MetricsAPI (#4406) After October 7th, the Metrics page and all adjacent functionality will be removed from Sentry and no longer be accessible. Any metrics sent after this date will not be accepted. Therefore, we can remove the metrics API entirely from the SDK. --- CHANGELOG.md | 6 + Samples/iOS-Swift/iOS-Swift/AppDelegate.swift | 1 - Sentry.xcodeproj/project.pbxproj | 105 ---- Sources/Sentry/Public/SentryOptions.h | 32 -- Sources/Sentry/SentryHub.m | 53 +-- Sources/Sentry/SentryOptions.m | 15 - Sources/Sentry/SentrySpan.m | 13 - Sources/Sentry/SentryStatsdClient.m | 51 -- Sources/Sentry/SentryTransaction.m | 12 +- Sources/Sentry/include/SentryPrivate.h | 1 - Sources/Sentry/include/SentrySpan.h | 3 - Sources/Sentry/include/SentryStatsdClient.h | 16 - .../Helper/SentryEnabledFeaturesBuilder.swift | 4 - .../Metrics/BucketsMetricsAggregator.swift | 241 ---------- Sources/Swift/Metrics/CounterMetric.swift | 20 - .../Swift/Metrics/DistributionMetric.swift | 22 - Sources/Swift/Metrics/EncodeMetrics.swift | 72 --- Sources/Swift/Metrics/GaugeMetric.swift | 34 -- .../Metrics/LocalMetricsAggregator.swift | 70 --- Sources/Swift/Metrics/Metric.swift | 31 -- Sources/Swift/Metrics/MetricsAggregator.swift | 48 -- Sources/Swift/Metrics/SentryMetricsAPI.swift | 148 ------ .../Swift/Metrics/SentryMetricsClient.swift | 19 - Sources/Swift/Metrics/SetMetric.swift | 22 - .../SentryEnabledFeaturesBuilderTests.swift | 2 - Tests/SentryTests/SentryHubTests.swift | 124 ----- Tests/SentryTests/SentryOptionsTest.m | 36 -- Tests/SentryTests/SentrySDKTests.swift | 41 -- .../BucketMetricsAggregatorTests.swift | 450 ------------------ .../Swift/Metrics/CounterMetricTests.swift | 34 -- .../Metrics/DistributionMetricTests.swift | 34 -- .../Swift/Metrics/EncodeMetricTests.swift | 117 ----- .../Swift/Metrics/GaugeMetricTests.swift | 44 -- .../Metrics/LocalMetricsAggregatorTests.swift | 110 ----- .../Swift/Metrics/SentryMetricsAPITests.swift | 247 ---------- .../Metrics/SentryMetricsClientTests.swift | 44 -- .../Swift/Metrics/SetMetricTests.swift | 48 -- .../Swift/Metrics/TestMetricsClient.swift | 22 - .../Transaction/SentrySpanTests.swift | 27 -- .../Transaction/SentryTransactionTests.swift | 19 - 40 files changed, 8 insertions(+), 2430 deletions(-) delete mode 100644 Sources/Sentry/SentryStatsdClient.m delete mode 100644 Sources/Sentry/include/SentryStatsdClient.h delete mode 100644 Sources/Swift/Metrics/BucketsMetricsAggregator.swift delete mode 100644 Sources/Swift/Metrics/CounterMetric.swift delete mode 100644 Sources/Swift/Metrics/DistributionMetric.swift delete mode 100644 Sources/Swift/Metrics/EncodeMetrics.swift delete mode 100644 Sources/Swift/Metrics/GaugeMetric.swift delete mode 100644 Sources/Swift/Metrics/LocalMetricsAggregator.swift delete mode 100644 Sources/Swift/Metrics/Metric.swift delete mode 100644 Sources/Swift/Metrics/MetricsAggregator.swift delete mode 100644 Sources/Swift/Metrics/SentryMetricsAPI.swift delete mode 100644 Sources/Swift/Metrics/SentryMetricsClient.swift delete mode 100644 Sources/Swift/Metrics/SetMetric.swift delete mode 100644 Tests/SentryTests/Swift/Metrics/BucketMetricsAggregatorTests.swift delete mode 100644 Tests/SentryTests/Swift/Metrics/CounterMetricTests.swift delete mode 100644 Tests/SentryTests/Swift/Metrics/DistributionMetricTests.swift delete mode 100644 Tests/SentryTests/Swift/Metrics/EncodeMetricTests.swift delete mode 100644 Tests/SentryTests/Swift/Metrics/GaugeMetricTests.swift delete mode 100644 Tests/SentryTests/Swift/Metrics/LocalMetricsAggregatorTests.swift delete mode 100644 Tests/SentryTests/Swift/Metrics/SentryMetricsAPITests.swift delete mode 100644 Tests/SentryTests/Swift/Metrics/SentryMetricsClientTests.swift delete mode 100644 Tests/SentryTests/Swift/Metrics/SetMetricTests.swift delete mode 100644 Tests/SentryTests/Swift/Metrics/TestMetricsClient.swift diff --git a/CHANGELOG.md b/CHANGELOG.md index 1736a243191..8cc28363cdf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## Unreleased + +### Removal of Experimental API + +- Remove the deprecated experimental Metrics API (#4406): [Learn more](https://sentry.zendesk.com/hc/en-us/articles/26369339769883-Metrics-Beta-Coming-to-an-End) + ## 8.38.0-beta.1 ### Features diff --git a/Samples/iOS-Swift/iOS-Swift/AppDelegate.swift b/Samples/iOS-Swift/iOS-Swift/AppDelegate.swift index ff01f04398e..90163bef6df 100644 --- a/Samples/iOS-Swift/iOS-Swift/AppDelegate.swift +++ b/Samples/iOS-Swift/iOS-Swift/AppDelegate.swift @@ -95,7 +95,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate { #endif options.enableTimeToFullDisplayTracing = true options.enablePerformanceV2 = true - options.enableMetrics = !args.contains("--disable-metrics") options.add(inAppInclude: "iOS_External") diff --git a/Sentry.xcodeproj/project.pbxproj b/Sentry.xcodeproj/project.pbxproj index c64e8f922a8..2926e5df337 100644 --- a/Sentry.xcodeproj/project.pbxproj +++ b/Sentry.xcodeproj/project.pbxproj @@ -92,10 +92,6 @@ 62375FB92B47F9F000CC55F1 /* SentryDependencyContainerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62375FB82B47F9F000CC55F1 /* SentryDependencyContainerTests.swift */; }; 623C45B02A651D8200D9E88B /* SentryCoreDataTracker+Test.m in Sources */ = {isa = PBXBuildFile; fileRef = 623C45AF2A651D8200D9E88B /* SentryCoreDataTracker+Test.m */; }; 624688192C048EF10006179C /* SentryBaggageSerialization.swift in Sources */ = {isa = PBXBuildFile; fileRef = 624688182C048EF10006179C /* SentryBaggageSerialization.swift */; }; - 626866722BA89641006995EA /* MetricsAggregator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 626866712BA89641006995EA /* MetricsAggregator.swift */; }; - 626866742BA89683006995EA /* BucketMetricsAggregatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 626866732BA89683006995EA /* BucketMetricsAggregatorTests.swift */; }; - 626866762BA896AD006995EA /* TestMetricsClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 626866752BA896AD006995EA /* TestMetricsClient.swift */; }; - 626866782BA89928006995EA /* BucketsMetricsAggregator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 626866772BA89928006995EA /* BucketsMetricsAggregator.swift */; }; 626E2D4C2BEA0C37005596FE /* SentryEnabledFeaturesBuilderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 626E2D4B2BEA0C37005596FE /* SentryEnabledFeaturesBuilderTests.swift */; }; 6271ADF32BA06D9B0098D2E9 /* SentryInternalSerializable.h in Headers */ = {isa = PBXBuildFile; fileRef = 6271ADF22BA06D9B0098D2E9 /* SentryInternalSerializable.h */; }; 627E7589299F6FE40085504D /* SentryInternalDefines.h in Headers */ = {isa = PBXBuildFile; fileRef = 627E7588299F6FE40085504D /* SentryInternalDefines.h */; }; @@ -107,25 +103,13 @@ 6294774C2C6F255F00846CBC /* SentryANRTrackerV2Delegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6294774B2C6F255F00846CBC /* SentryANRTrackerV2Delegate.swift */; }; 62950F1029E7FE0100A42624 /* SentryTransactionContextTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62950F0F29E7FE0100A42624 /* SentryTransactionContextTests.swift */; }; 629690532AD3E060000185FA /* SentryReachabilitySwiftTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 629690522AD3E060000185FA /* SentryReachabilitySwiftTests.swift */; }; - 62991A8D2BAC1B4A0078A8B8 /* SentryMetricsAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62991A8C2BAC1B4A0078A8B8 /* SentryMetricsAPI.swift */; }; - 62991A8F2BAC24ED0078A8B8 /* SentryMetricsAPITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62991A8E2BAC24ED0078A8B8 /* SentryMetricsAPITests.swift */; }; - 62A2F43E2BA9AC10000C9FDD /* DistributionMetric.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62A2F43D2BA9AC10000C9FDD /* DistributionMetric.swift */; }; - 62A2F4402BA9AC93000C9FDD /* GaugeMetric.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62A2F43F2BA9AC93000C9FDD /* GaugeMetric.swift */; }; - 62A2F4422BA9AE12000C9FDD /* SetMetric.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62A2F4412BA9AE12000C9FDD /* SetMetric.swift */; }; - 62A2F4442BA9BF08000C9FDD /* GaugeMetricTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62A2F4432BA9BF08000C9FDD /* GaugeMetricTests.swift */; }; 62A3C7BE2B7E2A6A00C75227 /* SentrySpotlightTransportTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62A3C7BD2B7E2A6A00C75227 /* SentrySpotlightTransportTests.swift */; }; 62A456E12B03704A003F19A1 /* SentryUIEventTrackerMode.h in Headers */ = {isa = PBXBuildFile; fileRef = 62A456E02B03704A003F19A1 /* SentryUIEventTrackerMode.h */; }; 62A456E32B0370AA003F19A1 /* SentryUIEventTrackerTransactionMode.h in Headers */ = {isa = PBXBuildFile; fileRef = 62A456E22B0370AA003F19A1 /* SentryUIEventTrackerTransactionMode.h */; }; 62A456E52B0370E0003F19A1 /* SentryUIEventTrackerTransactionMode.m in Sources */ = {isa = PBXBuildFile; fileRef = 62A456E42B0370E0003F19A1 /* SentryUIEventTrackerTransactionMode.m */; }; 62AB8C9E2BF3925700BFC2AC /* WeakReference.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62AB8C9D2BF3925700BFC2AC /* WeakReference.swift */; }; - 62B0C30D2BA9D39600648D59 /* CounterMetricTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62B0C30C2BA9D39600648D59 /* CounterMetricTests.swift */; }; - 62B0C30F2BA9D74800648D59 /* DistributionMetricTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62B0C30E2BA9D74800648D59 /* DistributionMetricTests.swift */; }; - 62B0C3112BA9D85C00648D59 /* SetMetricTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62B0C3102BA9D85C00648D59 /* SetMetricTests.swift */; }; 62B558B02C6B9C3C00C34FEC /* SentryFramesDelayResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62B558AF2C6B9C3C00C34FEC /* SentryFramesDelayResult.swift */; }; 62B86CFC29F052BB008F3947 /* SentryTestLogConfig.m in Sources */ = {isa = PBXBuildFile; fileRef = 62B86CFB29F052BB008F3947 /* SentryTestLogConfig.m */; }; - 62BAD74E2BA1C58D00EBAAFC /* EncodeMetricTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62262B952BA1C564004DA3DD /* EncodeMetricTests.swift */; }; - 62BAD7502BA1C5AF00EBAAFC /* SentryMetricsClientTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62BAD74F2BA1C5AF00EBAAFC /* SentryMetricsClientTests.swift */; }; - 62BAD7572BA2033F00EBAAFC /* SentryMetricsClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62BAD7552BA202C300EBAAFC /* SentryMetricsClient.swift */; }; 62C1AFAB2B7E10EA0038C5F7 /* SentrySpotlightTransport.m in Sources */ = {isa = PBXBuildFile; fileRef = 62C1AFAA2B7E10EA0038C5F7 /* SentrySpotlightTransport.m */; }; 62C25C862B075F4900C68CBD /* TestOptions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62C25C852B075F4900C68CBD /* TestOptions.swift */; }; 62C316812B1F2E93000D7031 /* SentryDelayedFramesTracker.h in Headers */ = {isa = PBXBuildFile; fileRef = 62C316802B1F2E93000D7031 /* SentryDelayedFramesTracker.h */; }; @@ -137,8 +121,6 @@ 62CFD9AE2C99770B00834E1B /* SentryInvalidJSONString.m in Sources */ = {isa = PBXBuildFile; fileRef = 62CFD9AC2C99770B00834E1B /* SentryInvalidJSONString.m */; }; 62E081A929ED4260000F69FC /* SentryBreadcrumbDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = 62E081A829ED4260000F69FC /* SentryBreadcrumbDelegate.h */; }; 62E081AB29ED4322000F69FC /* SentryBreadcrumbTestDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62E081AA29ED4322000F69FC /* SentryBreadcrumbTestDelegate.swift */; }; - 62E146D02BAAE47600ED34FD /* LocalMetricsAggregator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62E146CF2BAAE47600ED34FD /* LocalMetricsAggregator.swift */; }; - 62E146D22BAAF55B00ED34FD /* LocalMetricsAggregatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62E146D12BAAF55B00ED34FD /* LocalMetricsAggregatorTests.swift */; }; 62EF86A12C626D39004E058B /* SentryANRTrackerV2Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 621AE74E2C626CF70012E730 /* SentryANRTrackerV2Tests.swift */; }; 62F05D2B2C0DB1F100916E3F /* SentryLogTestHelper.m in Sources */ = {isa = PBXBuildFile; fileRef = 62F05D2A2C0DB1F100916E3F /* SentryLogTestHelper.m */; }; 62F226B729A37C120038080D /* SentryBooleanSerialization.m in Sources */ = {isa = PBXBuildFile; fileRef = 62F226B629A37C120038080D /* SentryBooleanSerialization.m */; }; @@ -1103,10 +1085,6 @@ 623C45AE2A651C4500D9E88B /* SentryCoreDataTracker+Test.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "SentryCoreDataTracker+Test.h"; sourceTree = ""; }; 623C45AF2A651D8200D9E88B /* SentryCoreDataTracker+Test.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "SentryCoreDataTracker+Test.m"; sourceTree = ""; }; 624688182C048EF10006179C /* SentryBaggageSerialization.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryBaggageSerialization.swift; sourceTree = ""; }; - 626866712BA89641006995EA /* MetricsAggregator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MetricsAggregator.swift; sourceTree = ""; }; - 626866732BA89683006995EA /* BucketMetricsAggregatorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BucketMetricsAggregatorTests.swift; sourceTree = ""; }; - 626866752BA896AD006995EA /* TestMetricsClient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestMetricsClient.swift; sourceTree = ""; }; - 626866772BA89928006995EA /* BucketsMetricsAggregator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BucketsMetricsAggregator.swift; sourceTree = ""; }; 626E2D4B2BEA0C37005596FE /* SentryEnabledFeaturesBuilderTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryEnabledFeaturesBuilderTests.swift; sourceTree = ""; }; 6271ADF22BA06D9B0098D2E9 /* SentryInternalSerializable.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SentryInternalSerializable.h; path = include/SentryInternalSerializable.h; sourceTree = ""; }; 627E7588299F6FE40085504D /* SentryInternalDefines.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SentryInternalDefines.h; path = include/SentryInternalDefines.h; sourceTree = ""; }; @@ -1118,24 +1096,13 @@ 6294774B2C6F255F00846CBC /* SentryANRTrackerV2Delegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryANRTrackerV2Delegate.swift; sourceTree = ""; }; 62950F0F29E7FE0100A42624 /* SentryTransactionContextTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryTransactionContextTests.swift; sourceTree = ""; }; 629690522AD3E060000185FA /* SentryReachabilitySwiftTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryReachabilitySwiftTests.swift; sourceTree = ""; }; - 62991A8C2BAC1B4A0078A8B8 /* SentryMetricsAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryMetricsAPI.swift; sourceTree = ""; }; - 62991A8E2BAC24ED0078A8B8 /* SentryMetricsAPITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryMetricsAPITests.swift; sourceTree = ""; }; - 62A2F43D2BA9AC10000C9FDD /* DistributionMetric.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DistributionMetric.swift; sourceTree = ""; }; - 62A2F43F2BA9AC93000C9FDD /* GaugeMetric.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GaugeMetric.swift; sourceTree = ""; }; - 62A2F4412BA9AE12000C9FDD /* SetMetric.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SetMetric.swift; sourceTree = ""; }; - 62A2F4432BA9BF08000C9FDD /* GaugeMetricTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GaugeMetricTests.swift; sourceTree = ""; }; 62A3C7BD2B7E2A6A00C75227 /* SentrySpotlightTransportTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentrySpotlightTransportTests.swift; sourceTree = ""; }; 62A456E02B03704A003F19A1 /* SentryUIEventTrackerMode.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SentryUIEventTrackerMode.h; path = include/SentryUIEventTrackerMode.h; sourceTree = ""; }; 62A456E22B0370AA003F19A1 /* SentryUIEventTrackerTransactionMode.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SentryUIEventTrackerTransactionMode.h; path = include/SentryUIEventTrackerTransactionMode.h; sourceTree = ""; }; 62A456E42B0370E0003F19A1 /* SentryUIEventTrackerTransactionMode.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SentryUIEventTrackerTransactionMode.m; sourceTree = ""; }; 62AB8C9D2BF3925700BFC2AC /* WeakReference.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WeakReference.swift; sourceTree = ""; }; - 62B0C30C2BA9D39600648D59 /* CounterMetricTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CounterMetricTests.swift; sourceTree = ""; }; - 62B0C30E2BA9D74800648D59 /* DistributionMetricTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DistributionMetricTests.swift; sourceTree = ""; }; - 62B0C3102BA9D85C00648D59 /* SetMetricTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SetMetricTests.swift; sourceTree = ""; }; 62B558AF2C6B9C3C00C34FEC /* SentryFramesDelayResult.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryFramesDelayResult.swift; sourceTree = ""; }; 62B86CFB29F052BB008F3947 /* SentryTestLogConfig.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SentryTestLogConfig.m; sourceTree = ""; }; - 62BAD74F2BA1C5AF00EBAAFC /* SentryMetricsClientTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryMetricsClientTests.swift; sourceTree = ""; }; - 62BAD7552BA202C300EBAAFC /* SentryMetricsClient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryMetricsClient.swift; sourceTree = ""; }; 62C1AFA92B7E10D30038C5F7 /* SentrySpotlightTransport.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SentrySpotlightTransport.h; path = include/SentrySpotlightTransport.h; sourceTree = ""; }; 62C1AFAA2B7E10EA0038C5F7 /* SentrySpotlightTransport.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SentrySpotlightTransport.m; sourceTree = ""; }; 62C25C852B075F4900C68CBD /* TestOptions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestOptions.swift; sourceTree = ""; }; @@ -1147,8 +1114,6 @@ 62CFD9AC2C99770B00834E1B /* SentryInvalidJSONString.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SentryInvalidJSONString.m; sourceTree = ""; }; 62E081A829ED4260000F69FC /* SentryBreadcrumbDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SentryBreadcrumbDelegate.h; path = include/SentryBreadcrumbDelegate.h; sourceTree = ""; }; 62E081AA29ED4322000F69FC /* SentryBreadcrumbTestDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryBreadcrumbTestDelegate.swift; sourceTree = ""; }; - 62E146CF2BAAE47600ED34FD /* LocalMetricsAggregator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalMetricsAggregator.swift; sourceTree = ""; }; - 62E146D12BAAF55B00ED34FD /* LocalMetricsAggregatorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalMetricsAggregatorTests.swift; sourceTree = ""; }; 62F05D292C0DB1C800916E3F /* SentryLogTestHelper.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SentryLogTestHelper.h; sourceTree = ""; }; 62F05D2A2C0DB1F100916E3F /* SentryLogTestHelper.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SentryLogTestHelper.m; sourceTree = ""; }; 62F226B629A37C120038080D /* SentryBooleanSerialization.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SentryBooleanSerialization.m; sourceTree = ""; }; @@ -2135,54 +2100,9 @@ path = Helper; sourceTree = ""; }; - 62262B842BA1C453004DA3DD /* Metrics */ = { - isa = PBXGroup; - children = ( - 62262B852BA1C46D004DA3DD /* SentryStatsdClient.h */, - 62262B872BA1C490004DA3DD /* SentryStatsdClient.m */, - ); - name = Metrics; - sourceTree = ""; - }; - 62262B892BA1C4B0004DA3DD /* Metrics */ = { - isa = PBXGroup; - children = ( - 62262B8C2BA1C4DB004DA3DD /* Metric.swift */, - 62262B902BA1C520004DA3DD /* CounterMetric.swift */, - 62A2F43D2BA9AC10000C9FDD /* DistributionMetric.swift */, - 62A2F43F2BA9AC93000C9FDD /* GaugeMetric.swift */, - 62A2F4412BA9AE12000C9FDD /* SetMetric.swift */, - 626866712BA89641006995EA /* MetricsAggregator.swift */, - 626866772BA89928006995EA /* BucketsMetricsAggregator.swift */, - 62E146CF2BAAE47600ED34FD /* LocalMetricsAggregator.swift */, - 62262B8A2BA1C4C1004DA3DD /* EncodeMetrics.swift */, - 62BAD7552BA202C300EBAAFC /* SentryMetricsClient.swift */, - 62991A8C2BAC1B4A0078A8B8 /* SentryMetricsAPI.swift */, - ); - path = Metrics; - sourceTree = ""; - }; - 62262B942BA1C550004DA3DD /* Metrics */ = { - isa = PBXGroup; - children = ( - 62991A8E2BAC24ED0078A8B8 /* SentryMetricsAPITests.swift */, - 62262B952BA1C564004DA3DD /* EncodeMetricTests.swift */, - 62BAD74F2BA1C5AF00EBAAFC /* SentryMetricsClientTests.swift */, - 626866732BA89683006995EA /* BucketMetricsAggregatorTests.swift */, - 62E146D12BAAF55B00ED34FD /* LocalMetricsAggregatorTests.swift */, - 626866752BA896AD006995EA /* TestMetricsClient.swift */, - 62B0C30C2BA9D39600648D59 /* CounterMetricTests.swift */, - 62B0C30E2BA9D74800648D59 /* DistributionMetricTests.swift */, - 62A2F4432BA9BF08000C9FDD /* GaugeMetricTests.swift */, - 62B0C3102BA9D85C00648D59 /* SetMetricTests.swift */, - ); - path = Metrics; - sourceTree = ""; - }; 62872B602BA1B84400A4FA7D /* Swift */ = { isa = PBXGroup; children = ( - 62262B942BA1C550004DA3DD /* Metrics */, 62872B612BA1B84C00A4FA7D /* Extensions */, ); path = Swift; @@ -2622,7 +2542,6 @@ 63AA75C61EB8B06100D153DE /* Sentry */ = { isa = PBXGroup; children = ( - 62262B842BA1C453004DA3DD /* Metrics */, 630436031EC058FA00C4D3FA /* Categories */, 639889D51EDF10BE00EA7442 /* Helper */, 630436001EBCB87500C4D3FA /* Networking */, @@ -3667,7 +3586,6 @@ isa = PBXGroup; children = ( D8CAC02D2BA0663E00E38F34 /* Integrations */, - 62262B892BA1C4B0004DA3DD /* Metrics */, 621D9F2D2B9B030E003D94DE /* Helper */, D8F016B42B962533007B9AFB /* Extensions */, 7BF65060292B8EFE00BBA5A8 /* MetricKit */, @@ -4055,7 +3973,6 @@ D88817DA26D72AB800BF2251 /* SentryTraceContext.h in Headers */, 7B6C5F8126034354007F7DFF /* SentryWatchdogTerminationLogic.h in Headers */, 63FE708520DA4C1000CDBAE8 /* SentryCrashReportFilter.h in Headers */, - 62262B862BA1C46D004DA3DD /* SentryStatsdClient.h in Headers */, 84354E1129BF944900CDBB8B /* SentryProfileTimeseries.h in Headers */, D8ACE3CD2762187D00F5A213 /* SentryNSDataSwizzling.h in Headers */, 7B08A3452924CF6C0059603A /* SentryMetricKitIntegration.h in Headers */, @@ -4627,7 +4544,6 @@ 03F84D3727DD4191008FE43F /* SentrySamplingProfiler.cpp in Sources */, 8453421628BE8A9500C22EEC /* SentrySpanStatus.m in Sources */, 7B08A3472924CF9C0059603A /* SentryMetricKitIntegration.m in Sources */, - 62262B8B2BA1C4C1004DA3DD /* EncodeMetrics.swift in Sources */, 7B63459B280EB9E200CFA05A /* SentryUIEventTrackingIntegration.m in Sources */, D8AE48AE2C577EAB0092A2A6 /* SentryLog.swift in Sources */, 15E0A8ED240F2CB000F044E3 /* SentrySerialization.m in Sources */, @@ -4641,7 +4557,6 @@ 7BA61CCA247D128B00C130A8 /* SentryThreadInspector.m in Sources */, D8CA12952C203E71005894F4 /* SentrySessionListener.swift in Sources */, 63FE718D20DA4C1100CDBAE8 /* SentryCrashReportStore.c in Sources */, - 62A2F43E2BA9AC10000C9FDD /* DistributionMetric.swift in Sources */, 7BA0C0482805600A003E0326 /* SentryTransportAdapter.m in Sources */, 63FE712920DA4C1000CDBAE8 /* SentryCrashCPU_arm.c in Sources */, 03F84D3427DD4191008FE43F /* SentryThreadMetadataCache.cpp in Sources */, @@ -4664,7 +4579,6 @@ 8E133FA225E72DEF00ABD0BF /* SentrySamplingContext.m in Sources */, D81988C02BEBFFF70020E36C /* SentryReplayRecording.swift in Sources */, 7B6C5F8726034395007F7DFF /* SentryWatchdogTerminationLogic.m in Sources */, - 62A2F4402BA9AC93000C9FDD /* GaugeMetric.swift in Sources */, 7BBC827125DFD039005F1ED8 /* SentryInAppLogic.m in Sources */, 63FE708D20DA4C1000CDBAE8 /* SentryCrashReportFilterBasic.m in Sources */, 63FE718120DA4C1100CDBAE8 /* SentryCrashDoctor.m in Sources */, @@ -4744,9 +4658,7 @@ 849B8F9A2C6E906900148E1F /* SentryUserFeedbackConfiguration.swift in Sources */, 63295AF71EF3C7DB002D4490 /* SentryNSDictionarySanitize.m in Sources */, 7B8ECBFC26498958005FE2EF /* SentryAppStateManager.m in Sources */, - 62262B8D2BA1C4DB004DA3DD /* Metric.swift in Sources */, 7B2A70DD27D6083D008B0D15 /* SentryThreadWrapper.m in Sources */, - 62E146D02BAAE47600ED34FD /* LocalMetricsAggregator.swift in Sources */, D8ACE3C72762187200F5A213 /* SentryNSDataSwizzling.m in Sources */, 638DC9A11EBC6B6400A66E41 /* SentryRequestOperation.m in Sources */, 63AA767A1EB8D20500D153DE /* SentryLogC.m in Sources */, @@ -4764,7 +4676,6 @@ 7BD4BD4927EB2A5D0071F4FF /* SentryDiscardedEvent.m in Sources */, 03F84D3827DD4191008FE43F /* SentryBacktrace.cpp in Sources */, D8739D182BEEA33F007D2F66 /* SentryLevelHelper.m in Sources */, - 62BAD7572BA2033F00EBAAFC /* SentryMetricsClient.swift in Sources */, 63FE712720DA4C1000CDBAE8 /* SentryCrashThread.c in Sources */, 7B127B0F27CF6F4700A71ED2 /* SentryANRTrackingIntegration.m in Sources */, 62C316832B1F2EA1000D7031 /* SentryDelayedFramesTracker.m in Sources */, @@ -4802,7 +4713,6 @@ 63FE712D20DA4C1100CDBAE8 /* SentryCrashJSONCodecObjC.m in Sources */, 7BBD18932449BEDD00427C76 /* SentryDefaultRateLimits.m in Sources */, 7BD729982463E93500EA3610 /* SentryDateUtil.m in Sources */, - 62262B882BA1C490004DA3DD /* SentryStatsdClient.m in Sources */, 639FCF9D1EBC7F9500778193 /* SentryThread.m in Sources */, 849B8F992C6E906900148E1F /* SentryUserFeedbackFormConfiguration.swift in Sources */, 8E8C57A225EEFC07001CEEFA /* SentrySampling.m in Sources */, @@ -4839,8 +4749,6 @@ D8CAC0412BA0984500E38F34 /* SentryIntegrationProtocol.swift in Sources */, 63FE710F20DA4C1000CDBAE8 /* SentryCrashNSErrorUtil.m in Sources */, 8ECC674925C23A20000E2BF6 /* SentrySpanId.m in Sources */, - 626866722BA89641006995EA /* MetricsAggregator.swift in Sources */, - 626866782BA89928006995EA /* BucketsMetricsAggregator.swift in Sources */, 6344DDB51EC309E000D9160D /* SentryCrashReportSink.m in Sources */, 8EAE9806261E87120073B6B3 /* SentryUIViewControllerPerformanceTracker.m in Sources */, D81988C72BEC18E20020E36C /* SentryRRWebVideoEvent.swift in Sources */, @@ -4877,14 +4785,11 @@ 63FE714520DA4C1100CDBAE8 /* SentryCrashObjC.c in Sources */, 63FE710520DA4C1000CDBAE8 /* SentryAsyncSafeLog.c in Sources */, 0A2D8D5B289815C0008720F6 /* SentryBaseIntegration.m in Sources */, - 62262B912BA1C520004DA3DD /* CounterMetric.swift in Sources */, 639FCF991EBC7B9700778193 /* SentryEvent.m in Sources */, D8BC28CA2BFF68CA0054DA4D /* NumberExtensions.swift in Sources */, D820CDB72BB1895F00BA339D /* SentrySessionReplayIntegration.m in Sources */, 632F43521F581D5400A18A36 /* SentryCrashExceptionApplication.m in Sources */, - 62A2F4422BA9AE12000C9FDD /* SetMetric.swift in Sources */, 620379DD2AFE1432005AC0C1 /* SentryBuildAppStartSpans.m in Sources */, - 62991A8D2BAC1B4A0078A8B8 /* SentryMetricsAPI.swift in Sources */, 7B77BE3727EC8460003C9020 /* SentryDiscardReasonMapper.m in Sources */, 63FE712520DA4C1000CDBAE8 /* SentryCrashSignalInfo.c in Sources */, 63FE70F320DA4C1000CDBAE8 /* SentryCrashMonitor_Signal.c in Sources */, @@ -4917,7 +4822,6 @@ 7B68345128F7EB3D00FB7064 /* SentryMeasurementUnitTests.swift in Sources */, 7B14089A248791660035403D /* SentryCrashStackEntryMapperTests.swift in Sources */, D85790292976A69F00C6AC1F /* TestDebugImageProvider.swift in Sources */, - 62E146D22BAAF55B00ED34FD /* LocalMetricsAggregatorTests.swift in Sources */, 7B869EBC249B91D8004F4FDB /* SentryDebugMetaEquality.swift in Sources */, 7B01CE3D271993AC00B5AF31 /* SentryTransportFactoryTests.swift in Sources */, 7B30B68026527C3C006B2752 /* SentryFramesTrackerTests.swift in Sources */, @@ -4929,7 +4833,6 @@ 7BE3C78724472E9800A38442 /* TestRequestManager.swift in Sources */, 7BD4E8E627FD84480086C410 /* TestFileManagerDelegate.swift in Sources */, 63FE722220DA66EC00CDBAE8 /* SentryCrashJSONCodec_Tests.m in Sources */, - 62B0C3112BA9D85C00648D59 /* SetMetricTests.swift in Sources */, 7B0A5452252311CE00A71716 /* SentryBreadcrumbTests.swift in Sources */, 7BE3C7752445C82300A38442 /* SentryCurrentDateTests.swift in Sources */, 7B3398672459C4AE00BD9C96 /* SentryEnvelopeRateLimitTests.swift in Sources */, @@ -4946,9 +4849,7 @@ 7B98D7EC25FB7C4900C5A389 /* SentryAppStateTests.swift in Sources */, 62A3C7BE2B7E2A6A00C75227 /* SentrySpotlightTransportTests.swift in Sources */, 8EE017A126704CD500470616 /* SentryUIViewControllerPerformanceTrackerTests.swift in Sources */, - 62B0C30D2BA9D39600648D59 /* CounterMetricTests.swift in Sources */, 7B5B94352657AD21002E474B /* SentryFramesTrackingIntegrationTests.swift in Sources */, - 626866762BA896AD006995EA /* TestMetricsClient.swift in Sources */, 8431EE5B29ADB8EA00D8DC56 /* SentryTimeTests.m in Sources */, 7B0A54562523178700A71716 /* SentryScopeSwiftTests.swift in Sources */, 7B5B94332657A816002E474B /* SentryAppStartTrackingIntegrationTests.swift in Sources */, @@ -4989,7 +4890,6 @@ 7B2A70DF27D60904008B0D15 /* SentryTestThreadWrapper.swift in Sources */, 62F4DDA12C04CB9700588890 /* SentryBaggageSerializationTests.swift in Sources */, 7BE912AF272166DD00E49E62 /* SentryNoOpSpanTests.swift in Sources */, - 62991A8F2BAC24ED0078A8B8 /* SentryMetricsAPITests.swift in Sources */, 6229416A2BB2F123004765D1 /* SentryNSDataUtilsTests.swift in Sources */, 7B56D73524616E5600B842DA /* SentryConcurrentRateLimitsDictionaryTests.swift in Sources */, 7B7D8730248648AD00D2ECFF /* SentryStacktraceBuilderTests.swift in Sources */, @@ -5027,7 +4927,6 @@ 62375FB92B47F9F000CC55F1 /* SentryDependencyContainerTests.swift in Sources */, 7BC6EC08255C36DE0059822A /* SentryStacktraceTests.swift in Sources */, D88817DD26D72BA500BF2251 /* SentryTraceStateTests.swift in Sources */, - 62B0C30F2BA9D74800648D59 /* DistributionMetricTests.swift in Sources */, 7B26BBFB24C0A66D00A79CCC /* SentrySdkInfoNilTests.m in Sources */, 7B984A9F28E572AF001F4BEE /* CrashReport.swift in Sources */, 7BED3576266F7BFF00EAA70D /* TestSentryCrashWrapper.m in Sources */, @@ -5038,7 +4937,6 @@ 7B87C916295ECFD700510C52 /* SentryMetricKitEventTests.swift in Sources */, 7B6D98ED24C703F8005502FA /* Async.swift in Sources */, 7BA0C04C28056556003E0326 /* SentryTransportAdapterTests.swift in Sources */, - 62BAD74E2BA1C58D00EBAAFC /* EncodeMetricTests.swift in Sources */, 7BE0DC29272A9E1C004FA8B7 /* SentryBreadcrumbTrackerTests.swift in Sources */, 63FE722520DA66EC00CDBAE8 /* SentryCrashFileUtils_Tests.m in Sources */, D86130122BB563FD004C0F5E /* SentrySessionReplayIntegrationTests.swift in Sources */, @@ -5094,7 +4992,6 @@ 7B6D135C27F4605D00331ED2 /* TestEnvelopeRateLimitDelegate.swift in Sources */, 7B2A70D827D5F080008B0D15 /* SentryANRTrackerV1Tests.swift in Sources */, 63FE722120DA66EC00CDBAE8 /* SentryCrashDynamicLinker_Tests.m in Sources */, - 62A2F4442BA9BF08000C9FDD /* GaugeMetricTests.swift in Sources */, 63FE720120DA66EC00CDBAE8 /* RFC3339UTFString_Tests.m in Sources */, 63AA76701EB8CB4B00D153DE /* SentryTests.m in Sources */, 8E70B0FD25CB72BE002B3155 /* SentrySpanTests.swift in Sources */, @@ -5133,7 +5030,6 @@ 7BA61CAF247BBF3C00C130A8 /* SentryDebugImageProviderTests.swift in Sources */, 7BB7E7C729267A28004BF96B /* EmptyIntegration.swift in Sources */, 7B965728268321CD00C66E25 /* SentryCrashScopeObserverTests.swift in Sources */, - 626866742BA89683006995EA /* BucketMetricsAggregatorTests.swift in Sources */, 7BD86ECB264A6DB5005439DB /* TestSysctl.swift in Sources */, D861301C2BB5A267004C0F5E /* SentrySessionReplayTests.swift in Sources */, 7B0DC73428869BF40039995F /* NSMutableDictionarySentryTests.swift in Sources */, @@ -5147,7 +5043,6 @@ 7B4F22DC294089530067EA17 /* FormatHexAddress.swift in Sources */, 8EAC7FF8265C8910005B44E5 /* SentryTracerTests.swift in Sources */, 0A1B497328E597DD00D7BFA3 /* TestLogOutput.swift in Sources */, - 62BAD7502BA1C5AF00EBAAFC /* SentryMetricsClientTests.swift in Sources */, D8CB742E294B294B00A5F964 /* MockUIScene.m in Sources */, 7BA61CBD247BC6B900C130A8 /* TestSentryCrashBinaryImageProvider.swift in Sources */, 7BFA69F627E0840400233199 /* SentryANRTrackingIntegrationTests.swift in Sources */, diff --git a/Sources/Sentry/Public/SentryOptions.h b/Sources/Sentry/Public/SentryOptions.h index 7914b54437e..597dd11e08d 100644 --- a/Sources/Sentry/Public/SentryOptions.h +++ b/Sources/Sentry/Public/SentryOptions.h @@ -714,38 +714,6 @@ NS_SWIFT_NAME(Options) */ @property (nonatomic, copy) NSString *spotlightUrl; -/** - * Wether to enable DDM (delightful developer metrics) or not. For more information see - * https://docs.sentry.io/product/metrics/. - * - * @warning This is an experimental feature and may still have bugs. - * @note Default value is @c NO . - */ -@property (nonatomic, assign) BOOL enableMetrics; - -/** - * Wether to enable adding some default tags to every metrics or not. You need to enable @c - * enableMetrics for this flag to work. - * - * @warning This is an experimental feature and may still have bugs. - * @note Default value is @c YES . - */ -@property (nonatomic, assign) BOOL enableDefaultTagsForMetrics; - -/** - * Wether to enable connecting metrics to spans and transactions or not. You need to enable @c - * enableMetrics for this flag to work. - * - * @warning This is an experimental feature and may still have bugs. - * @note Default value is @c YES . - */ -@property (nonatomic, assign) BOOL enableSpanLocalMetricAggregation; - -/** - * This block can be used to modify the event before it will be serialized and sent. - */ -@property (nullable, nonatomic, copy) SentryBeforeEmitMetricCallback beforeEmitMetric; - /** * This aggregates options for experimental features. * Be aware that the options available for experimental can change at any time. diff --git a/Sources/Sentry/SentryHub.m b/Sources/Sentry/SentryHub.m index 27549bf4f19..79989327f5b 100644 --- a/Sources/Sentry/SentryHub.m +++ b/Sources/Sentry/SentryHub.m @@ -22,7 +22,6 @@ #import "SentryScope+Private.h" #import "SentrySerialization.h" #import "SentrySession+Private.h" -#import "SentryStatsdClient.h" #import "SentrySwift.h" #import "SentryTraceOrigins.h" #import "SentryTracer.h" @@ -35,7 +34,7 @@ NS_ASSUME_NONNULL_BEGIN -@interface SentryHub () +@interface SentryHub () @property (nullable, nonatomic, strong) SentryClient *client; @property (nullable, nonatomic, strong) SentryScope *scope; @@ -73,18 +72,6 @@ - (instancetype)initWithClient:(nullable SentryClient *)client _scope = scope; _crashWrapper = crashWrapper; _dispatchQueue = dispatchQueue; - SentryStatsdClient *statsdClient = [[SentryStatsdClient alloc] initWithClient:client]; - SentryMetricsClient *metricsClient = - [[SentryMetricsClient alloc] initWithClient:statsdClient]; - _metrics = [[SentryMetricsAPI alloc] - initWithEnabled:client.options.enableMetrics - client:metricsClient - currentDate:SentryDependencyContainer.sharedInstance.dateProvider - dispatchQueue:_dispatchQueue - random:SentryDependencyContainer.sharedInstance.random - beforeEmitMetric:client.options.beforeEmitMetric]; - [_metrics setDelegate:self]; - _sessionLock = [[NSObject alloc] init]; _integrationsLock = [[NSObject alloc] init]; _installedIntegrations = [[NSMutableArray alloc] init]; @@ -762,7 +749,6 @@ - (NSString *)createSessionDebugString:(SentrySession *)session - (void)flush:(NSTimeInterval)timeout { - [_metrics flush]; SentryClient *client = _client; if (client != nil) { [client flush:timeout]; @@ -771,47 +757,10 @@ - (void)flush:(NSTimeInterval)timeout - (void)close { - [_metrics close]; [_client close]; SENTRY_LOG_DEBUG(@"Closed the Hub."); } -#pragma mark - SentryMetricsAPIDelegate - -- (NSDictionary *)getDefaultTagsForMetrics -{ - SentryOptions *options = [_client options]; - if (options == nil || options.enableDefaultTagsForMetrics == NO) { - return @{}; - } - - NSMutableDictionary *defaultTags = [NSMutableDictionary dictionary]; - - if (options.releaseName != nil) { - defaultTags[@"release"] = options.releaseName; - } - - defaultTags[@"environment"] = options.environment; - - return defaultTags; -} - -- (id _Nullable)getCurrentSpan -{ - return _scope.span; -} - -- (LocalMetricsAggregator *_Nullable)getLocalMetricsAggregatorWithSpan:(id)span -{ - // We don't want to add them LocalMetricsAggregator to the SentrySpan protocol and make it - // public. Instead, we check if the span responds to the getLocalMetricsAggregator which, every - // span should do. - if ([span isKindOfClass:SentrySpan.class]) { - return [(SentrySpan *)span getLocalMetricsAggregator]; - } - return nil; -} - - (void)registerSessionListener:(id)listener { _sessionListener = listener; diff --git a/Sources/Sentry/SentryOptions.m b/Sources/Sentry/SentryOptions.m index 554eb37b43b..0de5de589f8 100644 --- a/Sources/Sentry/SentryOptions.m +++ b/Sources/Sentry/SentryOptions.m @@ -139,9 +139,6 @@ - (instancetype)init self.swiftAsyncStacktraces = NO; self.enableSpotlight = NO; self.spotlightUrl = @"http://localhost:8969/stream"; - self.enableMetrics = NO; - self.enableDefaultTagsForMetrics = YES; - self.enableSpanLocalMetricAggregation = YES; #if TARGET_OS_OSX NSString *dsn = [[[NSProcessInfo processInfo] environment] objectForKey:@"SENTRY_DSN"]; @@ -550,18 +547,6 @@ - (BOOL)validateOptions:(NSDictionary *)options self.spotlightUrl = options[@"spotlightUrl"]; } - [self setBool:options[@"enableMetrics"] block:^(BOOL value) { self->_enableMetrics = value; }]; - - [self setBool:options[@"enableDefaultTagsForMetrics"] - block:^(BOOL value) { self->_enableDefaultTagsForMetrics = value; }]; - - [self setBool:options[@"enableSpanLocalMetricAggregation"] - block:^(BOOL value) { self->_enableSpanLocalMetricAggregation = value; }]; - - if ([self isBlock:options[@"beforeEmitMetric"]]) { - self.beforeEmitMetric = options[@"beforeEmitMetric"]; - } - if ([options[@"experimental"] isKindOfClass:NSDictionary.class]) { [self.experimental validateOptions:options[@"experimental"]]; } diff --git a/Sources/Sentry/SentrySpan.m b/Sources/Sentry/SentrySpan.m index 7f8395e6d0a..e76a7683ff0 100644 --- a/Sources/Sentry/SentrySpan.m +++ b/Sources/Sentry/SentrySpan.m @@ -42,7 +42,6 @@ @implementation SentrySpan { NSObject *_stateLock; BOOL _isFinished; uint64_t _startSystemTime; - LocalMetricsAggregator *localMetricsAggregator; #if SENTRY_HAS_UIKIT NSUInteger initTotalFrames; NSUInteger initSlowFrames; @@ -310,14 +309,6 @@ - (nullable SentryTraceContext *)traceContext return self.tracer.traceContext; } -- (LocalMetricsAggregator *)getLocalMetricsAggregator -{ - if (localMetricsAggregator == nil) { - localMetricsAggregator = [[LocalMetricsAggregator alloc] init]; - } - return localMetricsAggregator; -} - - (NSDictionary *)serialize { NSMutableDictionary *mutableDictionary = @{ @@ -358,10 +349,6 @@ - (NSDictionary *)serialize [mutableDictionary setValue:@(self.startTimestamp.timeIntervalSince1970) forKey:@"start_timestamp"]; - if (localMetricsAggregator != nil) { - mutableDictionary[@"_metrics_summary"] = [localMetricsAggregator serialize]; - } - @synchronized(_data) { NSMutableDictionary *data = _data.mutableCopy; diff --git a/Sources/Sentry/SentryStatsdClient.m b/Sources/Sentry/SentryStatsdClient.m deleted file mode 100644 index 219d683c480..00000000000 --- a/Sources/Sentry/SentryStatsdClient.m +++ /dev/null @@ -1,51 +0,0 @@ -#import "SentryStatsdClient.h" -#import "SentryClient+Private.h" -#import "SentryEnvelope.h" -#import "SentryEnvelopeItemHeader.h" -#import "SentryEnvelopeItemType.h" -#import "SentrySwift.h" - -NS_ASSUME_NONNULL_BEGIN - -@interface SentryStatsdClient () - -@property (nonatomic, strong) SentryClient *client; - -@end - -@implementation SentryStatsdClient - -- (instancetype)initWithClient:(SentryClient *)client -{ - if (self = [super init]) { - self.client = client; - } - - return self; -} - -- (void)captureStatsdEncodedData:(NSData *)statsdEncodedData -{ - if (statsdEncodedData.length == 0) { - return; - } - - SentryEnvelopeItemHeader *header = - [[SentryEnvelopeItemHeader alloc] initWithType:SentryEnvelopeItemTypeStatsd - length:statsdEncodedData.length - contentType:@"application/octet-stream"]; - - SentryEnvelopeItem *item = [[SentryEnvelopeItem alloc] initWithHeader:header - data:statsdEncodedData]; - - SentryEnvelopeHeader *envelopeHeader = - [[SentryEnvelopeHeader alloc] initWithId:[[SentryId alloc] init]]; - NSArray *items = @[ item ]; - SentryEnvelope *envelope = [[SentryEnvelope alloc] initWithHeader:envelopeHeader items:items]; - - [self.client captureEnvelope:envelope]; -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/Sources/Sentry/SentryTransaction.m b/Sources/Sentry/SentryTransaction.m index 57778f61419..9c55a92bc4f 100644 --- a/Sources/Sentry/SentryTransaction.m +++ b/Sources/Sentry/SentryTransaction.m @@ -45,17 +45,7 @@ - (instancetype)initWithTrace:(SentryTracer *)trace children:(NSArray *serializedTrace = [self.trace serialize].mutableCopy; - NSDictionary *metricsSummary = serializedTrace[@"_metrics_summary"]; - if (metricsSummary != nil) { - serializedData[@"_metrics_summary"] = metricsSummary; - [serializedTrace removeObjectForKey:@"_metrics_summary"]; - } - mutableContext[@"trace"] = serializedTrace; + mutableContext[@"trace"] = [self.trace serialize]; [serializedData setValue:mutableContext forKey:@"contexts"]; diff --git a/Sources/Sentry/include/SentryPrivate.h b/Sources/Sentry/include/SentryPrivate.h index 5d9f0e21906..5cbf613c036 100644 --- a/Sources/Sentry/include/SentryPrivate.h +++ b/Sources/Sentry/include/SentryPrivate.h @@ -2,7 +2,6 @@ #import "SentryDispatchQueueWrapper.h" #import "SentryNSDataUtils.h" #import "SentryRandom.h" -#import "SentryStatsdClient.h" #import "SentryTime.h" // Headers that also import SentryDefines should be at the end of this list diff --git a/Sources/Sentry/include/SentrySpan.h b/Sources/Sentry/include/SentrySpan.h index 2653239d384..acbc382ed96 100644 --- a/Sources/Sentry/include/SentrySpan.h +++ b/Sources/Sentry/include/SentrySpan.h @@ -5,7 +5,6 @@ NS_ASSUME_NONNULL_BEGIN @class SentryTracer, SentryId, SentrySpanId, SentryFrame, SentrySpanContext; -@class LocalMetricsAggregator; #if SENTRY_HAS_UIKIT @class SentryFramesTracker; @@ -85,8 +84,6 @@ SENTRY_NO_INIT */ @property (nullable, nonatomic, strong) NSArray *frames; -- (LocalMetricsAggregator *)getLocalMetricsAggregator; - /** * Init a @c SentrySpan with given transaction and context. * @param transaction The @c SentryTracer managing the transaction this span is associated with. diff --git a/Sources/Sentry/include/SentryStatsdClient.h b/Sources/Sentry/include/SentryStatsdClient.h deleted file mode 100644 index 1d332e09295..00000000000 --- a/Sources/Sentry/include/SentryStatsdClient.h +++ /dev/null @@ -1,16 +0,0 @@ -#import "SentryDefines.h" - -NS_ASSUME_NONNULL_BEGIN - -@class SentryClient; - -@interface SentryStatsdClient : NSObject -SENTRY_NO_INIT - -- (instancetype)initWithClient:(SentryClient *)client; - -- (void)captureStatsdEncodedData:(NSData *)statsdEncodedData; - -@end - -NS_ASSUME_NONNULL_END diff --git a/Sources/Swift/Helper/SentryEnabledFeaturesBuilder.swift b/Sources/Swift/Helper/SentryEnabledFeaturesBuilder.swift index 288b43e5372..b6f2db89a99 100644 --- a/Sources/Swift/Helper/SentryEnabledFeaturesBuilder.swift +++ b/Sources/Swift/Helper/SentryEnabledFeaturesBuilder.swift @@ -36,10 +36,6 @@ import Foundation features.append("swiftAsyncStacktraces") } - if options.enableMetrics { - features.append("metrics") - } - return features } } diff --git a/Sources/Swift/Metrics/BucketsMetricsAggregator.swift b/Sources/Swift/Metrics/BucketsMetricsAggregator.swift deleted file mode 100644 index beff3a4e499..00000000000 --- a/Sources/Swift/Metrics/BucketsMetricsAggregator.swift +++ /dev/null @@ -1,241 +0,0 @@ -@_implementationOnly import _SentryPrivate - -/// The bucket timestamp is calculated: -/// ( timeIntervalSince1970 / ROLLUP_IN_SECONDS ) * ROLLUP_IN_SECONDS -typealias BucketTimestamp = UInt64 -let ROLLUP_IN_SECONDS: TimeInterval = 10 - -extension SentryCurrentDateProvider { - var bucketTimestamp: BucketTimestamp { - let now = self.date() - let seconds = now.timeIntervalSince1970 - - return (UInt64(seconds) / UInt64(ROLLUP_IN_SECONDS)) * UInt64(ROLLUP_IN_SECONDS) - } -} - -class BucketMetricsAggregator: MetricsAggregator { - - private let client: SentryMetricsClient - private let currentDate: SentryCurrentDateProvider - private let dispatchQueue: SentryDispatchQueueWrapper - private let random: SentryRandomProtocol - private let beforeEmitMetric: BeforeEmitMetricCallback? - private let totalMaxWeight: UInt - private let flushShift: TimeInterval - private let flushInterval: TimeInterval - private let flushTolerance: TimeInterval - - private var timer: DispatchSourceTimer? - private var totalBucketsWeight: UInt = 0 - private var buckets: [BucketTimestamp: [String: Metric]] = [:] - private let lock = NSLock() - - init( - client: SentryMetricsClient, - currentDate: SentryCurrentDateProvider, - dispatchQueue: SentryDispatchQueueWrapper, - random: SentryRandomProtocol, - beforeEmitMetric: BeforeEmitMetricCallback? = nil, - totalMaxWeight: UInt = 1_000, - flushInterval: TimeInterval = 10.0, - flushTolerance: TimeInterval = 0.5 - ) { - self.client = client - self.currentDate = currentDate - self.dispatchQueue = dispatchQueue - self.random = random - self.beforeEmitMetric = beforeEmitMetric - - // The aggregator shifts its flushing by up to an entire rollup window to - // avoid multiple clients trampling on end of a 10 second window as all the - // buckets are anchored to multiples of ROLLUP seconds. We randomize this - // number once per aggregator boot to achieve some level of offsetting - // across a fleet of deployed SDKs. - let flushShift = random.nextNumber() * ROLLUP_IN_SECONDS - self.totalMaxWeight = totalMaxWeight - self.flushInterval = flushInterval - self.flushShift = flushShift - self.flushTolerance = flushTolerance - - startTimer() - } - - private func startTimer() { - let timer = DispatchSource.makeTimerSource(flags: [], queue: dispatchQueue.queue) - - // Set leeway to reduce energy impact - let leewayInMilliseconds: Int = Int(flushTolerance * 1_000) - timer.schedule(deadline: .now() + flushInterval, repeating: self.flushInterval, leeway: .milliseconds(leewayInMilliseconds)) - timer.setEventHandler { [weak self] in - self?.flush(force: false) - } - timer.activate() - self.timer = timer - } - - func increment(key: String, value: Double, unit: MeasurementUnit, tags: [String: String], localMetricsAggregator: LocalMetricsAggregator? = nil) { - self.add(type: MetricType.counter, - key: key, value: value, - unit: unit, - tags: tags, - localMetricsAggregator: localMetricsAggregator, - initMetric: { - CounterMetric(first: value, key: key, unit: unit, tags: tags) - }, addValueToMetric: { metric in - // Unit tests validate that the cast works. If it doesn't, a test will fail. - let castedMetric = metric as? CounterMetric - castedMetric?.add(value: value) - }) - } - - func gauge(key: String, value: Double, unit: MeasurementUnit, tags: [String: String], localMetricsAggregator: LocalMetricsAggregator? = nil) { - self.add(type: MetricType.gauge, - key: key, value: value, - unit: unit, - tags: tags, - localMetricsAggregator: localMetricsAggregator, - initMetric: { - GaugeMetric(first: value, key: key, unit: unit, tags: tags) - }, addValueToMetric: { metric in - // Unit tests validate that the cast works. If it doesn't, a test will fail. - let castedMetric = metric as? GaugeMetric - castedMetric?.add(value: value) - }) - } - - func distribution(key: String, value: Double, unit: MeasurementUnit, tags: [String: String], localMetricsAggregator: LocalMetricsAggregator? = nil) { - self.add(type: MetricType.distribution, - key: key, value: value, - unit: unit, - tags: tags, - localMetricsAggregator: localMetricsAggregator, - initMetric: { - DistributionMetric(first: value, key: key, unit: unit, tags: tags) - }, addValueToMetric: { metric in - // Unit tests validate that the cast works. If it doesn't, a test will fail. - let castedMetric = metric as? DistributionMetric - castedMetric?.add(value: value) - }) - } - - func set(key: String, value: UInt, unit: MeasurementUnit, tags: [String: String], localMetricsAggregator: LocalMetricsAggregator? = nil) { - self.add(type: MetricType.set, - key: key, - value: 0, // Value is not used for set - unit: unit, - tags: tags, - localMetricsAggregator: localMetricsAggregator, - initMetric: { - SetMetric(first: value, key: key, unit: unit, tags: tags) - }, addValueToMetric: { metric in - // Unit tests validate that the cast works. If it doesn't, a test will fail. - let castedMetric = metric as? SetMetric - castedMetric?.add(value: value) - }) - } - - private func add(type: MetricType, key: String, value: Double, unit: MeasurementUnit, tags: [String: String], localMetricsAggregator: LocalMetricsAggregator?, initMetric: () -> Metric, addValueToMetric: (Metric) -> Void) { - - if let beforeEmitMetric = self.beforeEmitMetric { - if !beforeEmitMetric(key, tags) { - return - } - } - - let tagsKey = tags.getMetricsTagsKey() - let bucketKey = "\(type)_\(key)_\(unit.unit)_\(tagsKey)" - - let bucketTimestamp = currentDate.bucketTimestamp - - var isOverWeight = false - - lock.synchronized { - var bucket = buckets[bucketTimestamp] ?? [:] - let oldWeight = bucket[bucketKey]?.weight ?? 0 - - let metric = bucket[bucketKey] ?? initMetric() - let metricExists = bucket[bucketKey] != nil - - if metricExists { - addValueToMetric(metric) - } - - let addedWeight = metric.weight - oldWeight - - bucket[bucketKey] = metric - totalBucketsWeight += addedWeight - - buckets[bucketTimestamp] = bucket - - let totalWeight = UInt(buckets.count) + totalBucketsWeight - isOverWeight = totalWeight >= totalMaxWeight - - // For sets, we only record that a value has been added to the set but not which one. See develop docs: https://develop.sentry.dev/sdk/metrics/#sets - if localMetricsAggregator != nil { - let localValue = type == .set ? Double(addedWeight) : value - localMetricsAggregator?.add(type: type, key: key, value: localValue, unit: unit, tags: tags) - } - } - - if isOverWeight { - dispatchQueue.dispatchAsync({ [weak self] in - self?.flush(force: true) - }) - } - } - - func flush(force: Bool) { - var flushableBuckets: [BucketTimestamp: [Metric]] = [:] - - if force { - lock.synchronized { - for (timestamp, metrics) in buckets { - flushableBuckets[timestamp] = Array(metrics.values) - } - - buckets.removeAll() - totalBucketsWeight = 0 - } - } else { - let cutoff = BucketTimestamp(currentDate.date().timeIntervalSince1970 - ROLLUP_IN_SECONDS - flushShift) - - lock.synchronized { - for (bucketTimestamp, bucket) in buckets { - if bucketTimestamp <= cutoff { - flushableBuckets[bucketTimestamp] = Array(bucket.values) - } - } - - var weightToRemove: UInt = 0 - for (bucketTimestamp, metrics) in flushableBuckets { - for metric in metrics { - weightToRemove += metric.weight - } - buckets.removeValue(forKey: bucketTimestamp) - } - - totalBucketsWeight -= weightToRemove - } - } - - if !flushableBuckets.isEmpty { - client.capture(flushableBuckets: flushableBuckets) - } - } - - func close() { - self.flush(force: true) - - cancelTimer() - } - - deinit { - cancelTimer() - } - - private func cancelTimer() { - self.timer?.cancel() - self.timer = nil - } -} diff --git a/Sources/Swift/Metrics/CounterMetric.swift b/Sources/Swift/Metrics/CounterMetric.swift deleted file mode 100644 index 0434d1d3afb..00000000000 --- a/Sources/Swift/Metrics/CounterMetric.swift +++ /dev/null @@ -1,20 +0,0 @@ -import Foundation - -class CounterMetric: Metric { - - private var value: Double - var weight: UInt = 1 - - init(first: Double, key: String, unit: MeasurementUnit, tags: [String: String]) { - value = first - super.init(type: .counter, key: key, unit: unit, tags: tags) - } - - func add(value: Double) { - self.value += value - } - - func serialize() -> [String] { - return ["\(value)"] - } -} diff --git a/Sources/Swift/Metrics/DistributionMetric.swift b/Sources/Swift/Metrics/DistributionMetric.swift deleted file mode 100644 index 4a31c49f435..00000000000 --- a/Sources/Swift/Metrics/DistributionMetric.swift +++ /dev/null @@ -1,22 +0,0 @@ -import Foundation - -class DistributionMetric: Metric { - - private var values: [Double] - var weight: UInt { - return UInt(values.count) - } - - init(first: Double, key: String, unit: MeasurementUnit, tags: [String: String]) { - values = [first] - super.init(type: .distribution, key: key, unit: unit, tags: tags) - } - - func add(value: Double) { - self.values.append(value) - } - - func serialize() -> [String] { - return values.map { "\($0)" } - } -} diff --git a/Sources/Swift/Metrics/EncodeMetrics.swift b/Sources/Swift/Metrics/EncodeMetrics.swift deleted file mode 100644 index 83b9762c115..00000000000 --- a/Sources/Swift/Metrics/EncodeMetrics.swift +++ /dev/null @@ -1,72 +0,0 @@ -import Foundation - -/// Encodes the metrics into a Statsd compatible format. -/// See https://github.com/statsd/statsd#usage and https://getsentry.github.io/relay/relay_metrics/index.html for more details about the format. -func encodeToStatsd(flushableBuckets: [BucketTimestamp: [Metric]]) -> Data { - var statsdString = "" - - for bucket in flushableBuckets { - let timestamp = bucket.key - let buckets = bucket.value - for metric in buckets { - - statsdString.append(sanitize(metricKey: metric.key)) - statsdString.append("@") - - statsdString.append(sanitize(metricUnit: metric.unit.unit)) - - for serializedValue in metric.serialize() { - statsdString.append(":\(serializedValue)") - } - - statsdString.append("|") - statsdString.append(metric.type.rawValue) - - var firstTag = true - for (tagKey, tagValue) in metric.tags { - let sanitizedTagKey = sanitize(tagKey: tagKey) - - if firstTag { - statsdString.append("|#") - firstTag = false - } else { - statsdString.append(",") - } - - statsdString.append("\(sanitizedTagKey):") - statsdString.append(replaceTagValueCharacters(tagValue: tagValue)) - } - - statsdString.append("|T") - statsdString.append("\(timestamp)") - statsdString.append("\n") - } - } - - return statsdString.data(using: .utf8) ?? Data() -} - -private func sanitize(metricUnit: String) -> String { - // We can't use \w because it includes chars like ä on Swift - return metricUnit.replacingOccurrences(of: "[^a-zA-Z0-9_]", with: "", options: .regularExpression) -} - -private func sanitize(metricKey: String) -> String { - // We can't use \w because it includes chars like ä on Swift - return metricKey.replacingOccurrences(of: "[^a-zA-Z0-9_.-]+", with: "_", options: .regularExpression) -} - -private func sanitize(tagKey: String) -> String { - // We can't use \w because it includes chars like ä on Swift - return tagKey.replacingOccurrences(of: "[^a-zA-Z0-9_/.-]+", with: "", options: .regularExpression) -} - -private func replaceTagValueCharacters(tagValue: String) -> String { - var result = tagValue.replacingOccurrences(of: "\\", with: #"\\\\"#) - result = result.replacingOccurrences(of: "\n", with: #"\\n"#) - result = result.replacingOccurrences(of: "\r", with: #"\\r"#) - result = result.replacingOccurrences(of: "\t", with: #"\\t"#) - result = result.replacingOccurrences(of: "|", with: #"\\u{7c}"#) - return result.replacingOccurrences(of: ",", with: #"\\u{2c}"#) - -} diff --git a/Sources/Swift/Metrics/GaugeMetric.swift b/Sources/Swift/Metrics/GaugeMetric.swift deleted file mode 100644 index f5b938498be..00000000000 --- a/Sources/Swift/Metrics/GaugeMetric.swift +++ /dev/null @@ -1,34 +0,0 @@ -import Foundation - -class GaugeMetric: Metric { - - private var last: Double - private var min: Double - private var max: Double - private var sum: Double - private var count: UInt - - var weight: UInt = 5 - - init(first: Double, key: String, unit: MeasurementUnit, tags: [String: String]) { - self.last = first - self.min = first - self.max = first - self.sum = first - self.count = 1 - - super.init(type: .gauge, key: key, unit: unit, tags: tags) - } - - func add(value: Double) { - self.last = value - min = Swift.min(min, value) - max = Swift.max(max, value) - sum += value - count += 1 - } - - func serialize() -> [String] { - return ["\(last)", "\(min)", "\(max)", "\(sum)", "\(count)"] - } -} diff --git a/Sources/Swift/Metrics/LocalMetricsAggregator.swift b/Sources/Swift/Metrics/LocalMetricsAggregator.swift deleted file mode 100644 index a9f393fcb22..00000000000 --- a/Sources/Swift/Metrics/LocalMetricsAggregator.swift +++ /dev/null @@ -1,70 +0,0 @@ -import Foundation - -/// Used for correlating metrics to spans. See https://github.com/getsentry/rfcs/blob/main/text/0123-metrics-correlation.md -/// -@objc class LocalMetricsAggregator: NSObject { - - private struct Metric { - let min: Double - let max: Double - let count: Int - let sum: Double - let tags: [String: String] - } - - private var metricBuckets: [String: [String: Metric]] = [:] - private let lock = NSLock() - - func add(type: MetricType, key: String, value: Double, unit: MeasurementUnit, tags: [String: String]) { - - let exportKey = unit.unit.isEmpty ? "\(type.rawValue):\(key)" : "\(type.rawValue):\(key)@\(unit.unit)" - let tagsKey = tags.getMetricsTagsKey() - - lock.synchronized { - var bucket = metricBuckets[exportKey] ?? [:] - - var metric = bucket[tagsKey] ?? Metric(min: value, max: value, count: 1, sum: value, tags: tags) - - if bucket[tagsKey] != nil { - let newMin = min(metric.min, value) - let newMax = max(metric.max, value) - let newSum = metric.sum + value - let count = metric.count + 1 - - metric = Metric(min: newMin, max: newMax, count: count, sum: newSum, tags: tags) - } - - bucket[tagsKey] = metric - metricBuckets[exportKey] = bucket - } - } - - @objc func serialize() -> [String: [[String: Any]]] { - var returnValue: [String: [[String: Any]]] = [:] - - lock.synchronized { - - for (exportKey, bucket) in metricBuckets { - - var metrics: [[String: Any]] = [] - for (_, metric) in bucket { - var dict: [String: Any] = [ - "min": metric.min, - "max": metric.max, - "count": metric.count, - "sum": metric.sum - ] - if !metric.tags.isEmpty { - dict["tags"] = metric.tags - } - - metrics.append(dict) - } - - returnValue[exportKey] = metrics - } - } - - return returnValue - } -} diff --git a/Sources/Swift/Metrics/Metric.swift b/Sources/Swift/Metrics/Metric.swift deleted file mode 100644 index f2ab5294fd5..00000000000 --- a/Sources/Swift/Metrics/Metric.swift +++ /dev/null @@ -1,31 +0,0 @@ -import Foundation - -typealias Metric = MetricBase & MetricProtocol - -protocol MetricProtocol { - - var weight: UInt { get } - func serialize() -> [String] -} - -class MetricBase { - - let type: MetricType - let key: String - let unit: MeasurementUnit - let tags: [String: String] - - init(type: MetricType, key: String, unit: MeasurementUnit, tags: [String: String]) { - self.type = type - self.key = key - self.unit = unit - self.tags = tags - } -} - -enum MetricType: Character { - case counter = "c" - case gauge = "g" - case distribution = "d" - case set = "s" -} diff --git a/Sources/Swift/Metrics/MetricsAggregator.swift b/Sources/Swift/Metrics/MetricsAggregator.swift deleted file mode 100644 index 08e8661f633..00000000000 --- a/Sources/Swift/Metrics/MetricsAggregator.swift +++ /dev/null @@ -1,48 +0,0 @@ -import Foundation - -protocol MetricsAggregator { - func increment(key: String, value: Double, unit: MeasurementUnit, tags: [String: String], localMetricsAggregator: LocalMetricsAggregator?) - - func gauge(key: String, value: Double, unit: MeasurementUnit, tags: [String: String], localMetricsAggregator: LocalMetricsAggregator?) - - func distribution(key: String, value: Double, unit: MeasurementUnit, tags: [String: String], localMetricsAggregator: LocalMetricsAggregator?) - - func set(key: String, value: UInt, unit: MeasurementUnit, tags: [String: String], localMetricsAggregator: LocalMetricsAggregator?) - - func flush(force: Bool) - func close() -} - -extension Dictionary where Key == String, Value == String { - func getMetricsTagsKey() -> String { - // It's important to sort the tags in order to - // obtain the same bucket key. - return self.sorted(by: { $0.key < $1.key }).map({ "\($0.key)=\($0.value)" }).joined(separator: ",") - } -} - -class NoOpMetricsAggregator: MetricsAggregator { - func increment(key: String, value: Double, unit: MeasurementUnit, tags: [String: String], localMetricsAggregator: LocalMetricsAggregator?) { - // empty on purpose - } - - func gauge(key: String, value: Double, unit: MeasurementUnit, tags: [String: String], localMetricsAggregator: LocalMetricsAggregator?) { - // empty on purpose - } - - func distribution(key: String, value: Double, unit: MeasurementUnit, tags: [String: String], localMetricsAggregator: LocalMetricsAggregator?) { - // empty on purpose - } - - func set(key: String, value: UInt, unit: MeasurementUnit, tags: [String: String], localMetricsAggregator: LocalMetricsAggregator?) { - // empty on purpose - } - - func flush(force: Bool) { - // empty on purpose - } - - func close() { - // empty on purpose - } -} diff --git a/Sources/Swift/Metrics/SentryMetricsAPI.swift b/Sources/Swift/Metrics/SentryMetricsAPI.swift deleted file mode 100644 index d4a2f856f22..00000000000 --- a/Sources/Swift/Metrics/SentryMetricsAPI.swift +++ /dev/null @@ -1,148 +0,0 @@ -@_implementationOnly import _SentryPrivate -import Foundation - -@objc protocol SentryMetricsAPIDelegate: AnyObject { - - func getDefaultTagsForMetrics() -> [String: String] - - func getCurrentSpan() -> Span? - - // We don't want to add the LocalMetricsAggregator to the SpanProtocol - // because it would be public then. Exposing the LocalMetricsAggregator - // on the Span internally in Swift is a bit tricky, so we ask the - // delegate written in ObjC for it. - func getLocalMetricsAggregator(span: Span) -> LocalMetricsAggregator? -} - -/// Using SentryBeforeEmitMetricCallback of SentryDefines.h leads to compiler errors because of -/// Swift to ObjC interoperability. Defining the callback again in Swift with the same signature is a workaround. -typealias BeforeEmitMetricCallback = (String, [String: String]) -> Bool - -@objc public class SentryMetricsAPI: NSObject { - - private let aggregator: MetricsAggregator - private let currentDate: SentryCurrentDateProvider - - private weak var delegate: SentryMetricsAPIDelegate? - - @objc init(enabled: Bool, client: SentryMetricsClient, currentDate: SentryCurrentDateProvider, dispatchQueue: SentryDispatchQueueWrapper, random: SentryRandomProtocol, beforeEmitMetric: BeforeEmitMetricCallback?) { - - self.currentDate = currentDate - - if enabled { - self.aggregator = BucketMetricsAggregator(client: client, currentDate: currentDate, dispatchQueue: dispatchQueue, random: random, beforeEmitMetric: beforeEmitMetric ?? { _, _ in true }) - } else { - self.aggregator = NoOpMetricsAggregator() - } - } - - @objc func setDelegate(_ delegate: SentryMetricsAPIDelegate?) { - self.delegate = delegate - } - - /// Emits a Counter metric. - /// - /// - Parameter key: A unique key identifying the metric. - /// - Parameter value: The value to be added. - /// - Parameter unit: The value for the metric see `MeasurementUnit`. - /// - Parameter tags: Tags to associate with the metric. - @objc public func increment(key: String, value: Double = 1.0, unit: MeasurementUnit = .none, tags: [String: String] = [:]) { - let mergedTags = mergeDefaultTagsInto(tags: tags) - - aggregator.increment(key: key, value: value, unit: unit, tags: mergedTags, localMetricsAggregator: getLocalMetricsAggregator()) - } - - /// Emits a Gauge metric. - /// - /// - Parameter key: A unique key identifying the metric. - /// - Parameter value: The value to be added. - /// - Parameter unit: The value for the metric see `MeasurementUnit`. - /// - Parameter tags: Tags to associate with the metric. - @objc - public func gauge(key: String, value: Double, unit: MeasurementUnit = .none, tags: [String: String] = [:]) { - let mergedTags = mergeDefaultTagsInto(tags: tags) - aggregator.gauge(key: key, value: value, unit: unit, tags: mergedTags, localMetricsAggregator: getLocalMetricsAggregator()) - } - - /// Emits a Distribution metric. - /// - /// - Parameter key: A unique key identifying the metric. - /// - Parameter value: The value to be added. - /// - Parameter unit: The value for the metric see `MeasurementUnit`. - /// - Parameter tags: Tags to associate with the metric. - @objc - public func distribution(key: String, value: Double, unit: MeasurementUnit = .none, tags: [String: String] = [:]) { - let mergedTags = mergeDefaultTagsInto(tags: tags) - aggregator.distribution(key: key, value: value, unit: unit, tags: mergedTags, localMetricsAggregator: getLocalMetricsAggregator()) - } - - /// Emits a Set metric. - /// - /// - Parameter key: A unique key identifying the metric. - /// - Parameter value: The value to be added. - /// - Parameter unit: The value for the metric see `MeasurementUnit`. - /// - Parameter tags: Tags to associate with the metric. - @objc - public func set(key: String, value: String, unit: MeasurementUnit = .none, tags: [String: String] = [:]) { - let mergedTags = mergeDefaultTagsInto(tags: tags) - let crc32 = sentry_crc32ofString(value) - - aggregator.set(key: key, value: crc32, unit: unit, tags: mergedTags, localMetricsAggregator: getLocalMetricsAggregator()) - } - - /// Measures how long it takes to run the given closure by emitting a distribution metric in seconds. - /// - /// - Note: This method also creates a child span with the operation `metric.timing` and the - /// description `key` if a span is bound to the scope. - /// - /// - Parameter key: A unique key identifying the metric. - /// - Parameter tags: Tags to associate with the metric. - public func timing(key: String, tags: [String: String] = [:], _ closure: () throws -> T) rethrows -> T { - - guard let currentSpan = delegate?.getCurrentSpan() else { - return try closure() - } - - let span = currentSpan.startChild(operation: "metric.timing", description: key) - let aggregator = delegate?.getLocalMetricsAggregator(span: span) - - let mergedTags = mergeDefaultTagsInto(tags: tags) - for tag in mergedTags { - span.setTag(value: tag.value, key: tag.key) - } - - defer { - span.finish() - if let timestamp = span.timestamp, let startTimestamp = span.startTimestamp { - let duration = timestamp.timeIntervalSince(startTimestamp) - - self.aggregator.distribution(key: key, value: duration, unit: MeasurementUnitDuration.second, tags: mergedTags, localMetricsAggregator: aggregator) - } - } - - return try closure() - } - - @objc public func close() { - aggregator.close() - delegate = nil - } - - @objc public func flush() { - aggregator.flush(force: true) - } - - /// Merges the default tags into the passed tags. If there are duplicates the method keeps the passed in tags and discards the default tags. - private func mergeDefaultTagsInto(tags: [String: String]) -> [String: String] { - let defaultTags = delegate?.getDefaultTagsForMetrics() ?? [:] - return tags.merging(defaultTags) { (tagValue, _) in tagValue } - } - - private func getLocalMetricsAggregator() -> LocalMetricsAggregator? { - if let currentSpan = delegate?.getCurrentSpan() { - return delegate?.getLocalMetricsAggregator(span: currentSpan) - } - return nil - } - -} diff --git a/Sources/Swift/Metrics/SentryMetricsClient.swift b/Sources/Swift/Metrics/SentryMetricsClient.swift deleted file mode 100644 index 7d2dc25495e..00000000000 --- a/Sources/Swift/Metrics/SentryMetricsClient.swift +++ /dev/null @@ -1,19 +0,0 @@ -@_implementationOnly import _SentryPrivate -import Foundation - -@objc class SentryMetricsClient: NSObject { - - /// Exposing envelopes to Swift code is challenging because the - /// SentryEnvelope.h is part of the podspec SentryHybridPublic, which causes - /// problems. As the envelope logic is simple, we keep it in ObjC and do the - /// rest in Swift. - private let client: SentryStatsdClient - - @objc init(client: SentryStatsdClient) { - self.client = client - } - - func capture(flushableBuckets: [BucketTimestamp: [Metric]]) { - client.captureStatsdEncodedData(encodeToStatsd(flushableBuckets: flushableBuckets)) - } -} diff --git a/Sources/Swift/Metrics/SetMetric.swift b/Sources/Swift/Metrics/SetMetric.swift deleted file mode 100644 index bc6116709aa..00000000000 --- a/Sources/Swift/Metrics/SetMetric.swift +++ /dev/null @@ -1,22 +0,0 @@ -import Foundation - -class SetMetric: Metric { - - private var set: Set - var weight: UInt { - return UInt(set.count) - } - - init(first: UInt, key: String, unit: MeasurementUnit, tags: [String: String]) { - set = [first] - super.init(type: .set, key: key, unit: unit, tags: tags) - } - - func add(value: UInt) { - set.insert(value) - } - - func serialize() -> [String] { - return set.map { "\($0)" } - } -} diff --git a/Tests/SentryTests/Helper/SentryEnabledFeaturesBuilderTests.swift b/Tests/SentryTests/Helper/SentryEnabledFeaturesBuilderTests.swift index 0c27d694632..1fb4f4547df 100644 --- a/Tests/SentryTests/Helper/SentryEnabledFeaturesBuilderTests.swift +++ b/Tests/SentryTests/Helper/SentryEnabledFeaturesBuilderTests.swift @@ -15,7 +15,6 @@ final class SentryEnabledFeaturesBuilderTests: XCTestCase { options.enablePerformanceV2 = true options.enableTimeToFullDisplayTracing = true options.swiftAsyncStacktraces = true - options.enableMetrics = true #if os(iOS) || os(macOS) || targetEnvironment(macCatalyst) options.enableAppLaunchProfiling = true @@ -33,7 +32,6 @@ final class SentryEnabledFeaturesBuilderTests: XCTestCase { XCTAssert(features.contains("performanceV2")) XCTAssert(features.contains("timeToFullDisplayTracing")) XCTAssert(features.contains("swiftAsyncStacktraces")) - XCTAssert(features.contains("metrics")) #if os(iOS) || os(macOS) || targetEnvironment(macCatalyst) XCTAssert(features.contains("appLaunchProfiling")) diff --git a/Tests/SentryTests/SentryHubTests.swift b/Tests/SentryTests/SentryHubTests.swift index 60ce83c389d..8a638ce39a5 100644 --- a/Tests/SentryTests/SentryHubTests.swift +++ b/Tests/SentryTests/SentryHubTests.swift @@ -1039,130 +1039,6 @@ class SentryHubTests: XCTestCase { ])) } - func testInitHubWithDefaultOptions_DoesNotEnableMetrics() { - let sut = fixture.getSut() - - sut.metrics.increment(key: "key") - sut.close() - - XCTAssertEqual(self.fixture.client.captureEnvelopeInvocations.count, 0) - } - - func testMetrics_IncrementOneValue() throws { - let options = fixture.options - options.enableMetrics = true - let sut = fixture.getSut(options) - - sut.metrics.increment(key: "key") - sut.flush(timeout: 1.0) - - let client = self.fixture.client - XCTAssertEqual(client.captureEnvelopeInvocations.count, 1) - - let envelope = try XCTUnwrap(client.captureEnvelopeInvocations.first) - XCTAssertNotNil(envelope.header.eventId) - - // We only check if it's an envelope with a statsd envelope item. - // We validate the contents of the envelope in SentryMetricsClientTests - XCTAssertEqual(envelope.items.count, 1) - let envelopeItem = try XCTUnwrap(envelope.items.first) - XCTAssertEqual(envelopeItem.header.type, SentryEnvelopeItemTypeStatsd) - XCTAssertEqual(envelopeItem.header.contentType, "application/octet-stream") - } - - func testAddIncrementMetric_GetsLocalMetricsAggregatorFromCurrentSpan() throws { - let options = fixture.options - options.enableMetrics = true - let sut = fixture.getSut(options) - - let span = sut.startTransaction(name: fixture.transactionName, operation: fixture.transactionOperation, bindToScope: true) - let tracer = try XCTUnwrap(span as? SentryTracer) - - sut.metrics.increment(key: "key") - - let aggregator = tracer.getLocalMetricsAggregator() - - let metricsSummary = aggregator.serialize() - XCTAssertEqual(metricsSummary.count, 1) - - let bucket = try XCTUnwrap(metricsSummary["c:key"]) - XCTAssertEqual(bucket.count, 1) - let metric = try XCTUnwrap(bucket.first) - XCTAssertEqual(metric["min"] as? Double, 1.0) - XCTAssertEqual(metric["max"] as? Double, 1.0) - XCTAssertEqual(metric["count"] as? Int, 1) - XCTAssertEqual(metric["sum"] as? Double, 1.0) - } - - func testAddIncrementMetric_AddsDefaultTags() throws { - let options = fixture.options - options.releaseName = "release1" - options.environment = "test" - options.enableMetrics = true - let sut = fixture.getSut(options) - - let span = sut.startTransaction(name: fixture.transactionName, operation: fixture.transactionOperation, bindToScope: true) - let tracer = try XCTUnwrap(span as? SentryTracer) - - sut.metrics.increment(key: "key", tags: ["my": "tag", "release": "overwritten"]) - - let aggregator = tracer.getLocalMetricsAggregator() - - let metricsSummary = aggregator.serialize() - XCTAssertEqual(metricsSummary.count, 1) - - let bucket = try XCTUnwrap(metricsSummary["c:key"]) - XCTAssertEqual(bucket.count, 1) - let metric = try XCTUnwrap(bucket.first) - XCTAssertEqual(metric["tags"] as? [String: String], ["my": "tag", "release": "overwritten", "environment": options.environment]) - } - - func testAddIncrementMetric_ReleaseNameNil() throws { - let options = fixture.options - options.releaseName = nil - options.enableMetrics = true - let sut = fixture.getSut(options) - - let span = sut.startTransaction(name: fixture.transactionName, operation: fixture.transactionOperation, bindToScope: true) - let tracer = try XCTUnwrap(span as? SentryTracer) - - sut.metrics.increment(key: "key", tags: ["my": "tag"]) - - let aggregator = tracer.getLocalMetricsAggregator() - - let metricsSummary = aggregator.serialize() - XCTAssertEqual(metricsSummary.count, 1) - - let bucket = try XCTUnwrap(metricsSummary["c:key"]) - XCTAssertEqual(bucket.count, 1) - let metric = try XCTUnwrap(bucket.first) - XCTAssertEqual(metric["tags"] as? [String: String], ["my": "tag", "environment": options.environment]) - } - - func testAddIncrementMetric_DefaultTagsDisabled() throws { - let options = fixture.options - options.releaseName = "release1" - options.environment = "test" - options.enableMetrics = true - options.enableDefaultTagsForMetrics = false - let sut = fixture.getSut(options) - - let span = sut.startTransaction(name: fixture.transactionName, operation: fixture.transactionOperation, bindToScope: true) - let tracer = try XCTUnwrap(span as? SentryTracer) - - sut.metrics.increment(key: "key", tags: ["my": "tag"]) - - let aggregator = tracer.getLocalMetricsAggregator() - - let metricsSummary = aggregator.serialize() - XCTAssertEqual(metricsSummary.count, 1) - - let bucket = try XCTUnwrap(metricsSummary["c:key"]) - XCTAssertEqual(bucket.count, 1) - let metric = try XCTUnwrap(bucket.first) - XCTAssertEqual(metric["tags"] as? [String: String], ["my": "tag"]) - } - private func captureEventEnvelope(level: SentryLevel) { let event = TestData.event event.level = level diff --git a/Tests/SentryTests/SentryOptionsTest.m b/Tests/SentryTests/SentryOptionsTest.m index 14925b4a48f..f240fbfc961 100644 --- a/Tests/SentryTests/SentryOptionsTest.m +++ b/Tests/SentryTests/SentryOptionsTest.m @@ -1504,42 +1504,6 @@ - (void)testSpotlightUrl XCTAssertEqualObjects(options3.spotlightUrl, @"http://localhost:8969/stream"); } -- (void)testEnableMetrics -{ - [self testBooleanField:@"enableMetrics" defaultValue:NO]; -} - -- (void)testEnableDefaultTagsForMetrics -{ - [self testBooleanField:@"enableDefaultTagsForMetrics" defaultValue:YES]; -} - -- (void)testEnableSpanLocalMetricAggregation -{ - [self testBooleanField:@"enableSpanLocalMetricAggregation" defaultValue:YES]; -} - -- (void)testBeforeEmitMetric -{ - SentryBeforeEmitMetricCallback callback - = ^(NSString *_Nonnull key, NSDictionary *_Nonnull tags) { - // Use tags and key to silence unused compiler error - XCTAssertNotNil(key); - XCTAssertNotNil(tags); - return YES; - }; - SentryOptions *options = [self getValidOptions:@{ @"beforeEmitMetric" : callback }]; - - XCTAssertEqual(callback, options.beforeEmitMetric); -} - -- (void)testDefaultBeforeEmitMetric -{ - SentryOptions *options = [self getValidOptions:@{}]; - - XCTAssertNil(options.beforeEmitMetric); -} - #pragma mark - Private - (void)assertArrayEquals:(NSArray *)expected actual:(NSArray *)actual diff --git a/Tests/SentryTests/SentrySDKTests.swift b/Tests/SentryTests/SentrySDKTests.swift index 8e316fd975d..75239a90d05 100644 --- a/Tests/SentryTests/SentrySDKTests.swift +++ b/Tests/SentryTests/SentrySDKTests.swift @@ -847,47 +847,6 @@ class SentrySDKTests: XCTestCase { XCTAssert(mainThreadIntegration.installedInTheMainThread, "SDK is not being initialized in the main thread") } - - func testMetrics_IncrementOneValue() throws { - let options = fixture.options - options.enableMetrics = true - - SentrySDK.start(options: options) - let client = try XCTUnwrap(TestClient(options: options)) - let hub = SentryHub(client: client, andScope: nil) - SentrySDK.setCurrentHub(hub) - - SentrySDK.metrics.increment(key: "key") - SentrySDK.flush(timeout: 1.0) - - XCTAssertEqual(client.captureEnvelopeInvocations.count, 1) - - let envelope = try XCTUnwrap(client.captureEnvelopeInvocations.first) - XCTAssertNotNil(envelope.header.eventId) - - // We only check if it's an envelope with a statsd envelope item. - // We validate the contents of the envelope in SentryMetricsClientTests - XCTAssertEqual(envelope.items.count, 1) - let envelopeItem = try XCTUnwrap(envelope.items.first) - XCTAssertEqual(envelopeItem.header.type, SentryEnvelopeItemTypeStatsd) - XCTAssertEqual(envelopeItem.header.contentType, "application/octet-stream") - } - - func testMetrics_BeforeEmitMetricCallback_DiscardEveryThing() throws { - let options = fixture.options - options.enableMetrics = true - options.beforeEmitMetric = { _, _ in false } - - SentrySDK.start(options: options) - let client = try XCTUnwrap(TestClient(options: options)) - let hub = SentryHub(client: client, andScope: nil) - SentrySDK.setCurrentHub(hub) - - SentrySDK.metrics.increment(key: "key") - SentrySDK.flush(timeout: 1.0) - - XCTAssertEqual(client.captureEnvelopeInvocations.count, 0) - } #if SENTRY_HAS_UIKIT diff --git a/Tests/SentryTests/Swift/Metrics/BucketMetricsAggregatorTests.swift b/Tests/SentryTests/Swift/Metrics/BucketMetricsAggregatorTests.swift deleted file mode 100644 index 7eb48087652..00000000000 --- a/Tests/SentryTests/Swift/Metrics/BucketMetricsAggregatorTests.swift +++ /dev/null @@ -1,450 +0,0 @@ -@testable import _SentryPrivate -@testable import Sentry -import SentryTestUtils -import XCTest - -final class BucketMetricsAggregatorTests: XCTestCase { - - private func getSut(totalMaxWeight: UInt = 4, flushShift: Double = 0.0, dispatchQueue: SentryDispatchQueueWrapper = TestSentryDispatchQueueWrapper()) throws -> (BucketMetricsAggregator, TestCurrentDateProvider, TestMetricsClient) { - let currentDate = TestCurrentDateProvider() - let metricsClient = try TestMetricsClient() - let random = TestRandom(value: flushShift) - - return (BucketMetricsAggregator(client: metricsClient, currentDate: currentDate, dispatchQueue: dispatchQueue, random: random, totalMaxWeight: totalMaxWeight, flushInterval: 10.0, flushTolerance: 1.0), currentDate, metricsClient) - } - - func testSameMetricAggregated_WhenInSameBucket() throws { - let (sut, currentDate, metricsClient) = try getSut() - - sut.distribution( key: "key", value: 1.0, unit: MeasurementUnitDuration.day, tags: [:]) - currentDate.setDate(date: currentDate.date().addingTimeInterval(9.99)) - sut.distribution(key: "key", value: 1.1, unit: MeasurementUnitDuration.day, tags: [:]) - - sut.flush(force: true) - - XCTAssertEqual(metricsClient.captureInvocations.count, 1) - let buckets = try XCTUnwrap(metricsClient.captureInvocations.first) - - let bucket = try XCTUnwrap(buckets[currentDate.bucketTimestamp]) - XCTAssertEqual(bucket.count, 1) - let counterMetric = try XCTUnwrap(bucket.first as? DistributionMetric) - - XCTAssertEqual(counterMetric.key, "key") - XCTAssert(counterMetric.serialize().contains("1.0")) - XCTAssert(counterMetric.serialize().contains("1.1")) - XCTAssertEqual(counterMetric.unit.unit, MeasurementUnitDuration.day.unit) - XCTAssertEqual(counterMetric.tags, [:]) - } - - func testFlushShift_MetricsUsuallyInSameBucket_AreInDifferent() throws { - let (sut, currentDate, metricsClient) = try getSut(totalMaxWeight: 100, flushShift: 0.1) - - sut.gauge(key: "key", value: 1.0, unit: MeasurementUnitDuration.day, tags: [:]) - - currentDate.setDate(date: currentDate.date().addingTimeInterval( 9.99)) - sut.gauge(key: "key", value: -1.0, unit: MeasurementUnitDuration.day, tags: [:]) - - // Not flushing yet - currentDate.setDate(date: currentDate.date().addingTimeInterval( 1.0)) - sut.flush(force: false) - XCTAssertEqual(metricsClient.captureInvocations.count, 0) - - // This ends up in a different bucket - sut.gauge(key: "key", value: 1.0, unit: MeasurementUnitDuration.day, tags: [:]) - - // Now we pass the flush shift threshold - currentDate.setDate(date: currentDate.date().addingTimeInterval( 0.01)) - sut.flush(force: false) - - XCTAssertEqual(metricsClient.captureInvocations.count, 1) - let buckets = try XCTUnwrap(metricsClient.captureInvocations.first) - - let previousBucketTimestamp = currentDate.bucketTimestamp - 10 - let bucket = try XCTUnwrap(buckets[previousBucketTimestamp]) - XCTAssertEqual(bucket.count, 1) - let metric = try XCTUnwrap(bucket.first as? GaugeMetric) - - XCTAssertEqual(metric.key, "key") - XCTAssertEqual(metric.serialize(), ["-1.0", "-1.0", "1.0", "0.0", "2"]) - XCTAssertEqual(metric.unit.unit, MeasurementUnitDuration.day.unit) - XCTAssertEqual(metric.tags, [:]) - } - - func testDifferentMetrics_NotInSameBucket() throws { - let (sut, currentDate, metricsClient) = try getSut() - - sut.set( key: "key1", value: 1, unit: MeasurementUnitDuration.day, tags: ["some": "tag", "and": "another-one"]) - sut.set(key: "key2", value: 2, unit: MeasurementUnitDuration.day, tags: ["and": "another-one", "some": "tag"]) - - sut.flush(force: true) - - XCTAssertEqual(metricsClient.captureInvocations.count, 1) - let buckets = try XCTUnwrap(metricsClient.captureInvocations.first) - - let bucket = try XCTUnwrap(buckets[currentDate.bucketTimestamp]) - XCTAssertEqual(bucket.count, 2) - - let metric1 = try XCTUnwrap(bucket.first { $0.key == "key1" } as? SetMetric) - XCTAssertEqual(metric1.key, "key1") - XCTAssertEqual(metric1.serialize(), ["1"]) - XCTAssertEqual(metric1.unit.unit, MeasurementUnitDuration.day.unit) - XCTAssertEqual(metric1.tags, ["some": "tag", "and": "another-one"]) - - let metric2 = try XCTUnwrap(bucket.first { $0.key == "key2" } as? SetMetric) - XCTAssertEqual(metric2.key, "key2") - XCTAssertEqual(metric2.serialize(), ["2"]) - XCTAssertEqual(metric2.unit.unit, MeasurementUnitDuration.day.unit) - XCTAssertEqual(metric2.tags, ["some": "tag", "and": "another-one"]) - } - - func testSameMetricDifferentTag_NotInSameBucket() throws { - let (sut, currentDate, metricsClient) = try getSut() - - sut.increment(key: "key", value: 1.0, unit: MeasurementUnitDuration.day, tags: ["some": "tag"]) - sut.increment(key: "key", value: 2.0, unit: MeasurementUnitDuration.day, tags: ["some": "other-tag"]) - - sut.flush(force: true) - - XCTAssertEqual(metricsClient.captureInvocations.count, 1) - let buckets = try XCTUnwrap(metricsClient.captureInvocations.first) - - let bucket = try XCTUnwrap(buckets[currentDate.bucketTimestamp]) - XCTAssertEqual(bucket.count, 2) - - let counterMetric1 = try XCTUnwrap(bucket.first { $0.tags == ["some": "tag"] } as? CounterMetric) - XCTAssertEqual(counterMetric1.key, "key") - XCTAssertEqual(counterMetric1.serialize(), ["1.0"]) - XCTAssertEqual(counterMetric1.unit.unit, MeasurementUnitDuration.day.unit) - XCTAssertEqual(counterMetric1.tags, ["some": "tag"]) - - let counterMetric2 = try XCTUnwrap(bucket.first { $0.tags == ["some": "other-tag"] } as? CounterMetric) - XCTAssertEqual(counterMetric2.key, "key") - XCTAssertEqual(counterMetric2.serialize(), ["2.0"]) - XCTAssertEqual(counterMetric2.unit.unit, MeasurementUnitDuration.day.unit) - XCTAssertEqual(counterMetric2.tags, ["some": "other-tag"]) - } - - func testSameMetricNotAggregated_WhenNotInSameBucket() throws { - let (sut, currentDate, metricsClient) = try getSut(totalMaxWeight: 5) - - sut.increment(key: "key", value: 1.0, unit: MeasurementUnitDuration.day, tags: [:]) - currentDate.setDate(date: currentDate.date().addingTimeInterval( 10.0)) - sut.increment(key: "key", value: 1.0, unit: MeasurementUnitDuration.day, tags: [:]) - - sut.flush(force: true) - - XCTAssertEqual(metricsClient.captureInvocations.count, 1) - let buckets = try XCTUnwrap(metricsClient.captureInvocations.first) - - XCTAssertEqual(buckets.count, 2) - - let bucket1 = try XCTUnwrap(buckets.values.first) - XCTAssertEqual(bucket1.count, 1) - let counterMetric1 = try XCTUnwrap(bucket1.first as? CounterMetric) - - XCTAssertEqual(counterMetric1.key, "key") - XCTAssertEqual(counterMetric1.serialize(), ["1.0"]) - XCTAssertEqual(counterMetric1.unit.unit, MeasurementUnitDuration.day.unit) - XCTAssertEqual(counterMetric1.tags, [:]) - - let bucket2 = try XCTUnwrap(Array(buckets.values).last) - let counterMetric2 = try XCTUnwrap(bucket2.first as? CounterMetric) - - XCTAssertEqual(counterMetric2.key, "key") - XCTAssertEqual(counterMetric2.serialize(), ["1.0"]) - XCTAssertEqual(counterMetric2.unit.unit, MeasurementUnitDuration.day.unit) - XCTAssertEqual(counterMetric2.tags, [:]) - } - - func testCallFlushWhenOverweight() throws { - let (sut, _, metricsClient) = try getSut(totalMaxWeight: 3, dispatchQueue: SentryDispatchQueueWrapper()) - - let expectation = expectation(description: "Before capture block called") - metricsClient.afterRecordingCaptureInvocationBlock = { - XCTAssertFalse(Thread.isMainThread, "Flush must be called on a background thread, but was called on the main thread.") - expectation.fulfill() - } - - sut.increment(key: "key1", value: 1.0, unit: MeasurementUnitDuration.day, tags: [:]) - sut.increment(key: "key2", value: 1.0, unit: MeasurementUnitDuration.day, tags: [:]) - - wait(for: [expectation], timeout: 1.0) - XCTAssertEqual(metricsClient.captureInvocations.count, 1) - } - - func testConvenienceInit_SetsCorrectMaxWeight() throws { - let metricsClient = try TestMetricsClient() - let sut = BucketMetricsAggregator(client: metricsClient, currentDate: TestCurrentDateProvider(), dispatchQueue: TestSentryDispatchQueueWrapper(), random: SentryRandom()) - - for i in 0..<998 { - sut.increment(key: "key\(i)", value: 1.0, unit: MeasurementUnitDuration.day, tags: [:]) - } - - // Total weight is now 999 because the bucket counts for one - // So nothing should be sent - XCTAssertEqual(metricsClient.captureInvocations.count, 0) - - // Now we pass the 1000 threshold - sut.increment(key: "another key", value: 1.0, unit: MeasurementUnitDuration.day, tags: [:]) - - XCTAssertEqual(metricsClient.captureInvocations.count, 1) - } - - func testFlushOnlyWhenNeeded() throws { - let (sut, currentDate, metricsClient) = try getSut(totalMaxWeight: 5) - - sut.increment(key: "key1", value: 1.0, unit: MeasurementUnitDuration.day, tags: [:]) - - sut.flush(force: false) - XCTAssertEqual(metricsClient.captureInvocations.invocations.count, 0) - - currentDate.setDate(date: currentDate.date().addingTimeInterval( 9.99)) - sut.flush(force: false) - XCTAssertEqual(metricsClient.captureInvocations.invocations.count, 0) - - currentDate.setDate(date: currentDate.date().addingTimeInterval( 0.01)) - sut.increment(key: "key2", value: 1.0, unit: MeasurementUnitDuration.day, tags: [:]) - - sut.flush(force: false) - let buckets1 = try XCTUnwrap(metricsClient.captureInvocations.first) - XCTAssertEqual(buckets1.count, 1) - XCTAssertEqual(buckets1.values.count, 1) - - // Key2 wasn't flushed. We increment it to 2.0 - sut.increment( key: "key2", value: 1.0, unit: MeasurementUnitDuration.day, tags: [:]) - - // The weight should be 2 now, so we need to add 3 more to trigger a flush - sut.increment(key: "key3", value: 1.0, unit: MeasurementUnitDuration.day, tags: [:]) - sut.increment(key: "key4", value: 1.0, unit: MeasurementUnitDuration.day, tags: [:]) - sut.increment(key: "key5", value: 1.0, unit: MeasurementUnitDuration.day, tags: [:]) - - XCTAssertEqual(metricsClient.captureInvocations.count, 2) - let buckets2 = try XCTUnwrap(try XCTUnwrap(metricsClient.captureInvocations.invocations.element(at: 1))) - - XCTAssertEqual(buckets2.count, 1) - let bucket = try XCTUnwrap(buckets2.first) - - // All 4 metrics should be in the bucket - XCTAssertEqual(bucket.value.count, 4) - - // Check that key2 was incremented - let counterMetric = try XCTUnwrap(bucket.value.first { $0.key == "key2" } as? CounterMetric) - XCTAssertEqual(counterMetric.serialize(), ["2.0"]) - } - - func testWeightWithMultipleDifferent() throws { - let (sut, currentDate, metricsClient) = try getSut(totalMaxWeight: 4) - - sut.distribution(key: "key", value: 1.0, unit: .none, tags: [:]) - sut.distribution(key: "key", value: 1.0, unit: .none, tags: [:]) - - // Weight should be 3, no flush yet - sut.flush(force: false) - XCTAssertEqual(metricsClient.captureInvocations.count, 0) - - // Time passed, must flush - currentDate.setDate(date: currentDate.date().addingTimeInterval(10.0)) - sut.flush(force: false) - XCTAssertEqual(metricsClient.captureInvocations.count, 1) - - sut.distribution(key: "key", value: 1.0, unit: .none, tags: [:]) - sut.distribution(key: "key", value: 1.0, unit: .none, tags: [:]) - sut.distribution(key: "key", value: 1.0, unit: .none, tags: [:]) - - // Reached overweight, must flush - XCTAssertEqual(metricsClient.captureInvocations.count, 2) - } - - func testInitStartsRepeatingTimer() throws { - let currentDate = TestCurrentDateProvider() - let metricsClient = try TestMetricsClient() - - // Start the flush timer with very high interval - let sut = BucketMetricsAggregator(client: metricsClient, currentDate: currentDate, dispatchQueue: SentryDispatchQueueWrapper(), random: SentryRandom(), totalMaxWeight: 1_000, flushInterval: 0.000001, flushTolerance: 0.0) - - SentryLog.withOutLogs { - let expectation = expectation(description: "Adding metrics") - expectation.expectedFulfillmentCount = 100 - - // Keep adding metrics async so the flush timer has a few chances to - // send metrics - for i in 0..<100 { - DispatchQueue.global().async { - sut.increment(key: "key\(i)", value: 1.0, unit: MeasurementUnitDuration.day, tags: [:]) - currentDate.setDate(date: currentDate.date().addingTimeInterval(10.0)) - - expectation.fulfill() - } - } - - wait(for: [expectation], timeout: 1.0) - - XCTAssertGreaterThan(metricsClient.captureInvocations.count, 0, "Repeating flush timer should send some metrics.") - } - } - - func testClose_InvalidatesTimer() throws { - let currentDate = TestCurrentDateProvider() - let metricsClient = try TestMetricsClient() - - // Start the flush timer with very high interval - let sut = BucketMetricsAggregator(client: metricsClient, currentDate: currentDate, dispatchQueue: SentryDispatchQueueWrapper(), random: SentryRandom(), totalMaxWeight: 1_000, flushInterval: 0.000001, flushTolerance: 0.0) - - sut.close() - - let expectation = expectation(description: "Adding metrics") - expectation.expectedFulfillmentCount = 100 - - // Keep adding metrics async so the flush timer has a few chances to - // send metrics - for i in 0..<100 { - DispatchQueue.global().async { - sut.increment(key: "key\(i)", value: 1.0, unit: MeasurementUnitDuration.day, tags: [:]) - currentDate.setDate(date: currentDate.date().addingTimeInterval(10.0)) - - expectation.fulfill() - } - } - - wait(for: [expectation], timeout: 1.0) - - XCTAssertEqual(metricsClient.captureInvocations.count, 0, "No metrics should be sent cause the flush timer should be cancelled.") - } - - func testFlushCalledOnCallingThread() throws { - let (sut, _, metricsClient) = try getSut(dispatchQueue: SentryDispatchQueueWrapper()) - - let expectation = expectation(description: "Before capture block called") - metricsClient.afterRecordingCaptureInvocationBlock = { - XCTAssertEqual(Thread.isMainThread, true, "Flush must be called on the calling thread, but was called on a background thread.") - expectation.fulfill() - } - - sut.increment(key: "key1", value: 1.0, unit: MeasurementUnitDuration.day, tags: [:]) - sut.increment(key: "key2", value: 1.0, unit: MeasurementUnitDuration.day, tags: [:]) - - sut.flush(force: true) - - wait(for: [expectation], timeout: 1.0) - XCTAssertEqual(metricsClient.captureInvocations.count, 1) - } - - func testCloseCallsFlush() throws { - let (sut, _, metricsClient) = try getSut() - - sut.increment(key: "key1", value: 1.0, unit: MeasurementUnitDuration.day, tags: [:]) - sut.increment(key: "key2", value: 1.0, unit: MeasurementUnitDuration.day, tags: [:]) - - sut.close() - - XCTAssertEqual(metricsClient.captureInvocations.count, 1) - } - - func testWriteMultipleMetricsInParallel_NonForceFlush_DoesNotCrash() throws { - let currentDate = TestCurrentDateProvider() - let metricsClient = try TestMetricsClient() - - // Start the flush timer with very high interval - let sut = BucketMetricsAggregator(client: metricsClient, currentDate: currentDate, dispatchQueue: SentryDispatchQueueWrapper(), random: SentryRandom(), totalMaxWeight: 1_000, flushInterval: 0.001, flushTolerance: 0.0) - - testConcurrentModifications(asyncWorkItems: 10, writeLoopCount: 1_000, writeWork: { i in - sut.increment(key: "key\(i)", value: 1.1, unit: .none, tags: ["some": "tag"]) - currentDate.setDate(date: currentDate.date().addingTimeInterval(0.01)) - }) - - sut.close() - } - - func testWriteMultipleMetricsInParallel_ForceFlush_DoesNotCrash() throws { - let (sut, _, _) = try getSut(totalMaxWeight: 5) - - testConcurrentModifications(asyncWorkItems: 10, writeLoopCount: 1_000, writeWork: { i in - sut.increment(key: "key\(i)", value: 1.1, unit: .none, tags: ["some": "tag"]) - sut.gauge(key: "key\(i)", value: 1.1, unit: .none, tags: ["some": "tag"]) - sut.distribution(key: "key\(i)", value: 1.1, unit: .none, tags: ["some": "tag"]) - sut.set(key: "key\(i)", value: 11, unit: .none, tags: ["some": "tag"]) - }) - } - - func testCounterMetricGetsForwardedToLocalAggregator() throws { - let localMetricsAggregator = LocalMetricsAggregator() - let (sut, _, _) = try getSut() - - sut.increment(key: "key1", value: 1.0, unit: MeasurementUnitDuration.day, tags: ["some": "tag"], localMetricsAggregator: localMetricsAggregator) - - let serialized = localMetricsAggregator.serialize() - XCTAssertEqual(serialized.count, 1) - let bucket = try XCTUnwrap(serialized["c:key1@day"]) - - XCTAssertEqual(bucket.count, 1) - let metric = try XCTUnwrap(bucket.first) - - XCTAssertEqual(metric["tags"] as? [String: String], ["some": "tag"]) - XCTAssertEqual(metric["min"] as? Double, 1.0) - XCTAssertEqual(metric["max"] as? Double, 1.0) - XCTAssertEqual(metric["count"] as? Int, 1) - XCTAssertEqual(metric["sum"] as? Double, 1.0) - } - - func testSetMetricOnlyForwardsAddedWeight() throws { - let localMetricsAggregator = LocalMetricsAggregator() - let (sut, _, _) = try getSut() - - sut.set(key: "key1", value: 1, unit: MeasurementUnitDuration.day, tags: ["some": "tag"], localMetricsAggregator: localMetricsAggregator) - // This one doesn't add new weight - sut.set(key: "key1", value: 1, unit: MeasurementUnitDuration.day, tags: ["some": "tag"], localMetricsAggregator: localMetricsAggregator) - - let serialized = localMetricsAggregator.serialize() - XCTAssertEqual(serialized.count, 1) - let bucket = try XCTUnwrap(serialized["s:key1@day"]) - - XCTAssertEqual(bucket.count, 1) - let metric = try XCTUnwrap(bucket.first) - - XCTAssertEqual(metric["tags"] as? [String: String], ["some": "tag"]) - // When no weight added the value is 0.0 - XCTAssertEqual(metric["min"] as? Double, 0.0) - XCTAssertEqual(metric["max"] as? Double, 1.0) - XCTAssertEqual(metric["count"] as? Int, 2) - XCTAssertEqual(metric["sum"] as? Double, 1.0) - } - - func testBeforeEmitMetricCallback() throws { - let currentDate = TestCurrentDateProvider() - let metricsClient = try TestMetricsClient() - - let sut = BucketMetricsAggregator(client: metricsClient, currentDate: currentDate, dispatchQueue: SentryDispatchQueueWrapper(), random: SentryRandom(), beforeEmitMetric: { key, tags in - if key == "key" { - return false - } - - if tags == ["my": "tag"] { - return false - } - - return true - }, totalMaxWeight: 1_000) - - // removed - sut.distribution( key: "key", value: 1.0, unit: MeasurementUnitDuration.day, tags: [:]) - - // kept - sut.distribution(key: "key1", value: 1.0, unit: MeasurementUnitDuration.day, tags: [:]) - - // removed - sut.distribution(key: "key1", value: 1.0, unit: MeasurementUnitDuration.day, tags: ["my": "tag"]) - - sut.flush(force: true) - - XCTAssertEqual(metricsClient.captureInvocations.count, 1) - let buckets = try XCTUnwrap(metricsClient.captureInvocations.first) - - let bucket = try XCTUnwrap(buckets[currentDate.bucketTimestamp]) - XCTAssertEqual(bucket.count, 1) - let metric = try XCTUnwrap(bucket.first as? DistributionMetric) - - XCTAssertEqual(metric.key, "key1") - XCTAssertEqual(metric.tags.isEmpty, true) - } - -} diff --git a/Tests/SentryTests/Swift/Metrics/CounterMetricTests.swift b/Tests/SentryTests/Swift/Metrics/CounterMetricTests.swift deleted file mode 100644 index 1bb5b2e1889..00000000000 --- a/Tests/SentryTests/Swift/Metrics/CounterMetricTests.swift +++ /dev/null @@ -1,34 +0,0 @@ -@testable import Sentry -import XCTest - -final class CounterMetricTests: XCTestCase { - - func testAddingValues() { - let sut = CounterMetric(first: 1.0, key: "key", unit: MeasurementUnitDuration.hour, tags: [:]) - - sut.add(value: 1.0) - sut.add(value: -1.0) - sut.add(value: 2.0) - - XCTAssertEqual(sut.serialize(), ["3.0"]) - } - - func testType() { - let sut = CounterMetric(first: 0.0, key: "key", unit: MeasurementUnitDuration.hour, tags: [:]) - - XCTAssertEqual(sut.type, .counter) - } - - func testWeight() { - let sut = CounterMetric(first: 0.0, key: "key", unit: MeasurementUnitDuration.hour, tags: [:]) - - XCTAssertEqual(sut.weight, 1) - - sut.add(value: 5.0) - sut.add(value: 5.0) - - // The weight stays the same - XCTAssertEqual(sut.weight, 1) - } - -} diff --git a/Tests/SentryTests/Swift/Metrics/DistributionMetricTests.swift b/Tests/SentryTests/Swift/Metrics/DistributionMetricTests.swift deleted file mode 100644 index cfbce775f58..00000000000 --- a/Tests/SentryTests/Swift/Metrics/DistributionMetricTests.swift +++ /dev/null @@ -1,34 +0,0 @@ -@testable import Sentry -import XCTest - -final class DistributionMetricTests: XCTestCase { - - func testAddingValues() { - let sut = DistributionMetric(first: 1.0, key: "key", unit: MeasurementUnitDuration.hour, tags: [:]) - - sut.add(value: 0.0) - sut.add(value: -1.0) - sut.add(value: 2.0) - - XCTAssertEqual(sut.serialize(), ["1.0", "0.0", "-1.0", "2.0"]) - } - - func testType() { - let sut = DistributionMetric(first: 0.0, key: "key", unit: MeasurementUnitDuration.hour, tags: [:]) - - XCTAssertEqual(sut.type, .distribution) - } - - func testWeight() { - let sut = DistributionMetric(first: 0.0, key: "key", unit: MeasurementUnitDuration.hour, tags: [:]) - - XCTAssertEqual(sut.weight, 1) - - for _ in 0..<100 { - sut.add(value: 5.0) - } - - XCTAssertEqual(sut.weight, 101) - } - -} diff --git a/Tests/SentryTests/Swift/Metrics/EncodeMetricTests.swift b/Tests/SentryTests/Swift/Metrics/EncodeMetricTests.swift deleted file mode 100644 index 39f8921e981..00000000000 --- a/Tests/SentryTests/Swift/Metrics/EncodeMetricTests.swift +++ /dev/null @@ -1,117 +0,0 @@ -@testable import Sentry -import SentryTestUtils -import XCTest - -final class EncodeMetricTests: XCTestCase { - - func testEncodeCounterMetricWithoutTags() { - let counterMetric = CounterMetric(first: 1.0, key: "app.start", unit: .none, tags: [:]) - - let data = encodeToStatsd(flushableBuckets: [12_345: [counterMetric]]) - - XCTAssertEqual(data.decodeStatsd(), "app.start@:1.0|c|T12345\n") - } - - func testEncodeGaugeMetricWithOneTag() { - let metric = GaugeMetric(first: 0.0, key: "app.start", unit: .none, tags: ["key": "value"]) - metric.add(value: 5.0) - metric.add(value: 4.0) - metric.add(value: 3.0) - metric.add(value: 2.0) - metric.add(value: 1.0) - - let data = encodeToStatsd(flushableBuckets: [12_345: [metric]]) - - XCTAssertEqual(data.decodeStatsd(), "app.start@:1.0:0.0:5.0:15.0:6|g|#key:value|T12345\n") - } - - func testEncodeDistributionMetricWithOutTags() { - let metric = DistributionMetric(first: 0.0, key: "app.start", unit: .none, tags: [:]) - metric.add(value: 5.12) - metric.add(value: 1.0) - - let data = encodeToStatsd(flushableBuckets: [12_345: [metric]]) - - XCTAssertEqual(data.decodeStatsd(), "app.start@:0.0:5.12:1.0|d|T12345\n") - } - - func testEncodeSetMetricWithOutTags() { - let metric = SetMetric(first: 0, key: "app.start", unit: .none, tags: [:]) - metric.add(value: 0) - metric.add(value: 1) - metric.add(value: 1) - - let data = encodeToStatsd(flushableBuckets: [12_345: [metric]]) - - let statsd = data.decodeStatsd() - XCTAssert(statsd.contains("app.start@:")) - XCTAssert(statsd.contains("|s|T12345\n")) - - // the set is unordered, so we have to check for both - XCTAssert(statsd.contains("1:0") || statsd.contains("0:1"), "statsd expected to contain either '1:0' or '0:1' for the set metric values") - } - - func testEncodeCounterMetricWithFractionalPart() { - let counterMetric = CounterMetric(first: 1.123456, key: "app.start", unit: MeasurementUnitDuration.second, tags: [:]) - - let data = encodeToStatsd(flushableBuckets: [10_234: [counterMetric]]) - - XCTAssertEqual(data.decodeStatsd(), "app.start@second:1.123456|c|T10234\n") - } - - func testEncodeCounterMetricWithOneTag() { - let counterMetric = CounterMetric(first: 10.1, key: "app.start", unit: MeasurementUnitDuration.second, tags: ["key": "value"]) - - let data = encodeToStatsd(flushableBuckets: [10_234: [counterMetric]]) - - XCTAssertEqual(data.decodeStatsd(), "app.start@second:10.1|c|#key:value|T10234\n") - } - - func testEncodeCounterMetricWithTwoTags() { - let counterMetric = CounterMetric(first: 10.1, key: "app.start", unit: MeasurementUnitDuration.second, tags: ["key1": "value1", "key2": "value2"]) - - let data = encodeToStatsd(flushableBuckets: [10_234: [counterMetric]]).decodeStatsd() - XCTAssert(data.hasPrefix("app.start@second:10.1|c|")) - XCTAssert(data.hasSuffix("|T10234\n")) - XCTAssert(data.contains("key1:value1")) - XCTAssert(data.contains("key2:value2")) - } - - func testEncodeCounterMetricWithKeyToSanitize() { - let counterMetric = CounterMetric(first: 10.1, key: "abyzABYZ09_/.-!@a#$Äa", unit: MeasurementUnitDuration.second, tags: [:]) - - let data = encodeToStatsd(flushableBuckets: [10_234: [counterMetric]]) - - XCTAssertEqual(data.decodeStatsd(), "abyzABYZ09__.-_a_a@second:10.1|c|T10234\n") - } - - func testEncodeCounterMetricWithTagKeyToSanitize() { - let counterMetric = CounterMetric(first: 10.1, key: "app.start", unit: MeasurementUnitDuration.second, tags: ["abcABC123_-./äöü$%&abcABC123": "value"]) - - let data = encodeToStatsd(flushableBuckets: [10_234: [counterMetric]]) - - XCTAssertEqual(data.decodeStatsd(), "app.start@second:10.1|c|#abcABC123_-./abcABC123:value|T10234\n") - } - - func testEncodeCounterMetricWithTagValueToSanitize() { - let counterMetric = CounterMetric(first: 10.1, key: "app.start", unit: MeasurementUnitDuration.second, tags: ["key": "abc\n\r\t|,\\123"]) - - let data = encodeToStatsd(flushableBuckets: [10_234: [counterMetric]]) - - XCTAssert(data.decodeStatsd().contains(#"abc\\n\\r\\t\\u{7c}\\u{2c}\\\\123"#)) - } - - func testEncodeCounterMetricWithUnitToSanitize() { - let counterMetric = CounterMetric(first: 10.1, key: "app.start", unit: MeasurementUnit(unit: "abyzABYZ09_/.ä"), tags: [:]) - - let data = encodeToStatsd(flushableBuckets: [10_234: [counterMetric]]) - - XCTAssertEqual(data.decodeStatsd(), "app.start@abyzABYZ09_:10.1|c|T10234\n") - } -} - -private extension Data { - func decodeStatsd() -> String { - return String(data: self, encoding: .utf8) ?? "" - } -} diff --git a/Tests/SentryTests/Swift/Metrics/GaugeMetricTests.swift b/Tests/SentryTests/Swift/Metrics/GaugeMetricTests.swift deleted file mode 100644 index cd826a1fa84..00000000000 --- a/Tests/SentryTests/Swift/Metrics/GaugeMetricTests.swift +++ /dev/null @@ -1,44 +0,0 @@ -@testable import Sentry -import XCTest - -final class GaugeMetricTests: XCTestCase { - - func testAddingValues() throws { - let sut = GaugeMetric(first: 1.0, key: "key", unit: MeasurementUnitDuration.hour, tags: [:]) - - XCTAssertEqual(sut.serialize(), ["1.0", "1.0", "1.0", "1.0", "1"]) - - sut.add(value: 5.0) - sut.add(value: 4.0) - sut.add(value: 3.0) - sut.add(value: 2.0) - sut.add(value: 2.5) - sut.add(value: 1.0) - - XCTAssertEqual(sut.serialize(), [ - "1.0", // last - "1.0", // min - "5.0", // max - "18.5", // sum - "7" // count - ]) - } - - func testType() { - let sut = GaugeMetric(first: 1.0, key: "key", unit: MeasurementUnitDuration.hour, tags: [:]) - - XCTAssertEqual(sut.type, .gauge) - } - - func testWeight() { - let sut = GaugeMetric(first: 1.0, key: "key", unit: MeasurementUnitDuration.hour, tags: [:]) - - XCTAssertEqual(sut.weight, 5) - - sut.add(value: 5.0) - sut.add(value: 5.0) - - // The weight stays the same - XCTAssertEqual(sut.weight, 5) - } -} diff --git a/Tests/SentryTests/Swift/Metrics/LocalMetricsAggregatorTests.swift b/Tests/SentryTests/Swift/Metrics/LocalMetricsAggregatorTests.swift deleted file mode 100644 index 9df8a839757..00000000000 --- a/Tests/SentryTests/Swift/Metrics/LocalMetricsAggregatorTests.swift +++ /dev/null @@ -1,110 +0,0 @@ -@testable import Sentry -import XCTest - -final class LocalMetricsAggregatorTests: XCTestCase { - - func testAddOneCounterMetric() throws { - let sut = LocalMetricsAggregator() - - sut.add(type: .counter, key: "key", value: 1.0, unit: MeasurementUnitDuration.second, tags: [:]) - - let serialized = sut.serialize() - XCTAssertEqual(serialized.count, 1) - let bucket = try XCTUnwrap(serialized["c:key@second"]) - - XCTAssertEqual(bucket.count, 1) - let metric = try XCTUnwrap(bucket.first) - - XCTAssertNil(metric["tags"]) - XCTAssertEqual(metric["min"] as? Double, 1.0) - XCTAssertEqual(metric["max"] as? Double, 1.0) - XCTAssertEqual(metric["count"] as? Int, 1) - XCTAssertEqual(metric["sum"] as? Double, 1.0) - } - - func testAddTwoSameDistributionMetrics() throws { - let sut = LocalMetricsAggregator() - - sut.add(type: .distribution, key: "key", value: 1.0, unit: .none, tags: [:]) - sut.add(type: .distribution, key: "key", value: 1.1, unit: .none, tags: [:]) - - let serialized = sut.serialize() - XCTAssertEqual(serialized.count, 1) - let bucket = try XCTUnwrap(serialized["d:key"]) - - XCTAssertEqual(bucket.count, 1) - let metric = try XCTUnwrap(bucket.first) - - XCTAssertNil(metric["tags"]) - XCTAssertEqual(metric["min"] as? Double, 1.0) - XCTAssertEqual(metric["max"] as? Double, 1.1) - XCTAssertEqual(metric["count"] as? Int, 2) - XCTAssertEqual(metric["sum"] as? Double, 2.1) - } - - func testAddTwoGaugeMetrics_WithDifferentTags() throws { - let sut = LocalMetricsAggregator() - - sut.add(type: .gauge, key: "key", value: 1.0, unit: MeasurementUnitDuration.second, tags: ["some0": "tag0"]) - sut.add(type: .gauge, key: "key", value: 10.0, unit: MeasurementUnitDuration.second, tags: ["some1": "tag1"]) - - let serialized = sut.serialize() - XCTAssertEqual(serialized.count, 1) - let bucket = try XCTUnwrap(serialized["g:key@second"]) - - XCTAssertEqual(bucket.count, 2) - let metric0 = try XCTUnwrap(bucket.first { $0.contains { $0.value as? [String: String] == ["some0": "tag0"] } }) - - XCTAssertEqual(metric0["min"] as? Double, 1.0) - XCTAssertEqual(metric0["max"] as? Double, 1.0) - XCTAssertEqual(metric0["count"] as? Int, 1) - XCTAssertEqual(metric0["sum"] as? Double, 1.0) - - let metric1 = try XCTUnwrap(bucket.first { $0.contains { $0.value as? [String: String] == ["some1": "tag1"] } }) - - XCTAssertEqual(metric1["min"] as? Double, 10.0) - XCTAssertEqual(metric1["max"] as? Double, 10.0) - XCTAssertEqual(metric1["count"] as? Int, 1) - XCTAssertEqual(metric1["sum"] as? Double, 10.0) - } - - func testAddTwoDifferentMetrics() throws { - let sut = LocalMetricsAggregator() - - sut.add(type: .gauge, key: "key", value: 1.0, unit: MeasurementUnitDuration.day, tags: ["some0": "tag0"]) - sut.add(type: .gauge, key: "key", value: 10.0, unit: MeasurementUnitDuration.second, tags: ["some1": "tag1"]) - sut.add(type: .gauge, key: "key", value: -10.0, unit: MeasurementUnitDuration.second, tags: ["some1": "tag1"]) - - let serialized = sut.serialize() - XCTAssertEqual(serialized.count, 2) - let dayBucket = try XCTUnwrap(serialized["g:key@day"]) - - XCTAssertEqual(dayBucket.count, 1) - let dayMetric = try XCTUnwrap(dayBucket.first) - XCTAssertEqual(dayMetric["min"] as? Double, 1.0) - XCTAssertEqual(dayMetric["max"] as? Double, 1.0) - XCTAssertEqual(dayMetric["count"] as? Int, 1) - XCTAssertEqual(dayMetric["sum"] as? Double, 1.0) - - let secondBucket = try XCTUnwrap(serialized["g:key@second"]) - XCTAssertEqual(secondBucket.count, 1) - let secondMetric = try XCTUnwrap(secondBucket.first) - XCTAssertEqual(secondMetric["min"] as? Double, -10.0) - XCTAssertEqual(secondMetric["max"] as? Double, 10.0) - XCTAssertEqual(secondMetric["count"] as? Int, 2) - XCTAssertEqual(secondMetric["sum"] as? Double, 0.0) - } - - func testWriteMultipleMetricsInParallel_DoesNotCrash() throws { - let sut = LocalMetricsAggregator() - - testConcurrentModifications(asyncWorkItems: 10, writeLoopCount: 100, writeWork: { i in - sut.add(type: .counter, key: "key\(i)", value: 1.1, unit: .none, tags: ["some": "tag"]) - sut.add(type: .gauge, key: "key\(i)", value: 1.1, unit: .none, tags: ["some": "tag"]) - sut.add(type: .distribution, key: "key\(i)", value: 1.1, unit: .none, tags: ["some": "tag"]) - sut.add(type: .set, key: "key\(i)", value: 1.1, unit: .none, tags: ["some": "tag"]) - }, readWork: { - XCTAssertNotNil(sut.serialize()) - }) - } -} diff --git a/Tests/SentryTests/Swift/Metrics/SentryMetricsAPITests.swift b/Tests/SentryTests/Swift/Metrics/SentryMetricsAPITests.swift deleted file mode 100644 index f90aa29fd5c..00000000000 --- a/Tests/SentryTests/Swift/Metrics/SentryMetricsAPITests.swift +++ /dev/null @@ -1,247 +0,0 @@ -import _SentryPrivate -@testable import Sentry -import SentryTestUtils -import XCTest - -final class SentryMetricsAPITests: XCTestCase { - - override func tearDown() { - super.tearDown() - clearTestState() - } - - private func getSut(enabled: Bool = true) throws -> (SentryMetricsAPI, TestMetricsClient, TestCurrentDateProvider, TestHub, Options) { - - let metricsClient = try TestMetricsClient() - let currentDate = TestCurrentDateProvider() - let sut = SentryMetricsAPI(enabled: enabled, client: metricsClient, currentDate: currentDate, dispatchQueue: SentryDispatchQueueWrapper(), random: SentryRandom(), beforeEmitMetric: nil) - - SentryDependencyContainer.sharedInstance().dateProvider = currentDate - - let options = Options() - options.enableMetrics = true - - let testClient = try XCTUnwrap(TestClient(options: options)) - let testHub = TestHub(client: testClient, andScope: Scope()) - - sut.setDelegate(testHub as? SentryMetricsAPIDelegate) - - return (sut, metricsClient, currentDate, testHub, options) - } - - func testInitWithDisabled_AllOperationsAreNoOps() throws { - let (sut, metricsClient, _, _, _) = try getSut(enabled: false) - - sut.increment(key: "some", value: 1.0, unit: .none, tags: ["yeah": "sentry"]) - sut.gauge(key: "some", value: 1.0, unit: .none, tags: ["yeah": "sentry"]) - sut.distribution(key: "some", value: 1.0, unit: .none, tags: ["yeah": "sentry"]) - sut.set(key: "some", value: "value", unit: .none, tags: ["yeah": "sentry"]) - - sut.close() - - XCTAssertEqual(metricsClient.captureInvocations.count, 0) - } - - func testIncrement_EmitsIncrementMetric() throws { - let (sut, metricsClient, _, _, _) = try getSut() - let delegate = TestSentryMetricsAPIDelegate() - sut.setDelegate(delegate) - - sut.increment(key: "key", value: 1.0, unit: MeasurementUnitFraction.percent, tags: ["yeah": "sentry"]) - - sut.flush() - - XCTAssertEqual(metricsClient.captureInvocations.count, 1) - let buckets = try XCTUnwrap(metricsClient.captureInvocations.first) - - let bucket = try XCTUnwrap(buckets.first?.value) - XCTAssertEqual(bucket.count, 1) - let metric = try XCTUnwrap(bucket.first as? CounterMetric) - - XCTAssertEqual(metric.key, "key") - XCTAssert(metric.serialize().contains("1.0")) - XCTAssertEqual(metric.unit.unit, MeasurementUnitFraction.percent.unit) - XCTAssertEqual(metric.tags, ["yeah": "sentry", "some": "tag"]) - } - - func testGauge_EmitsGaugeMetric() throws { - let (sut, metricsClient, _, _, _) = try getSut() - let delegate = TestSentryMetricsAPIDelegate() - sut.setDelegate(delegate) - - sut.gauge(key: "key", value: 1.0, unit: MeasurementUnitFraction.percent, tags: ["yeah": "sentry"]) - - sut.flush() - - XCTAssertEqual(metricsClient.captureInvocations.count, 1) - let buckets = try XCTUnwrap(metricsClient.captureInvocations.first) - - let bucket = try XCTUnwrap(buckets.first?.value) - XCTAssertEqual(bucket.count, 1) - let metric = try XCTUnwrap(bucket.first as? GaugeMetric) - - XCTAssertEqual(metric.key, "key") - XCTAssertEqual(metric.serialize(), ["1.0", "1.0", "1.0", "1.0", "1"]) - XCTAssertEqual(metric.unit.unit, MeasurementUnitFraction.percent.unit) - XCTAssertEqual(metric.tags, ["yeah": "sentry", "some": "tag"]) - } - - func testDistribution_EmitsDistributionMetric() throws { - let (sut, metricsClient, _, _, _) = try getSut() - let delegate = TestSentryMetricsAPIDelegate() - sut.setDelegate(delegate) - - sut.distribution(key: "key", value: 1.0, unit: MeasurementUnitFraction.percent, tags: ["yeah": "sentry"]) - sut.distribution(key: "key", value: 12.0, unit: MeasurementUnitFraction.percent, tags: ["yeah": "sentry"]) - - sut.flush() - - XCTAssertEqual(metricsClient.captureInvocations.count, 1) - let buckets = try XCTUnwrap(metricsClient.captureInvocations.first) - - let bucket = try XCTUnwrap(buckets.first?.value) - XCTAssertEqual(bucket.count, 1) - let metric = try XCTUnwrap(bucket.first as? DistributionMetric) - - XCTAssertEqual(metric.key, "key") - XCTAssertEqual(metric.serialize(), ["1.0", "12.0"]) - XCTAssertEqual(metric.unit.unit, MeasurementUnitFraction.percent.unit) - XCTAssertEqual(metric.tags, ["yeah": "sentry", "some": "tag"]) - } - - func testSet_EmitsSetMetric() throws { - let (sut, metricsClient, _, _, _) = try getSut() - let delegate = TestSentryMetricsAPIDelegate() - sut.setDelegate(delegate) - - sut.set(key: "key", value: "value1", unit: MeasurementUnitFraction.percent, tags: ["yeah": "sentry"]) - sut.set(key: "key", value: "value1", unit: MeasurementUnitFraction.percent, tags: ["yeah": "sentry"]) - sut.set(key: "key", value: "value12", unit: MeasurementUnitFraction.percent, tags: ["yeah": "sentry"]) - - sut.flush() - - XCTAssertEqual(metricsClient.captureInvocations.count, 1) - let buckets = try XCTUnwrap(metricsClient.captureInvocations.first) - - let bucket = try XCTUnwrap(buckets.first?.value) - XCTAssertEqual(bucket.count, 1) - let metric = try XCTUnwrap(bucket.first as? SetMetric) - - XCTAssertEqual(metric.key, "key") - XCTAssert(metric.serialize().contains("2445898635")) - XCTAssert(metric.serialize().contains("2725604442")) - XCTAssertEqual(metric.unit.unit, MeasurementUnitFraction.percent.unit) - XCTAssertEqual(metric.tags, ["yeah": "sentry", "some": "tag"]) - } - - func testTiming_WhenNoCurrentSpan_NoSpanCreatedAndNoMetricEmitted() throws { - let (sut, metricsClient, _, _, _) = try getSut() - let delegate = TestSentryMetricsAPIDelegate() - sut.setDelegate(delegate) - - let errorMessage = "It's broken" - do { - try sut.timing(key: "key") { - throw MetricsAPIError.runtimeError(errorMessage) - } - } catch MetricsAPIError.runtimeError(let actualErrorMessage) { - XCTAssertEqual(actualErrorMessage, errorMessage) - } - - XCTAssertEqual(metricsClient.captureInvocations.count, 0) - } - - func testTiming_WithCurrentSpan_CapturesGaugeMetric() throws { - let (sut, metricsClient, currentDate, testHub, options) = try getSut() - - let transaction = testHub.startTransaction(name: "hello", operation: "operation", bindToScope: true) - - let errorMessage = "It's broken" - do { - try sut.timing(key: "key", tags: ["some": "tag"]) { - currentDate.setDate(date: currentDate.date().addingTimeInterval(1.0)) - throw MetricsAPIError.runtimeError(errorMessage) - } - } catch MetricsAPIError.runtimeError(let actualErrorMessage) { - XCTAssertEqual(actualErrorMessage, errorMessage) - } - - sut.flush() - transaction.finish() - - XCTAssertEqual(metricsClient.captureInvocations.count, 1) - let buckets = try XCTUnwrap(metricsClient.captureInvocations.first) - - let bucket = try XCTUnwrap(buckets.first?.value) - XCTAssertEqual(bucket.count, 1) - let metric = try XCTUnwrap(bucket.first as? DistributionMetric) - - XCTAssertEqual(metric.key, "key") - XCTAssertEqual(metric.serialize(), ["1.0"]) - XCTAssertEqual(metric.unit.unit, MeasurementUnitDuration.second.unit) - XCTAssertEqual(metric.tags, ["some": "tag", "release": options.releaseName, "environment": options.environment]) - } - - func testTiming_WithCurrentSpan_CreatesSpanWithMetricsSummary() throws { - let (sut, _, currentDate, testHub, options) = try getSut() - - let transaction = testHub.startTransaction(name: "hello", operation: "operation", bindToScope: true) - - sut.timing(key: "key", tags: ["some": "tag"]) { - currentDate.setDate(date: currentDate.date().addingTimeInterval(1.0)) - } - - transaction.finish() - - XCTAssertEqual(testHub.capturedTransactionsWithScope.count, 1) - let serializedTransaction = try XCTUnwrap(testHub.capturedTransactionsWithScope.first?.transaction) - XCTAssertNotEqual(serializedTransaction.count, 0) - - let spans = try XCTUnwrap(serializedTransaction["spans"] as? [[String: Any]]) - XCTAssertEqual(spans.count, 1) - let span = try XCTUnwrap(spans.first) - - XCTAssertEqual(span["op"] as? String, "metric.timing") - XCTAssertEqual(span["description"] as? String, "key") - XCTAssertEqual(span["origin"] as? String, "manual") - XCTAssertEqual(span["start_timestamp"] as? Int, 978_307_200) - XCTAssertEqual(span["timestamp"] as? Int, 978_307_201) - XCTAssertEqual(span["parent_span_id"] as? String, transaction.spanId.sentrySpanIdString) - XCTAssertEqual(try XCTUnwrap(span["tags"] as? [String: String]), ["some": "tag", "release": options.releaseName, "environment": options.environment]) - - let metricsSummary = try XCTUnwrap(span["_metrics_summary"] as? [String: [[String: Any]]]) - XCTAssertEqual(metricsSummary.count, 1) - - let bucket = try XCTUnwrap(metricsSummary["d:key@second"] ) - XCTAssertEqual(bucket.count, 1) - let metric = try XCTUnwrap(bucket.first) - XCTAssertEqual(metric["min"] as? Double, 1.0) - XCTAssertEqual(metric["max"] as? Double, 1.0) - XCTAssertEqual(metric["count"] as? Int, 1) - XCTAssertEqual(metric["sum"] as? Double, 1.0) - } - - enum MetricsAPIError: Error { - case runtimeError(String) - } -} - -class TestSentryMetricsAPIDelegate: SentryMetricsAPIDelegate { - var currentSpan: SentrySpan? - - func getDefaultTagsForMetrics() -> [String: String] { - return ["some": "tag", "yeah": "not-taken"] - } - - func getLocalMetricsAggregator() -> Sentry.LocalMetricsAggregator? { - return currentSpan?.getLocalMetricsAggregator() - } - - func getCurrentSpan() -> Span? { - return currentSpan - } - - func getLocalMetricsAggregator(span: Span) -> Sentry.LocalMetricsAggregator? { - return (span as? SentrySpan)?.getLocalMetricsAggregator() - } -} diff --git a/Tests/SentryTests/Swift/Metrics/SentryMetricsClientTests.swift b/Tests/SentryTests/Swift/Metrics/SentryMetricsClientTests.swift deleted file mode 100644 index a2257301d5a..00000000000 --- a/Tests/SentryTests/Swift/Metrics/SentryMetricsClientTests.swift +++ /dev/null @@ -1,44 +0,0 @@ -import _SentryPrivate -@testable import Sentry -import SentryTestUtils -import XCTest - -final class SentryMetricsClientTests: XCTestCase { - - func testCaptureMetricsWithCounterMetric() throws { - let testClient = try XCTUnwrap(TestClient(options: Options())) - - let sut = SentryMetricsClient(client: SentryStatsdClient(client: testClient)) - - let metric = CounterMetric(first: 0.0, key: "app.start", unit: MeasurementUnitDuration.second, tags: ["sentry": "awesome"]) - let flushableBuckets: [BucketTimestamp: [Metric]] = [0: [metric]] - - let encodedMetricsData = encodeToStatsd(flushableBuckets: flushableBuckets) - - sut.capture(flushableBuckets: flushableBuckets) - - XCTAssertEqual(testClient.captureEnvelopeInvocations.count, 1) - - let envelope = try XCTUnwrap(testClient.captureEnvelopeInvocations.first) - XCTAssertNotNil(envelope.header.eventId) - - XCTAssertEqual(envelope.items.count, 1) - let envelopeItem = try XCTUnwrap(envelope.items.first) - XCTAssertEqual(envelopeItem.header.type, SentryEnvelopeItemTypeStatsd) - XCTAssertEqual(envelopeItem.header.contentType, "application/octet-stream") - XCTAssertEqual(envelopeItem.header.length, UInt(encodedMetricsData.count)) - XCTAssertEqual(envelopeItem.data, encodedMetricsData) - } - - func testCaptureMetricsWithNoMetrics() throws { - let testClient = try XCTUnwrap(TestClient(options: Options())) - - let sut = SentryMetricsClient(client: SentryStatsdClient(client: testClient)) - - let flushableBuckets: [BucketTimestamp: [CounterMetric]] = [:] - sut.capture(flushableBuckets: flushableBuckets) - - XCTAssertEqual(testClient.captureEnvelopeInvocations.count, 0) - } - -} diff --git a/Tests/SentryTests/Swift/Metrics/SetMetricTests.swift b/Tests/SentryTests/Swift/Metrics/SetMetricTests.swift deleted file mode 100644 index 51f6c7e6741..00000000000 --- a/Tests/SentryTests/Swift/Metrics/SetMetricTests.swift +++ /dev/null @@ -1,48 +0,0 @@ -@testable import Sentry -import XCTest - -final class SetMetricTests: XCTestCase { - - func testAddingValues() { - let sut = SetMetric(first: 1, key: "key", unit: MeasurementUnitDuration.hour, tags: [:]) - - sut.add(value: 0) - sut.add(value: 1) - sut.add(value: 2) - - let serialized = sut.serialize() - XCTAssert(serialized.contains("0")) - XCTAssert(serialized.contains("1")) - XCTAssert(serialized.contains("2")) - } - - func testAddUIntMax() { - let sut = SetMetric(first: 1, key: "key", unit: MeasurementUnitDuration.hour, tags: [:]) - - sut.add(value: UInt.max) - - XCTAssert(sut.serialize().contains("\(UInt.max)")) - } - - func testType() { - let sut = SetMetric(first: 0, key: "key", unit: MeasurementUnitDuration.hour, tags: [:]) - - XCTAssertEqual(sut.type, .set) - } - - func testWeight() { - let sut = SetMetric(first: 1, key: "key", unit: MeasurementUnitDuration.hour, tags: [:]) - - XCTAssertEqual(sut.weight, 1) - - for _ in 0..<10 { - sut.add(value: 5) - } - - sut.add(value: 3) - sut.add(value: 2) - - XCTAssertEqual(sut.weight, 4) - } - -} diff --git a/Tests/SentryTests/Swift/Metrics/TestMetricsClient.swift b/Tests/SentryTests/Swift/Metrics/TestMetricsClient.swift deleted file mode 100644 index 106ff5745ad..00000000000 --- a/Tests/SentryTests/Swift/Metrics/TestMetricsClient.swift +++ /dev/null @@ -1,22 +0,0 @@ -import _SentryPrivate -import Foundation -@testable import Sentry -import SentryTestUtils -import XCTest - -class TestMetricsClient: SentryMetricsClient { - - init() throws { - let testClient = try XCTUnwrap(TestClient(options: Options())) - let statsdClient = SentryStatsdClient(client: testClient) - - super.init(client: statsdClient) - } - - var afterRecordingCaptureInvocationBlock: (() -> Void)? - var captureInvocations = Invocations<[BucketTimestamp: [Metric]]>() - override func capture(flushableBuckets: [BucketTimestamp: [Metric]]) { - captureInvocations.record(flushableBuckets) - afterRecordingCaptureInvocationBlock?() - } -} diff --git a/Tests/SentryTests/Transaction/SentrySpanTests.swift b/Tests/SentryTests/Transaction/SentrySpanTests.swift index b52f6109517..409a129d2c0 100644 --- a/Tests/SentryTests/Transaction/SentrySpanTests.swift +++ b/Tests/SentryTests/Transaction/SentrySpanTests.swift @@ -534,33 +534,6 @@ class SentrySpanTests: XCTestCase { XCTAssertNil(serialization["tag"]) } - func testInit_DoesNotInitializeLocalMetricAggregator() { - let sut = fixture.getSut() - - let serialized = sut.serialize() - XCTAssertNil(serialized["_metrics_summary"]) - } - - func testLocalMetricsAggregator_GetsSerializedAsMetricsSummary() throws { - let sut = fixture.getSutWithTracer() - - let aggregator = sut.getLocalMetricsAggregator() - aggregator.add(type: .counter, key: "key", value: 1.0, unit: .none, tags: [:]) - - let serialized = sut.serialize() - - let metricsSummary = try XCTUnwrap(serialized["_metrics_summary"] as? [String: [[String: Any]]]) - XCTAssertEqual(metricsSummary.count, 1) - - let bucket = try XCTUnwrap(metricsSummary["c:key"]) - XCTAssertEqual(bucket.count, 1) - let metric = try XCTUnwrap(bucket.first) - XCTAssertEqual(metric["min"] as? Double, 1.0) - XCTAssertEqual(metric["max"] as? Double, 1.0) - XCTAssertEqual(metric["count"] as? Int, 1) - XCTAssertEqual(metric["sum"] as? Double, 1.0) - } - func testTraceHeaderNotSampled() { fixture.options.tracesSampleRate = 0 let span = fixture.getSut() diff --git a/Tests/SentryTests/Transaction/SentryTransactionTests.swift b/Tests/SentryTests/Transaction/SentryTransactionTests.swift index e501c1df96d..b6a79f0bb7d 100644 --- a/Tests/SentryTests/Transaction/SentryTransactionTests.swift +++ b/Tests/SentryTests/Transaction/SentryTransactionTests.swift @@ -197,25 +197,6 @@ class SentryTransactionTests: XCTestCase { XCTAssertEqual(actualTransaction, fixture.transactionName) } - func testSerializeMetricsSummary() throws { - let sut = fixture.getTransaction() - let aggregator = sut.trace.getLocalMetricsAggregator() - aggregator.add(type: .counter, key: "key", value: 1.0, unit: .none, tags: [:]) - - let serialized = sut.serialize() - - let metricsSummary = try XCTUnwrap(serialized["_metrics_summary"] as? [String: [[String: Any]]]) - XCTAssertEqual(metricsSummary.count, 1) - - let bucket = try XCTUnwrap(metricsSummary["c:key"]) - XCTAssertEqual(bucket.count, 1) - let metric = try XCTUnwrap(bucket.first) - XCTAssertEqual(metric["min"] as? Double, 1.0) - XCTAssertEqual(metric["max"] as? Double, 1.0) - XCTAssertEqual(metric["count"] as? Int, 1) - XCTAssertEqual(metric["sum"] as? Double, 1.0) - } - func testSerializedSpanData() throws { let sut = fixture.getTransaction() let serialized = sut.serialize() From 9cd19dd93b0f89dd653100007166aa46634aec8b Mon Sep 17 00:00:00 2001 From: Philipp Hofmann Date: Tue, 8 Oct 2024 15:22:34 +0200 Subject: [PATCH 15/36] fix: Edge case for swizzleClassNameExclude (#4405) Skip creating transactions for UIViewControllers ignored for swizzling via the option swizzleClassNameExclude. Due to some edge cases with nib files, the SDK doesn't swizzle the loadView method of the UIViewController subclasses, but instead, it swizzles the UIViewController.loadView method directly. Although the SDK doesn't swizzle the classes specified in swizzleClassNameExclude, it created transactions. Now, this is fixed. Fixes GH-4386 --- CHANGELOG.md | 5 +++ Sentry.xcodeproj/project.pbxproj | 4 ++ Sources/Sentry/Public/SentryOptions.h | 8 ++-- Sources/Sentry/SentrySubClassFinder.m | 11 ++---- ...SentryUIViewControllerPerformanceTracker.m | 12 ++++++ .../Sentry/SentryUIViewControllerSwizzling.m | 11 ++++++ .../Performance/SwizzleClassNameExclude.swift | 13 +++++++ ...iewControllerPerformanceTrackerTests.swift | 39 ++++++++++++++----- ...SentryUIViewControllerSwizzlingTests.swift | 21 +++++++--- 9 files changed, 97 insertions(+), 27 deletions(-) create mode 100644 Sources/Swift/Integrations/Performance/SwizzleClassNameExclude.swift diff --git a/CHANGELOG.md b/CHANGELOG.md index 8cc28363cdf..b97c1640b91 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,11 @@ - Remove the deprecated experimental Metrics API (#4406): [Learn more](https://sentry.zendesk.com/hc/en-us/articles/26369339769883-Metrics-Beta-Coming-to-an-End) +### Fixes + +- Edge case for swizzleClassNameExclude (#4405): Skip creating transactions for UIViewControllers ignored for swizzling +via the option `swizzleClassNameExclude`. + ## 8.38.0-beta.1 ### Features diff --git a/Sentry.xcodeproj/project.pbxproj b/Sentry.xcodeproj/project.pbxproj index 2926e5df337..d402f6fb71b 100644 --- a/Sentry.xcodeproj/project.pbxproj +++ b/Sentry.xcodeproj/project.pbxproj @@ -100,6 +100,7 @@ 62872B5F2BA1B7F300A4FA7D /* NSLock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62872B5E2BA1B7F300A4FA7D /* NSLock.swift */; }; 62872B632BA1B86100A4FA7D /* NSLockTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62872B622BA1B86100A4FA7D /* NSLockTests.swift */; }; 62885DA729E946B100554F38 /* TestConncurrentModifications.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62885DA629E946B100554F38 /* TestConncurrentModifications.swift */; }; + 629428802CB3BF69002C454C /* SwizzleClassNameExclude.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6294287F2CB3BF4E002C454C /* SwizzleClassNameExclude.swift */; }; 6294774C2C6F255F00846CBC /* SentryANRTrackerV2Delegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6294774B2C6F255F00846CBC /* SentryANRTrackerV2Delegate.swift */; }; 62950F1029E7FE0100A42624 /* SentryTransactionContextTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62950F0F29E7FE0100A42624 /* SentryTransactionContextTests.swift */; }; 629690532AD3E060000185FA /* SentryReachabilitySwiftTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 629690522AD3E060000185FA /* SentryReachabilitySwiftTests.swift */; }; @@ -1093,6 +1094,7 @@ 62872B5E2BA1B7F300A4FA7D /* NSLock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSLock.swift; sourceTree = ""; }; 62872B622BA1B86100A4FA7D /* NSLockTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSLockTests.swift; sourceTree = ""; }; 62885DA629E946B100554F38 /* TestConncurrentModifications.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestConncurrentModifications.swift; sourceTree = ""; }; + 6294287F2CB3BF4E002C454C /* SwizzleClassNameExclude.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwizzleClassNameExclude.swift; sourceTree = ""; }; 6294774B2C6F255F00846CBC /* SentryANRTrackerV2Delegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryANRTrackerV2Delegate.swift; sourceTree = ""; }; 62950F0F29E7FE0100A42624 /* SentryTransactionContextTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryTransactionContextTests.swift; sourceTree = ""; }; 629690522AD3E060000185FA /* SentryReachabilitySwiftTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryReachabilitySwiftTests.swift; sourceTree = ""; }; @@ -3759,6 +3761,7 @@ D8739CF72BECFF92007D2F66 /* Performance */ = { isa = PBXGroup; children = ( + 6294287F2CB3BF4E002C454C /* SwizzleClassNameExclude.swift */, D8739CF82BECFFB5007D2F66 /* SentryTransactionNameSource.swift */, ); path = Performance; @@ -4660,6 +4663,7 @@ 7B8ECBFC26498958005FE2EF /* SentryAppStateManager.m in Sources */, 7B2A70DD27D6083D008B0D15 /* SentryThreadWrapper.m in Sources */, D8ACE3C72762187200F5A213 /* SentryNSDataSwizzling.m in Sources */, + 629428802CB3BF69002C454C /* SwizzleClassNameExclude.swift in Sources */, 638DC9A11EBC6B6400A66E41 /* SentryRequestOperation.m in Sources */, 63AA767A1EB8D20500D153DE /* SentryLogC.m in Sources */, 6344DDBA1EC3115C00D9160D /* SentryCrashReportConverter.m in Sources */, diff --git a/Sources/Sentry/Public/SentryOptions.h b/Sources/Sentry/Public/SentryOptions.h index 597dd11e08d..bb67691dccd 100644 --- a/Sources/Sentry/Public/SentryOptions.h +++ b/Sources/Sentry/Public/SentryOptions.h @@ -446,17 +446,17 @@ NS_SWIFT_NAME(Options) @property (nonatomic, assign) BOOL enableSwizzling; /** - * An array of class names to ignore for swizzling. + * A set of class names to ignore for swizzling. * * @discussion The SDK checks if a class name of a class to swizzle contains a class name of this * array. For example, if you add MyUIViewController to this list, the SDK excludes the following * classes from swizzling: YourApp.MyUIViewController, YourApp.MyUIViewControllerA, * MyApp.MyUIViewController. - * We can't use an @c NSArray here because we use this as a workaround for which users have + * We can't use an @c NSSet here because we use this as a workaround for which users have * to pass in class names that aren't available on specific iOS versions. By using @c - * NSArray, users can specify unavailable class names. + * NSSet, users can specify unavailable class names. * - * @note Default is an empty array. + * @note Default is an empty set. */ @property (nonatomic, strong) NSSet *swizzleClassNameExcludes; diff --git a/Sources/Sentry/SentrySubClassFinder.m b/Sources/Sentry/SentrySubClassFinder.m index 2960b540ef1..b730f01f687 100644 --- a/Sources/Sentry/SentrySubClassFinder.m +++ b/Sources/Sentry/SentrySubClassFinder.m @@ -2,6 +2,7 @@ #import "SentryDispatchQueueWrapper.h" #import "SentryLog.h" #import "SentryObjCRuntimeWrapper.h" +#import "SentrySwift.h" #import #import @@ -61,13 +62,9 @@ - (void)actOnSubclassesOfViewControllerInImage:(NSString *)imageName block:(void for (int i = 0; i < count; i++) { NSString *className = [NSString stringWithUTF8String:classes[i]]; - BOOL shouldExcludeClassFromSwizzling = NO; - for (NSString *swizzleClassNameExclude in self.swizzleClassNameExcludes) { - if ([className containsString:swizzleClassNameExclude]) { - shouldExcludeClassFromSwizzling = YES; - break; - } - } + BOOL shouldExcludeClassFromSwizzling = [SentrySwizzleClassNameExclude + shouldExcludeClassWithClassName:className + swizzleClassNameExcludes:self.swizzleClassNameExcludes]; // It is vital to avoid calling NSClassFromString for the excluded classes because we // had crashes for specific classes when calling NSClassFromString, such as diff --git a/Sources/Sentry/SentryUIViewControllerPerformanceTracker.m b/Sources/Sentry/SentryUIViewControllerPerformanceTracker.m index 51be581e7d6..200a6f933df 100644 --- a/Sources/Sentry/SentryUIViewControllerPerformanceTracker.m +++ b/Sources/Sentry/SentryUIViewControllerPerformanceTracker.m @@ -68,6 +68,18 @@ - (void)viewControllerLoadView:(UIViewController *)controller return; } + SentryOptions *options = [SentrySDK options]; + + if ([SentrySwizzleClassNameExclude + shouldExcludeClassWithClassName:NSStringFromClass([controller class]) + swizzleClassNameExcludes:options.swizzleClassNameExcludes]) { + SENTRY_LOG_DEBUG(@"Won't track view controller because it's excluded with the option " + @"swizzleClassNameExcludes: %@", + controller); + callbackToOrigin(); + return; + } + [self limitOverride:@"loadView" target:controller callbackToOrigin:callbackToOrigin diff --git a/Sources/Sentry/SentryUIViewControllerSwizzling.m b/Sources/Sentry/SentryUIViewControllerSwizzling.m index 0cd42b99119..456dd933e16 100644 --- a/Sources/Sentry/SentryUIViewControllerSwizzling.m +++ b/Sources/Sentry/SentryUIViewControllerSwizzling.m @@ -36,6 +36,7 @@ @interface UIApplication (SentryUIApplication) @interface SentryUIViewControllerSwizzling () +@property (nonatomic, strong) SentryOptions *options; @property (nonatomic, strong) SentryInAppLogic *inAppLogic; @property (nonatomic, strong) SentryDispatchQueueWrapper *dispatchQueue; @property (nonatomic, strong) id objcRuntimeWrapper; @@ -56,6 +57,7 @@ - (instancetype)initWithOptions:(SentryOptions *)options binaryImageCache:(SentryBinaryImageCache *)binaryImageCache { if (self = [super init]) { + self.options = options; self.inAppLogic = [[SentryInAppLogic alloc] initWithInAppIncludes:options.inAppIncludes inAppExcludes:options.inAppExcludes]; self.dispatchQueue = dispatchQueue; @@ -341,6 +343,15 @@ - (void)swizzleViewControllerSubClass:(Class)class */ - (BOOL)shouldSwizzleViewController:(Class)class { + NSString *className = NSStringFromClass(class); + + BOOL shouldExcludeClassFromSwizzling = [SentrySwizzleClassNameExclude + shouldExcludeClassWithClassName:className + swizzleClassNameExcludes:self.options.swizzleClassNameExcludes]; + if (shouldExcludeClassFromSwizzling) { + return NO; + } + return [self.inAppLogic isClassInApp:class]; } diff --git a/Sources/Swift/Integrations/Performance/SwizzleClassNameExclude.swift b/Sources/Swift/Integrations/Performance/SwizzleClassNameExclude.swift new file mode 100644 index 00000000000..9b60ae87ff1 --- /dev/null +++ b/Sources/Swift/Integrations/Performance/SwizzleClassNameExclude.swift @@ -0,0 +1,13 @@ +import Foundation + +@objcMembers +class SentrySwizzleClassNameExclude: NSObject { + static func shouldExcludeClass(className: String, swizzleClassNameExcludes: Set) -> Bool { + for exclude in swizzleClassNameExcludes { + if className.contains(exclude) { + return true + } + } + return false + } +} diff --git a/Tests/SentryTests/Integrations/Performance/UIViewController/SentryUIViewControllerPerformanceTrackerTests.swift b/Tests/SentryTests/Integrations/Performance/UIViewController/SentryUIViewControllerPerformanceTrackerTests.swift index dac6d9af223..24fa222d4a3 100644 --- a/Tests/SentryTests/Integrations/Performance/UIViewController/SentryUIViewControllerPerformanceTrackerTests.swift +++ b/Tests/SentryTests/Integrations/Performance/UIViewController/SentryUIViewControllerPerformanceTrackerTests.swift @@ -26,15 +26,7 @@ class SentryUIViewControllerPerformanceTrackerTests: XCTestCase { private class Fixture { - var options: Options { - let options = Options.noIntegrations() - let imageName = String( - cString: class_getImageName(SentryUIViewControllerSwizzlingTests.self)!, - encoding: .utf8)! as NSString - options.add(inAppInclude: imageName.lastPathComponent) - options.debug = true - return options - } + var options: Options let viewController = TestViewController() let tracker = SentryPerformanceTracker.shared @@ -50,6 +42,13 @@ class SentryUIViewControllerPerformanceTrackerTests: XCTestCase { } init() { + options = Options.noIntegrations() + let imageName = String( + cString: class_getImageName(SentryUIViewControllerSwizzlingTests.self)!, + encoding: .utf8)! as NSString + options.add(inAppInclude: imageName.lastPathComponent) + options.debug = true + framesTracker = SentryFramesTracker(displayLinkWrapper: displayLinkWrapper, dateProvider: dateProvider, dispatchQueueWrapper: TestSentryDispatchQueueWrapper(), notificationCenter: TestNSNotificationCenterWrapper(), keepDelayedFramesDuration: 0) SentryDependencyContainer.sharedInstance().framesTracker = framesTracker @@ -500,7 +499,7 @@ class SentryUIViewControllerPerformanceTrackerTests: XCTestCase { wait(for: [callbackExpectation], timeout: 0) } - func testLoadView_withUIViewController() { + func testLoadView_withNonInAppUIViewController_DoesNotStartTransaction() { let sut = fixture.getSut() let viewController = UIViewController() let tracker = fixture.tracker @@ -519,6 +518,26 @@ class SentryUIViewControllerPerformanceTrackerTests: XCTestCase { wait(for: [callbackExpectation], timeout: 0) } + func testLoadView_withIgnoreSwizzleUIViewController_DoesNotStartTransaction() { + fixture.options.swizzleClassNameExcludes = ["TestViewController"] + let sut = fixture.getSut() + let viewController = fixture.viewController + let tracker = fixture.tracker + var transactionSpan: Span! + let callbackExpectation = expectation(description: "Callback Expectation") + + XCTAssertTrue(getStack(tracker).isEmpty) + + sut.viewControllerLoadView(viewController) { + let spans = self.getStack(tracker) + transactionSpan = spans.first + callbackExpectation.fulfill() + } + + XCTAssertNil(transactionSpan, "Expected to transaction.") + wait(for: [callbackExpectation], timeout: 0) + } + func testSecondLoadView() throws { let sut = fixture.getSut() let viewController = fixture.viewController diff --git a/Tests/SentryTests/Integrations/Performance/UIViewController/SentryUIViewControllerSwizzlingTests.swift b/Tests/SentryTests/Integrations/Performance/UIViewController/SentryUIViewControllerSwizzlingTests.swift index f11525dc60b..fa4a7cad04c 100644 --- a/Tests/SentryTests/Integrations/Performance/UIViewController/SentryUIViewControllerSwizzlingTests.swift +++ b/Tests/SentryTests/Integrations/Performance/UIViewController/SentryUIViewControllerSwizzlingTests.swift @@ -13,14 +13,13 @@ class SentryUIViewControllerSwizzlingTests: XCTestCase { let subClassFinder: TestSubClassFinder let processInfoWrapper = SentryNSProcessInfoWrapper() let binaryImageCache: SentryBinaryImageCache + var options: Options init() { subClassFinder = TestSubClassFinder(dispatchQueue: dispatchQueue, objcRuntimeWrapper: objcRuntimeWrapper, swizzleClassNameExcludes: []) binaryImageCache = SentryDependencyContainer.sharedInstance().binaryImageCache - } - - var options: Options { - let options = Options.noIntegrations() + + options = Options.noIntegrations() let imageName = String( cString: class_getImageName(SentryUIViewControllerSwizzlingTests.self)!, @@ -31,8 +30,6 @@ class SentryUIViewControllerSwizzlingTests: XCTestCase { cString: class_getImageName(ExternalUIViewController.self)!, encoding: .utf8)! as NSString options.add(inAppInclude: externalImageName.lastPathComponent) - - return options } var sut: SentryUIViewControllerSwizzling { @@ -98,6 +95,18 @@ class SentryUIViewControllerSwizzlingTests: XCTestCase { XCTAssertFalse(result) } + func testShouldNotSwizzle_UIViewControllerExcludedFromSwizzling() { + fixture.options.swizzleClassNameExcludes = ["TestViewController"] + + XCTAssertFalse(fixture.sut.shouldSwizzleViewController(TestViewController.self)) + } + + func testShouldSwizzle_UIViewControllerNotExcludedFromSwizzling() { + fixture.options.swizzleClassNameExcludes = ["TestViewController1"] + + XCTAssertTrue(fixture.sut.shouldSwizzleViewController(TestViewController.self)) + } + func testUIViewController_loadView_noTransactionBoundToScope() { fixture.sut.start() let controller = UIViewController() From 9b973722a5714efc79e095c6697e34f70a90b635 Mon Sep 17 00:00:00 2001 From: Philipp Hofmann Date: Tue, 8 Oct 2024 16:54:10 +0200 Subject: [PATCH 16/36] fix: Swizzling RootUIViewController if ignored (#4407) The SDK didn't exclude the RootViewController from swizzling when ignored by the option swizzleClassNameExclude. This is fixed now. Fixes GH-4385 --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b97c1640b91..93c5cdf60f3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -32,6 +32,9 @@ via the option `swizzleClassNameExclude`. - Fix the versioning to support app release with Beta versions (#4368) - Linking ongoing trace to crash event (#4393) +- Edge case for swizzleClassNameExclude (#4405): Skip creating transactions for UIViewControllers ignored for swizzling +via the option `swizzleClassNameExclude`. +- Swizzling RootUIViewController if ignored by `swizzleClassNameExclude` (#4407) ## 8.37.0 From 75f7fc893a18f02a58d5f9843adaaf857084d9c7 Mon Sep 17 00:00:00 2001 From: Philipp Hofmann Date: Wed, 9 Oct 2024 10:55:43 +0200 Subject: [PATCH 17/36] Fix CHANGELOG.md (#4416) --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 93c5cdf60f3..9afd0f2b5d7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ - Edge case for swizzleClassNameExclude (#4405): Skip creating transactions for UIViewControllers ignored for swizzling via the option `swizzleClassNameExclude`. +- Swizzling RootUIViewController if ignored by `swizzleClassNameExclude` (#4407) ## 8.38.0-beta.1 @@ -34,7 +35,6 @@ via the option `swizzleClassNameExclude`. - Linking ongoing trace to crash event (#4393) - Edge case for swizzleClassNameExclude (#4405): Skip creating transactions for UIViewControllers ignored for swizzling via the option `swizzleClassNameExclude`. -- Swizzling RootUIViewController if ignored by `swizzleClassNameExclude` (#4407) ## 8.37.0 From 887502e715b9885c637e0573934cf75128a63097 Mon Sep 17 00:00:00 2001 From: Philipp Hofmann Date: Wed, 9 Oct 2024 11:42:07 +0200 Subject: [PATCH 18/36] fix: Add TTID/TTFD spans when loadView skipped (#4415) The SDK creates a transaction when only viewDidLoad gets called for a UIViewController but doesn't create TTID/TTFD spans, leading to transactions missing TTID/TTFD data. Now, the SDK also creates TTID/TTFD spans when only viewDidLoad gets called and loadView gets skipped. Fixes GH-4396 Co-authored-by: Andrew McKnight --- CHANGELOG.md | 1 + .../ViewControllers/SplitViewController.swift | 1 - ...SentryUIViewControllerPerformanceTracker.m | 1 + ...iewControllerPerformanceTrackerTests.swift | 95 +++++++++++++++++-- 4 files changed, 89 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9afd0f2b5d7..f00e3c97189 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ - Edge case for swizzleClassNameExclude (#4405): Skip creating transactions for UIViewControllers ignored for swizzling via the option `swizzleClassNameExclude`. +- Add TTID/TTFD spans when loadView gets skipped (#4415) - Swizzling RootUIViewController if ignored by `swizzleClassNameExclude` (#4407) ## 8.38.0-beta.1 diff --git a/Samples/iOS-Swift/iOS-Swift/ViewControllers/SplitViewController.swift b/Samples/iOS-Swift/iOS-Swift/ViewControllers/SplitViewController.swift index b7ad604e61c..1356fcca4f3 100644 --- a/Samples/iOS-Swift/iOS-Swift/ViewControllers/SplitViewController.swift +++ b/Samples/iOS-Swift/iOS-Swift/ViewControllers/SplitViewController.swift @@ -66,7 +66,6 @@ class SecondarySplitViewController: UIViewController { override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) - SentrySDK.reportFullyDisplayed() if let topvc = TopViewControllerInspector.shared { topvc.bringToFront() diff --git a/Sources/Sentry/SentryUIViewControllerPerformanceTracker.m b/Sources/Sentry/SentryUIViewControllerPerformanceTracker.m index 200a6f933df..408356f11e9 100644 --- a/Sources/Sentry/SentryUIViewControllerPerformanceTracker.m +++ b/Sources/Sentry/SentryUIViewControllerPerformanceTracker.m @@ -102,6 +102,7 @@ - (void)viewControllerViewDidLoad:(UIViewController *)controller block:^{ SENTRY_LOG_DEBUG(@"Tracking viewDidLoad"); [self createTransaction:controller]; + [self createTimeToDisplay:controller]; [self measurePerformance:@"viewDidLoad" target:controller callbackToOrigin:callbackToOrigin]; diff --git a/Tests/SentryTests/Integrations/Performance/UIViewController/SentryUIViewControllerPerformanceTrackerTests.swift b/Tests/SentryTests/Integrations/Performance/UIViewController/SentryUIViewControllerPerformanceTrackerTests.swift index 24fa222d4a3..8ec08e52b50 100644 --- a/Tests/SentryTests/Integrations/Performance/UIViewController/SentryUIViewControllerPerformanceTrackerTests.swift +++ b/Tests/SentryTests/Integrations/Performance/UIViewController/SentryUIViewControllerPerformanceTrackerTests.swift @@ -576,7 +576,6 @@ class SentryUIViewControllerPerformanceTrackerTests: XCTestCase { sut.enableWaitForFullDisplay = true - //The first view controller creates a transaction sut.viewControllerLoadView(firstController) { tracer = self.getStack(tracker).first as? SentryTracer } @@ -594,13 +593,92 @@ class SentryUIViewControllerPerformanceTrackerTests: XCTestCase { sut.enableWaitForFullDisplay = false - //The first view controller creates a transaction sut.viewControllerLoadView(firstController) { tracer = self.getStack(tracker).first as? SentryTracer } XCTAssertEqual(tracer?.children.count, 2) } + + func test_OnlyViewDidLoad_CreatesTTIDSpan() throws { + let sut = fixture.getSut() + let tracker = fixture.tracker + + var tracer: SentryTracer! + + sut.viewControllerViewDidLoad(TestViewController()) { + tracer = self.getStack(tracker).first as? SentryTracer + } + + let children: [Span]? = Dynamic(tracer).children as [Span]? + + XCTAssertEqual(children?.count, 2) + + let firstChild = try XCTUnwrap(children?.first) + XCTAssertEqual("ui.load.initial_display", firstChild.operation) + XCTAssertEqual("TestViewController initial display", firstChild.spanDescription) + let secondChild = try XCTUnwrap(children?[1]) + XCTAssertEqual("ui.load", secondChild.operation) + XCTAssertEqual("viewDidLoad", secondChild.spanDescription) + } + + func test_OnlyViewDidLoadTTFDEnabled_CreatesTTIDAndTTFDSpans() throws { + let sut = fixture.getSut() + sut.enableWaitForFullDisplay = true + let tracker = fixture.tracker + + var tracer: SentryTracer! + + sut.viewControllerViewDidLoad(TestViewController()) { + tracer = self.getStack(tracker).first as? SentryTracer + } + + let children: [Span]? = Dynamic(tracer).children as [Span]? + + XCTAssertEqual(children?.count, 3) + + let child1 = try XCTUnwrap(children?.first) + XCTAssertEqual("ui.load.initial_display", child1.operation) + XCTAssertEqual("TestViewController initial display", child1.spanDescription) + + let child2 = try XCTUnwrap(children?[1]) + XCTAssertEqual("ui.load.full_display", child2.operation) + XCTAssertEqual("TestViewController full display", child2.spanDescription) + + let child3 = try XCTUnwrap(children?[2]) + XCTAssertEqual("ui.load", child3.operation) + XCTAssertEqual("viewDidLoad", child3.spanDescription) + } + + func test_BothLoadViewAndViewDidLoad_CreatesOneTTIDSpan() throws { + let sut = fixture.getSut() + let tracker = fixture.tracker + let controller = TestViewController() + var tracer: SentryTracer! + + sut.viewControllerLoadView(controller) { + tracer = self.getStack(tracker).first as? SentryTracer + } + + sut.viewControllerViewDidLoad(controller) { + // Empty on purpose + } + + let children: [Span]? = Dynamic(tracer).children as [Span]? + + XCTAssertEqual(children?.count, 3) + + let child1 = try XCTUnwrap(children?.first) + XCTAssertEqual("ui.load.initial_display", child1.operation) + + let child2 = try XCTUnwrap(children?[1]) + XCTAssertEqual("ui.load", child2.operation) + XCTAssertEqual("loadView", child2.spanDescription) + + let child3 = try XCTUnwrap(children?[2]) + XCTAssertEqual("ui.load", child3.operation) + XCTAssertEqual("viewDidLoad", child3.spanDescription) + } func test_captureAllAutomaticSpans() { let sut = fixture.getSut() @@ -643,12 +721,13 @@ class SentryUIViewControllerPerformanceTrackerTests: XCTestCase { let children: [Span]? = Dynamic(tracer).children as [Span]? - //First Controller viewDidLoad - //Second Controller root span - //Second Controller viewDidLoad - //Third Controller root span - //Third Controller viewDidLoad - XCTAssertEqual(children?.count, 5) + // First Controller initial_display + // First Controller viewDidLoad + // Second Controller root span + // Second Controller viewDidLoad + // Third Controller root span + // Third Controller viewDidLoad + XCTAssertEqual(children?.count, 6) } private func assertSpanDuration(span: Span?, expectedDuration: TimeInterval) throws { From 0418702573fb6cd470099060bc9fc5cbd5bea085 Mon Sep 17 00:00:00 2001 From: Philipp Hofmann Date: Wed, 9 Oct 2024 11:52:23 +0200 Subject: [PATCH 19/36] impr: Capture trace based profiles on a BG Thread (#4377) Serializing and capturing a profile can run on the main thread and block it for a couple of milliseconds. Therefore, we move this to a background thread to avoid potentially blocking the main thread. Co-authored-by: Dhiogo Brustolin --- CHANGELOG.md | 4 ++ Sentry.xcodeproj/project.pbxproj | 8 +++ .../SentryCaptureTransactionWithProfile.mm | 50 +++++++++++++++++++ .../Profiling/SentryProfilerSerialization.mm | 14 ++---- Sources/Sentry/SentryTracer.m | 24 ++------- .../SentryCaptureTransactionWithProfile.h | 22 ++++++++ .../include/SentryProfilerSerialization.h | 4 +- .../SentryProfileTestFixture.swift | 2 +- .../SentryTraceProfilerTests.swift | 23 +++++++++ 9 files changed, 118 insertions(+), 33 deletions(-) create mode 100644 Sources/Sentry/Profiling/SentryCaptureTransactionWithProfile.mm create mode 100644 Sources/Sentry/include/SentryCaptureTransactionWithProfile.h diff --git a/CHANGELOG.md b/CHANGELOG.md index f00e3c97189..ec8e51cac9e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,10 @@ via the option `swizzleClassNameExclude`. - Add TTID/TTFD spans when loadView gets skipped (#4415) - Swizzling RootUIViewController if ignored by `swizzleClassNameExclude` (#4407) +### Improvements + +- Serializing profile on a BG Thread (#4377) to avoid potentially slightly blocking the main thread. + ## 8.38.0-beta.1 ### Features diff --git a/Sentry.xcodeproj/project.pbxproj b/Sentry.xcodeproj/project.pbxproj index d402f6fb71b..da002f2c39b 100644 --- a/Sentry.xcodeproj/project.pbxproj +++ b/Sentry.xcodeproj/project.pbxproj @@ -78,6 +78,8 @@ 620379DD2AFE1432005AC0C1 /* SentryBuildAppStartSpans.m in Sources */ = {isa = PBXBuildFile; fileRef = 620379DC2AFE1432005AC0C1 /* SentryBuildAppStartSpans.m */; }; 621AE74B2C626C230012E730 /* SentryANRTrackerV2.h in Headers */ = {isa = PBXBuildFile; fileRef = 621AE74A2C626C230012E730 /* SentryANRTrackerV2.h */; }; 621AE74D2C626C510012E730 /* SentryANRTrackerV2.m in Sources */ = {isa = PBXBuildFile; fileRef = 621AE74C2C626C510012E730 /* SentryANRTrackerV2.m */; }; + 621C88482CAD23B9000EABCB /* SentryCaptureTransactionWithProfile.h in Headers */ = {isa = PBXBuildFile; fileRef = 621C88472CAD23B9000EABCB /* SentryCaptureTransactionWithProfile.h */; }; + 621C884A2CAD23E9000EABCB /* SentryCaptureTransactionWithProfile.mm in Sources */ = {isa = PBXBuildFile; fileRef = 621C88492CAD23E9000EABCB /* SentryCaptureTransactionWithProfile.mm */; }; 621D9F2F2B9B0320003D94DE /* SentryCurrentDateProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 621D9F2E2B9B0320003D94DE /* SentryCurrentDateProvider.swift */; }; 621F61F12BEA073A005E654F /* SentryEnabledFeaturesBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 621F61F02BEA073A005E654F /* SentryEnabledFeaturesBuilder.swift */; }; 6221BBCA2CAA932100C627CA /* SentryANRType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6221BBC92CAA932100C627CA /* SentryANRType.swift */; }; @@ -1070,6 +1072,8 @@ 621AE74A2C626C230012E730 /* SentryANRTrackerV2.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SentryANRTrackerV2.h; path = include/SentryANRTrackerV2.h; sourceTree = ""; }; 621AE74C2C626C510012E730 /* SentryANRTrackerV2.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SentryANRTrackerV2.m; sourceTree = ""; }; 621AE74E2C626CF70012E730 /* SentryANRTrackerV2Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryANRTrackerV2Tests.swift; sourceTree = ""; }; + 621C88472CAD23B9000EABCB /* SentryCaptureTransactionWithProfile.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SentryCaptureTransactionWithProfile.h; path = Sources/Sentry/include/SentryCaptureTransactionWithProfile.h; sourceTree = SOURCE_ROOT; }; + 621C88492CAD23E9000EABCB /* SentryCaptureTransactionWithProfile.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = SentryCaptureTransactionWithProfile.mm; sourceTree = ""; }; 621D9F2E2B9B0320003D94DE /* SentryCurrentDateProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryCurrentDateProvider.swift; sourceTree = ""; }; 621F61F02BEA073A005E654F /* SentryEnabledFeaturesBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryEnabledFeaturesBuilder.swift; sourceTree = ""; }; 6221BBC92CAA932100C627CA /* SentryANRType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryANRType.swift; sourceTree = ""; }; @@ -3381,6 +3385,8 @@ 8459FCBD2BD73E810038E9C9 /* SentryProfilerSerialization.h */, 8459FCBF2BD73EB20038E9C9 /* SentryProfilerSerialization.mm */, 8459FCC12BD73EEF0038E9C9 /* SentryProfilerSerialization+Test.h */, + 621C88472CAD23B9000EABCB /* SentryCaptureTransactionWithProfile.h */, + 621C88492CAD23E9000EABCB /* SentryCaptureTransactionWithProfile.mm */, 84AF45A429A7FFA500FBB177 /* SentryProfiledTracerConcurrency.h */, 84AF45A529A7FFA500FBB177 /* SentryProfiledTracerConcurrency.mm */, 840B7EF22BBF83DF008B8120 /* SentryProfiler+Private.h */, @@ -3995,6 +4001,7 @@ 7D7F0A5F23DF3D2C00A4629C /* SentryGlobalEventProcessor.h in Headers */, D867063D27C3BC2400048851 /* SentryCoreDataTrackingIntegration.h in Headers */, 0A2D8D5D289815EB008720F6 /* SentryBaseIntegration.h in Headers */, + 621C88482CAD23B9000EABCB /* SentryCaptureTransactionWithProfile.h in Headers */, 84AC61D629F75A98009EEF61 /* SentryDispatchFactory.h in Headers */, 63FE716520DA4C1100CDBAE8 /* SentryCrashMemory.h in Headers */, 63FE713F20DA4C1100CDBAE8 /* SentryCrashStackCursor_SelfThread.h in Headers */, @@ -4573,6 +4580,7 @@ D80CD8D32B751447002F710B /* SentryMXCallStackTree.swift in Sources */, 7BCFA71627D0BB50008C662C /* SentryANRTrackerV1.m in Sources */, 8459FCC02BD73EB20038E9C9 /* SentryProfilerSerialization.mm in Sources */, + 621C884A2CAD23E9000EABCB /* SentryCaptureTransactionWithProfile.mm in Sources */, 63EED6C02237923600E02400 /* SentryOptions.m in Sources */, D8CB741B2947286500A5F964 /* SentryEnvelopeItemHeader.m in Sources */, D8CB7417294724CC00A5F964 /* SentryEnvelopeAttachmentHeader.m in Sources */, diff --git a/Sources/Sentry/Profiling/SentryCaptureTransactionWithProfile.mm b/Sources/Sentry/Profiling/SentryCaptureTransactionWithProfile.mm new file mode 100644 index 00000000000..35b3fa2a27c --- /dev/null +++ b/Sources/Sentry/Profiling/SentryCaptureTransactionWithProfile.mm @@ -0,0 +1,50 @@ +#import "SentryCaptureTransactionWithProfile.h" + +#if SENTRY_TARGET_PROFILING_SUPPORTED + +# import "SentryDispatchQueueWrapper.h" +# import "SentryHub+Private.h" +# import "SentryLog.h" +# import "SentryProfiledTracerConcurrency.h" +# import "SentryProfiler+Private.h" +# import "SentryProfilerSerialization.h" +# import "SentryProfilerState.h" +# import "SentrySwift.h" +# import "SentryTracer+Private.h" +# import "SentryTransaction.h" + +NS_ASSUME_NONNULL_BEGIN + +void +sentry_captureTransactionWithProfile(SentryHub *hub, SentryDispatchQueueWrapper *dispatchQueue, + SentryTransaction *transaction, NSDate *startTimestamp) +{ + const auto profiler = sentry_profilerForFinishedTracer(transaction.trace.internalID); + if (!profiler) { + [hub captureTransaction:transaction withScope:hub.scope]; + return; + } + + // This code can run on the main thread, and the profile serialization can take a couple of + // milliseconds. Therefore, we move this to a background thread to avoid potentially blocking + // the main thread. + [dispatchQueue dispatchAsyncWithBlock:^{ + const auto profilingData = [profiler.state copyProfilingData]; + + const auto profileEnvelopeItem = sentry_traceProfileEnvelopeItem( + hub, profiler, profilingData, transaction, startTimestamp); + + if (!profileEnvelopeItem) { + [hub captureTransaction:transaction withScope:hub.scope]; + return; + } + + [hub captureTransaction:transaction + withScope:hub.scope + additionalEnvelopeItems:@[ profileEnvelopeItem ]]; + }]; +} + +NS_ASSUME_NONNULL_END + +#endif // SENTRY_TARGET_PROFILING_SUPPORTED diff --git a/Sources/Sentry/Profiling/SentryProfilerSerialization.mm b/Sources/Sentry/Profiling/SentryProfilerSerialization.mm index c61ce414aec..a2335a68303 100644 --- a/Sources/Sentry/Profiling/SentryProfilerSerialization.mm +++ b/Sources/Sentry/Profiling/SentryProfilerSerialization.mm @@ -346,22 +346,16 @@ return [[SentryEnvelope alloc] initWithId:chunkID singleItem:envelopeItem]; } -SentryEnvelopeItem *_Nullable sentry_traceProfileEnvelopeItem( +SentryEnvelopeItem *_Nullable sentry_traceProfileEnvelopeItem(SentryHub *hub, + SentryProfiler *profiler, NSDictionary *profilingData, SentryTransaction *transaction, NSDate *startTimestamp) { - SENTRY_LOG_DEBUG(@"Creating profiling envelope item"); - const auto profiler = sentry_profilerForFinishedTracer(transaction.trace.internalID); - if (!profiler) { - return nil; - } - const auto payload = sentry_serializedTraceProfileData( - [profiler.state copyProfilingData], transaction.startSystemTime, transaction.endSystemTime, + profilingData, transaction.startSystemTime, transaction.endSystemTime, sentry_profilerTruncationReasonName(profiler.truncationReason), [profiler.metricProfiler serializeTraceProfileMetricsBetween:transaction.startSystemTime and:transaction.endSystemTime], - [SentryDependencyContainer.sharedInstance.debugImageProvider getDebugImagesCrashed:NO], - transaction.trace.hub + [SentryDependencyContainer.sharedInstance.debugImageProvider getDebugImagesCrashed:NO], hub # if SENTRY_HAS_UIKIT , profiler.screenFrameData diff --git a/Sources/Sentry/SentryTracer.m b/Sources/Sentry/SentryTracer.m index 1a7b7e72629..2001a21da35 100644 --- a/Sources/Sentry/SentryTracer.m +++ b/Sources/Sentry/SentryTracer.m @@ -36,6 +36,7 @@ #import #if SENTRY_TARGET_PROFILING_SUPPORTED +# import "SentryCaptureTransactionWithProfile.h" # import "SentryLaunchProfiling.h" # import "SentryProfiledTracerConcurrency.h" # import "SentryProfilerSerialization.h" @@ -623,7 +624,8 @@ - (void)finishInternal startTimestamp = [SentryDependencyContainer.sharedInstance.dateProvider date]; } - [self captureTransactionWithProfile:transaction startTimestamp:startTimestamp]; + sentry_captureTransactionWithProfile( + self.hub, self.dispatchQueue, transaction, startTimestamp); return; } #endif // SENTRY_TARGET_PROFILING_SUPPORTED @@ -631,26 +633,6 @@ - (void)finishInternal [_hub captureTransaction:transaction withScope:_hub.scope]; } -#if SENTRY_TARGET_PROFILING_SUPPORTED -- (void)captureTransactionWithProfile:(SentryTransaction *)transaction - startTimestamp:(NSDate *)startTimestamp -{ - SentryEnvelopeItem *profileEnvelopeItem - = sentry_traceProfileEnvelopeItem(transaction, startTimestamp); - - if (!profileEnvelopeItem) { - [_hub captureTransaction:transaction withScope:_hub.scope]; - return; - } - - SENTRY_LOG_DEBUG(@"Capturing transaction id %@ with profiling data attached for tracer id %@.", - transaction.eventId.sentryIdString, self.internalID.sentryIdString); - [_hub captureTransaction:transaction - withScope:_hub.scope - additionalEnvelopeItems:@[ profileEnvelopeItem ]]; -} -#endif // SENTRY_TARGET_PROFILING_SUPPORTED - - (void)trimEndTimestamp { NSDate *oldest = self.startTimestamp; diff --git a/Sources/Sentry/include/SentryCaptureTransactionWithProfile.h b/Sources/Sentry/include/SentryCaptureTransactionWithProfile.h new file mode 100644 index 00000000000..23a9491ad35 --- /dev/null +++ b/Sources/Sentry/include/SentryCaptureTransactionWithProfile.h @@ -0,0 +1,22 @@ +#import "SentryProfilingConditionals.h" + +#if SENTRY_TARGET_PROFILING_SUPPORTED + +# import "SentryDefines.h" +# import + +@class SentryEnvelope; +@class SentryEnvelopeItem; +@class SentryHub; +@class SentryTransaction; +@class SentryDispatchQueueWrapper; + +NS_ASSUME_NONNULL_BEGIN + +SENTRY_EXTERN void sentry_captureTransactionWithProfile(SentryHub *hub, + SentryDispatchQueueWrapper *dispatchQueue, SentryTransaction *transaction, + NSDate *startTimestamp); + +NS_ASSUME_NONNULL_END + +#endif // SENTRY_TARGET_PROFILING_SUPPORTED diff --git a/Sources/Sentry/include/SentryProfilerSerialization.h b/Sources/Sentry/include/SentryProfilerSerialization.h index b3efeb002cd..e4da299fbab 100644 --- a/Sources/Sentry/include/SentryProfilerSerialization.h +++ b/Sources/Sentry/include/SentryProfilerSerialization.h @@ -11,10 +11,12 @@ @class SentryId; @class SentryScreenFrames; @class SentryTransaction; +@class SentryProfiler; NS_ASSUME_NONNULL_BEGIN -SENTRY_EXTERN SentryEnvelopeItem *_Nullable sentry_traceProfileEnvelopeItem( +SENTRY_EXTERN SentryEnvelopeItem *_Nullable sentry_traceProfileEnvelopeItem(SentryHub *hub, + SentryProfiler *profiler, NSDictionary *profilingData, SentryTransaction *transaction, NSDate *startTimestamp); SentryEnvelope *_Nullable sentry_continuousProfileChunkEnvelope( diff --git a/Tests/SentryProfilerTests/SentryProfileTestFixture.swift b/Tests/SentryProfilerTests/SentryProfileTestFixture.swift index 71560b48934..e65d235febb 100644 --- a/Tests/SentryProfilerTests/SentryProfileTestFixture.swift +++ b/Tests/SentryProfilerTests/SentryProfileTestFixture.swift @@ -78,7 +78,7 @@ class SentryProfileTestFixture { } /// Advance the mock date provider, start a new transaction and return its handle. - func newTransaction(testingAppLaunchSpans: Bool = false, automaticTransaction: Bool = false, idleTimeout: TimeInterval? = nil, expectedProfileMetrics: MockMetric = MockMetric()) throws -> SentryTracer { + func newTransaction(testingAppLaunchSpans: Bool = false, automaticTransaction: Bool = false, idleTimeout: TimeInterval? = nil) throws -> SentryTracer { let operation = testingAppLaunchSpans ? SentrySpanOperationUILoad : transactionOperation if automaticTransaction { diff --git a/Tests/SentryProfilerTests/SentryTraceProfilerTests.swift b/Tests/SentryProfilerTests/SentryTraceProfilerTests.swift index 24e89e6f4a6..291709da2e2 100644 --- a/Tests/SentryProfilerTests/SentryTraceProfilerTests.swift +++ b/Tests/SentryProfilerTests/SentryTraceProfilerTests.swift @@ -31,6 +31,29 @@ class SentryTraceProfilerTests: XCTestCase { span.finish() try self.assertMetricsPayload() } + + func testCaptureTransactionWithProfile_StopsProfileOnCallingThread() throws { + let span = try fixture.newTransaction() + try addMockSamples() + try fixture.gatherMockedTraceProfileMetrics() + + self.fixture.dispatchQueueWrapper.dispatchAsyncExecutesBlock = false + let currentProfiler = try XCTUnwrap(SentryTraceProfiler.getCurrentProfiler()) + + XCTAssertTrue(currentProfiler.isRunning()) + + span.finish() + + XCTAssertFalse(currentProfiler.isRunning(), "Profiler must be stopped on the calling thread.") + XCTAssertEqual(SentryProfilerTruncationReason.normal, currentProfiler.truncationReason) + + self.fixture.currentDateProvider.advanceBy(nanoseconds: 1.toNanoSeconds()) + self.fixture.dispatchQueueWrapper.dispatchAsyncExecutesBlock = true + self.fixture.dispatchQueueWrapper.invokeLastDispatchAsync() + + try self.assertMetricsPayload() + try self.assertValidTraceProfileData() + } func testTransactionWithMutatedTracerID() throws { let span = try fixture.newTransaction() From 0a23401352a8449cd5a38140bb36d941194e8800 Mon Sep 17 00:00:00 2001 From: Dhiogo Brustolin Date: Wed, 9 Oct 2024 13:12:51 +0200 Subject: [PATCH 20/36] ref: SwiftUI custom redact (#4392) Added sentryReplayUnmask as SwiftUI modifier --- CHANGELOG.md | 4 ++ .../iOS-SwiftUI/iOS-SwiftUI/ContentView.swift | 20 +++---- .../iOS-SwiftUI/iOS-SwiftUI/SwiftUIApp.swift | 2 - Sources/SentrySwiftUI/SentryReplayView.swift | 37 ++++++++++--- .../Swift/Extensions/UIViewExtensions.swift | 1 + .../Swift/Tools/SentryViewPhotographer.swift | 2 +- Sources/Swift/Tools/UIRedactBuilder.swift | 54 +++++++++++++++---- .../SwiftUI/SentryRedactModifierTests.swift | 9 +++- 8 files changed, 101 insertions(+), 28 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ec8e51cac9e..56ea7c02058 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +### Features + +- Custom redact modifier for SwiftUI (#4362, #4392) + ### Removal of Experimental API - Remove the deprecated experimental Metrics API (#4406): [Learn more](https://sentry.zendesk.com/hc/en-us/articles/26369339769883-Metrics-Beta-Coming-to-an-End) diff --git a/Samples/iOS-SwiftUI/iOS-SwiftUI/ContentView.swift b/Samples/iOS-SwiftUI/iOS-SwiftUI/ContentView.swift index 2767e1574f2..be7b0b0f7ff 100644 --- a/Samples/iOS-SwiftUI/iOS-SwiftUI/ContentView.swift +++ b/Samples/iOS-SwiftUI/iOS-SwiftUI/ContentView.swift @@ -118,14 +118,16 @@ struct ContentView: View { return SentryTracedView("Content View Body") { NavigationView { VStack(alignment: HorizontalAlignment.center, spacing: 16) { - Text(getCurrentTracer()?.transactionContext.name ?? "NO SPAN") - .accessibilityIdentifier("TRANSACTION_NAME") - Text(getCurrentTracer()?.transactionContext.spanId.sentrySpanIdString ?? "NO ID") - .accessibilityIdentifier("TRANSACTION_ID") - - Text(getCurrentTracer()?.transactionContext.origin ?? "NO ORIGIN") - .accessibilityIdentifier("TRACE_ORIGIN") - + Group { + Text(getCurrentTracer()?.transactionContext.name ?? "NO SPAN") + .accessibilityIdentifier("TRANSACTION_NAME") + Text(getCurrentTracer()?.transactionContext.spanId.sentrySpanIdString ?? "NO ID") + .accessibilityIdentifier("TRANSACTION_ID") + .sentryReplayMask() + + Text(getCurrentTracer()?.transactionContext.origin ?? "NO ORIGIN") + .accessibilityIdentifier("TRACE_ORIGIN") + }.sentryReplayUnmask() SentryTracedView("Child Span") { VStack { Text(getCurrentSpan()?.spanDescription ?? "NO SPAN") @@ -199,7 +201,7 @@ struct ContentView: View { Text("Form Screen") } } - .sentryReplayMask() + .background(Color.white) } SecondView() } diff --git a/Samples/iOS-SwiftUI/iOS-SwiftUI/SwiftUIApp.swift b/Samples/iOS-SwiftUI/iOS-SwiftUI/SwiftUIApp.swift index 5a490cd2861..ee92d4a10c3 100644 --- a/Samples/iOS-SwiftUI/iOS-SwiftUI/SwiftUIApp.swift +++ b/Samples/iOS-SwiftUI/iOS-SwiftUI/SwiftUIApp.swift @@ -11,8 +11,6 @@ struct SwiftUIApp: App { options.tracesSampleRate = 1.0 options.profilesSampleRate = 1.0 options.experimental.sessionReplay.sessionSampleRate = 1.0 - options.experimental.sessionReplay.maskAllImages = false - options.experimental.sessionReplay.maskAllText = false options.initialScope = { scope in scope.injectGitInformation() return scope diff --git a/Sources/SentrySwiftUI/SentryReplayView.swift b/Sources/SentrySwiftUI/SentryReplayView.swift index e35db36e512..78fe5d8342f 100644 --- a/Sources/SentrySwiftUI/SentryReplayView.swift +++ b/Sources/SentrySwiftUI/SentryReplayView.swift @@ -3,26 +3,41 @@ import Sentry import SwiftUI import UIKit +#if CARTHAGE || SWIFT_PACKAGE +@_implementationOnly import SentryInternal +#endif + +enum MaskBehavior { + case mask + case unmask +} + @available(iOS 13, macOS 10.15, tvOS 13, *) struct SentryReplayView: UIViewRepresentable { + let maskBehavior: MaskBehavior + class SentryRedactView: UIView { } func makeUIView(context: Context) -> UIView { - let result = SentryRedactView() - result.sentryReplayMask() - return result + let view = SentryRedactView() + view.isUserInteractionEnabled = false + return view } func updateUIView(_ uiView: UIView, context: Context) { - // This is blank on purpose. UIViewRepresentable requires this function. + switch maskBehavior { + case .mask: SentryRedactViewHelper.maskSwiftUI(uiView) + case .unmask: SentryRedactViewHelper.clipOutView(uiView) + } } } @available(iOS 13, macOS 10.15, tvOS 13, *) struct SentryReplayModifier: ViewModifier { + let behavior: MaskBehavior func body(content: Content) -> some View { - content.background(SentryReplayView()) + content.overlay(SentryReplayView(maskBehavior: behavior)) } } @@ -38,7 +53,17 @@ public extension View { /// - Returns: A modifier that redacts sensitive information during session replays. /// - Experiment: This is an experimental feature and may still have bugs. func sentryReplayMask() -> some View { - modifier(SentryReplayModifier()) + modifier(SentryReplayModifier(behavior: .mask)) + } + + /// Marks the view as safe to not be masked during session replay. + /// + /// Anything that is behind this view will also not be masked anymore. + /// + /// - Returns: A modifier that prevents a view from being masked in the session replay. + /// - Experiment: This is an experimental feature and may still have bugs. + func sentryReplayUnmask() -> some View { + modifier(SentryReplayModifier(behavior: .unmask)) } } #endif diff --git a/Sources/Swift/Extensions/UIViewExtensions.swift b/Sources/Swift/Extensions/UIViewExtensions.swift index 83bf0ade79d..7c5e8cf27c6 100644 --- a/Sources/Swift/Extensions/UIViewExtensions.swift +++ b/Sources/Swift/Extensions/UIViewExtensions.swift @@ -21,6 +21,7 @@ public extension UIView { func sentryReplayUnmask() { SentryRedactViewHelper.unmaskView(self) } + } #endif diff --git a/Sources/Swift/Tools/SentryViewPhotographer.swift b/Sources/Swift/Tools/SentryViewPhotographer.swift index 192585ff217..322a7017a84 100644 --- a/Sources/Swift/Tools/SentryViewPhotographer.swift +++ b/Sources/Swift/Tools/SentryViewPhotographer.swift @@ -58,7 +58,7 @@ class SentryViewPhotographer: NSObject, SentryViewScreenshotProvider { let path = CGPath(rect: rect, transform: &transform) switch region.type { - case .redact: + case .redact, .redactSwiftUI: (region.color ?? UIImageHelper.averageColor(of: context.currentImage, at: rect.applying(region.transform))).setFill() context.cgContext.addPath(path) context.cgContext.fillPath() diff --git a/Sources/Swift/Tools/UIRedactBuilder.swift b/Sources/Swift/Tools/UIRedactBuilder.swift index 01b99a3e296..72ff985d95a 100644 --- a/Sources/Swift/Tools/UIRedactBuilder.swift +++ b/Sources/Swift/Tools/UIRedactBuilder.swift @@ -22,6 +22,9 @@ enum RedactRegionType { /// Pop the last Pushed region from the drawing context. /// Used after prossing every child of a view that clip to its bounds. case clipEnd + + /// These regions are redacted first, there is no way to avoid it. + case redactSwiftUI } struct RedactRegion { @@ -155,7 +158,19 @@ class UIRedactBuilder { rootFrame: view.frame, transform: CGAffineTransform.identity) - return redactingRegions.reversed() + var swiftUIRedact = [RedactRegion]() + var otherRegions = [RedactRegion]() + + for region in redactingRegions { + if region.type == .redactSwiftUI { + swiftUIRedact.append(region) + } else { + otherRegions.append(region) + } + } + + //The swiftUI type needs to appear first in the list so it always get masked + return swiftUIRedact + otherRegions.reversed() } private func shouldIgnore(view: UIView) -> Bool { @@ -187,11 +202,12 @@ class UIRedactBuilder { let newTransform = concatenateTranform(transform, with: layer) let ignore = !forceRedact && shouldIgnore(view: view) - let redact = forceRedact || shouldRedact(view: view) + let swiftUI = SentryRedactViewHelper.shouldRedactSwiftUI(view) + let redact = forceRedact || shouldRedact(view: view) || swiftUI var enforceRedact = forceRedact if !ignore && redact { - redacting.append(RedactRegion(size: layer.bounds.size, transform: newTransform, type: .redact, color: self.color(for: view))) + redacting.append(RedactRegion(size: layer.bounds.size, transform: newTransform, type: swiftUI ? .redactSwiftUI : .redact, color: self.color(for: view))) guard !view.clipsToBounds else { return } enforceRedact = true @@ -248,14 +264,22 @@ class UIRedactBuilder { Indicates whether the view is opaque and will block other view behind it */ private func isOpaque(_ view: UIView) -> Bool { - return view.alpha == 1 && view.backgroundColor != nil && (view.backgroundColor?.cgColor.alpha ?? 0) == 1 + return SentryRedactViewHelper.shouldClipOut(view) || (view.alpha == 1 && view.backgroundColor != nil && (view.backgroundColor?.cgColor.alpha ?? 0) == 1) } } @objcMembers -class SentryRedactViewHelper: NSObject { +public class SentryRedactViewHelper: NSObject { private static var associatedRedactObjectHandle: UInt8 = 0 private static var associatedIgnoreObjectHandle: UInt8 = 0 + private static var associatedClipOutObjectHandle: UInt8 = 0 + private static var associatedSwiftUIRedactObjectHandle: UInt8 = 0 + + override private init() {} + + static func maskView(_ view: UIView) { + objc_setAssociatedObject(view, &associatedRedactObjectHandle, true, .OBJC_ASSOCIATION_ASSIGN) + } static func shouldMaskView(_ view: UIView) -> Bool { (objc_getAssociatedObject(view, &associatedRedactObjectHandle) as? NSNumber)?.boolValue ?? false @@ -265,13 +289,25 @@ class SentryRedactViewHelper: NSObject { (objc_getAssociatedObject(view, &associatedIgnoreObjectHandle) as? NSNumber)?.boolValue ?? false } - static func maskView(_ view: UIView) { - objc_setAssociatedObject(view, &associatedRedactObjectHandle, true, .OBJC_ASSOCIATION_ASSIGN) - } - static func unmaskView(_ view: UIView) { objc_setAssociatedObject(view, &associatedIgnoreObjectHandle, true, .OBJC_ASSOCIATION_ASSIGN) } + + static func shouldClipOut(_ view: UIView) -> Bool { + (objc_getAssociatedObject(view, &associatedClipOutObjectHandle) as? NSNumber)?.boolValue ?? false + } + + static public func clipOutView(_ view: UIView) { + objc_setAssociatedObject(view, &associatedClipOutObjectHandle, true, .OBJC_ASSOCIATION_ASSIGN) + } + + static func shouldRedactSwiftUI(_ view: UIView) -> Bool { + (objc_getAssociatedObject(view, &associatedSwiftUIRedactObjectHandle) as? NSNumber)?.boolValue ?? false + } + + static public func maskSwiftUI(_ view: UIView) { + objc_setAssociatedObject(view, &associatedSwiftUIRedactObjectHandle, true, .OBJC_ASSOCIATION_ASSIGN) + } } #endif diff --git a/Tests/SentryTests/SwiftUI/SentryRedactModifierTests.swift b/Tests/SentryTests/SwiftUI/SentryRedactModifierTests.swift index d2ae74fc7ed..48caa3246a4 100644 --- a/Tests/SentryTests/SwiftUI/SentryRedactModifierTests.swift +++ b/Tests/SentryTests/SwiftUI/SentryRedactModifierTests.swift @@ -6,12 +6,19 @@ import XCTest class SentryRedactModifierTests: XCTestCase { - func testViewRedacted() throws { + func testViewMask() throws { let text = Text("Hello, World!") let redactedText = text.sentryReplayMask() XCTAssertTrue(redactedText is ModifiedContent) } + + func testViewUnmask() throws { + let text = Text("Hello, World!") + let redactedText = text.sentryReplayUnmask() + + XCTAssertTrue(redactedText is ModifiedContent) + } } From 7cb89e0cbd28986c4ed3c10b41318d1989bf2abf Mon Sep 17 00:00:00 2001 From: Dhiogo Brustolin Date: Wed, 9 Oct 2024 16:43:05 +0200 Subject: [PATCH 21/36] Update build-xcframework.sh (#4420) --- scripts/build-xcframework.sh | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/scripts/build-xcframework.sh b/scripts/build-xcframework.sh index b108d743baf..f97db64a06f 100755 --- a/scripts/build-xcframework.sh +++ b/scripts/build-xcframework.sh @@ -6,6 +6,8 @@ args="${1:-}" if [ "$args" = "iOSOnly" ]; then sdks=( iphoneos iphonesimulator ) +elif [ "$args" = "gameOnly" ]; then + sdks=( iphoneos iphonesimulator macosx ) else sdks=( iphoneos iphonesimulator macosx appletvos appletvsimulator watchos watchsimulator xros xrsimulator ) fi @@ -124,8 +126,9 @@ generate_xcframework "Sentry" "-Dynamic" if [ "$args" != "iOSOnly" ]; then generate_xcframework "Sentry" "" staticlib - - generate_xcframework "SentrySwiftUI" - - generate_xcframework "Sentry" "-WithoutUIKitOrAppKit" mh_dylib WithoutUIKit + + if [ "$args" != "gameOnly" ]; then + generate_xcframework "SentrySwiftUI" + generate_xcframework "Sentry" "-WithoutUIKitOrAppKit" mh_dylib WithoutUIKit + fi fi From eae2b596111cf7fd8bfc17f1edaae76a48f72aca Mon Sep 17 00:00:00 2001 From: Philipp Hofmann Date: Thu, 10 Oct 2024 10:09:56 +0200 Subject: [PATCH 22/36] test: Crash on deallocated object (#4418) Add a method to cause a sigsev crash by calling a method on a deallocated object. --- .../iOS-ObjectiveC.xcodeproj/project.pbxproj | 6 ++++++ .../iOS-ObjectiveC/Base.lproj/Main.storyboard | 15 +++++++++++---- .../iOS-ObjectiveC/iOS-ObjectiveC/NoARCCrash.h | 6 ++++++ .../iOS-ObjectiveC/iOS-ObjectiveC/NoARCCrash.m | 10 ++++++++++ .../iOS-ObjectiveC/ViewController.m | 6 ++++++ Sentry.xcodeproj/project.pbxproj | 11 ----------- 6 files changed, 39 insertions(+), 15 deletions(-) create mode 100644 Samples/iOS-ObjectiveC/iOS-ObjectiveC/NoARCCrash.h create mode 100644 Samples/iOS-ObjectiveC/iOS-ObjectiveC/NoARCCrash.m diff --git a/Samples/iOS-ObjectiveC/iOS-ObjectiveC.xcodeproj/project.pbxproj b/Samples/iOS-ObjectiveC/iOS-ObjectiveC.xcodeproj/project.pbxproj index 45484f91824..d3051035aa8 100644 --- a/Samples/iOS-ObjectiveC/iOS-ObjectiveC.xcodeproj/project.pbxproj +++ b/Samples/iOS-ObjectiveC/iOS-ObjectiveC.xcodeproj/project.pbxproj @@ -7,6 +7,7 @@ objects = { /* Begin PBXBuildFile section */ + 6250B97C2CB69C96009512D6 /* NoARCCrash.m in Sources */ = {isa = PBXBuildFile; fileRef = 6250B97B2CB69C96009512D6 /* NoARCCrash.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; 630853492440C46E00DDE4CE /* Sentry.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6308533B2440C45500DDE4CE /* Sentry.framework */; }; 6308534A2440C46E00DDE4CE /* Sentry.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 6308533B2440C45500DDE4CE /* Sentry.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 637AFDCB243B036B0034958B /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 637AFDCA243B036B0034958B /* AppDelegate.m */; }; @@ -95,6 +96,8 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 6250B9752CB69C86009512D6 /* NoARCCrash.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = NoARCCrash.h; sourceTree = ""; }; + 6250B97B2CB69C96009512D6 /* NoARCCrash.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = NoARCCrash.m; sourceTree = ""; }; 630853352440C45500DDE4CE /* Sentry.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = Sentry.xcodeproj; path = ../../Sentry.xcodeproj; sourceTree = ""; }; 634C7EC724406A4900AFDE9F /* Sentry.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Sentry.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 637AFDC6243B036B0034958B /* iOS-ObjectiveC.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "iOS-ObjectiveC.app"; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -186,6 +189,8 @@ 637AFDD0243B036B0034958B /* ViewController.m */, 7B8A5C9C2715B5CF008ACD3B /* InitializerViewController.h */, 7B8A5C9D2715B5DE008ACD3B /* InitializerViewController.m */, + 6250B9752CB69C86009512D6 /* NoARCCrash.h */, + 6250B97B2CB69C96009512D6 /* NoARCCrash.m */, 637AFDD2243B036B0034958B /* Main.storyboard */, 637AFDD5243B036D0034958B /* Assets.xcassets */, 7B3427F925876DDB00056519 /* Tongariro.jpg */, @@ -415,6 +420,7 @@ 637AFDCB243B036B0034958B /* AppDelegate.m in Sources */, 84BA72AB2C9369A40045B828 /* GitInjections.swift in Sources */, 637AFDDC243B036D0034958B /* main.m in Sources */, + 6250B97C2CB69C96009512D6 /* NoARCCrash.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Samples/iOS-ObjectiveC/iOS-ObjectiveC/Base.lproj/Main.storyboard b/Samples/iOS-ObjectiveC/iOS-ObjectiveC/Base.lproj/Main.storyboard index 2c900cc5bf7..cc7f38e8d3b 100644 --- a/Samples/iOS-ObjectiveC/iOS-ObjectiveC/Base.lproj/Main.storyboard +++ b/Samples/iOS-ObjectiveC/iOS-ObjectiveC/Base.lproj/Main.storyboard @@ -1,9 +1,9 @@ - + - + @@ -24,7 +24,7 @@ - + - +