From c90a5063fdc31da66478d90188b68f80c0d1ab3e Mon Sep 17 00:00:00 2001 From: Robert Bartoszewski <126675445+robert-smartbear@users.noreply.github.com> Date: Tue, 9 Dec 2025 12:14:23 +0100 Subject: [PATCH 01/12] Merge pull request #537 from bugsnag/robert/plat-15319_enable_setting_attributes_onSpanEnd_for_blocked_spans Fixed an issue where onSpanEnd callbacks would fail to set attributes of blocked spans --- Sources/BugsnagPerformance/Private/Tracer.mm | 4 +++- features/default/manual_spans.feature | 7 +++++-- .../ios/Scenarios/OnEndCallbackScenario.swift | 12 ++++++++++++ features/steps/app_steps.rb | 7 +++++++ 4 files changed, 27 insertions(+), 3 deletions(-) diff --git a/Sources/BugsnagPerformance/Private/Tracer.mm b/Sources/BugsnagPerformance/Private/Tracer.mm index 03ddc442..5c64720f 100644 --- a/Sources/BugsnagPerformance/Private/Tracer.mm +++ b/Sources/BugsnagPerformance/Private/Tracer.mm @@ -190,7 +190,9 @@ } if (span != nil && span.state == SpanStateEnded) { - callOnSpanEndCallbacks(span); + [span forceMutate:^{ + callOnSpanEndCallbacks(span); + }]; if (span.state == SpanStateAborted) { BSGLogTrace(@"Tracer::onSpanClosed: span %@ was rejected in the OnEnd callbacks, so dropping", span.name); return; diff --git a/features/default/manual_spans.feature b/features/default/manual_spans.feature index 9b352bbe..91ce4c69 100644 --- a/features/default/manual_spans.feature +++ b/features/default/manual_spans.feature @@ -379,10 +379,13 @@ Feature: Manual creation of spans Scenario: Set OnEnd Given I run "OnEndCallbackScenario" - And I wait to receive 1 span - * the trace "Bugsnag-Span-Sampling" header equals "1:1" + And I wait to receive 3 spans * the trace "Bugsnag-Sent-At" header matches the regex "^\d\d\d\d-\d\d-\d\dT\d\d:\d\d:\d\d\.\d\d\dZ$" * a span field "name" equals "OnEndCallbackScenario" + * a span field "name" equals "OnEndCallbackScenarioEarlySpan" + * a span field "name" equals "OnEndCallbackScenarioBlockedSpan" + * every span string attribute "OnSpanEndAttribute" equals "OnEndCallbackScenarioValue" + * every span float attribute "bugsnag.span.callbacks_duration" is greater than 1.0 Scenario: Frame metrics - no slow frames Given I run "FrameMetricsNoSlowFramesScenario" diff --git a/features/fixtures/ios/Scenarios/OnEndCallbackScenario.swift b/features/fixtures/ios/Scenarios/OnEndCallbackScenario.swift index 4fe0001e..451198f9 100644 --- a/features/fixtures/ios/Scenarios/OnEndCallbackScenario.swift +++ b/features/fixtures/ios/Scenarios/OnEndCallbackScenario.swift @@ -15,6 +15,8 @@ class OnEndCallbackScenario: Scenario { override func setInitialBugsnagConfiguration() { super.setInitialBugsnagConfiguration() + + BugsnagPerformance.startSpan(name: "OnEndCallbackScenarioEarlySpan").end() bugsnagPerfConfig.add(onSpanEndCallback: { (span: BugsnagPerformanceSpan) -> Bool in return true @@ -36,11 +38,21 @@ class OnEndCallbackScenario: Scenario { bugsnagPerfConfig.add(onSpanEndCallback: { (span: BugsnagPerformanceSpan) -> Bool in return span.name != "drop_me_too" }) + bugsnagPerfConfig.add(onSpanEndCallback: { (span: BugsnagPerformanceSpan) -> Bool in + Thread.sleep(forTimeInterval: 1.2) + span.setAttribute("OnSpanEndAttribute", withValue: "OnEndCallbackScenarioValue") + return true + }) } override func run() { BugsnagPerformance.startSpan(name: "OnEndCallbackScenario").end() BugsnagPerformance.startSpan(name: "drop_me").end() BugsnagPerformance.startSpan(name: "drop_me_too").end() + let blockedSpan = BugsnagPerformance.startSpan(name: "OnEndCallbackScenarioBlockedSpan") + let condition = blockedSpan.block(timeout: 0.7) + condition?.upgrade() + blockedSpan.end() + condition?.close(endTime: Date()) } } diff --git a/features/steps/app_steps.rb b/features/steps/app_steps.rb index 3ec3d6bf..b2ca6d09 100644 --- a/features/steps/app_steps.rb +++ b/features/steps/app_steps.rb @@ -212,6 +212,13 @@ def run_command(action, args) Maze.check.false(attribute_values.empty?) end +Then('every span float attribute {string} is greater than {float}') do |attribute, expected| + spans = spans_from_request_list(Maze::Server.list_for('traces')) + selected_attributes = spans.map { |span| span['attributes'].find { |a| a['key'].eql?(attribute) && a['value'].has_key?('doubleValue') } }.compact + attribute_values = selected_attributes.map { |a| a['value']['doubleValue'].to_f > expected } + Maze.check.not_includes attribute_values, false +end + Then('a span float attribute {string} equals {float}') do |attribute, expected| spans = spans_from_request_list(Maze::Server.list_for('traces')) selected_attributes = spans.map { |span| span['attributes'].find { |a| a['key'].eql?(attribute) && a['value'].has_key?('doubleValue') } }.compact From b84ab834c0485eba3d7d0a283e66ae7badb89a99 Mon Sep 17 00:00:00 2001 From: Robert Bartoszewski <126675445+robert-smartbear@users.noreply.github.com> Date: Tue, 9 Dec 2025 12:15:05 +0100 Subject: [PATCH 02/12] Fixed an issue where early network span attributes wouldn't be updated in reprocessing (#538) Co-authored-by: Robert Bartoszewski --- .../Instrumentation/NetworkInstrumentation.mm | 4 +- features/default/network.feature | 14 +++++ .../ios/Fixture.xcodeproj/project.pbxproj | 4 ++ .../project.pbxproj | 4 ++ ...strumentNetworkEarlyCallbackScenario.swift | 55 +++++++++++++++++++ 5 files changed, 80 insertions(+), 1 deletion(-) create mode 100644 features/fixtures/ios/Scenarios/AutoInstrumentNetworkEarlyCallbackScenario.swift diff --git a/Sources/BugsnagPerformance/Private/Instrumentation/NetworkInstrumentation.mm b/Sources/BugsnagPerformance/Private/Instrumentation/NetworkInstrumentation.mm index 275ceb06..9724872d 100644 --- a/Sources/BugsnagPerformance/Private/Instrumentation/NetworkInstrumentation.mm +++ b/Sources/BugsnagPerformance/Private/Instrumentation/NetworkInstrumentation.mm @@ -215,7 +215,9 @@ static bool didVetoTracing(NSURL * _Nullable originalUrl, BSGLogTrace(@"NetworkInstrumentation::endEarlySpansPhase: info.url is nil, so we will end on destroy"); [span endOnDestroy]; } else { - [span internalSetMultipleAttributes:spanAttributesProvider_->networkSpanUrlAttributes(info.url, nil)]; + [span forceMutate:^{ + [span internalSetMultipleAttributes:spanAttributesProvider_->networkSpanUrlAttributes(info.url, nil)]; + }]; } } } diff --git a/features/default/network.feature b/features/default/network.feature index e280e1cd..75f960bd 100644 --- a/features/default/network.feature +++ b/features/default/network.feature @@ -14,6 +14,20 @@ Feature: Automatic instrumentation spans * a span string attribute "http.url" equals "https://bugsnag.com" * a span string attribute "http.url" equals "https://bugsnag.com/changed" + Scenario: AutoInstrumentNetworkEarlyCallbackScenario + Given I run "AutoInstrumentNetworkEarlyCallbackScenario" + And I wait to receive 2 spans + Then the trace "Content-Type" header equals "application/json" + * the trace "Bugsnag-Sent-At" header matches the regex "^\d\d\d\d-\d\d-\d\dT\d\d:\d\d:\d\d\.\d\d\dZ$" + * every span field "name" equals "[HTTP/GET]" + * every span field "spanId" matches the regex "^[A-Fa-f0-9]{16}$" + * every span field "traceId" matches the regex "^[A-Fa-f0-9]{32}$" + * every span field "kind" equals 3 + * every span field "startTimeUnixNano" matches the regex "^[0-9]+$" + * every span field "endTimeUnixNano" matches the regex "^[0-9]+$" + * a span string attribute "http.url" equals "https://bugsnag.com" + * a span string attribute "http.url" equals "https://bugsnag.com/changed" + Scenario: AutoInstrumentNullNetworkCallbackScenario Given I run "AutoInstrumentNullNetworkCallbackScenario" # Wait for a long time because there can be a LOT of maze-runner related URL requests before the scenario starts. diff --git a/features/fixtures/ios/Fixture.xcodeproj/project.pbxproj b/features/fixtures/ios/Fixture.xcodeproj/project.pbxproj index e09b9b03..ede2a5f2 100644 --- a/features/fixtures/ios/Fixture.xcodeproj/project.pbxproj +++ b/features/fixtures/ios/Fixture.xcodeproj/project.pbxproj @@ -81,6 +81,7 @@ 96DADF512EAFCB1100B56CE6 /* ManualSpanEndOnDestroyScenario.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96DADF502EAFCB1100B56CE6 /* ManualSpanEndOnDestroyScenario.swift */; }; 96F129352DCE0CFE00A6FB2B /* ManualSpanWithRemoteContextParentScenario.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96F129342DCE0CFE00A6FB2B /* ManualSpanWithRemoteContextParentScenario.swift */; }; 96F5268C2C259E4E0095D600 /* ManualNetworkSpanCallbackSetToNilScenario.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96F5268B2C259E4E0095D600 /* ManualNetworkSpanCallbackSetToNilScenario.swift */; }; + 96F901092EE7B7FF0026F5B9 /* AutoInstrumentNetworkEarlyCallbackScenario.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96F901082EE7B7FF0026F5B9 /* AutoInstrumentNetworkEarlyCallbackScenario.swift */; }; CB0496942913CA300097E526 /* BatchingWithTimeoutScenario.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB0496932913CA300097E526 /* BatchingWithTimeoutScenario.swift */; }; CB0AD76E2965BBDA002A3FB6 /* InitialPScenario.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB0AD76D2965BBDA002A3FB6 /* InitialPScenario.swift */; }; CB2B8A9D2A0CCEF90054FBBE /* AutoInstrumentFileURLRequestScenario.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB2B8A9C2A0CCEF90054FBBE /* AutoInstrumentFileURLRequestScenario.swift */; }; @@ -186,6 +187,7 @@ 96DADF572EAFCE6D00B56CE6 /* BugsnagPerformanceSpan+Internal.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "BugsnagPerformanceSpan+Internal.h"; sourceTree = ""; }; 96F129342DCE0CFE00A6FB2B /* ManualSpanWithRemoteContextParentScenario.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ManualSpanWithRemoteContextParentScenario.swift; sourceTree = ""; }; 96F5268B2C259E4E0095D600 /* ManualNetworkSpanCallbackSetToNilScenario.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ManualNetworkSpanCallbackSetToNilScenario.swift; sourceTree = ""; }; + 96F901082EE7B7FF0026F5B9 /* AutoInstrumentNetworkEarlyCallbackScenario.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutoInstrumentNetworkEarlyCallbackScenario.swift; sourceTree = ""; }; CB0496932913CA300097E526 /* BatchingWithTimeoutScenario.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BatchingWithTimeoutScenario.swift; sourceTree = ""; }; CB0AD76D2965BBDA002A3FB6 /* InitialPScenario.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InitialPScenario.swift; sourceTree = ""; }; CB211D0629EEB615008F748D /* BugsnagPerformanceConfiguration+Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "BugsnagPerformanceConfiguration+Private.h"; path = "../../../../Sources/BugsnagPerformance/Private/BugsnagPerformanceConfiguration+Private.h"; sourceTree = ""; }; @@ -300,6 +302,7 @@ 9657A89A2A3D06EB001CEF5D /* AutoInstrumentNavigationViewLoadScenario.swift */, CB2B8A9E2A0E80B80054FBBE /* AutoInstrumentNetworkBadAddressScenario.swift */, 0921F02D2A69262300C764EB /* AutoInstrumentNetworkCallbackScenario.swift */, + 96F901082EE7B7FF0026F5B9 /* AutoInstrumentNetworkEarlyCallbackScenario.swift */, CBEC89222A458BA70088A3CE /* AutoInstrumentNetworkMultiple.swift */, CBE0872A29F81BBB007455F2 /* AutoInstrumentNetworkNoParentScenario.swift */, 09F025082BA08817007D9F73 /* AutoInstrumentNetworkNullURLScenario.swift */, @@ -529,6 +532,7 @@ 9657A8992A3CF75B001CEF5D /* AutoInstrumentTabViewLoadScenario.swift in Sources */, CBEC89452A4ED0590088A3CE /* MaxPayloadSizeScenario.swift in Sources */, 964735CB2CCF137A00759ED9 /* AutoInstrumentNetworkSharedSessionInvalidateScenario.swift in Sources */, + 96F901092EE7B7FF0026F5B9 /* AutoInstrumentNetworkEarlyCallbackScenario.swift in Sources */, CB572EAD29BB829800FD7A2A /* BackgroundForegroundScenario.swift in Sources */, 966634E02C9DE384004A934D /* FrameMetricsSlowFramesScenario.swift in Sources */, CB2B8A9F2A0E80B80054FBBE /* AutoInstrumentNetworkBadAddressScenario.swift in Sources */, diff --git a/features/fixtures/ios/FixtureXcFramework.xcodeproj/project.pbxproj b/features/fixtures/ios/FixtureXcFramework.xcodeproj/project.pbxproj index 64127479..2c960406 100644 --- a/features/fixtures/ios/FixtureXcFramework.xcodeproj/project.pbxproj +++ b/features/fixtures/ios/FixtureXcFramework.xcodeproj/project.pbxproj @@ -80,6 +80,7 @@ 96E0B34B2CD0E21C008AEB9C /* AutoInstrumentNetworkSharedSessionInvalidateScenario.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96E0B34A2CD0E21C008AEB9C /* AutoInstrumentNetworkSharedSessionInvalidateScenario.swift */; }; 96F129332DCE0CDD00A6FB2B /* RenderingMetricsScenario.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96F129322DCE0CDD00A6FB2B /* RenderingMetricsScenario.swift */; }; 96F5268C2C259E4E0095D600 /* ManualNetworkSpanCallbackSetToNilScenario.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96F5268B2C259E4E0095D600 /* ManualNetworkSpanCallbackSetToNilScenario.swift */; }; + 96F9010B2EE7B81F0026F5B9 /* AutoInstrumentNetworkEarlyCallbackScenario.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96F9010A2EE7B81F0026F5B9 /* AutoInstrumentNetworkEarlyCallbackScenario.swift */; }; CB0496942913CA300097E526 /* BatchingWithTimeoutScenario.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB0496932913CA300097E526 /* BatchingWithTimeoutScenario.swift */; }; CB0AD76E2965BBDA002A3FB6 /* InitialPScenario.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB0AD76D2965BBDA002A3FB6 /* InitialPScenario.swift */; }; CB2B8A9D2A0CCEF90054FBBE /* AutoInstrumentFileURLRequestScenario.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB2B8A9C2A0CCEF90054FBBE /* AutoInstrumentFileURLRequestScenario.swift */; }; @@ -201,6 +202,7 @@ 96E0B34A2CD0E21C008AEB9C /* AutoInstrumentNetworkSharedSessionInvalidateScenario.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AutoInstrumentNetworkSharedSessionInvalidateScenario.swift; sourceTree = ""; }; 96F129322DCE0CDD00A6FB2B /* RenderingMetricsScenario.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RenderingMetricsScenario.swift; sourceTree = ""; }; 96F5268B2C259E4E0095D600 /* ManualNetworkSpanCallbackSetToNilScenario.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ManualNetworkSpanCallbackSetToNilScenario.swift; sourceTree = ""; }; + 96F9010A2EE7B81F0026F5B9 /* AutoInstrumentNetworkEarlyCallbackScenario.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutoInstrumentNetworkEarlyCallbackScenario.swift; sourceTree = ""; }; CB0496932913CA300097E526 /* BatchingWithTimeoutScenario.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BatchingWithTimeoutScenario.swift; sourceTree = ""; }; CB0AD76D2965BBDA002A3FB6 /* InitialPScenario.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InitialPScenario.swift; sourceTree = ""; }; CB211D0629EEB615008F748D /* BugsnagPerformanceConfiguration+Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "BugsnagPerformanceConfiguration+Private.h"; path = "../../../../Sources/BugsnagPerformance/Private/BugsnagPerformanceConfiguration+Private.h"; sourceTree = ""; }; @@ -318,6 +320,7 @@ 9657A89A2A3D06EB001CEF5D /* AutoInstrumentNavigationViewLoadScenario.swift */, CB2B8A9E2A0E80B80054FBBE /* AutoInstrumentNetworkBadAddressScenario.swift */, 0921F02D2A69262300C764EB /* AutoInstrumentNetworkCallbackScenario.swift */, + 96F9010A2EE7B81F0026F5B9 /* AutoInstrumentNetworkEarlyCallbackScenario.swift */, CBEC89222A458BA70088A3CE /* AutoInstrumentNetworkMultiple.swift */, CBE0872A29F81BBB007455F2 /* AutoInstrumentNetworkNoParentScenario.swift */, 09F025082BA08817007D9F73 /* AutoInstrumentNetworkNullURLScenario.swift */, @@ -496,6 +499,7 @@ 96D528D02C77F38400FEA2E2 /* FixedSamplingProbabilityZeroScenario.swift in Sources */, 09D59E1D2BE105F700199E1B /* AutoInstrumentNetworkTracePropagationScenario.swift in Sources */, 09F3F52A2D6C72B300BAA0A3 /* CPUMetricsScenario.swift in Sources */, + 96F9010B2EE7B81F0026F5B9 /* AutoInstrumentNetworkEarlyCallbackScenario.swift in Sources */, 01FE4DAD28E1AEBD00D1F239 /* ViewController.swift in Sources */, CBEC89232A458BA70088A3CE /* AutoInstrumentNetworkMultiple.swift in Sources */, 093EE63D2C32E5B900632B30 /* ManualParentSpanScenario.swift in Sources */, diff --git a/features/fixtures/ios/Scenarios/AutoInstrumentNetworkEarlyCallbackScenario.swift b/features/fixtures/ios/Scenarios/AutoInstrumentNetworkEarlyCallbackScenario.swift new file mode 100644 index 00000000..91ee65dd --- /dev/null +++ b/features/fixtures/ios/Scenarios/AutoInstrumentNetworkEarlyCallbackScenario.swift @@ -0,0 +1,55 @@ +// +// AutoInstrumentNetworkEarlyCallbackScenario.swift +// Fixture +// +// Created by Robert Bartoszewski on 09/12/2025. +// + +import Foundation + +@objcMembers +class AutoInstrumentNetworkEarlyCallbackScenario: Scenario { + + override func postLoad() { + super.postLoad() + query(url: URL(string: "https://bugsnag.com")!) + query(url: URL(string: "https://bugsnag.com/changeme")!) + query(url: URL(string: "https://google.com")!) + + // Wait for the query to finish before starting bugsnag + Thread.sleep(forTimeInterval: 2.0) + } + + override func setInitialBugsnagConfiguration() { + super.setInitialBugsnagConfiguration() + bugsnagPerfConfig.autoInstrumentNetworkRequests = true + bugsnagPerfConfig.networkRequestCallback = { (origInfo: BugsnagPerformanceNetworkRequestInfo) -> BugsnagPerformanceNetworkRequestInfo in + let info = self.filterAdminMazeRunnerNetRequests(info: origInfo) + + let testUrl = info.url + if (testUrl == nil) { + return info + } + + let url = testUrl! + + if url.absoluteString == "https://google.com" { + info.url = nil + } else if url.lastPathComponent == "changeme" { + info.url = URL(string:"changed", relativeTo:url.deletingLastPathComponent()) + } + + return info + } + } + + func query(url: URL) { + let task = URLSession.shared.dataTask(with: url) {(data, response, error) in + } + task.resume() + + } + + override func run() { + } +} From 1fc957a3cf60d094cdd160ef2665e1708963d6c9 Mon Sep 17 00:00:00 2001 From: Robert Bartoszewski <126675445+robert-smartbear@users.noreply.github.com> Date: Tue, 9 Dec 2025 23:08:28 +0100 Subject: [PATCH 03/12] Fixed an issue preventing some preloaded ViewLoad spans from being adjusted (#541) Co-authored-by: Robert Bartoszewski --- .../Private/Instrumentation/ViewLoadInstrumentation.mm | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/Sources/BugsnagPerformance/Private/Instrumentation/ViewLoadInstrumentation.mm b/Sources/BugsnagPerformance/Private/Instrumentation/ViewLoadInstrumentation.mm index 20e7701b..a7983892 100644 --- a/Sources/BugsnagPerformance/Private/Instrumentation/ViewLoadInstrumentation.mm +++ b/Sources/BugsnagPerformance/Private/Instrumentation/ViewLoadInstrumentation.mm @@ -424,9 +424,13 @@ - (void)dealloc { reinterpret_cast(viewWillAppear)(self, selector, animated); } [span end]; - adjustSpanIfPreloaded(overallSpan, instrumentationState, [span startTime], self); - BugsnagPerformanceSpan *viewAppearingSpan = startViewLoadPhaseSpan(self, @"View appearing"); - instrumentationState.viewAppearingSpan = viewAppearingSpan; + [span forceMutate:^{ + adjustSpanIfPreloaded(overallSpan, instrumentationState, [span startTime], self); + }]; + if (instrumentationState.viewAppearingSpan == nil) { + BugsnagPerformanceSpan *viewAppearingSpan = startViewLoadPhaseSpan(self, @"View appearing"); + instrumentationState.viewAppearingSpan = viewAppearingSpan; + } }); } From 39aabf4a0a80cdd0c8726f7080a1dbfc801abaa6 Mon Sep 17 00:00:00 2001 From: Robert Bartoszewski <126675445+robert-smartbear@users.noreply.github.com> Date: Tue, 9 Dec 2025 23:08:48 +0100 Subject: [PATCH 04/12] Fixed an issue where current context would not calculated properly whenever there is an ended and blocked span on the top of the stack (#540) Co-authored-by: Robert Bartoszewski --- .../Private/SpanStackingHandler.mm | 12 +++---- features/default/manual_spans.feature | 15 ++++++++ .../ios/Fixture.xcodeproj/project.pbxproj | 4 +++ .../project.pbxproj | 4 +++ .../ManualParentBlockedSpanScenario.swift | 35 +++++++++++++++++++ 5 files changed, 64 insertions(+), 6 deletions(-) create mode 100644 features/fixtures/ios/Scenarios/ManualParentBlockedSpanScenario.swift diff --git a/Sources/BugsnagPerformance/Private/SpanStackingHandler.mm b/Sources/BugsnagPerformance/Private/SpanStackingHandler.mm index e6565499..b847634d 100644 --- a/Sources/BugsnagPerformance/Private/SpanStackingHandler.mm +++ b/Sources/BugsnagPerformance/Private/SpanStackingHandler.mm @@ -52,13 +52,13 @@ static inline os_activity_id_t currentActivityId() { SpanStackingHandler::currentSpan() { std::lock_guard guard(mutex_); std::shared_ptr state = spanStateForActivity(currentActivityId()); - if (state == nullptr) { - return nullptr; - } - if (!(state->span.state == SpanStateOpen)) { - return nullptr; + while (state != nullptr) { + if (state->span.state == SpanStateOpen) { + return state->span; + } + state = spanStateForActivity(state->parentActivityId); } - return state->span; + return nullptr; } void diff --git a/features/default/manual_spans.feature b/features/default/manual_spans.feature index 91ce4c69..b5b9403c 100644 --- a/features/default/manual_spans.feature +++ b/features/default/manual_spans.feature @@ -567,3 +567,18 @@ Feature: Manual creation of spans * the trace payload field "resourceSpans.0.scopeSpans.0.spans.0.parentSpanId" is null * the trace payload field "resourceSpans.0.scopeSpans.0.spans.1.parentSpanId" matches the regex "^[A-Fa-f0-9]{16}$" * the trace payload field "resourceSpans.0.scopeSpans.0.spans.2.parentSpanId" is null + + Scenario: Parent context should be calculated despite blocked spans on the stack + Given I run "ManualParentBlockedSpanScenario" + And I wait to receive at least 4 spans + Then the trace "Content-Type" header equals "application/json" + * the trace "Bugsnag-Integrity" header matches the regex "^sha1 [A-Fa-f0-9]{40}$" + * the trace "Bugsnag-Sent-At" header matches the regex "^\d\d\d\d-\d\d-\d\dT\d\d:\d\d:\d\d\.\d\d\dZ$" + * a span field "name" equals "ManualParentBlockedSpanScenarioParent" + * a span field "name" equals "ManualParentBlockedSpanScenarioBlocked1" + * a span field "name" equals "ManualParentBlockedSpanScenarioBlocked2" + * a span field "name" equals "ManualParentBlockedSpanScenarioChild" + * a span named "ManualParentBlockedSpanScenarioBlocked1" is a child of span named "ManualParentBlockedSpanScenarioParent" + * a span named "ManualParentBlockedSpanScenarioBlocked2" is a child of span named "ManualParentBlockedSpanScenarioBlocked1" + * a span named "ManualParentBlockedSpanScenarioChild" is a child of span named "ManualParentBlockedSpanScenarioParent" + diff --git a/features/fixtures/ios/Fixture.xcodeproj/project.pbxproj b/features/fixtures/ios/Fixture.xcodeproj/project.pbxproj index ede2a5f2..a0249a51 100644 --- a/features/fixtures/ios/Fixture.xcodeproj/project.pbxproj +++ b/features/fixtures/ios/Fixture.xcodeproj/project.pbxproj @@ -81,6 +81,7 @@ 96DADF512EAFCB1100B56CE6 /* ManualSpanEndOnDestroyScenario.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96DADF502EAFCB1100B56CE6 /* ManualSpanEndOnDestroyScenario.swift */; }; 96F129352DCE0CFE00A6FB2B /* ManualSpanWithRemoteContextParentScenario.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96F129342DCE0CFE00A6FB2B /* ManualSpanWithRemoteContextParentScenario.swift */; }; 96F5268C2C259E4E0095D600 /* ManualNetworkSpanCallbackSetToNilScenario.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96F5268B2C259E4E0095D600 /* ManualNetworkSpanCallbackSetToNilScenario.swift */; }; + 96F901172EE836CC0026F5B9 /* ManualParentBlockedSpanScenario.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96F901162EE836CC0026F5B9 /* ManualParentBlockedSpanScenario.swift */; }; 96F901092EE7B7FF0026F5B9 /* AutoInstrumentNetworkEarlyCallbackScenario.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96F901082EE7B7FF0026F5B9 /* AutoInstrumentNetworkEarlyCallbackScenario.swift */; }; CB0496942913CA300097E526 /* BatchingWithTimeoutScenario.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB0496932913CA300097E526 /* BatchingWithTimeoutScenario.swift */; }; CB0AD76E2965BBDA002A3FB6 /* InitialPScenario.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB0AD76D2965BBDA002A3FB6 /* InitialPScenario.swift */; }; @@ -187,6 +188,7 @@ 96DADF572EAFCE6D00B56CE6 /* BugsnagPerformanceSpan+Internal.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "BugsnagPerformanceSpan+Internal.h"; sourceTree = ""; }; 96F129342DCE0CFE00A6FB2B /* ManualSpanWithRemoteContextParentScenario.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ManualSpanWithRemoteContextParentScenario.swift; sourceTree = ""; }; 96F5268B2C259E4E0095D600 /* ManualNetworkSpanCallbackSetToNilScenario.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ManualNetworkSpanCallbackSetToNilScenario.swift; sourceTree = ""; }; + 96F901162EE836CC0026F5B9 /* ManualParentBlockedSpanScenario.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ManualParentBlockedSpanScenario.swift; sourceTree = ""; }; 96F901082EE7B7FF0026F5B9 /* AutoInstrumentNetworkEarlyCallbackScenario.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutoInstrumentNetworkEarlyCallbackScenario.swift; sourceTree = ""; }; CB0496932913CA300097E526 /* BatchingWithTimeoutScenario.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BatchingWithTimeoutScenario.swift; sourceTree = ""; }; CB0AD76D2965BBDA002A3FB6 /* InitialPScenario.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InitialPScenario.swift; sourceTree = ""; }; @@ -343,6 +345,7 @@ 96F5268B2C259E4E0095D600 /* ManualNetworkSpanCallbackSetToNilScenario.swift */, CBE6B66A28FD66B400D1CF78 /* ManualNetworkSpanScenario.swift */, 09D59E162BDFA23600199E1B /* ManualNetworkTracePropagationScenario.swift */, + 96F901162EE836CC0026F5B9 /* ManualParentBlockedSpanScenario.swift */, 093EE63C2C32E5B900632B30 /* ManualParentSpanScenario.swift */, 01E7918928EC7B5E00855993 /* ManualSpanBeforeStartScenario.swift */, 96DADF502EAFCB1100B56CE6 /* ManualSpanEndOnDestroyScenario.swift */, @@ -481,6 +484,7 @@ 09F3F5302D6F17B300BAA0A3 /* RenderingMetricsScenario.swift in Sources */, 966634E22C9DE648004A934D /* FrameMetricsNoSlowFramesScenario.swift in Sources */, 9691A9DF2CA5E62800707CDF /* FrameMetricsSpanInstrumentRenderingOffScenario.swift in Sources */, + 96F901172EE836CC0026F5B9 /* ManualParentBlockedSpanScenario.swift in Sources */, 09F025092BA08817007D9F73 /* AutoInstrumentNetworkNullURLScenario.swift in Sources */, 0185C47228F6C983006F9BDC /* AutoInstrumentViewLoadScenario.swift in Sources */, 09D59E172BDFA23600199E1B /* ManualNetworkTracePropagationScenario.swift in Sources */, diff --git a/features/fixtures/ios/FixtureXcFramework.xcodeproj/project.pbxproj b/features/fixtures/ios/FixtureXcFramework.xcodeproj/project.pbxproj index 2c960406..acef935d 100644 --- a/features/fixtures/ios/FixtureXcFramework.xcodeproj/project.pbxproj +++ b/features/fixtures/ios/FixtureXcFramework.xcodeproj/project.pbxproj @@ -80,6 +80,7 @@ 96E0B34B2CD0E21C008AEB9C /* AutoInstrumentNetworkSharedSessionInvalidateScenario.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96E0B34A2CD0E21C008AEB9C /* AutoInstrumentNetworkSharedSessionInvalidateScenario.swift */; }; 96F129332DCE0CDD00A6FB2B /* RenderingMetricsScenario.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96F129322DCE0CDD00A6FB2B /* RenderingMetricsScenario.swift */; }; 96F5268C2C259E4E0095D600 /* ManualNetworkSpanCallbackSetToNilScenario.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96F5268B2C259E4E0095D600 /* ManualNetworkSpanCallbackSetToNilScenario.swift */; }; + 96F901192EE836E10026F5B9 /* ManualParentBlockedSpanScenario.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96F901182EE836E10026F5B9 /* ManualParentBlockedSpanScenario.swift */; }; 96F9010B2EE7B81F0026F5B9 /* AutoInstrumentNetworkEarlyCallbackScenario.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96F9010A2EE7B81F0026F5B9 /* AutoInstrumentNetworkEarlyCallbackScenario.swift */; }; CB0496942913CA300097E526 /* BatchingWithTimeoutScenario.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB0496932913CA300097E526 /* BatchingWithTimeoutScenario.swift */; }; CB0AD76E2965BBDA002A3FB6 /* InitialPScenario.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB0AD76D2965BBDA002A3FB6 /* InitialPScenario.swift */; }; @@ -202,6 +203,7 @@ 96E0B34A2CD0E21C008AEB9C /* AutoInstrumentNetworkSharedSessionInvalidateScenario.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AutoInstrumentNetworkSharedSessionInvalidateScenario.swift; sourceTree = ""; }; 96F129322DCE0CDD00A6FB2B /* RenderingMetricsScenario.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RenderingMetricsScenario.swift; sourceTree = ""; }; 96F5268B2C259E4E0095D600 /* ManualNetworkSpanCallbackSetToNilScenario.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ManualNetworkSpanCallbackSetToNilScenario.swift; sourceTree = ""; }; + 96F901182EE836E10026F5B9 /* ManualParentBlockedSpanScenario.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ManualParentBlockedSpanScenario.swift; sourceTree = ""; }; 96F9010A2EE7B81F0026F5B9 /* AutoInstrumentNetworkEarlyCallbackScenario.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutoInstrumentNetworkEarlyCallbackScenario.swift; sourceTree = ""; }; CB0496932913CA300097E526 /* BatchingWithTimeoutScenario.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BatchingWithTimeoutScenario.swift; sourceTree = ""; }; CB0AD76D2965BBDA002A3FB6 /* InitialPScenario.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InitialPScenario.swift; sourceTree = ""; }; @@ -361,6 +363,7 @@ 96F5268B2C259E4E0095D600 /* ManualNetworkSpanCallbackSetToNilScenario.swift */, CBE6B66A28FD66B400D1CF78 /* ManualNetworkSpanScenario.swift */, 09D59E162BDFA23600199E1B /* ManualNetworkTracePropagationScenario.swift */, + 96F901182EE836E10026F5B9 /* ManualParentBlockedSpanScenario.swift */, 093EE63C2C32E5B900632B30 /* ManualParentSpanScenario.swift */, 01E7918928EC7B5E00855993 /* ManualSpanBeforeStartScenario.swift */, 96DADF532EAFCB6300B56CE6 /* ManualSpanEndOnDestroyScenario.swift */, @@ -531,6 +534,7 @@ 0983A1792B14B20C00DDF4FF /* AutoInstrumentSwiftUIScenario.swift in Sources */, CBF62109291A4F47004BEE0B /* RetryScenario.swift in Sources */, CB7FD92B299BB4E300499E13 /* ManualUIViewLoadScenario.swift in Sources */, + 96F901192EE836E10026F5B9 /* ManualParentBlockedSpanScenario.swift in Sources */, 9691A9E12CA7588700707CDF /* FrameMetricsNonFirstClassSpanInstrumentRenderingOnScenario.swift in Sources */, 09F3F52C2D6C72BD00BAA0A3 /* MemoryMetricsScenario.swift in Sources */, 09637A3F2B06082200F4F776 /* Logging.m in Sources */, diff --git a/features/fixtures/ios/Scenarios/ManualParentBlockedSpanScenario.swift b/features/fixtures/ios/Scenarios/ManualParentBlockedSpanScenario.swift new file mode 100644 index 00000000..3addee44 --- /dev/null +++ b/features/fixtures/ios/Scenarios/ManualParentBlockedSpanScenario.swift @@ -0,0 +1,35 @@ +// +// ManualParentBlockedSpanScenario.swift +// Fixture +// +// Created by Robert Bartoszewski on 09/12/2025. +// + +import BugsnagPerformance + +@objcMembers +class ManualParentBlockedSpanScenario: Scenario { + + override func setInitialBugsnagConfiguration() { + super.setInitialBugsnagConfiguration() + bugsnagPerfConfig.internal.autoTriggerExportOnBatchSize = 4; + } + + override func run() { + let parentSpan = BugsnagPerformance.startSpan(name: "ManualParentBlockedSpanScenarioParent") + let blockedSpan1 = BugsnagPerformance.startSpan(name: "ManualParentBlockedSpanScenarioBlocked1") + let blockedSpan2 = BugsnagPerformance.startSpan(name: "ManualParentBlockedSpanScenarioBlocked2") + let condition1 = blockedSpan1.block(timeout: 1.0) + condition1?.upgrade() + blockedSpan1.end() + let condition2 = blockedSpan2.block(timeout: 1.0) + condition2?.upgrade() + blockedSpan2.end() + + BugsnagPerformance.startSpan(name: "ManualParentBlockedSpanScenarioChild").end() + parentSpan.end() + + condition1?.cancel() + condition2?.cancel() + } +} From 6f3ab08cc1e6305e46d6fdde0d29b05fb32d54f7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 9 Dec 2025 23:20:09 +0100 Subject: [PATCH 05/12] Bump github/codeql-action from 4.31.6 to 4.31.7 (#536) Bumps [github/codeql-action](https://github.com/github/codeql-action) from 4.31.6 to 4.31.7. - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/fe4161a26a8629af62121b670040955b330f9af2...cf1bb45a277cb3c205638b2cd5c984db1c46a412) --- updated-dependencies: - dependency-name: github/codeql-action dependency-version: 4.31.7 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/scorecard.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml index eb9453b9..ee7a2a81 100644 --- a/.github/workflows/scorecard.yml +++ b/.github/workflows/scorecard.yml @@ -68,6 +68,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard (optional). # Commenting out will disable upload of results to your repo's Code Scanning dashboard - name: "Upload to code-scanning" - uses: github/codeql-action/upload-sarif@fe4161a26a8629af62121b670040955b330f9af2 # v4.31.6 + uses: github/codeql-action/upload-sarif@cf1bb45a277cb3c205638b2cd5c984db1c46a412 # v4.31.7 with: sarif_file: results.sarif From 0d95e1e761818d992c6c98b48d6f54f540609329 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 9 Dec 2025 23:21:00 +0100 Subject: [PATCH 06/12] Bump actions/checkout from 6.0.0 to 6.0.1 (#535) Bumps [actions/checkout](https://github.com/actions/checkout) from 6.0.0 to 6.0.1. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/1af3b93b6815bc44a9784bd300feb67ff0d1eeb3...8e8c483db84b4bee98b60c0593521ed34d9990e8) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: 6.0.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/pull_request.yml | 4 ++-- .github/workflows/scorecard.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index 367d18b1..ff8e1a6e 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -10,13 +10,13 @@ jobs: runs-on: macos-latest steps: - name: Checkout target branch - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 with: ref: ${{ github.base_ref }} - name: Build run: xcodebuild -scheme BugsnagPerformance-iOS -destination generic/platform=iOS -configuration Release -quiet -derivedDataPath $PWD/DerivedData.old VALID_ARCHS=arm64 - name: Checkout pull request merge branch - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 with: clean: false fetch-depth: 100 diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml index ee7a2a81..f3f533eb 100644 --- a/.github/workflows/scorecard.yml +++ b/.github/workflows/scorecard.yml @@ -32,7 +32,7 @@ jobs: steps: - name: "Checkout code" - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 with: persist-credentials: false From ac8d49d958efe4209e334ac13aecab42a583c60e Mon Sep 17 00:00:00 2001 From: Robert Bartoszewski <126675445+robert-smartbear@users.noreply.github.com> Date: Wed, 10 Dec 2025 10:02:18 +0100 Subject: [PATCH 07/12] Fixed an issue where spans weren't sent when BugsnagPerformance has been started on a background thread (#539) Co-authored-by: Robert Bartoszewski --- BugsnagPerformance.xcodeproj/project.pbxproj | 12 +++++-- .../Private/BugsnagPerformanceImpl.mm | 7 ++-- .../Private/ConditionTimeoutExecutor.h | 3 +- .../Private/NSTimer+MainThread.h | 19 ++++++++++ .../Private/NSTimer+MainThread.m | 27 ++++++++++++++ features/default/manual_spans.feature | 10 +++++- .../ios/Fixture.xcodeproj/project.pbxproj | 4 +++ .../project.pbxproj | 4 +++ .../BackgroundThreadStartScenario.swift | 35 +++++++++++++++++++ 9 files changed, 114 insertions(+), 7 deletions(-) create mode 100644 Sources/BugsnagPerformance/Private/NSTimer+MainThread.h create mode 100644 Sources/BugsnagPerformance/Private/NSTimer+MainThread.m create mode 100644 features/fixtures/ios/Scenarios/BackgroundThreadStartScenario.swift diff --git a/BugsnagPerformance.xcodeproj/project.pbxproj b/BugsnagPerformance.xcodeproj/project.pbxproj index 1128022e..e42015ad 100644 --- a/BugsnagPerformance.xcodeproj/project.pbxproj +++ b/BugsnagPerformance.xcodeproj/project.pbxproj @@ -147,6 +147,8 @@ 96F129312DCD325E00A6FB2B /* BugsnagPerformanceSpanTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 96F129302DCD325E00A6FB2B /* BugsnagPerformanceSpanTests.mm */; }; 96F417152E3B8E7000EABD8E /* BugsnagPerformanceNamedSpans.h in Headers */ = {isa = PBXBuildFile; fileRef = 96F417122E3B8E7000EABD8E /* BugsnagPerformanceNamedSpans.h */; }; 96F417162E3B8E7000EABD8E /* BugsnagPerformanceNamedSpans.docc in Sources */ = {isa = PBXBuildFile; fileRef = 96F417132E3B8E7000EABD8E /* BugsnagPerformanceNamedSpans.docc */; }; + 96F9010F2EE817620026F5B9 /* NSTimer+MainThread.h in Headers */ = {isa = PBXBuildFile; fileRef = 96F9010E2EE8175B0026F5B9 /* NSTimer+MainThread.h */; }; + 96F901112EE817700026F5B9 /* NSTimer+MainThread.m in Sources */ = {isa = PBXBuildFile; fileRef = 96F901102EE817690026F5B9 /* NSTimer+MainThread.m */; }; CB04969729150D860097E526 /* OtlpPackage.h in Headers */ = {isa = PBXBuildFile; fileRef = CB04969529150D860097E526 /* OtlpPackage.h */; }; CB04969829150D860097E526 /* OtlpPackage.mm in Sources */ = {isa = PBXBuildFile; fileRef = CB04969629150D860097E526 /* OtlpPackage.mm */; }; CB04969B2915194E0097E526 /* OtlpUploader.h in Headers */ = {isa = PBXBuildFile; fileRef = CB0496992915194E0097E526 /* OtlpUploader.h */; }; @@ -486,6 +488,8 @@ 96F129302DCD325E00A6FB2B /* BugsnagPerformanceSpanTests.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = BugsnagPerformanceSpanTests.mm; sourceTree = ""; }; 96F417122E3B8E7000EABD8E /* BugsnagPerformanceNamedSpans.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = BugsnagPerformanceNamedSpans.h; sourceTree = ""; }; 96F417132E3B8E7000EABD8E /* BugsnagPerformanceNamedSpans.docc */ = {isa = PBXFileReference; lastKnownFileType = folder.documentationcatalog; path = BugsnagPerformanceNamedSpans.docc; sourceTree = ""; }; + 96F9010E2EE8175B0026F5B9 /* NSTimer+MainThread.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "NSTimer+MainThread.h"; sourceTree = ""; }; + 96F901102EE817690026F5B9 /* NSTimer+MainThread.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = "NSTimer+MainThread.m"; sourceTree = ""; }; CB04969529150D860097E526 /* OtlpPackage.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OtlpPackage.h; sourceTree = ""; }; CB04969629150D860097E526 /* OtlpPackage.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = OtlpPackage.mm; sourceTree = ""; }; CB0496992915194E0097E526 /* OtlpUploader.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OtlpUploader.h; sourceTree = ""; }; @@ -721,8 +725,6 @@ 0122C21F29019770002D243C /* Private */ = { isa = PBXGroup; children = ( - 1C7852A32E5F697D00BB8E2D /* SpanContext.mm */, - 1C7852A12E5F5B2E00BB8E2D /* SpanContext.h */, CB572EA829BB783200FD7A2A /* AppStateTracker.h */, CB572EA929BB783200FD7A2A /* AppStateTracker.m */, CBE8EA1C294B5E1500702950 /* Batch.h */, @@ -768,6 +770,8 @@ 098FC8772D3E8D43001B627D /* Metrics.h */, 09D59E182BDFE0D900199E1B /* NetworkHeaderInjector.h */, 09D59E192BDFE0D900199E1B /* NetworkHeaderInjector.mm */, + 96F9010E2EE8175B0026F5B9 /* NSTimer+MainThread.h */, + 96F901102EE817690026F5B9 /* NSTimer+MainThread.m */, CBB48A3A295EE1E10044E9AC /* ObjCUtils.h */, CBB48A3B295EE1E10044E9AC /* ObjCUtils.mm */, CB04969529150D860097E526 /* OtlpPackage.h */, @@ -795,6 +799,8 @@ 01A414CC2913C0F0003152A4 /* SpanAttributes.mm */, 96D4160D29F276FE00AEE435 /* SpanAttributesProvider.h */, 96D4160B29F276E400AEE435 /* SpanAttributesProvider.mm */, + 1C7852A12E5F5B2E00BB8E2D /* SpanContext.h */, + 1C7852A32E5F697D00BB8E2D /* SpanContext.mm */, 963726C42DEAB14D00C739E6 /* SpanControl */, 0122C22329019770002D243C /* SpanKind.h */, CB7FD935299D330500499E13 /* SpanOptions.h */, @@ -1179,6 +1185,7 @@ CBEBE59A29F671A800BF0B4F /* Instrumentation.h in Headers */, CBE8EA1E294B5E1500702950 /* Batch.h in Headers */, CBEBE59329F2783C00BF0B4F /* Swizzle.h in Headers */, + 96F9010F2EE817620026F5B9 /* NSTimer+MainThread.h in Headers */, CBEC51BC296D9EEE009C0CE3 /* PersistentState.h in Headers */, 0122C23829019770002D243C /* BugsnagPerformanceSpan.h in Headers */, 966634DA2C8A39B1004A934D /* FrozenFrameData.h in Headers */, @@ -1632,6 +1639,7 @@ CBE8EA1B294B5AB800702950 /* Worker.mm in Sources */, CB78819C29E587CE00A58906 /* BugsnagPerformanceLibrary.mm in Sources */, 963726E02DF0B4AD00C739E6 /* BugsnagPerformancePluginContext.m in Sources */, + 96F901112EE817700026F5B9 /* NSTimer+MainThread.m in Sources */, 098FC8552D37A08D001B627D /* SystemInfoSampler.mm in Sources */, 963726C82DEAB1FC00C739E6 /* BugsnagPerformancePriority.m in Sources */, 0122C23C29019770002D243C /* BugsnagPerformanceConfiguration.mm in Sources */, diff --git a/Sources/BugsnagPerformance/Private/BugsnagPerformanceImpl.mm b/Sources/BugsnagPerformance/Private/BugsnagPerformanceImpl.mm index 71bf455d..e23fd71d 100644 --- a/Sources/BugsnagPerformance/Private/BugsnagPerformanceImpl.mm +++ b/Sources/BugsnagPerformance/Private/BugsnagPerformanceImpl.mm @@ -18,6 +18,7 @@ #import "FrameRateMetrics/FrameMetricsCollector.h" #import "ConditionTimeoutExecutor.h" #import "BugsnagPerformanceSpan+Private.h" +#import "NSTimer+MainThread.h" using namespace bugsnag; @@ -246,9 +247,9 @@ [worker_ start]; [frameMetricsCollector_ start]; - workerTimer_ = [NSTimer scheduledTimerWithTimeInterval:performWorkInterval_ - repeats:YES - block:^(__unused NSTimer * _Nonnull timer) { + workerTimer_ = [NSTimer mainThreadTimerWithTimeInterval:performWorkInterval_ + repeats:YES + block:^(__unused NSTimer * _Nonnull timer) { blockThis->onWorkInterval(); }]; diff --git a/Sources/BugsnagPerformance/Private/ConditionTimeoutExecutor.h b/Sources/BugsnagPerformance/Private/ConditionTimeoutExecutor.h index 4984c535..9562800b 100644 --- a/Sources/BugsnagPerformance/Private/ConditionTimeoutExecutor.h +++ b/Sources/BugsnagPerformance/Private/ConditionTimeoutExecutor.h @@ -10,6 +10,7 @@ #import #import "BugsnagPerformanceSpanCondition+Private.h" +#import "NSTimer+MainThread.h" #import #import @@ -21,7 +22,7 @@ class ConditionTimeoutExecutor { void sheduleTimeout(BugsnagPerformanceSpanCondition *condition, NSTimeInterval timeout) noexcept { std::lock_guard guard(mutex_); - this->conditionIdToTimer_[condition.conditionId] = [NSTimer scheduledTimerWithTimeInterval:timeout repeats:NO block:^(NSTimer *) { + this->conditionIdToTimer_[condition.conditionId] = [NSTimer mainThreadTimerWithTimeInterval:timeout repeats:NO block:^(NSTimer *) { [condition didTimeout]; }]; } diff --git a/Sources/BugsnagPerformance/Private/NSTimer+MainThread.h b/Sources/BugsnagPerformance/Private/NSTimer+MainThread.h new file mode 100644 index 00000000..7d5fa989 --- /dev/null +++ b/Sources/BugsnagPerformance/Private/NSTimer+MainThread.h @@ -0,0 +1,19 @@ +// +// NSTimer+MainThread.h +// BugsnagPerformance +// +// Created by Robert Bartoszewski on 09/12/2025. +// Copyright © 2025 Bugsnag. All rights reserved. +// + +#import + +@interface NSTimer (MainThread) + +/// Creates and returns a new NSTimer object initialized with the specified block object and schedules it on the main run loop in the default mode. +/// - parameter: ti The number of seconds between firings of the timer. If seconds is less than or equal to 0.0, this method chooses the nonnegative value of 0.1 milliseconds instead +/// - parameter: repeats If YES, the timer will repeatedly reschedule itself until invalidated. If NO, the timer will be invalidated after it fires. +/// - parameter: block The execution body of the timer; the timer itself is passed as the parameter to this block when executed to aid in avoiding cyclical references ++ (NSTimer *)mainThreadTimerWithTimeInterval:(NSTimeInterval)interval repeats:(BOOL)repeats block:(void (NS_SWIFT_SENDABLE ^)(NSTimer *timer))block API_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0)); + +@end diff --git a/Sources/BugsnagPerformance/Private/NSTimer+MainThread.m b/Sources/BugsnagPerformance/Private/NSTimer+MainThread.m new file mode 100644 index 00000000..62eebc7b --- /dev/null +++ b/Sources/BugsnagPerformance/Private/NSTimer+MainThread.m @@ -0,0 +1,27 @@ +// +// NSTimer+MainThread.m +// BugsnagPerformance +// +// Created by Robert Bartoszewski on 09/12/2025. +// Copyright © 2025 Bugsnag. All rights reserved. +// + +#import "NSTimer+MainThread.h" + +@implementation NSTimer (MainThread) + ++ (NSTimer *)mainThreadTimerWithTimeInterval:(NSTimeInterval)interval repeats:(BOOL)repeats block:(void (NS_SWIFT_SENDABLE ^)(NSTimer *timer))block { + NSTimer *timer = [self timerWithTimeInterval:interval repeats:repeats block:block]; + if ([NSThread isMainThread]) { + [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes]; + } else { + dispatch_async(dispatch_get_main_queue(), ^{ + if ([timer isValid]) { + [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode]; + } + }); + } + return timer; +} + +@end diff --git a/features/default/manual_spans.feature b/features/default/manual_spans.feature index b5b9403c..0b21d12c 100644 --- a/features/default/manual_spans.feature +++ b/features/default/manual_spans.feature @@ -568,6 +568,15 @@ Feature: Manual creation of spans * the trace payload field "resourceSpans.0.scopeSpans.0.spans.1.parentSpanId" matches the regex "^[A-Fa-f0-9]{16}$" * the trace payload field "resourceSpans.0.scopeSpans.0.spans.2.parentSpanId" is null +Scenario: Manually start and end spans after starting BugsnagPerformance on a background thread + Given I run "BackgroundThreadStartScenario" + And I wait to receive at least 2 spans + Then the trace "Content-Type" header equals "application/json" + * the trace "Bugsnag-Integrity" header matches the regex "^sha1 [A-Fa-f0-9]{40}$" + * the trace "Bugsnag-Sent-At" header matches the regex "^\d\d\d\d-\d\d-\d\dT\d\d:\d\d:\d\d\.\d\d\dZ$" + * a span field "name" equals "BackgroundThreadStartScenarioEarlySpan" + * a span field "name" equals "BackgroundThreadStartScenarioSpan" + Scenario: Parent context should be calculated despite blocked spans on the stack Given I run "ManualParentBlockedSpanScenario" And I wait to receive at least 4 spans @@ -581,4 +590,3 @@ Feature: Manual creation of spans * a span named "ManualParentBlockedSpanScenarioBlocked1" is a child of span named "ManualParentBlockedSpanScenarioParent" * a span named "ManualParentBlockedSpanScenarioBlocked2" is a child of span named "ManualParentBlockedSpanScenarioBlocked1" * a span named "ManualParentBlockedSpanScenarioChild" is a child of span named "ManualParentBlockedSpanScenarioParent" - diff --git a/features/fixtures/ios/Fixture.xcodeproj/project.pbxproj b/features/fixtures/ios/Fixture.xcodeproj/project.pbxproj index a0249a51..1d4202af 100644 --- a/features/fixtures/ios/Fixture.xcodeproj/project.pbxproj +++ b/features/fixtures/ios/Fixture.xcodeproj/project.pbxproj @@ -81,6 +81,7 @@ 96DADF512EAFCB1100B56CE6 /* ManualSpanEndOnDestroyScenario.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96DADF502EAFCB1100B56CE6 /* ManualSpanEndOnDestroyScenario.swift */; }; 96F129352DCE0CFE00A6FB2B /* ManualSpanWithRemoteContextParentScenario.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96F129342DCE0CFE00A6FB2B /* ManualSpanWithRemoteContextParentScenario.swift */; }; 96F5268C2C259E4E0095D600 /* ManualNetworkSpanCallbackSetToNilScenario.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96F5268B2C259E4E0095D600 /* ManualNetworkSpanCallbackSetToNilScenario.swift */; }; + 96F901132EE81E580026F5B9 /* BackgroundThreadStartScenario.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96F901122EE81E580026F5B9 /* BackgroundThreadStartScenario.swift */; }; 96F901172EE836CC0026F5B9 /* ManualParentBlockedSpanScenario.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96F901162EE836CC0026F5B9 /* ManualParentBlockedSpanScenario.swift */; }; 96F901092EE7B7FF0026F5B9 /* AutoInstrumentNetworkEarlyCallbackScenario.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96F901082EE7B7FF0026F5B9 /* AutoInstrumentNetworkEarlyCallbackScenario.swift */; }; CB0496942913CA300097E526 /* BatchingWithTimeoutScenario.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB0496932913CA300097E526 /* BatchingWithTimeoutScenario.swift */; }; @@ -188,6 +189,7 @@ 96DADF572EAFCE6D00B56CE6 /* BugsnagPerformanceSpan+Internal.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "BugsnagPerformanceSpan+Internal.h"; sourceTree = ""; }; 96F129342DCE0CFE00A6FB2B /* ManualSpanWithRemoteContextParentScenario.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ManualSpanWithRemoteContextParentScenario.swift; sourceTree = ""; }; 96F5268B2C259E4E0095D600 /* ManualNetworkSpanCallbackSetToNilScenario.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ManualNetworkSpanCallbackSetToNilScenario.swift; sourceTree = ""; }; + 96F901122EE81E580026F5B9 /* BackgroundThreadStartScenario.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackgroundThreadStartScenario.swift; sourceTree = ""; }; 96F901162EE836CC0026F5B9 /* ManualParentBlockedSpanScenario.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ManualParentBlockedSpanScenario.swift; sourceTree = ""; }; 96F901082EE7B7FF0026F5B9 /* AutoInstrumentNetworkEarlyCallbackScenario.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutoInstrumentNetworkEarlyCallbackScenario.swift; sourceTree = ""; }; CB0496932913CA300097E526 /* BatchingWithTimeoutScenario.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BatchingWithTimeoutScenario.swift; sourceTree = ""; }; @@ -321,6 +323,7 @@ 9657A8982A3CF75B001CEF5D /* AutoInstrumentTabViewLoadScenario.swift */, 0185C47128F6C983006F9BDC /* AutoInstrumentViewLoadScenario.swift */, CB572EAC29BB829800FD7A2A /* BackgroundForegroundScenario.swift */, + 96F901122EE81E580026F5B9 /* BackgroundThreadStartScenario.swift */, CBAAE2582912601D006D4AA0 /* BatchingScenario.swift */, CB0496932913CA300097E526 /* BatchingWithTimeoutScenario.swift */, 09301DC02B63A65A000A7C12 /* ComplexViewScenario.swift */, @@ -540,6 +543,7 @@ CB572EAD29BB829800FD7A2A /* BackgroundForegroundScenario.swift in Sources */, 966634E02C9DE384004A934D /* FrameMetricsSlowFramesScenario.swift in Sources */, CB2B8A9F2A0E80B80054FBBE /* AutoInstrumentNetworkBadAddressScenario.swift in Sources */, + 96F901132EE81E580026F5B9 /* BackgroundThreadStartScenario.swift in Sources */, DA58B7D62DF87EC500CB80A4 /* PluginInstallErrorScenario.swift in Sources */, 09F025152BAC50EC007D9F73 /* ViewDidLoadDoesntTriggerScenario.swift in Sources */, 0988B5372CAD32C500D131B1 /* InfraCheckNoBugsnagScenario.swift in Sources */, diff --git a/features/fixtures/ios/FixtureXcFramework.xcodeproj/project.pbxproj b/features/fixtures/ios/FixtureXcFramework.xcodeproj/project.pbxproj index acef935d..ed6185a1 100644 --- a/features/fixtures/ios/FixtureXcFramework.xcodeproj/project.pbxproj +++ b/features/fixtures/ios/FixtureXcFramework.xcodeproj/project.pbxproj @@ -80,6 +80,7 @@ 96E0B34B2CD0E21C008AEB9C /* AutoInstrumentNetworkSharedSessionInvalidateScenario.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96E0B34A2CD0E21C008AEB9C /* AutoInstrumentNetworkSharedSessionInvalidateScenario.swift */; }; 96F129332DCE0CDD00A6FB2B /* RenderingMetricsScenario.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96F129322DCE0CDD00A6FB2B /* RenderingMetricsScenario.swift */; }; 96F5268C2C259E4E0095D600 /* ManualNetworkSpanCallbackSetToNilScenario.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96F5268B2C259E4E0095D600 /* ManualNetworkSpanCallbackSetToNilScenario.swift */; }; + 96F901152EE81E6D0026F5B9 /* BackgroundThreadStartScenario.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96F901142EE81E6D0026F5B9 /* BackgroundThreadStartScenario.swift */; }; 96F901192EE836E10026F5B9 /* ManualParentBlockedSpanScenario.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96F901182EE836E10026F5B9 /* ManualParentBlockedSpanScenario.swift */; }; 96F9010B2EE7B81F0026F5B9 /* AutoInstrumentNetworkEarlyCallbackScenario.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96F9010A2EE7B81F0026F5B9 /* AutoInstrumentNetworkEarlyCallbackScenario.swift */; }; CB0496942913CA300097E526 /* BatchingWithTimeoutScenario.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB0496932913CA300097E526 /* BatchingWithTimeoutScenario.swift */; }; @@ -203,6 +204,7 @@ 96E0B34A2CD0E21C008AEB9C /* AutoInstrumentNetworkSharedSessionInvalidateScenario.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AutoInstrumentNetworkSharedSessionInvalidateScenario.swift; sourceTree = ""; }; 96F129322DCE0CDD00A6FB2B /* RenderingMetricsScenario.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RenderingMetricsScenario.swift; sourceTree = ""; }; 96F5268B2C259E4E0095D600 /* ManualNetworkSpanCallbackSetToNilScenario.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ManualNetworkSpanCallbackSetToNilScenario.swift; sourceTree = ""; }; + 96F901142EE81E6D0026F5B9 /* BackgroundThreadStartScenario.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackgroundThreadStartScenario.swift; sourceTree = ""; }; 96F901182EE836E10026F5B9 /* ManualParentBlockedSpanScenario.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ManualParentBlockedSpanScenario.swift; sourceTree = ""; }; 96F9010A2EE7B81F0026F5B9 /* AutoInstrumentNetworkEarlyCallbackScenario.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutoInstrumentNetworkEarlyCallbackScenario.swift; sourceTree = ""; }; CB0496932913CA300097E526 /* BatchingWithTimeoutScenario.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BatchingWithTimeoutScenario.swift; sourceTree = ""; }; @@ -339,6 +341,7 @@ 9657A8982A3CF75B001CEF5D /* AutoInstrumentTabViewLoadScenario.swift */, 0185C47128F6C983006F9BDC /* AutoInstrumentViewLoadScenario.swift */, CB572EAC29BB829800FD7A2A /* BackgroundForegroundScenario.swift */, + 96F901142EE81E6D0026F5B9 /* BackgroundThreadStartScenario.swift */, CBAAE2582912601D006D4AA0 /* BatchingScenario.swift */, CB0496932913CA300097E526 /* BatchingWithTimeoutScenario.swift */, 09301DC02B63A65A000A7C12 /* ComplexViewScenario.swift */, @@ -537,6 +540,7 @@ 96F901192EE836E10026F5B9 /* ManualParentBlockedSpanScenario.swift in Sources */, 9691A9E12CA7588700707CDF /* FrameMetricsNonFirstClassSpanInstrumentRenderingOnScenario.swift in Sources */, 09F3F52C2D6C72BD00BAA0A3 /* MemoryMetricsScenario.swift in Sources */, + 96F901152EE81E6D0026F5B9 /* BackgroundThreadStartScenario.swift in Sources */, 09637A3F2B06082200F4F776 /* Logging.m in Sources */, CB3477182901481F0033759C /* AutoInstrumentNetworkWithParentScenario.swift in Sources */, 091B95742CA18F66007DC8A9 /* AutoInstrumentNetworkPreStartDisabledScenario.swift in Sources */, diff --git a/features/fixtures/ios/Scenarios/BackgroundThreadStartScenario.swift b/features/fixtures/ios/Scenarios/BackgroundThreadStartScenario.swift new file mode 100644 index 00000000..138592a2 --- /dev/null +++ b/features/fixtures/ios/Scenarios/BackgroundThreadStartScenario.swift @@ -0,0 +1,35 @@ +// +// BackgroundThreadStartScenario.swift +// Fixture +// +// Created by Robert Bartoszewski on 09/12/2025. +// + +import BugsnagPerformance + +@objcMembers +class BackgroundThreadStartScenario: Scenario { + + override func setInitialBugsnagConfiguration() { + super.setInitialBugsnagConfiguration() + + // Ensure the batch doesn't get full, as we want the spans to be delivered due to reaching work interval + bugsnagPerfConfig.internal.autoTriggerExportOnBatchSize = 100 + bugsnagPerfConfig.internal.performWorkInterval = 5.0 + } + + override func startBugsnag() { + BugsnagPerformance.startSpan(name: "BackgroundThreadStartScenarioEarlySpan").end() + DispatchQueue + .global(qos: .background) + .asyncAfter(deadline: .now() + 2.0) { + super.startBugsnag() + } + } + + override func run() { + DispatchQueue.main.asyncAfter(deadline: .now() + 3.0) { + BugsnagPerformance.startSpan(name: "BackgroundThreadStartScenarioSpan").end() + } + } +} From 97a8729a8c946511dac8802be1184d84e3fef01c Mon Sep 17 00:00:00 2001 From: Robert Bartoszewski Date: Wed, 10 Dec 2025 16:09:46 +0100 Subject: [PATCH 08/12] Removed commented out code --- .../Lifecycle/ViewLoadLifecycleHandlerImpl.mm | 8 -------- 1 file changed, 8 deletions(-) diff --git a/Sources/BugsnagPerformance/Private/Instrumentation/ViewLoadInstrumentation/Lifecycle/ViewLoadLifecycleHandlerImpl.mm b/Sources/BugsnagPerformance/Private/Instrumentation/ViewLoadInstrumentation/Lifecycle/ViewLoadLifecycleHandlerImpl.mm index b33b495a..9ba452f4 100644 --- a/Sources/BugsnagPerformance/Private/Instrumentation/ViewLoadInstrumentation/Lifecycle/ViewLoadLifecycleHandlerImpl.mm +++ b/Sources/BugsnagPerformance/Private/Instrumentation/ViewLoadInstrumentation/Lifecycle/ViewLoadLifecycleHandlerImpl.mm @@ -167,14 +167,6 @@ loadingIndicatorsHandler_->onLoadingIndicatorWasAdded(loadingIndicator); } -//void -//ViewLoadLifecycleHandlerImpl::onLoadingIndicatorWasRemoved(BugsnagPerformanceLoadingIndicatorView *loadingIndicator) noexcept { -// if (loadingIndicator == nil) { -// return; -// } -// loadingIndicatorsHandler_->onLoadingIndicatorWasRemoved(loadingIndicator); -//} - #pragma mark Helpers void From 18684de8fb52bf70454623e366d051e0c1b850f2 Mon Sep 17 00:00:00 2001 From: Robert Bartoszewski Date: Wed, 10 Dec 2025 23:06:33 +0100 Subject: [PATCH 09/12] Fixed an issue causing viewLoad phase child spans being wrongly attributed to viewDataLoading phase --- .../ViewLoadLoadingIndicatorState.h | 2 +- .../ViewLoadLoadingIndicatorState.mm | 10 +++ .../ViewLoadLoadingIndicatorsHandler.h | 1 + .../ViewLoadLoadingIndicatorsHandlerImpl.h | 5 ++ .../ViewLoadLoadingIndicatorsHandlerImpl.mm | 69 +++++++++++-------- .../Lifecycle/ViewLoadLifecycleHandlerImpl.mm | 7 +- .../State/ViewLoadInstrumentationState.h | 1 + features/default/loading_indicator.feature | 12 ++++ 8 files changed, 75 insertions(+), 32 deletions(-) diff --git a/Sources/BugsnagPerformance/Private/Instrumentation/ViewLoadInstrumentation/Lifecycle/LoadingIndicators/ViewLoadLoadingIndicatorState.h b/Sources/BugsnagPerformance/Private/Instrumentation/ViewLoadInstrumentation/Lifecycle/LoadingIndicators/ViewLoadLoadingIndicatorState.h index 2be2bc3f..299e7115 100644 --- a/Sources/BugsnagPerformance/Private/Instrumentation/ViewLoadInstrumentation/Lifecycle/LoadingIndicators/ViewLoadLoadingIndicatorState.h +++ b/Sources/BugsnagPerformance/Private/Instrumentation/ViewLoadInstrumentation/Lifecycle/LoadingIndicators/ViewLoadLoadingIndicatorState.h @@ -11,7 +11,7 @@ @interface ViewLoadLoadingIndicatorState : NSObject -@property(nonatomic, strong) NSArray *conditions; +@property(nonatomic, strong) NSMutableArray *conditions; @property(nonatomic, strong) BugsnagPerformanceSpan *loadingIndicatorSpan; @property(nonatomic) BOOL needsSpanUpdate; diff --git a/Sources/BugsnagPerformance/Private/Instrumentation/ViewLoadInstrumentation/Lifecycle/LoadingIndicators/ViewLoadLoadingIndicatorState.mm b/Sources/BugsnagPerformance/Private/Instrumentation/ViewLoadInstrumentation/Lifecycle/LoadingIndicators/ViewLoadLoadingIndicatorState.mm index a4df3bc3..25f9b9a6 100644 --- a/Sources/BugsnagPerformance/Private/Instrumentation/ViewLoadInstrumentation/Lifecycle/LoadingIndicators/ViewLoadLoadingIndicatorState.mm +++ b/Sources/BugsnagPerformance/Private/Instrumentation/ViewLoadInstrumentation/Lifecycle/LoadingIndicators/ViewLoadLoadingIndicatorState.mm @@ -9,4 +9,14 @@ #import "ViewLoadLoadingIndicatorState.h" @implementation ViewLoadLoadingIndicatorState + +- (instancetype)init +{ + self = [super init]; + if (self) { + _conditions = [NSMutableArray array]; + } + return self; +} + @end diff --git a/Sources/BugsnagPerformance/Private/Instrumentation/ViewLoadInstrumentation/Lifecycle/LoadingIndicators/ViewLoadLoadingIndicatorsHandler.h b/Sources/BugsnagPerformance/Private/Instrumentation/ViewLoadInstrumentation/Lifecycle/LoadingIndicators/ViewLoadLoadingIndicatorsHandler.h index c5d5728b..8d789ec3 100644 --- a/Sources/BugsnagPerformance/Private/Instrumentation/ViewLoadInstrumentation/Lifecycle/LoadingIndicators/ViewLoadLoadingIndicatorsHandler.h +++ b/Sources/BugsnagPerformance/Private/Instrumentation/ViewLoadInstrumentation/Lifecycle/LoadingIndicators/ViewLoadLoadingIndicatorsHandler.h @@ -19,6 +19,7 @@ class ViewLoadLoadingIndicatorsHandler { public: virtual void onLoadingIndicatorWasAdded(BugsnagPerformanceLoadingIndicatorView *loadingIndicator) noexcept = 0; virtual void onViewControllerUpdatedView(UIViewController *viewController) noexcept = 0; + virtual void onViewControllerDidAppear(UIViewController *viewController) noexcept = 0; virtual void setCallbacks(ViewLoadLoadingIndicatorsHandlerCallbacks *callbacks) noexcept = 0; virtual ~ViewLoadLoadingIndicatorsHandler() {} }; diff --git a/Sources/BugsnagPerformance/Private/Instrumentation/ViewLoadInstrumentation/Lifecycle/LoadingIndicators/ViewLoadLoadingIndicatorsHandlerImpl.h b/Sources/BugsnagPerformance/Private/Instrumentation/ViewLoadInstrumentation/Lifecycle/LoadingIndicators/ViewLoadLoadingIndicatorsHandlerImpl.h index cb570163..25cf3895 100644 --- a/Sources/BugsnagPerformance/Private/Instrumentation/ViewLoadInstrumentation/Lifecycle/LoadingIndicators/ViewLoadLoadingIndicatorsHandlerImpl.h +++ b/Sources/BugsnagPerformance/Private/Instrumentation/ViewLoadInstrumentation/Lifecycle/LoadingIndicators/ViewLoadLoadingIndicatorsHandlerImpl.h @@ -27,6 +27,7 @@ class ViewLoadLoadingIndicatorsHandlerImpl: public ViewLoadLoadingIndicatorsHand void onLoadingIndicatorWasAdded(BugsnagPerformanceLoadingIndicatorView *loadingIndicator) noexcept; void onViewControllerUpdatedView(UIViewController *viewController) noexcept; + void onViewControllerDidAppear(UIViewController *viewController) noexcept; void setCallbacks(ViewLoadLoadingIndicatorsHandlerCallbacks *callbacks) noexcept { callbacks_ = callbacks; } @@ -39,6 +40,10 @@ class ViewLoadLoadingIndicatorsHandlerImpl: public ViewLoadLoadingIndicatorsHand void updateIndicatorsState(BugsnagPerformanceLoadingIndicatorView *loadingIndicator, ViewLoadLoadingIndicatorState *state) noexcept; ViewLoadLoadingIndicatorState *newState(BugsnagPerformanceLoadingIndicatorView *loadingIndicator) noexcept; + void addToState(ViewLoadLoadingIndicatorState *state, + BugsnagPerformanceLoadingIndicatorView *loadingIndicator, + ViewLoadInstrumentationState *viewLoadState, + BOOL isFirstViewController) noexcept; void updateLoadingIndicators(UIView *view) noexcept; bool checkNeedsSpanUpdate(BugsnagPerformanceLoadingIndicatorView *loadingIndicator, BugsnagPerformanceSpanContext *parentContext) noexcept; diff --git a/Sources/BugsnagPerformance/Private/Instrumentation/ViewLoadInstrumentation/Lifecycle/LoadingIndicators/ViewLoadLoadingIndicatorsHandlerImpl.mm b/Sources/BugsnagPerformance/Private/Instrumentation/ViewLoadInstrumentation/Lifecycle/LoadingIndicators/ViewLoadLoadingIndicatorsHandlerImpl.mm index c59ba16e..3ba3bea7 100644 --- a/Sources/BugsnagPerformance/Private/Instrumentation/ViewLoadInstrumentation/Lifecycle/LoadingIndicators/ViewLoadLoadingIndicatorsHandlerImpl.mm +++ b/Sources/BugsnagPerformance/Private/Instrumentation/ViewLoadInstrumentation/Lifecycle/LoadingIndicators/ViewLoadLoadingIndicatorsHandlerImpl.mm @@ -29,6 +29,12 @@ updateLoadingIndicators(viewController.view); } +void +ViewLoadLoadingIndicatorsHandlerImpl::onViewControllerDidAppear(UIViewController *viewController) noexcept { + std::lock_guard guard(mutex_); + updateLoadingIndicators(viewController.view); +} + #pragma mark Helpers void @@ -44,45 +50,54 @@ ViewLoadLoadingIndicatorState * ViewLoadLoadingIndicatorsHandlerImpl::newState(BugsnagPerformanceLoadingIndicatorView *loadingIndicator) noexcept { NSMutableArray *newConditions = [NSMutableArray array]; - auto needsSpanUpdate = false; auto hasFoundFirstViewController = false; - BugsnagPerformanceSpan *loadingIndicatorSpan; + auto state = [ViewLoadLoadingIndicatorState new]; UIView *view = loadingIndicator; while (view != nil) { - ViewLoadInstrumentationState *state = repository_->getInstrumentationState(view); - __strong UIViewController *viewController = state.viewController; - if (state != nil && - state.overallSpan.isValid && - viewController != nil) { - - if (callbacks_.onLoading) { - BugsnagPerformanceSpanCondition *condition = callbacks_.onLoading(viewController); - if (condition != nil) { - [newConditions addObject:condition]; - } - } - if (callbacks_.getParentContext && - !hasFoundFirstViewController && - loadingIndicator.name != nil) { - BugsnagPerformanceSpanContext *parentContext = callbacks_.getParentContext(viewController); - needsSpanUpdate = checkNeedsSpanUpdate(loadingIndicator, parentContext); - if (parentContext && needsSpanUpdate) { - loadingIndicatorSpan = spanFactory_->startLoadingIndicatorSpan(loadingIndicator.name, parentContext); - } - } + ViewLoadInstrumentationState *viewLoadState = repository_->getInstrumentationState(view); + if (viewLoadState.overallSpan.isValid && viewLoadState.viewController != nil) { + addToState(state, + loadingIndicator, + viewLoadState, + !hasFoundFirstViewController); hasFoundFirstViewController = true; } + view = view.superview; } - auto state = [ViewLoadLoadingIndicatorState new]; - state.conditions = newConditions; - state.loadingIndicatorSpan = loadingIndicatorSpan; - state.needsSpanUpdate = needsSpanUpdate; return state; } +void +ViewLoadLoadingIndicatorsHandlerImpl::addToState(ViewLoadLoadingIndicatorState *state, + BugsnagPerformanceLoadingIndicatorView *loadingIndicator, + ViewLoadInstrumentationState *viewLoadState, + BOOL isFirstViewController) noexcept { + __strong UIViewController *viewController = viewLoadState.viewController; + + if (viewController == nil || !viewLoadState.hasAppeared) { + return; + } + if (callbacks_.onLoading) { + BugsnagPerformanceSpanCondition *condition = callbacks_.onLoading(viewController); + if (condition != nil) { + [state.conditions addObject:condition]; + } + } + if (callbacks_.getParentContext && + isFirstViewController && + loadingIndicator.name != nil) { + BugsnagPerformanceSpanContext *parentContext = callbacks_.getParentContext(viewController); + BOOL needsSpanUpdate = checkNeedsSpanUpdate(loadingIndicator, parentContext); + if (parentContext && needsSpanUpdate) { + state.needsSpanUpdate = needsSpanUpdate; + state.loadingIndicatorSpan = spanFactory_->startLoadingIndicatorSpan(loadingIndicator.name, parentContext); + } + } +} + void ViewLoadLoadingIndicatorsHandlerImpl::updateLoadingIndicators(UIView *view) noexcept { if ([view isKindOfClass:[BugsnagPerformanceLoadingIndicatorView class]]) { diff --git a/Sources/BugsnagPerformance/Private/Instrumentation/ViewLoadInstrumentation/Lifecycle/ViewLoadLifecycleHandlerImpl.mm b/Sources/BugsnagPerformance/Private/Instrumentation/ViewLoadInstrumentation/Lifecycle/ViewLoadLifecycleHandlerImpl.mm index 9ba452f4..0f9a7575 100644 --- a/Sources/BugsnagPerformance/Private/Instrumentation/ViewLoadInstrumentation/Lifecycle/ViewLoadLifecycleHandlerImpl.mm +++ b/Sources/BugsnagPerformance/Private/Instrumentation/ViewLoadInstrumentation/Lifecycle/ViewLoadLifecycleHandlerImpl.mm @@ -96,7 +96,9 @@ state.overallSpan); originalImplementation(); [state.viewDidAppearSpan end]; + state.hasAppeared = true; updateViewIfNeeded(state, viewController); + loadingIndicatorsHandler_->onViewControllerDidAppear(viewController); endOverallSpan(state, viewController, CFAbsoluteTimeGetCurrent()); } @@ -178,10 +180,7 @@ BugsnagPerformanceSpan *overallSpan = state.overallSpan; [crosstalkAPI_ willEndViewLoadSpan:overallSpan viewController:viewController]; - if (state.loadingPhaseSpan != nil) { - // Adjust span start time to reflect the view appearing time - [state.loadingPhaseSpan updateStartTime:[NSDate date]]; - } else { + if (state.loadingPhaseSpan == nil) { [state.overallSpan blockWithTimeout:kLoadingBlockTimeout]; } diff --git a/Sources/BugsnagPerformance/Private/Instrumentation/ViewLoadInstrumentation/State/ViewLoadInstrumentationState.h b/Sources/BugsnagPerformance/Private/Instrumentation/ViewLoadInstrumentation/State/ViewLoadInstrumentationState.h index 2fa4e0f5..8aca4acb 100644 --- a/Sources/BugsnagPerformance/Private/Instrumentation/ViewLoadInstrumentation/State/ViewLoadInstrumentationState.h +++ b/Sources/BugsnagPerformance/Private/Instrumentation/ViewLoadInstrumentation/State/ViewLoadInstrumentationState.h @@ -18,6 +18,7 @@ typedef void (^ ViewLoadInstrumentationStateOnDeallocCallback)(ViewLoadInstrumen @interface ViewLoadInstrumentationState : NSObject @property (nonatomic) BOOL isMarkedAsPreloaded; +@property (nonatomic) BOOL hasAppeared; @property (nonatomic, nullable, weak) UIViewController *viewController; @property (nonatomic, nullable, weak) UIView *view; @property (nonatomic, nullable, strong) BugsnagPerformanceSpan *overallSpan; diff --git a/features/default/loading_indicator.feature b/features/default/loading_indicator.feature index f4dadf47..02d23c27 100644 --- a/features/default/loading_indicator.feature +++ b/features/default/loading_indicator.feature @@ -27,6 +27,7 @@ Feature: LoadingIndicator view to mark data loading phase * a span named "[ViewLoadPhase/viewDataLoading]/Fixture.LoadingIndicatorViewSimpleStopScenario_ViewController" is a child of span named "[ViewLoad/UIKit]/Fixture.LoadingIndicatorViewSimpleStopScenario_ViewController" * a span named "[ViewLoad/UIKit]/Fixture.LoadingIndicatorViewSimpleStopScenario_ViewController" ended at the same time as a span named "[ViewLoadPhase/viewDataLoading]/Fixture.LoadingIndicatorViewSimpleStopScenario_ViewController" * a span named "[ViewLoadPhase/viewDataLoading]/Fixture.LoadingIndicatorViewSimpleStopScenario_ViewController" duration is equal or greater than 2.0 + * a span named "[ViewLoadPhase/viewDidAppear]/Fixture.LoadingIndicatorViewSimpleStopScenario_ViewController" ended before a span named "[ViewLoadPhase/viewDataLoading]/Fixture.LoadingIndicatorViewSimpleStopScenario_ViewController" started * every span field "kind" equals 1 * a span string attribute "bugsnag.span.category" equals "view_load" * a span string attribute "bugsnag.view.name" equals "Fixture.LoadingIndicatorViewSimpleStopScenario_ViewController" @@ -69,6 +70,8 @@ Feature: LoadingIndicator view to mark data loading phase * a span named "[ViewLoad/UIKit]/Fixture.LoadingIndicatorViewSimpleStopScenario_ViewController" ended at the same time as a span named "[ViewLoadPhase/viewDataLoading]/Fixture.LoadingIndicatorViewSimpleStopScenario_ViewController" * a span named "[ViewLoadPhase/viewDataLoading]/Fixture.LoadingIndicatorViewSimpleStopScenario_ViewController" duration is equal or greater than 2.0 * a span named "SimpleStopScenarioIndicatorName" duration is equal or greater than 2.0 + * a span named "[ViewLoadPhase/viewDidAppear]/Fixture.LoadingIndicatorViewSimpleStopScenario_ViewController" ended before a span named "[ViewLoadPhase/viewDataLoading]/Fixture.LoadingIndicatorViewSimpleStopScenario_ViewController" started + * a span named "[ViewLoadPhase/viewDidAppear]/Fixture.LoadingIndicatorViewSimpleStopScenario_ViewController" ended before a span named "SimpleStopScenarioIndicatorName" started * every span field "kind" equals 1 * a span string attribute "bugsnag.span.category" equals "view_load" * a span string attribute "bugsnag.view.name" equals "Fixture.LoadingIndicatorViewSimpleStopScenario_ViewController" @@ -105,6 +108,7 @@ Feature: LoadingIndicator view to mark data loading phase * a span named "[ViewLoadPhase/viewDataLoading]/Fixture.LoadingIndicatorViewSimpleRemoveScenario_ViewController" is a child of span named "[ViewLoad/UIKit]/Fixture.LoadingIndicatorViewSimpleRemoveScenario_ViewController" * a span named "[ViewLoad/UIKit]/Fixture.LoadingIndicatorViewSimpleRemoveScenario_ViewController" ended at the same time as a span named "[ViewLoadPhase/viewDataLoading]/Fixture.LoadingIndicatorViewSimpleRemoveScenario_ViewController" * a span named "[ViewLoadPhase/viewDataLoading]/Fixture.LoadingIndicatorViewSimpleRemoveScenario_ViewController" duration is equal or greater than 2.0 + * a span named "[ViewLoadPhase/viewDidAppear]/Fixture.LoadingIndicatorViewSimpleRemoveScenario_ViewController" ended before a span named "[ViewLoadPhase/viewDataLoading]/Fixture.LoadingIndicatorViewSimpleRemoveScenario_ViewController" started * every span field "kind" equals 1 * a span string attribute "bugsnag.span.category" equals "view_load" * a span string attribute "bugsnag.view.name" equals "Fixture.LoadingIndicatorViewSimpleRemoveScenario_ViewController" @@ -147,6 +151,8 @@ Feature: LoadingIndicator view to mark data loading phase * a span named "[ViewLoad/UIKit]/Fixture.LoadingIndicatorViewSimpleRemoveScenario_ViewController" ended at the same time as a span named "[ViewLoadPhase/viewDataLoading]/Fixture.LoadingIndicatorViewSimpleRemoveScenario_ViewController" * a span named "[ViewLoadPhase/viewDataLoading]/Fixture.LoadingIndicatorViewSimpleRemoveScenario_ViewController" duration is equal or greater than 2.0 * a span named "SimpleRemoveScenarioIndicatorName" duration is equal or greater than 2.0 + * a span named "[ViewLoadPhase/viewDidAppear]/Fixture.LoadingIndicatorViewSimpleRemoveScenario_ViewController" ended before a span named "[ViewLoadPhase/viewDataLoading]/Fixture.LoadingIndicatorViewSimpleRemoveScenario_ViewController" started + * a span named "[ViewLoadPhase/viewDidAppear]/Fixture.LoadingIndicatorViewSimpleRemoveScenario_ViewController" ended before a span named "SimpleRemoveScenarioIndicatorName" started * every span field "kind" equals 1 * a span string attribute "bugsnag.span.category" equals "view_load" * a span string attribute "bugsnag.view.name" equals "Fixture.LoadingIndicatorViewSimpleRemoveScenario_ViewController" @@ -196,6 +202,8 @@ Feature: LoadingIndicator view to mark data loading phase * a span named "[ViewLoad/UIKit]/Fixture.LoadingIndicatorViewNestedViewStopScenario_ChildViewController" ended at the same time as a span named "[ViewLoadPhase/viewDataLoading]/Fixture.LoadingIndicatorViewNestedViewStopScenario_ChildViewController" * a span named "[ViewLoadPhase/viewDataLoading]/Fixture.LoadingIndicatorViewNestedViewStopScenario_ParentViewController" duration is equal or greater than 3.0 * a span named "[ViewLoadPhase/viewDataLoading]/Fixture.LoadingIndicatorViewNestedViewStopScenario_ChildViewController" duration is equal or greater than 3.0 + * a span named "[ViewLoadPhase/viewDidAppear]/Fixture.LoadingIndicatorViewNestedViewStopScenario_ParentViewController" ended before a span named "[ViewLoadPhase/viewDataLoading]/Fixture.LoadingIndicatorViewNestedViewStopScenario_ParentViewController" started + * a span named "[ViewLoadPhase/viewDidAppear]/Fixture.LoadingIndicatorViewNestedViewStopScenario_ChildViewController" ended before a span named "[ViewLoadPhase/viewDataLoading]/Fixture.LoadingIndicatorViewNestedViewStopScenario_ChildViewController" started * every span field "kind" equals 1 * a span string attribute "bugsnag.span.category" equals "view_load" * a span string attribute "bugsnag.view.name" equals "Fixture.LoadingIndicatorViewNestedViewStopScenario_ParentViewController" @@ -291,6 +299,8 @@ Feature: LoadingIndicator view to mark data loading phase * a span named "[ViewLoad/UIKit]/Fixture.LoadingIndicatorViewNestedViewStopScenario_ChildViewController" ended at the same time as a span named "[ViewLoadPhase/viewDataLoading]/Fixture.LoadingIndicatorViewNestedViewStopScenario_ChildViewController" * a span named "[ViewLoadPhase/viewDataLoading]/Fixture.LoadingIndicatorViewNestedViewStopScenario_ParentViewController" duration is equal or greater than 3.0 * a span named "[ViewLoadPhase/viewDataLoading]/Fixture.LoadingIndicatorViewNestedViewStopScenario_ChildViewController" duration is equal or greater than 2.0 + * a span named "[ViewLoadPhase/viewDidAppear]/Fixture.LoadingIndicatorViewNestedViewStopScenario_ParentViewController" ended before a span named "[ViewLoadPhase/viewDataLoading]/Fixture.LoadingIndicatorViewNestedViewStopScenario_ParentViewController" started + * a span named "[ViewLoadPhase/viewDidAppear]/Fixture.LoadingIndicatorViewNestedViewStopScenario_ChildViewController" ended before a span named "[ViewLoadPhase/viewDataLoading]/Fixture.LoadingIndicatorViewNestedViewStopScenario_ChildViewController" started * every span field "kind" equals 1 * a span string attribute "bugsnag.span.category" equals "view_load" * a span string attribute "bugsnag.view.name" equals "Fixture.LoadingIndicatorViewNestedViewStopScenario_ParentViewController" @@ -350,6 +360,8 @@ Feature: LoadingIndicator view to mark data loading phase * a span named "[ViewLoad/UIKit]/Fixture.LoadingIndicatorViewNestedViewStopScenario_ChildViewController" ended at the same time as a span named "[ViewLoadPhase/viewDataLoading]/Fixture.LoadingIndicatorViewNestedViewStopScenario_ChildViewController" * a span named "[ViewLoadPhase/viewDataLoading]/Fixture.LoadingIndicatorViewNestedViewStopScenario_ParentViewController" duration is equal or greater than 3.0 * a span named "[ViewLoadPhase/viewDataLoading]/Fixture.LoadingIndicatorViewNestedViewStopScenario_ChildViewController" duration is equal or greater than 2.0 + * a span named "[ViewLoadPhase/viewDidAppear]/Fixture.LoadingIndicatorViewNestedViewStopScenario_ParentViewController" ended before a span named "[ViewLoadPhase/viewDataLoading]/Fixture.LoadingIndicatorViewNestedViewStopScenario_ParentViewController" started + * a span named "[ViewLoadPhase/viewDidAppear]/Fixture.LoadingIndicatorViewNestedViewStopScenario_ChildViewController" ended before a span named "[ViewLoadPhase/viewDataLoading]/Fixture.LoadingIndicatorViewNestedViewStopScenario_ChildViewController" started * a span named "NestedViewStopScenarioIndicatorName1" duration is equal or greater than 4.0 * a span named "NestedViewStopScenarioIndicatorName2" duration is equal or greater than 2.0 * a span named "NestedViewStopScenarioIndicatorName3" duration is equal or greater than 3.0 From a1e31d73231ffe1a849363ab7f2c63a1e31c5c38 Mon Sep 17 00:00:00 2001 From: Robert Bartoszewski Date: Thu, 11 Dec 2025 01:59:58 +0100 Subject: [PATCH 10/12] Fixed build error --- .../LoadingIndicators/ViewLoadLoadingIndicatorsHandlerImpl.mm | 1 - 1 file changed, 1 deletion(-) diff --git a/Sources/BugsnagPerformance/Private/Instrumentation/ViewLoadInstrumentation/Lifecycle/LoadingIndicators/ViewLoadLoadingIndicatorsHandlerImpl.mm b/Sources/BugsnagPerformance/Private/Instrumentation/ViewLoadInstrumentation/Lifecycle/LoadingIndicators/ViewLoadLoadingIndicatorsHandlerImpl.mm index 3ba3bea7..b82a1581 100644 --- a/Sources/BugsnagPerformance/Private/Instrumentation/ViewLoadInstrumentation/Lifecycle/LoadingIndicators/ViewLoadLoadingIndicatorsHandlerImpl.mm +++ b/Sources/BugsnagPerformance/Private/Instrumentation/ViewLoadInstrumentation/Lifecycle/LoadingIndicators/ViewLoadLoadingIndicatorsHandlerImpl.mm @@ -49,7 +49,6 @@ ViewLoadLoadingIndicatorState * ViewLoadLoadingIndicatorsHandlerImpl::newState(BugsnagPerformanceLoadingIndicatorView *loadingIndicator) noexcept { - NSMutableArray *newConditions = [NSMutableArray array]; auto hasFoundFirstViewController = false; auto state = [ViewLoadLoadingIndicatorState new]; From db407e6a67a9971ccd29ed44d3ee1d3bbfa8a396 Mon Sep 17 00:00:00 2001 From: Robert Bartoszewski Date: Thu, 11 Dec 2025 02:01:59 +0100 Subject: [PATCH 11/12] Fixed an issue where app start type change wouldn't be reflected if it happened during first view viewDataLoading phase --- .../BugsnagPerformanceAppStartSpanControl.mm | 22 +++--- features/default/plugins.feature | 8 +++ .../ios/Fixture.xcodeproj/project.pbxproj | 10 ++- .../project.pbxproj | 10 ++- .../AppStartTypeLoadingScenario.swift | 68 +++++++++++++++++++ 5 files changed, 102 insertions(+), 16 deletions(-) create mode 100644 features/fixtures/ios/Scenarios/AppStartTypeLoadingScenario.swift diff --git a/Sources/BugsnagPerformance/Public/BugsnagPerformanceAppStartSpanControl.mm b/Sources/BugsnagPerformance/Public/BugsnagPerformanceAppStartSpanControl.mm index d6726a1c..e3a9223a 100644 --- a/Sources/BugsnagPerformance/Public/BugsnagPerformanceAppStartSpanControl.mm +++ b/Sources/BugsnagPerformance/Public/BugsnagPerformanceAppStartSpanControl.mm @@ -32,19 +32,21 @@ - (instancetype)initWithSpan:(BugsnagPerformanceSpan *)span { - (void)setType:(NSString *_Nullable)type { @synchronized (self) { __strong BugsnagPerformanceSpan *span = self.span; - if (span == nil || !span.isValid) { + if (span == nil || !(span.isValid || span.isBlocked)) { return; } - if (type == nil) { - [span updateName:self.spanPreviousName]; - } else { - NSString *typeStr = type; - // Original span name should be in format "[AppStart/$platform$type]" - NSString *newName = [NSString stringWithFormat:@"%@%@", self.spanPreviousName, typeStr]; - [span updateName:newName]; - } - [span setAttribute:AppStartNameAttribute withValue:type]; + [span forceMutate:^{ + if (type == nil) { + [span updateName:self.spanPreviousName]; + } else { + NSString *typeStr = type; + // Original span name should be in format "[AppStart/$platform$type]" + NSString *newName = [NSString stringWithFormat:@"%@%@", self.spanPreviousName, typeStr]; + [span updateName:newName]; + } + [span setAttribute:AppStartNameAttribute withValue:type]; + }]; } } diff --git a/features/default/plugins.feature b/features/default/plugins.feature index ce9e253a..37ad34e1 100644 --- a/features/default/plugins.feature +++ b/features/default/plugins.feature @@ -57,3 +57,11 @@ Feature: Plugins And I wait to receive at least 5 spans Then the trace "Content-Type" header equals "application/json" * a span field "name" equals "[AppStart/iOSCold]" + + Scenario: App start type plugin correctly changes the span name during data loading phase of the first view + Given I run "AppStartTypeLoadingScenario" + Then I relaunch the app after shutdown + And I wait to receive at least 14 spans + Then the trace "Content-Type" header equals "application/json" + * a span field "name" equals "[AppStart/iOSCold]AppStartTypeLoadingScenario" + diff --git a/features/fixtures/ios/Fixture.xcodeproj/project.pbxproj b/features/fixtures/ios/Fixture.xcodeproj/project.pbxproj index c2b575ce..90fa54c1 100644 --- a/features/fixtures/ios/Fixture.xcodeproj/project.pbxproj +++ b/features/fixtures/ios/Fixture.xcodeproj/project.pbxproj @@ -88,6 +88,7 @@ 969EE0E92E7851F000600F63 /* ConditionsBlockingBlockedEndedSpanScenario.swift in Sources */ = {isa = PBXBuildFile; fileRef = 969EE0E82E7851F000600F63 /* ConditionsBlockingBlockedEndedSpanScenario.swift */; }; 96A25F3C2D6BC98100A18116 /* AutoInstrumentAppStartsWithViewLoadScenario.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96A25F3A2D6BC98100A18116 /* AutoInstrumentAppStartsWithViewLoadScenario.swift */; }; 96CD493D2EB2F85000E03155 /* AutoInstrumentAppStartsLoadingScenario.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96CD493C2EB2F85000E03155 /* AutoInstrumentAppStartsLoadingScenario.swift */; }; + 96D090CB2EEA299C00BAA6D4 /* AppStartTypeLoadingScenario.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96D090CA2EEA299C00BAA6D4 /* AppStartTypeLoadingScenario.swift */; }; 96D528CC2C72B14300FEA2E2 /* AppDataOverrideScenario.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96D528CB2C72B14300FEA2E2 /* AppDataOverrideScenario.swift */; }; 96D528CE2C75DC7000FEA2E2 /* FixedSamplingProbabilityOneScenario.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96D528CD2C75DC7000FEA2E2 /* FixedSamplingProbabilityOneScenario.swift */; }; 96D528D02C77F38400FEA2E2 /* FixedSamplingProbabilityZeroScenario.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96D528CF2C77F38400FEA2E2 /* FixedSamplingProbabilityZeroScenario.swift */; }; @@ -95,9 +96,9 @@ 96EB8B502EB26B4400DDBF86 /* StartupEnabledMetrics.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96EB8B4F2EB26B3C00DDBF86 /* StartupEnabledMetrics.swift */; }; 96F129352DCE0CFE00A6FB2B /* ManualSpanWithRemoteContextParentScenario.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96F129342DCE0CFE00A6FB2B /* ManualSpanWithRemoteContextParentScenario.swift */; }; 96F5268C2C259E4E0095D600 /* ManualNetworkSpanCallbackSetToNilScenario.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96F5268B2C259E4E0095D600 /* ManualNetworkSpanCallbackSetToNilScenario.swift */; }; + 96F901092EE7B7FF0026F5B9 /* AutoInstrumentNetworkEarlyCallbackScenario.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96F901082EE7B7FF0026F5B9 /* AutoInstrumentNetworkEarlyCallbackScenario.swift */; }; 96F901132EE81E580026F5B9 /* BackgroundThreadStartScenario.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96F901122EE81E580026F5B9 /* BackgroundThreadStartScenario.swift */; }; 96F901172EE836CC0026F5B9 /* ManualParentBlockedSpanScenario.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96F901162EE836CC0026F5B9 /* ManualParentBlockedSpanScenario.swift */; }; - 96F901092EE7B7FF0026F5B9 /* AutoInstrumentNetworkEarlyCallbackScenario.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96F901082EE7B7FF0026F5B9 /* AutoInstrumentNetworkEarlyCallbackScenario.swift */; }; CB0496942913CA300097E526 /* BatchingWithTimeoutScenario.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB0496932913CA300097E526 /* BatchingWithTimeoutScenario.swift */; }; CB0AD76E2965BBDA002A3FB6 /* InitialPScenario.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB0AD76D2965BBDA002A3FB6 /* InitialPScenario.swift */; }; CB2B8A9D2A0CCEF90054FBBE /* AutoInstrumentFileURLRequestScenario.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB2B8A9C2A0CCEF90054FBBE /* AutoInstrumentFileURLRequestScenario.swift */; }; @@ -209,6 +210,7 @@ 969EE0E82E7851F000600F63 /* ConditionsBlockingBlockedEndedSpanScenario.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConditionsBlockingBlockedEndedSpanScenario.swift; sourceTree = ""; }; 96A25F3A2D6BC98100A18116 /* AutoInstrumentAppStartsWithViewLoadScenario.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AutoInstrumentAppStartsWithViewLoadScenario.swift; sourceTree = ""; }; 96CD493C2EB2F85000E03155 /* AutoInstrumentAppStartsLoadingScenario.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutoInstrumentAppStartsLoadingScenario.swift; sourceTree = ""; }; + 96D090CA2EEA299C00BAA6D4 /* AppStartTypeLoadingScenario.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppStartTypeLoadingScenario.swift; sourceTree = ""; }; 96D528CB2C72B14300FEA2E2 /* AppDataOverrideScenario.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDataOverrideScenario.swift; sourceTree = ""; }; 96D528CD2C75DC7000FEA2E2 /* FixedSamplingProbabilityOneScenario.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FixedSamplingProbabilityOneScenario.swift; sourceTree = ""; }; 96D528CF2C77F38400FEA2E2 /* FixedSamplingProbabilityZeroScenario.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FixedSamplingProbabilityZeroScenario.swift; sourceTree = ""; }; @@ -217,9 +219,9 @@ 96EB8B4F2EB26B3C00DDBF86 /* StartupEnabledMetrics.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StartupEnabledMetrics.swift; sourceTree = ""; }; 96F129342DCE0CFE00A6FB2B /* ManualSpanWithRemoteContextParentScenario.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ManualSpanWithRemoteContextParentScenario.swift; sourceTree = ""; }; 96F5268B2C259E4E0095D600 /* ManualNetworkSpanCallbackSetToNilScenario.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ManualNetworkSpanCallbackSetToNilScenario.swift; sourceTree = ""; }; + 96F901082EE7B7FF0026F5B9 /* AutoInstrumentNetworkEarlyCallbackScenario.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutoInstrumentNetworkEarlyCallbackScenario.swift; sourceTree = ""; }; 96F901122EE81E580026F5B9 /* BackgroundThreadStartScenario.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackgroundThreadStartScenario.swift; sourceTree = ""; }; 96F901162EE836CC0026F5B9 /* ManualParentBlockedSpanScenario.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ManualParentBlockedSpanScenario.swift; sourceTree = ""; }; - 96F901082EE7B7FF0026F5B9 /* AutoInstrumentNetworkEarlyCallbackScenario.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutoInstrumentNetworkEarlyCallbackScenario.swift; sourceTree = ""; }; CB0496932913CA300097E526 /* BatchingWithTimeoutScenario.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BatchingWithTimeoutScenario.swift; sourceTree = ""; }; CB0AD76D2965BBDA002A3FB6 /* InitialPScenario.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InitialPScenario.swift; sourceTree = ""; }; CB211D0629EEB615008F748D /* BugsnagPerformanceConfiguration+Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "BugsnagPerformanceConfiguration+Private.h"; path = "../../../../Sources/BugsnagPerformance/Private/BugsnagPerformanceConfiguration+Private.h"; sourceTree = ""; }; @@ -327,9 +329,9 @@ 01FE4DC128E1AF0700D1F239 /* Scenarios */ = { isa = PBXGroup; children = ( - 1C8CB8C02EC3C970007BF492 /* DebugModeScenario.swift */, 96D528CB2C72B14300FEA2E2 /* AppDataOverrideScenario.swift */, 1CFB95482EA798D600025C16 /* AppStartTypeLateScenario.swift */, + 96D090CA2EEA299C00BAA6D4 /* AppStartTypeLoadingScenario.swift */, 1CC791742E6783850015FFBA /* AppStartTypeScenario.swift */, 96CD493C2EB2F85000E03155 /* AutoInstrumentAppStartsLoadingScenario.swift */, 01D3A7DF28F0290D0063D79E /* AutoInstrumentAppStartsScenario.swift */, @@ -366,6 +368,7 @@ 969EE0E82E7851F000600F63 /* ConditionsBlockingBlockedEndedSpanScenario.swift */, 969EE0E12E784E6400600F63 /* ConditionsOverrideEndTimeBackwardsScenario.swift */, 098FC87B2D40EAB8001B627D /* CPUMetricsScenario.swift */, + 1C8CB8C02EC3C970007BF492 /* DebugModeScenario.swift */, 09DC62292C6DE242000AA8E1 /* EarlySpanOnEndScenario.swift */, CBC90CDD29CDCFF700280884 /* FirstClassNoScenario.swift */, CBC90CDF29CDD02800280884 /* FirstClassYesScenario.swift */, @@ -590,6 +593,7 @@ 967F6F1A29C4AD300054EED8 /* ReleaseStageNotEnabledScenario.swift in Sources */, 9657A8992A3CF75B001CEF5D /* AutoInstrumentTabViewLoadScenario.swift in Sources */, CBEC89452A4ED0590088A3CE /* MaxPayloadSizeScenario.swift in Sources */, + 96D090CB2EEA299C00BAA6D4 /* AppStartTypeLoadingScenario.swift in Sources */, 964735CB2CCF137A00759ED9 /* AutoInstrumentNetworkSharedSessionInvalidateScenario.swift in Sources */, 96F901092EE7B7FF0026F5B9 /* AutoInstrumentNetworkEarlyCallbackScenario.swift in Sources */, CB572EAD29BB829800FD7A2A /* BackgroundForegroundScenario.swift in Sources */, diff --git a/features/fixtures/ios/FixtureXcFramework.xcodeproj/project.pbxproj b/features/fixtures/ios/FixtureXcFramework.xcodeproj/project.pbxproj index 7c692e76..2d2a3572 100644 --- a/features/fixtures/ios/FixtureXcFramework.xcodeproj/project.pbxproj +++ b/features/fixtures/ios/FixtureXcFramework.xcodeproj/project.pbxproj @@ -88,6 +88,7 @@ 969EE0ED2E78521500600F63 /* ConditionsBlockingBlockedEndedSpanScenario.swift in Sources */ = {isa = PBXBuildFile; fileRef = 969EE0EA2E78521500600F63 /* ConditionsBlockingBlockedEndedSpanScenario.swift */; }; 96A25F402D6BC9BF00A18116 /* AutoInstrumentAppStartsWithViewLoadScenario.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96A25F3E2D6BC9BF00A18116 /* AutoInstrumentAppStartsWithViewLoadScenario.swift */; }; 96CD493F2EB2F87700E03155 /* AutoInstrumentAppStartsLoadingScenario.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96CD493E2EB2F87700E03155 /* AutoInstrumentAppStartsLoadingScenario.swift */; }; + 96D090CD2EEA29F500BAA6D4 /* AppStartTypeLoadingScenario.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96D090CC2EEA29F500BAA6D4 /* AppStartTypeLoadingScenario.swift */; }; 96D528CC2C72B14300FEA2E2 /* AppDataOverrideScenario.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96D528CB2C72B14300FEA2E2 /* AppDataOverrideScenario.swift */; }; 96D528CE2C75DC7000FEA2E2 /* FixedSamplingProbabilityOneScenario.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96D528CD2C75DC7000FEA2E2 /* FixedSamplingProbabilityOneScenario.swift */; }; 96D528D02C77F38400FEA2E2 /* FixedSamplingProbabilityZeroScenario.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96D528CF2C77F38400FEA2E2 /* FixedSamplingProbabilityZeroScenario.swift */; }; @@ -96,9 +97,9 @@ 96EB8B522EB277CE00DDBF86 /* StartupEnabledMetrics.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96EB8B512EB277CE00DDBF86 /* StartupEnabledMetrics.swift */; }; 96F129332DCE0CDD00A6FB2B /* RenderingMetricsScenario.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96F129322DCE0CDD00A6FB2B /* RenderingMetricsScenario.swift */; }; 96F5268C2C259E4E0095D600 /* ManualNetworkSpanCallbackSetToNilScenario.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96F5268B2C259E4E0095D600 /* ManualNetworkSpanCallbackSetToNilScenario.swift */; }; + 96F9010B2EE7B81F0026F5B9 /* AutoInstrumentNetworkEarlyCallbackScenario.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96F9010A2EE7B81F0026F5B9 /* AutoInstrumentNetworkEarlyCallbackScenario.swift */; }; 96F901152EE81E6D0026F5B9 /* BackgroundThreadStartScenario.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96F901142EE81E6D0026F5B9 /* BackgroundThreadStartScenario.swift */; }; 96F901192EE836E10026F5B9 /* ManualParentBlockedSpanScenario.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96F901182EE836E10026F5B9 /* ManualParentBlockedSpanScenario.swift */; }; - 96F9010B2EE7B81F0026F5B9 /* AutoInstrumentNetworkEarlyCallbackScenario.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96F9010A2EE7B81F0026F5B9 /* AutoInstrumentNetworkEarlyCallbackScenario.swift */; }; CB0496942913CA300097E526 /* BatchingWithTimeoutScenario.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB0496932913CA300097E526 /* BatchingWithTimeoutScenario.swift */; }; CB0AD76E2965BBDA002A3FB6 /* InitialPScenario.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB0AD76D2965BBDA002A3FB6 /* InitialPScenario.swift */; }; CB2B8A9D2A0CCEF90054FBBE /* AutoInstrumentFileURLRequestScenario.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB2B8A9C2A0CCEF90054FBBE /* AutoInstrumentFileURLRequestScenario.swift */; }; @@ -227,6 +228,7 @@ 969EE0EB2E78521500600F63 /* ConditionsOverrideEndTimeBackwardsScenario.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConditionsOverrideEndTimeBackwardsScenario.swift; sourceTree = ""; }; 96A25F3E2D6BC9BF00A18116 /* AutoInstrumentAppStartsWithViewLoadScenario.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AutoInstrumentAppStartsWithViewLoadScenario.swift; sourceTree = ""; }; 96CD493E2EB2F87700E03155 /* AutoInstrumentAppStartsLoadingScenario.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutoInstrumentAppStartsLoadingScenario.swift; sourceTree = ""; }; + 96D090CC2EEA29F500BAA6D4 /* AppStartTypeLoadingScenario.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppStartTypeLoadingScenario.swift; sourceTree = ""; }; 96D528CB2C72B14300FEA2E2 /* AppDataOverrideScenario.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDataOverrideScenario.swift; sourceTree = ""; }; 96D528CD2C75DC7000FEA2E2 /* FixedSamplingProbabilityOneScenario.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FixedSamplingProbabilityOneScenario.swift; sourceTree = ""; }; 96D528CF2C77F38400FEA2E2 /* FixedSamplingProbabilityZeroScenario.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FixedSamplingProbabilityZeroScenario.swift; sourceTree = ""; }; @@ -236,9 +238,9 @@ 96EB8B512EB277CE00DDBF86 /* StartupEnabledMetrics.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StartupEnabledMetrics.swift; sourceTree = ""; }; 96F129322DCE0CDD00A6FB2B /* RenderingMetricsScenario.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RenderingMetricsScenario.swift; sourceTree = ""; }; 96F5268B2C259E4E0095D600 /* ManualNetworkSpanCallbackSetToNilScenario.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ManualNetworkSpanCallbackSetToNilScenario.swift; sourceTree = ""; }; + 96F9010A2EE7B81F0026F5B9 /* AutoInstrumentNetworkEarlyCallbackScenario.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutoInstrumentNetworkEarlyCallbackScenario.swift; sourceTree = ""; }; 96F901142EE81E6D0026F5B9 /* BackgroundThreadStartScenario.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackgroundThreadStartScenario.swift; sourceTree = ""; }; 96F901182EE836E10026F5B9 /* ManualParentBlockedSpanScenario.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ManualParentBlockedSpanScenario.swift; sourceTree = ""; }; - 96F9010A2EE7B81F0026F5B9 /* AutoInstrumentNetworkEarlyCallbackScenario.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutoInstrumentNetworkEarlyCallbackScenario.swift; sourceTree = ""; }; CB0496932913CA300097E526 /* BatchingWithTimeoutScenario.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BatchingWithTimeoutScenario.swift; sourceTree = ""; }; CB0AD76D2965BBDA002A3FB6 /* InitialPScenario.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InitialPScenario.swift; sourceTree = ""; }; CB211D0629EEB615008F748D /* BugsnagPerformanceConfiguration+Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "BugsnagPerformanceConfiguration+Private.h"; path = "../../../../Sources/BugsnagPerformance/Private/BugsnagPerformanceConfiguration+Private.h"; sourceTree = ""; }; @@ -350,9 +352,9 @@ 01FE4DC128E1AF0700D1F239 /* Scenarios */ = { isa = PBXGroup; children = ( - 1C8CB8C22EC3D0D2007BF492 /* DebugModeScenario.swift */, 96D528CB2C72B14300FEA2E2 /* AppDataOverrideScenario.swift */, 1C3E2DAD2EAA5F4900B32AD2 /* AppStartTypeLateScenario.swift */, + 96D090CC2EEA29F500BAA6D4 /* AppStartTypeLoadingScenario.swift */, 1C3E2DAE2EAA5F4900B32AD2 /* AppStartTypeScenario.swift */, 96CD493E2EB2F87700E03155 /* AutoInstrumentAppStartsLoadingScenario.swift */, 01D3A7DF28F0290D0063D79E /* AutoInstrumentAppStartsScenario.swift */, @@ -389,6 +391,7 @@ 969EE0EA2E78521500600F63 /* ConditionsBlockingBlockedEndedSpanScenario.swift */, 969EE0EB2E78521500600F63 /* ConditionsOverrideEndTimeBackwardsScenario.swift */, 09F3F5292D6C72B300BAA0A3 /* CPUMetricsScenario.swift */, + 1C8CB8C22EC3D0D2007BF492 /* DebugModeScenario.swift */, 09DC62292C6DE242000AA8E1 /* EarlySpanOnEndScenario.swift */, CBC90CDD29CDCFF700280884 /* FirstClassNoScenario.swift */, CBC90CDF29CDD02800280884 /* FirstClassYesScenario.swift */, @@ -640,6 +643,7 @@ 098C3B502C53CEC0006F9886 /* ErrorGenerator.m in Sources */, 0921F02E2A69262300C764EB /* AutoInstrumentNetworkCallbackScenario.swift in Sources */, 01E7918A28EC7B5E00855993 /* ManualSpanBeforeStartScenario.swift in Sources */, + 96D090CD2EEA29F500BAA6D4 /* AppStartTypeLoadingScenario.swift in Sources */, 1C8CB8C32EC3D0D2007BF492 /* DebugModeScenario.swift in Sources */, 962CE8012E64FF6100380522 /* LoadingIndicatorViewNestedViewStopScenario.swift in Sources */, 09DC622A2C6DE242000AA8E1 /* EarlySpanOnEndScenario.swift in Sources */, diff --git a/features/fixtures/ios/Scenarios/AppStartTypeLoadingScenario.swift b/features/fixtures/ios/Scenarios/AppStartTypeLoadingScenario.swift new file mode 100644 index 00000000..d6f8bea2 --- /dev/null +++ b/features/fixtures/ios/Scenarios/AppStartTypeLoadingScenario.swift @@ -0,0 +1,68 @@ +// +// AppStartTypeLoadingScenario.swift +// Fixture +// +// Created by Robert Bartoszewski on 10/12/2025. +// + +import BugsnagPerformance + +@objcMembers +class AppStartTypeLoadingScenario: Scenario { + + override func setInitialBugsnagConfiguration() { + bugsnagPerfConfig.internal.autoTriggerExportOnBatchSize = 100 + bugsnagPerfConfig.internal.performWorkInterval = 1 + } + + override func run() { + // Save a startup configuration + let startupConfig = StartupConfiguration(configFile: nil) + startupConfig.autoInstrumentAppStarts = true + startupConfig.autoInstrumentViewControllers = true + startupConfig.scenarioName = String(describing: AppStartTypeLoadingScenario.self) + startupConfig.endpoint = fixtureConfig.tracesURL + + startupConfig.saveStartupConfig() + + DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { + exit(0) + } + } + + override func customViewController() -> UIViewController? { + return AppStartTypeLoadingScenario_ViewController() + } +} + +class AppStartTypeLoadingScenario_ViewController: UIViewController { + + var loadingIndicator: BugsnagPerformanceLoadingIndicatorView? + + required convenience init?(coder: NSCoder) { + self.init() + } + + override func loadView() { + // we are creating a class property because we may have delegates + // assign your delegates here, before view + let customView = UIView() + customView.backgroundColor = .green + loadingIndicator = BugsnagPerformanceLoadingIndicatorView() + customView.addSubview(loadingIndicator!) + + view = customView + } + + override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) { + let query = BugsnagPerformanceAppStartSpanQuery() + let spanControl = BugsnagPerformance.getSpanControls(with: query) as! BugsnagPerformanceAppStartSpanControl? + spanControl?.setType("AppStartTypeLoadingScenario") + DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) { + self.loadingIndicator?.finishLoading() + } + } + } +} From 4a338252611357e4452c7c47f107e8e58c2986df Mon Sep 17 00:00:00 2001 From: Robert Bartoszewski Date: Thu, 11 Dec 2025 15:37:38 +0100 Subject: [PATCH 12/12] Fixed E2E tests --- features/default/plugins.feature | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/features/default/plugins.feature b/features/default/plugins.feature index 37ad34e1..514b9f19 100644 --- a/features/default/plugins.feature +++ b/features/default/plugins.feature @@ -61,7 +61,7 @@ Feature: Plugins Scenario: App start type plugin correctly changes the span name during data loading phase of the first view Given I run "AppStartTypeLoadingScenario" Then I relaunch the app after shutdown - And I wait to receive at least 14 spans + And I wait to receive at least 6 spans Then the trace "Content-Type" header equals "application/json" * a span field "name" equals "[AppStart/iOSCold]AppStartTypeLoadingScenario"