diff --git a/BugsnagPerformance.xcodeproj/project.pbxproj b/BugsnagPerformance.xcodeproj/project.pbxproj index 434f9b96..2699d4c6 100644 --- a/BugsnagPerformance.xcodeproj/project.pbxproj +++ b/BugsnagPerformance.xcodeproj/project.pbxproj @@ -52,7 +52,6 @@ 094FA7472B10EDE700112ED4 /* BugsnagPerformanceSwift.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 094FA7322B10EDE600112ED4 /* BugsnagPerformanceSwift.framework */; }; 094FA7482B10EDE700112ED4 /* BugsnagPerformanceSwift.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 094FA7322B10EDE600112ED4 /* BugsnagPerformanceSwift.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 094FA7522B10EEB600112ED4 /* BugsnagPerformance.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 72E4BB63359EA30E80116E2A /* BugsnagPerformance.framework */; }; - 09509B752ADFE9A900A358EC /* TracerTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 09509B742ADFE9A900A358EC /* TracerTests.mm */; }; 096CBC172B1752F700534F0C /* BugsnagPerformanceSwiftUIInstrumentation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 096CBC152B1752F100534F0C /* BugsnagPerformanceSwiftUIInstrumentation.swift */; }; 09856B9E2B9606A500620907 /* BugsnagPerformanceSwiftUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 094FA7422B10EDE700112ED4 /* BugsnagPerformanceSwiftUITests.swift */; }; 0986B7C02B287C9D00BD2CA3 /* WeakSpansList.mm in Sources */ = {isa = PBXBuildFile; fileRef = 0986B7BF2B287C9D00BD2CA3 /* WeakSpansList.mm */; }; @@ -188,6 +187,9 @@ 967949B62E97CC9E005FD87F /* SpanLifecycleHandler.h in Headers */ = {isa = PBXBuildFile; fileRef = 967949B52E97CC95005FD87F /* SpanLifecycleHandler.h */; }; 967949B82E97CCA9005FD87F /* SpanLifecycleHandlerImpl.mm in Sources */ = {isa = PBXBuildFile; fileRef = 967949B72E97CCA6005FD87F /* SpanLifecycleHandlerImpl.mm */; }; 967949BA2E97D5A5005FD87F /* SpanLifecycleHandlerImpl.h in Headers */ = {isa = PBXBuildFile; fileRef = 967949B92E97D59B005FD87F /* SpanLifecycleHandlerImpl.h */; }; + 967949BD2E99373E005FD87F /* SpanStore.h in Headers */ = {isa = PBXBuildFile; fileRef = 967949BC2E993735005FD87F /* SpanStore.h */; }; + 967949BF2E993749005FD87F /* SpanStoreImpl.h in Headers */ = {isa = PBXBuildFile; fileRef = 967949BE2E993742005FD87F /* SpanStoreImpl.h */; }; + 967949C12E993750005FD87F /* SpanStoreImpl.mm in Sources */ = {isa = PBXBuildFile; fileRef = 967949C02E99374E005FD87F /* SpanStoreImpl.mm */; }; 967F6F1829C3783B0054EED8 /* BugsnagPerformanceConfiguration+Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 967F6F1729C3782D0054EED8 /* BugsnagPerformanceConfiguration+Private.h */; }; 968AA5FB2CCA5A9200BA69CF /* BSGPerformanceSharedSessionProxy.h in Headers */ = {isa = PBXBuildFile; fileRef = 968AA5FA2CCA5A9200BA69CF /* BSGPerformanceSharedSessionProxy.h */; }; 968AA5FD2CCA5AB000BA69CF /* BSGPerformanceSharedSessionProxy.mm in Sources */ = {isa = PBXBuildFile; fileRef = 968AA5FC2CCA5AB000BA69CF /* BSGPerformanceSharedSessionProxy.mm */; }; @@ -408,7 +410,6 @@ 094FA7352B10EDE600112ED4 /* BugsnagPerformanceSwift.docc */ = {isa = PBXFileReference; lastKnownFileType = folder.documentationcatalog; path = BugsnagPerformanceSwift.docc; sourceTree = ""; }; 094FA73B2B10EDE700112ED4 /* BugsnagPerformanceSwiftTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = BugsnagPerformanceSwiftTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 094FA7422B10EDE700112ED4 /* BugsnagPerformanceSwiftUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BugsnagPerformanceSwiftUITests.swift; sourceTree = ""; }; - 09509B742ADFE9A900A358EC /* TracerTests.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = TracerTests.mm; sourceTree = ""; }; 096CBC152B1752F100534F0C /* BugsnagPerformanceSwiftUIInstrumentation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BugsnagPerformanceSwiftUIInstrumentation.swift; sourceTree = ""; }; 0986B7BF2B287C9D00BD2CA3 /* WeakSpansList.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = WeakSpansList.mm; sourceTree = ""; }; 0987F2772C32D4AD00777FD8 /* BugsnagPerformanceSpanContext.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = BugsnagPerformanceSpanContext.h; sourceTree = ""; }; @@ -546,6 +547,9 @@ 967949B52E97CC95005FD87F /* SpanLifecycleHandler.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SpanLifecycleHandler.h; sourceTree = ""; }; 967949B72E97CCA6005FD87F /* SpanLifecycleHandlerImpl.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = SpanLifecycleHandlerImpl.mm; sourceTree = ""; }; 967949B92E97D59B005FD87F /* SpanLifecycleHandlerImpl.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SpanLifecycleHandlerImpl.h; sourceTree = ""; }; + 967949BC2E993735005FD87F /* SpanStore.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SpanStore.h; sourceTree = ""; }; + 967949BE2E993742005FD87F /* SpanStoreImpl.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SpanStoreImpl.h; sourceTree = ""; }; + 967949C02E99374E005FD87F /* SpanStoreImpl.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = SpanStoreImpl.mm; sourceTree = ""; }; 967F6F1729C3782D0054EED8 /* BugsnagPerformanceConfiguration+Private.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "BugsnagPerformanceConfiguration+Private.h"; sourceTree = ""; }; 968AA5FA2CCA5A9200BA69CF /* BSGPerformanceSharedSessionProxy.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = BSGPerformanceSharedSessionProxy.h; sourceTree = ""; }; 968AA5FC2CCA5AB000BA69CF /* BSGPerformanceSharedSessionProxy.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = BSGPerformanceSharedSessionProxy.mm; sourceTree = ""; }; @@ -867,6 +871,7 @@ CB7FD935299D330500499E13 /* SpanOptions.h */, 96D55C7D2A1EA5A8006D1F29 /* SpanStackingHandler.h */, 96D55C7F2A1EA5C6006D1F29 /* SpanStackingHandler.mm */, + 967949BB2E99372C005FD87F /* SpanStore */, CBEBE59129F2783C00BF0B4F /* Swizzle.h */, CBEBE59029F2783C00BF0B4F /* Swizzle.mm */, 098FC8532D37A08D001B627D /* SystemInfoSampler.h */, @@ -947,7 +952,6 @@ CB2B8A9A2A0924A50054FBBE /* SpanTests.mm */, 098FC87A2D3FD095001B627D /* TestHelpers.h */, CB747D20299E5458003CA1B4 /* TimeTests.mm */, - 09509B742ADFE9A900A358EC /* TracerTests.mm */, 09B473092B2313570024CF11 /* WeakSpansListTests.mm */, CB0AD75A295F27DD002A3FB6 /* WorkerTests.mm */, ); @@ -1197,6 +1201,16 @@ path = SpanLifecycle; sourceTree = ""; }; + 967949BB2E99372C005FD87F /* SpanStore */ = { + isa = PBXGroup; + children = ( + 967949BC2E993735005FD87F /* SpanStore.h */, + 967949BE2E993742005FD87F /* SpanStoreImpl.h */, + 967949C02E99374E005FD87F /* SpanStoreImpl.mm */, + ); + path = SpanStore; + sourceTree = ""; + }; 969EE0EE2E7872A600600F63 /* SpanFactory */ = { isa = PBXGroup; children = ( @@ -1331,6 +1345,7 @@ 0122C24C29019770002D243C /* NetworkInstrumentation.h in Headers */, 962CE8352E67972300380522 /* NetworkSpanFactoryImpl.h in Headers */, 962CE7CE2E60766000380522 /* ViewLoadInstrumentationStateRepository.h in Headers */, + 967949BD2E99373E005FD87F /* SpanStore.h in Headers */, 963726C12DEA4E5700C739E6 /* BugsnagPerformancePriority.h in Headers */, 962CE7E82E6123E700380522 /* ViewLoadLoadingIndicatorsHandlerImpl.h in Headers */, 962CE7E92E6123E700380522 /* ViewLoadLoadingIndicatorsHandler.h in Headers */, @@ -1371,6 +1386,7 @@ 09B473072B23087D0024CF11 /* WeakSpansList.h in Headers */, 96F1292B2DCCB9B900A6FB2B /* BugsnagPerformanceRemoteSpanContext.h in Headers */, 962CE8282E67189800380522 /* NetworkEarlyPhaseHandlerImpl.h in Headers */, + 967949BF2E993749005FD87F /* SpanStoreImpl.h in Headers */, 09F23A8C2CE351ED00F0D769 /* BugsnagSwiftTools.h in Headers */, 0921F02B2A67CBD600C764EB /* BugsnagPerformanceNetworkRequestInfo.h in Headers */, 962CE7D52E60967A00380522 /* BugsnagPerformanceLoadingIndicatorView+Private.h in Headers */, @@ -1736,7 +1752,6 @@ 09E313042BF363020081F219 /* CrossTalkTests.mm in Sources */, CB747D21299E5458003CA1B4 /* TimeTests.mm in Sources */, 0122C27129019CEF002D243C /* OtlpTraceEncodingTests.mm in Sources */, - 09509B752ADFE9A900A358EC /* TracerTests.mm in Sources */, 098FC8792D3FADFE001B627D /* SpanAttributesTests.mm in Sources */, 962F80F229DB03A400BE82BC /* PerformanceMicrobenchmarkTests.swift in Sources */, CB747D1F299E4984003CA1B4 /* SpanOptionsTests.mm in Sources */, @@ -1812,6 +1827,7 @@ 09F23A8D2CE351ED00F0D769 /* BugsnagSwiftTools.m in Sources */, 962CE8302E673A9900380522 /* NetworkLifecycleHandlerImpl.mm in Sources */, 968AA5FD2CCA5AB000BA69CF /* BSGPerformanceSharedSessionProxy.mm in Sources */, + 967949C12E993750005FD87F /* SpanStoreImpl.mm in Sources */, 964B78562D4B924C00FF077D /* BugsnagPerformanceSpanCondition.mm in Sources */, CBEC51DD2976F1F9009C0CE3 /* RetryQueue.mm in Sources */, 01A414CE2913C0F0003152A4 /* SpanAttributes.mm in Sources */, diff --git a/Sources/BugsnagPerformance/Private/BugsnagPerformanceImpl.h b/Sources/BugsnagPerformance/Private/BugsnagPerformanceImpl.h index 0460c1bf..fb49f318 100644 --- a/Sources/BugsnagPerformance/Private/BugsnagPerformanceImpl.h +++ b/Sources/BugsnagPerformance/Private/BugsnagPerformanceImpl.h @@ -36,6 +36,7 @@ #import "SpanFactory/ViewLoad/ViewLoadSpanFactoryImpl.h" #import "SpanFactory/Network/NetworkSpanFactoryImpl.h" #import "SpanLifecycle/SpanLifecycleHandlerImpl.h" +#import "SpanStore/SpanStoreImpl.h" #import @@ -109,6 +110,7 @@ class BugsnagPerformanceImpl: public PhasedStartup { std::shared_ptr appStartupSpanFactory_; std::shared_ptr viewLoadSpanFactory_; std::shared_ptr networkSpanFactory_; + std::shared_ptr spanStore_; std::shared_ptr spanLifecycleHandler_; std::shared_ptr tracer_; std::unique_ptr retryQueue_; diff --git a/Sources/BugsnagPerformance/Private/BugsnagPerformanceImpl.mm b/Sources/BugsnagPerformance/Private/BugsnagPerformanceImpl.mm index c26aca23..f52eae08 100644 --- a/Sources/BugsnagPerformance/Private/BugsnagPerformanceImpl.mm +++ b/Sources/BugsnagPerformance/Private/BugsnagPerformanceImpl.mm @@ -56,8 +56,9 @@ , appStartupSpanFactory_(std::make_shared(plainSpanFactory_, spanAttributesProvider_)) , viewLoadSpanFactory_(std::make_shared(plainSpanFactory_, spanAttributesProvider_)) , networkSpanFactory_(std::make_shared(plainSpanFactory_, spanAttributesProvider_)) -, spanLifecycleHandler_(std::make_shared(sampler_, spanStackingHandler_, conditionTimeoutExecutor_, plainSpanFactory_, batch_, frameMetricsCollector_, spanStartCallbacks_, spanEndCallbacks_, ^{this->onSpanStarted();})) -, tracer_(std::make_shared(plainSpanFactory_, viewLoadSpanFactory_, networkSpanFactory_, spanLifecycleHandler_, spanStackingHandler_)) +, spanStore_(std::make_shared(spanStackingHandler_)) +, spanLifecycleHandler_(std::make_shared(sampler_, spanStore_, conditionTimeoutExecutor_, plainSpanFactory_, batch_, frameMetricsCollector_, spanStartCallbacks_, spanEndCallbacks_, ^{this->onSpanStarted();})) +, tracer_(std::make_shared(plainSpanFactory_, viewLoadSpanFactory_, networkSpanFactory_, spanLifecycleHandler_, spanStore_)) , retryQueue_(std::make_unique([persistence_->bugsnagPerformanceDir() stringByAppendingPathComponent:@"retry-queue"])) , appStateTracker_(appStateTracker) , viewControllersToSpans_([NSMapTable mapTableWithKeyOptions:NSMapTableWeakMemory | NSMapTableObjectPointerPersonality @@ -418,7 +419,7 @@ bool BugsnagPerformanceImpl::sweepTracerTask() noexcept { BSGLogDebug(@"BugsnagPerformanceImpl::sweepTracerTask()"); - spanLifecycleHandler_->sweep(); + spanStore_->sweep(); // Never auto-repeat this task, even if work was done; it can wait. return false; } diff --git a/Sources/BugsnagPerformance/Private/ConditionTimeoutExecutor.h b/Sources/BugsnagPerformance/Private/ConditionTimeoutExecutor.h index 4984c535..a20f199b 100644 --- a/Sources/BugsnagPerformance/Private/ConditionTimeoutExecutor.h +++ b/Sources/BugsnagPerformance/Private/ConditionTimeoutExecutor.h @@ -19,7 +19,7 @@ class ConditionTimeoutExecutor { ConditionTimeoutExecutor() noexcept {}; ~ConditionTimeoutExecutor() {}; - void sheduleTimeout(BugsnagPerformanceSpanCondition *condition, NSTimeInterval timeout) noexcept { + void scheduleTimeout(BugsnagPerformanceSpanCondition *condition, NSTimeInterval timeout) noexcept { std::lock_guard guard(mutex_); this->conditionIdToTimer_[condition.conditionId] = [NSTimer scheduledTimerWithTimeInterval:timeout repeats:NO block:^(NSTimer *) { [condition didTimeout]; diff --git a/Sources/BugsnagPerformance/Private/SpanLifecycle/SpanLifecycleHandlerImpl.h b/Sources/BugsnagPerformance/Private/SpanLifecycle/SpanLifecycleHandlerImpl.h index 262e02c2..cd46bf72 100644 --- a/Sources/BugsnagPerformance/Private/SpanLifecycle/SpanLifecycleHandlerImpl.h +++ b/Sources/BugsnagPerformance/Private/SpanLifecycle/SpanLifecycleHandlerImpl.h @@ -17,6 +17,7 @@ #import "../ConditionTimeoutExecutor.h" #import "../BSGPrioritizedStore.h" #import "../WeakSpansList.h" +#import "../SpanStore/SpanStore.h" #import @@ -25,7 +26,7 @@ namespace bugsnag { class SpanLifecycleHandlerImpl: public SpanLifecycleHandler { public: SpanLifecycleHandlerImpl(std::shared_ptr sampler, - std::shared_ptr spanStackingHandler, + std::shared_ptr store, std::shared_ptr conditionTimeoutExecutor, std::shared_ptr plainSpanFactory, std::shared_ptr batch, @@ -34,16 +35,14 @@ class SpanLifecycleHandlerImpl: public SpanLifecycleHandler { BSGPrioritizedStore *onSpanEndCallbacks, void (^onSpanStarted)()) noexcept : sampler_(sampler) - , spanStackingHandler_(spanStackingHandler) + , store_(store) , conditionTimeoutExecutor_(conditionTimeoutExecutor) , plainSpanFactory_(plainSpanFactory) , batch_(batch) , frameMetricsCollector_(frameMetricsCollector) , onSpanStartCallbacks_(onSpanStartCallbacks) , onSpanEndCallbacks_(onSpanEndCallbacks) - , onSpanStarted_(onSpanStarted) - , blockedSpans_([NSMutableArray new]) - , potentiallyOpenSpans_(std::make_shared()) {} + , onSpanStarted_(onSpanStarted) {} void earlyConfigure(BSGEarlyConfiguration *) noexcept {} void earlySetup() noexcept {} @@ -62,27 +61,22 @@ class SpanLifecycleHandlerImpl: public SpanLifecycleHandler { BugsnagPerformanceSpanCondition *onSpanBlocked(BugsnagPerformanceSpan *blocked, NSTimeInterval timeout) noexcept; void onSpanCancelled(BugsnagPerformanceSpan *span) noexcept; - void sweep() noexcept; - private: std::shared_ptr sampler_; - std::shared_ptr spanStackingHandler_; std::shared_ptr conditionTimeoutExecutor_; std::shared_ptr plainSpanFactory_; + std::shared_ptr store_; FrameMetricsCollector *frameMetricsCollector_; bool isStarted_{false}; void (^onSpanStarted_)(){ ^(){} }; std::shared_ptr batch_; - std::shared_ptr potentiallyOpenSpans_; - NSMutableArray *blockedSpans_; BSGPrioritizedStore *onSpanStartCallbacks_; BSGPrioritizedStore *onSpanEndCallbacks_; BugsnagPerformanceEnabledMetrics *enabledMetrics_{[BugsnagPerformanceEnabledMetrics withAllEnabled]}; void processClosedSpan(BugsnagPerformanceSpan *span) noexcept; - void cancelQueuedSpan(BugsnagPerformanceSpan *span) noexcept; bool shouldInstrumentRendering(BugsnagPerformanceSpan *span) noexcept; void processFrameMetrics(BugsnagPerformanceSpan *span) noexcept; void callOnSpanStartCallbacks(BugsnagPerformanceSpan *span) noexcept; diff --git a/Sources/BugsnagPerformance/Private/SpanLifecycle/SpanLifecycleHandlerImpl.mm b/Sources/BugsnagPerformance/Private/SpanLifecycle/SpanLifecycleHandlerImpl.mm index 0c2d9692..1a092675 100644 --- a/Sources/BugsnagPerformance/Private/SpanLifecycle/SpanLifecycleHandlerImpl.mm +++ b/Sources/BugsnagPerformance/Private/SpanLifecycle/SpanLifecycleHandlerImpl.mm @@ -22,10 +22,7 @@ if (shouldInstrumentRendering(span)) { span.startFramerateSnapshot = [frameMetricsCollector_ currentSnapshot]; } - if (options.makeCurrentContext) { - spanStackingHandler_->push(span); - } - potentiallyOpenSpans_->add(span); + store_->addNewSpan(span, options.makeCurrentContext); callOnSpanStartCallbacks(span); onSpanStarted_(); } @@ -41,10 +38,6 @@ SpanLifecycleHandlerImpl::onSpanClosed(BugsnagPerformanceSpan *span) noexcept { if (!span.isBlocked) { processClosedSpan(span); - } else { - @synchronized (this->blockedSpans_) { - [blockedSpans_ addObject:span]; - } } } @@ -63,9 +56,8 @@ } } onUpgradedCallback:^BugsnagPerformanceSpanContext *(BugsnagPerformanceSpanCondition *c) { __strong BugsnagPerformanceSpan *strongSpan = c.span; - @synchronized (this->blockedSpans_) { - this->conditionTimeoutExecutor_->cancelTimeout(c); - } + this->conditionTimeoutExecutor_->cancelTimeout(c); + @synchronized (c) { if (c.isActive) { return strongSpan; @@ -76,22 +68,24 @@ [condition addOnDeactivatedCallback:^(BugsnagPerformanceSpanCondition *c) { __strong BugsnagPerformanceSpan *strongSpan = c.span; if (strongSpan.state == SpanStateEnded && !strongSpan.isBlocked) { - @synchronized (this->blockedSpans_) { - [this->blockedSpans_ removeObject:strongSpan]; - this->conditionTimeoutExecutor_->cancelTimeout(c); - } + this->store_->removeSpanFromBlocked(span); + this->conditionTimeoutExecutor_->cancelTimeout(c); this->onSpanClosed(strongSpan); } }]; - this->conditionTimeoutExecutor_->sheduleTimeout(condition, timeout); + conditionTimeoutExecutor_->scheduleTimeout(condition, timeout); + store_->addSpanToBlocked(span); return condition; } void SpanLifecycleHandlerImpl::onSpanCancelled(BugsnagPerformanceSpan *span) noexcept { - if (span) { - batch_->removeSpan(span.traceIdHi, span.traceIdLo, span.spanId); - [blockedSpans_ removeObject:span]; + if (!span) { + return; + } + batch_->removeSpan(span.traceIdHi, span.traceIdLo, span.spanId); + if (span.isBlocked) { + store_->removeSpanFromBlocked(span); } } @@ -225,15 +219,9 @@ void SpanLifecycleHandlerImpl::abortAllOpenSpans() noexcept { - potentiallyOpenSpans_->abortAllOpen(); -} - -void -SpanLifecycleHandlerImpl::sweep() noexcept { - constexpr unsigned minEntriesBeforeCompacting = 10000; - if (potentiallyOpenSpans_->count() >= minEntriesBeforeCompacting) { - potentiallyOpenSpans_->compact(); - } + store_->performActionAndClearOpenSpans(^(BugsnagPerformanceSpan *span) { + [span abortIfOpen]; + }); } void @@ -244,7 +232,7 @@ } } - spanStackingHandler_->onSpanClosed(span.spanId); + store_->removeSpan(span); if(span.state == SpanStateAborted) { return; diff --git a/Sources/BugsnagPerformance/Private/SpanStore/SpanStore.h b/Sources/BugsnagPerformance/Private/SpanStore/SpanStore.h new file mode 100644 index 00000000..9e525ffe --- /dev/null +++ b/Sources/BugsnagPerformance/Private/SpanStore/SpanStore.h @@ -0,0 +1,32 @@ +// +// SpanStore.h +// BugsnagPerformance +// +// Created by Robert Bartoszewski on 10/10/2025. +// Copyright © 2025 Bugsnag. All rights reserved. +// + +#import + +namespace bugsnag { + +/** + * SpanStore keeps track of all open spans. It allows adding and removing spans, + * quering the current stack as well as performing actions on all open spans + * (e.g., when the app goes to background). + */ +class SpanStore { +public: + virtual void addNewSpan(BugsnagPerformanceSpan *span, bool makeCurrentContext) noexcept = 0; + virtual void removeSpan(BugsnagPerformanceSpan *span) noexcept = 0; + virtual void performActionAndClearOpenSpans(void (^action)(BugsnagPerformanceSpan *span)) noexcept = 0; + + virtual void addSpanToBlocked(BugsnagPerformanceSpan *span) noexcept = 0; + virtual void removeSpanFromBlocked(BugsnagPerformanceSpan *span) noexcept = 0; + + virtual bool hasSpanOnCurrentStack(NSString *attribute, NSString *value) noexcept = 0; + virtual void sweep() noexcept = 0; + + virtual ~SpanStore() {} +}; +} diff --git a/Sources/BugsnagPerformance/Private/SpanStore/SpanStoreImpl.h b/Sources/BugsnagPerformance/Private/SpanStore/SpanStoreImpl.h new file mode 100644 index 00000000..20034826 --- /dev/null +++ b/Sources/BugsnagPerformance/Private/SpanStore/SpanStoreImpl.h @@ -0,0 +1,40 @@ +// +// SpanStoreImpl.h +// BugsnagPerformance +// +// Created by Robert Bartoszewski on 10/10/2025. +// Copyright © 2025 Bugsnag. All rights reserved. +// + +#import "SpanStore.h" +#import "../SpanStackingHandler.h" +#import "../WeakSpansList.h" + +#import + +namespace bugsnag { + +class SpanStoreImpl: public SpanStore { +public: + SpanStoreImpl(std::shared_ptr spanStackingHandler) noexcept + : spanStackingHandler_(spanStackingHandler) + , blockedSpans_([NSMutableArray new]) + , potentiallyOpenSpans_(std::make_shared()) {} + + void addNewSpan(BugsnagPerformanceSpan *span, bool makeCurrentContext) noexcept; + void removeSpan(BugsnagPerformanceSpan *span) noexcept; + void addSpanToBlocked(BugsnagPerformanceSpan *span) noexcept; + void removeSpanFromBlocked(BugsnagPerformanceSpan *span) noexcept; + void performActionAndClearOpenSpans(void (^action)(BugsnagPerformanceSpan *span)) noexcept; + bool hasSpanOnCurrentStack(NSString *attribute, NSString *value) noexcept; + void sweep() noexcept; + +private: + std::shared_ptr spanStackingHandler_; + std::shared_ptr potentiallyOpenSpans_; + NSMutableArray *blockedSpans_; + std::mutex blockedSpansMutex_; + + SpanStoreImpl() = delete; +}; +} diff --git a/Sources/BugsnagPerformance/Private/SpanStore/SpanStoreImpl.mm b/Sources/BugsnagPerformance/Private/SpanStore/SpanStoreImpl.mm new file mode 100644 index 00000000..7d2957f2 --- /dev/null +++ b/Sources/BugsnagPerformance/Private/SpanStore/SpanStoreImpl.mm @@ -0,0 +1,52 @@ +// +// SpanStoreImpl.mm +// BugsnagPerformance +// +// Created by Robert Bartoszewski on 10/10/2025. +// Copyright © 2025 Bugsnag. All rights reserved. +// + +#import "SpanStoreImpl.h" + +void +SpanStoreImpl::addNewSpan(BugsnagPerformanceSpan *span, bool makeCurrentContext) noexcept { + if (makeCurrentContext) { + spanStackingHandler_->push(span); + } + potentiallyOpenSpans_->add(span); +} + +void +SpanStoreImpl::removeSpan(BugsnagPerformanceSpan *span) noexcept { + spanStackingHandler_->onSpanClosed(span.spanId); +} + +void +SpanStoreImpl::addSpanToBlocked(BugsnagPerformanceSpan *span) noexcept { + std::lock_guard guard(blockedSpansMutex_); + [blockedSpans_ addObject:span]; +} + +void +SpanStoreImpl::removeSpanFromBlocked(BugsnagPerformanceSpan *span) noexcept { + std::lock_guard guard(blockedSpansMutex_); + [blockedSpans_ removeObject:span]; +} + +void +SpanStoreImpl::performActionAndClearOpenSpans(void (^action)(BugsnagPerformanceSpan *span)) noexcept { + potentiallyOpenSpans_->performActionAndClear(action); +} + +bool +SpanStoreImpl::hasSpanOnCurrentStack(NSString *attribute, NSString *value) noexcept { + return spanStackingHandler_->hasSpanWithAttribute(attribute, value); +} + +void +SpanStoreImpl::sweep() noexcept { + constexpr unsigned minEntriesBeforeCompacting = 10000; + if (potentiallyOpenSpans_->count() >= minEntriesBeforeCompacting) { + potentiallyOpenSpans_->compact(); + } +} diff --git a/Sources/BugsnagPerformance/Private/Tracer.h b/Sources/BugsnagPerformance/Private/Tracer.h index c9116a86..41dfc637 100644 --- a/Sources/BugsnagPerformance/Private/Tracer.h +++ b/Sources/BugsnagPerformance/Private/Tracer.h @@ -18,6 +18,7 @@ #import "SpanFactory/Network/NetworkSpanFactoryImpl.h" #import "SpanFactory/ViewLoad/ViewLoadSpanFactoryCallbacks.h" #import "SpanLifecycle/SpanLifecycleHandler.h" +#import "SpanStore/SpanStore.h" #import @@ -33,12 +34,12 @@ class Tracer: public PhasedStartup { std::shared_ptr viewLoadSpanFactory, std::shared_ptr networkSpanFactory, std::shared_ptr spanLifecycleHandler, - std::shared_ptr spanStackingHandler) noexcept + std::shared_ptr spanStore) noexcept : plainSpanFactory_(plainSpanFactory) , viewLoadSpanFactory_(viewLoadSpanFactory) , networkSpanFactory_(networkSpanFactory) , spanLifecycleHandler_(spanLifecycleHandler) - , spanStackingHandler_(spanStackingHandler) + , spanStore_(spanStore) { plainSpanFactory_->setup(createPlainSpanFactoryCallbacks()); viewLoadSpanFactory_->setup(createViewLoadSpanFactoryCallbacks()); @@ -79,7 +80,7 @@ class Tracer: public PhasedStartup { NSArray *conditionsToEndOnClose) noexcept; private: Tracer() = delete; - std::shared_ptr spanStackingHandler_; + std::shared_ptr spanStore_; std::shared_ptr plainSpanFactory_; std::shared_ptr viewLoadSpanFactory_; std::shared_ptr networkSpanFactory_; diff --git a/Sources/BugsnagPerformance/Private/Tracer.mm b/Sources/BugsnagPerformance/Private/Tracer.mm index bf60399c..f125a26a 100644 --- a/Sources/BugsnagPerformance/Private/Tracer.mm +++ b/Sources/BugsnagPerformance/Private/Tracer.mm @@ -101,7 +101,7 @@ return nil; }; callbacks.isViewLoadInProgress = ^BOOL() { - return blockThis->spanStackingHandler_->hasSpanWithAttribute(@"bugsnag.span.category", @"view_load"); + return blockThis->spanStore_->hasSpanOnCurrentStack(@"bugsnag.span.category", @"view_load"); }; auto onViewLoadSpanStarted = ^(NSString * _Nonnull className) { diff --git a/Sources/BugsnagPerformance/Private/WeakSpansList.h b/Sources/BugsnagPerformance/Private/WeakSpansList.h index 850015e5..cc12cd2d 100644 --- a/Sources/BugsnagPerformance/Private/WeakSpansList.h +++ b/Sources/BugsnagPerformance/Private/WeakSpansList.h @@ -55,19 +55,16 @@ class WeakSpansList { spans_ = newSpans; } } - - void abortAllUnconditionally() noexcept { + + void performActionAndClear(void (^action)(BugsnagPerformanceSpan *span)) noexcept { std::lock_guard guard(mutex_); - for (BSGWeakSpanPointer *ptr in spans_) { - [ptr.span abortUnconditionally]; - } - [spans_ removeAllObjects]; - } - - void abortAllOpen() noexcept { - std::lock_guard guard(mutex_); - for (BSGWeakSpanPointer *ptr in spans_) { - [ptr.span abortIfOpen]; + if (action) { + for (BSGWeakSpanPointer *ptr in spans_) { + __strong auto span = ptr.span; + if (span) { + action(span); + } + } } [spans_ removeAllObjects]; } diff --git a/Tests/BugsnagPerformanceTests/TracerTests.mm b/Tests/BugsnagPerformanceTests/TracerTests.mm deleted file mode 100644 index b5501961..00000000 --- a/Tests/BugsnagPerformanceTests/TracerTests.mm +++ /dev/null @@ -1,141 +0,0 @@ -// -// TracerTests.m -// BugsnagPerformance-iOSTests -// -// Created by Karl Stenerud on 18.10.23. -// Copyright © 2023 Bugsnag. All rights reserved. -// - -#import -#import "Tracer.h" -#import "AppStartupSpanFactoryImpl.h" -#import "ViewLoadSpanFactoryImpl.h" -#import "NetworkSpanFactoryImpl.h" -#import "SpanLifecycleHandlerImpl.h" - -using namespace bugsnag; - -@interface TracerTests : XCTestCase - -@end - -@implementation TracerTests - -- (void)testNetworkSpan { - auto stackingHandler = std::make_shared(); - auto sampler = std::make_shared(); - sampler->setProbability(1.0); - auto batch = std::make_shared(); - auto frameMetricsCollector = [FrameMetricsCollector new]; - auto conditionTimeoutExecutor = std::make_shared(); - auto spanAttributesProvider = std::make_shared(); - auto spanStartCallbacks = [BSGPrioritizedStore new]; - auto spanEndCallbacks = [BSGPrioritizedStore new]; - - auto plainSpanFactory = std::make_shared(sampler, stackingHandler, spanAttributesProvider); - auto appStartupSpanFactory = std::make_shared(plainSpanFactory, spanAttributesProvider); - auto viewLoadSpanFactory = std::make_shared(plainSpanFactory, spanAttributesProvider); - auto networkSpanFactory = std::make_shared(plainSpanFactory, spanAttributesProvider); - auto spanLifecycleHandler = std::make_shared(sampler, - stackingHandler, - conditionTimeoutExecutor, - plainSpanFactory, - batch, - frameMetricsCollector, - spanStartCallbacks, - spanEndCallbacks, - ^(){}); - - auto tracer = std::make_shared(plainSpanFactory, - viewLoadSpanFactory, - networkSpanFactory, - spanLifecycleHandler, - stackingHandler); - - SpanOptions spanOptions; - auto span = tracer->startNetworkSpan(@"GET", spanOptions); - XCTAssertEqual(span.kind, SPAN_KIND_CLIENT); - XCTAssertEqualObjects([span getAttribute:@"bugsnag.span.category"], @"network"); - XCTAssertEqualObjects(span.name, @"[HTTP/GET]"); -} - -- (void)testNetworkSpanWithUnknownMethod { - auto stackingHandler = std::make_shared(); - auto sampler = std::make_shared(); - sampler->setProbability(1.0); - auto batch = std::make_shared(); - auto frameMetricsCollector = [FrameMetricsCollector new]; - auto conditionTimeoutExecutor = std::make_shared(); - auto spanAttributesProvider = std::make_shared(); - auto spanStartCallbacks = [BSGPrioritizedStore new]; - auto spanEndCallbacks = [BSGPrioritizedStore new]; - - auto plainSpanFactory = std::make_shared(sampler, stackingHandler, spanAttributesProvider); - auto appStartupSpanFactory = std::make_shared(plainSpanFactory, spanAttributesProvider); - auto viewLoadSpanFactory = std::make_shared(plainSpanFactory, spanAttributesProvider); - auto networkSpanFactory = std::make_shared(plainSpanFactory, spanAttributesProvider); - - auto spanLifecycleHandler = std::make_shared(sampler, - stackingHandler, - conditionTimeoutExecutor, - plainSpanFactory, - batch, - frameMetricsCollector, - spanStartCallbacks, - spanEndCallbacks, - ^(){}); - - auto tracer = std::make_shared(plainSpanFactory, - viewLoadSpanFactory, - networkSpanFactory, - spanLifecycleHandler, - stackingHandler); - - SpanOptions spanOptions; - auto span = tracer->startNetworkSpan(nil, spanOptions); - XCTAssertEqual(span.kind, SPAN_KIND_CLIENT); - XCTAssertEqualObjects([span getAttribute:@"bugsnag.span.category"], @"network"); - XCTAssertEqualObjects(span.name, @"[HTTP/unknown]"); -} - -- (void)testStartSpan { - auto expectedSamplingProbability = 0.4; - auto stackingHandler = std::make_shared(); - auto sampler = std::make_shared(); - sampler->setProbability(expectedSamplingProbability); - auto batch = std::make_shared(); - auto frameMetricsCollector = [FrameMetricsCollector new]; - auto conditionTimeoutExecutor = std::make_shared(); - auto spanAttributesProvider = std::make_shared(); - auto spanStartCallbacks = [BSGPrioritizedStore new]; - auto spanEndCallbacks = [BSGPrioritizedStore new]; - - auto plainSpanFactory = std::make_shared(sampler, stackingHandler, spanAttributesProvider); - auto appStartupSpanFactory = std::make_shared(plainSpanFactory, spanAttributesProvider); - auto viewLoadSpanFactory = std::make_shared(plainSpanFactory, spanAttributesProvider); - auto networkSpanFactory = std::make_shared(plainSpanFactory, spanAttributesProvider); - - auto spanLifecycleHandler = std::make_shared(sampler, - stackingHandler, - conditionTimeoutExecutor, - plainSpanFactory, - batch, - frameMetricsCollector, - spanStartCallbacks, - spanEndCallbacks, - ^(){}); - - auto tracer = std::make_shared(plainSpanFactory, - viewLoadSpanFactory, - networkSpanFactory, - spanLifecycleHandler, - stackingHandler); - - SpanOptions spanOptions; - auto span = tracer->startSpan(@"TestSpan", spanOptions, BSGTriStateYes, @[]); - XCTAssertEqual(span.kind, SPAN_KIND_INTERNAL); - XCTAssertEqualObjects(span.name, @"TestSpan"); - XCTAssertEqual(span.samplingProbability, expectedSamplingProbability); -} - -@end