From 5ac70bd6bf100c167599d1456fd4e62d16fa2de7 Mon Sep 17 00:00:00 2001 From: Yousif Ahmed Date: Mon, 28 Jul 2025 11:57:45 +0100 Subject: [PATCH 01/53] Added basic module structure for BugsnagPerformanceNamedSpans --- BugsnagPerformance.xcodeproj/project.pbxproj | 511 +++++++++++++++++- BugsnagPerformanceNamedSpans.podspec.json | 32 ++ .../BugsnagPerformanceNamedSpans.md | 13 + .../BugsnagPerformanceNamedSpans.h | 19 + .../BugsnagPerformanceNamedSpansTests.m | 37 ++ Makefile | 6 +- Package.swift | 15 + .../BugsnagPerformanceNamedSpansPlugin.m | 9 + .../BugsnagPerformanceNamedSpansPlugin.h | 10 + .../BugsnagPerformanceNamedSpansTests.m | 9 + scripts/build-xcframework.sh | 7 +- 11 files changed, 651 insertions(+), 17 deletions(-) create mode 100644 BugsnagPerformanceNamedSpans.podspec.json create mode 100644 BugsnagPerformanceNamedSpans/BugsnagPerformanceNamedSpans.docc/BugsnagPerformanceNamedSpans.md create mode 100644 BugsnagPerformanceNamedSpans/BugsnagPerformanceNamedSpans.h create mode 100644 BugsnagPerformanceNamedSpansTests/BugsnagPerformanceNamedSpansTests.m create mode 100644 Sources/BugsnagPerformanceNamedSpans/Public/BugsnagPerformanceNamedSpansPlugin.m create mode 100644 Sources/BugsnagPerformanceNamedSpans/include/BugsnagPerformanceNamedSpans/BugsnagPerformanceNamedSpansPlugin.h create mode 100644 Tests/BugsnagPerformanceNamedSpansTests/BugsnagPerformanceNamedSpansTests.m diff --git a/BugsnagPerformance.xcodeproj/project.pbxproj b/BugsnagPerformance.xcodeproj/project.pbxproj index 38a432f7..d84e7088 100644 --- a/BugsnagPerformance.xcodeproj/project.pbxproj +++ b/BugsnagPerformance.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 46; + objectVersion = 70; objects = { /* Begin PBXBuildFile section */ @@ -198,6 +198,15 @@ CBF7C5E1297A8E9100D47719 /* Gzip.h in Headers */ = {isa = PBXBuildFile; fileRef = CBF7C5DF297A8E9100D47719 /* Gzip.h */; }; CBF7C5E2297A8E9100D47719 /* Gzip.m in Sources */ = {isa = PBXBuildFile; fileRef = CBF7C5E0297A8E9100D47719 /* Gzip.m */; }; CBF7C5E4297A964900D47719 /* libz.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = CBF7C5E3297A963A00D47719 /* libz.tbd */; }; + DA87DAEF2E2FDFE60040F086 /* BugsnagPerformanceNamedSpansPlugin.m in Sources */ = {isa = PBXBuildFile; fileRef = DA87DAEE2E2FDFE60040F086 /* BugsnagPerformanceNamedSpansPlugin.m */; }; + DA87DAF02E2FDFE60040F086 /* BugsnagPerformanceNamedSpansPlugin.m in Sources */ = {isa = PBXBuildFile; fileRef = DA87DAEE2E2FDFE60040F086 /* BugsnagPerformanceNamedSpansPlugin.m */; }; + DA8E5EE02E2FC3E80049F7AB /* BugsnagPerformanceNamedSpans.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DA8E5ED62E2FC3E80049F7AB /* BugsnagPerformanceNamedSpans.framework */; }; + DA8E5EEB2E2FC3E80049F7AB /* BugsnagPerformanceNamedSpans.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DA8E5ED62E2FC3E80049F7AB /* BugsnagPerformanceNamedSpans.framework */; }; + DA8E5EEC2E2FC3E80049F7AB /* BugsnagPerformanceNamedSpans.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = DA8E5ED62E2FC3E80049F7AB /* BugsnagPerformanceNamedSpans.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + DA8E5EF42E2FC45B0049F7AB /* BugsnagPerformance.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 72E4BB63359EA30E80116E2A /* BugsnagPerformance.framework */; }; + DA8E5EFE2E2FCA020049F7AB /* BugsnagPerformanceNamedSpansPlugin.h in Headers */ = {isa = PBXBuildFile; fileRef = DA8E5EFD2E2FCA020049F7AB /* BugsnagPerformanceNamedSpansPlugin.h */; }; + DA8E5EFF2E2FCA020049F7AB /* BugsnagPerformanceNamedSpansPlugin.h in Headers */ = {isa = PBXBuildFile; fileRef = DA8E5EFD2E2FCA020049F7AB /* BugsnagPerformanceNamedSpansPlugin.h */; }; + DA8E5F112E2FCDED0049F7AB /* BugsnagPerformanceNamedSpansTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DA8E5F0F2E2FCDED0049F7AB /* BugsnagPerformanceNamedSpansTests.m */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -271,6 +280,34 @@ remoteGlobalIDString = 96D415EF29E6ADC500AEE435; remoteInfo = BugsnagPerformanceTestsApp; }; + DA8E5EE12E2FC3E80049F7AB /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 5DB9DB2E409A040615FFD4FE /* Project object */; + proxyType = 1; + remoteGlobalIDString = DA8E5ED52E2FC3E80049F7AB; + remoteInfo = BugsnagPerformanceNamedSpans; + }; + DA8E5EE32E2FC3E80049F7AB /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 5DB9DB2E409A040615FFD4FE /* Project object */; + proxyType = 1; + remoteGlobalIDString = 96D415EF29E6ADC500AEE435; + remoteInfo = BugsnagPerformanceTestsApp; + }; + DA8E5EE92E2FC3E80049F7AB /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 5DB9DB2E409A040615FFD4FE /* Project object */; + proxyType = 1; + remoteGlobalIDString = DA8E5ED52E2FC3E80049F7AB; + remoteInfo = BugsnagPerformanceNamedSpans; + }; + DA8E5EF62E2FC45B0049F7AB /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 5DB9DB2E409A040615FFD4FE /* Project object */; + proxyType = 1; + remoteGlobalIDString = B4A4A4DE1EA43C6C4D6D3894; + remoteInfo = "BugsnagPerformance-iOS"; + }; /* End PBXContainerItemProxy section */ /* Begin PBXCopyFilesBuildPhase section */ @@ -281,6 +318,7 @@ dstSubfolderSpec = 10; files = ( 966DD5022A211D7F002030B2 /* BugsnagPerformance.framework in Embed Frameworks */, + DA8E5EEC2E2FC3E80049F7AB /* BugsnagPerformanceNamedSpans.framework in Embed Frameworks */, 094FA7482B10EDE700112ED4 /* BugsnagPerformanceSwift.framework in Embed Frameworks */, ); name = "Embed Frameworks"; @@ -489,9 +527,29 @@ CBF7C5DF297A8E9100D47719 /* Gzip.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Gzip.h; sourceTree = ""; }; CBF7C5E0297A8E9100D47719 /* Gzip.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = Gzip.m; sourceTree = ""; }; CBF7C5E3297A963A00D47719 /* libz.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libz.tbd; path = usr/lib/libz.tbd; sourceTree = SDKROOT; }; + DA87DAEE2E2FDFE60040F086 /* BugsnagPerformanceNamedSpansPlugin.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = BugsnagPerformanceNamedSpansPlugin.m; sourceTree = ""; }; + DA8E5ED62E2FC3E80049F7AB /* BugsnagPerformanceNamedSpans.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = BugsnagPerformanceNamedSpans.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + DA8E5EDF2E2FC3E80049F7AB /* BugsnagPerformanceNamedSpansTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = BugsnagPerformanceNamedSpansTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + DA8E5EFD2E2FCA020049F7AB /* BugsnagPerformanceNamedSpansPlugin.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = BugsnagPerformanceNamedSpansPlugin.h; sourceTree = ""; }; + DA8E5F0F2E2FCDED0049F7AB /* BugsnagPerformanceNamedSpansTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = BugsnagPerformanceNamedSpansTests.m; sourceTree = ""; }; EDE8339F2AF550E20042A78F /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; lastKnownFileType = text.xml; name = PrivacyInfo.xcprivacy; path = Sources/BugsnagPerformance/resources/PrivacyInfo.xcprivacy; sourceTree = ""; }; /* End PBXFileReference section */ +/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */ + DA8E5EED2E2FC3E80049F7AB /* PBXFileSystemSynchronizedBuildFileExceptionSet */ = { + isa = PBXFileSystemSynchronizedBuildFileExceptionSet; + publicHeaders = ( + BugsnagPerformanceNamedSpans.h, + ); + target = DA8E5ED52E2FC3E80049F7AB /* BugsnagPerformanceNamedSpans */; + }; +/* End PBXFileSystemSynchronizedBuildFileExceptionSet section */ + +/* Begin PBXFileSystemSynchronizedRootGroup section */ + DA8E5ED72E2FC3E80049F7AB /* BugsnagPerformanceNamedSpans */ = {isa = PBXFileSystemSynchronizedRootGroup; exceptions = (DA8E5EED2E2FC3E80049F7AB /* PBXFileSystemSynchronizedBuildFileExceptionSet */, ); explicitFileTypes = {}; explicitFolders = (); path = BugsnagPerformanceNamedSpans; sourceTree = ""; }; + DA8E5EE52E2FC3E80049F7AB /* BugsnagPerformanceNamedSpansTests */ = {isa = PBXFileSystemSynchronizedRootGroup; explicitFileTypes = {}; explicitFolders = (); path = BugsnagPerformanceNamedSpansTests; sourceTree = ""; }; +/* End PBXFileSystemSynchronizedRootGroup section */ + /* Begin PBXFrameworksBuildPhase section */ 0122C26229019CE1002D243C /* Frameworks */ = { isa = PBXFrameworksBuildPhase; @@ -550,16 +608,34 @@ buildActionMask = 2147483647; files = ( 966DD5012A211D7F002030B2 /* BugsnagPerformance.framework in Frameworks */, + DA8E5EEB2E2FC3E80049F7AB /* BugsnagPerformanceNamedSpans.framework in Frameworks */, 094FA7472B10EDE700112ED4 /* BugsnagPerformanceSwift.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; + DA8E5ED32E2FC3E80049F7AB /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + DA8E5EF42E2FC45B0049F7AB /* BugsnagPerformance.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + DA8E5EDC2E2FC3E80049F7AB /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + DA8E5EE02E2FC3E80049F7AB /* BugsnagPerformanceNamedSpans.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ 0122C21329019770002D243C /* Sources */ = { isa = PBXGroup; children = ( + DA8E5ECD2E2FC32C0049F7AB /* BugsnagPerformanceNamedSpans */, 0122C21429019770002D243C /* BugsnagPerformance */, 096CBC142B1752B000534F0C /* BugsnagPerformanceSwift */, ); @@ -755,8 +831,9 @@ 0122C25B29019C05002D243C /* Tests */ = { isa = PBXGroup; children = ( - 8A80DA882966CE940035BDA9 /* BugsnagPerformanceTestsSwift */, + DA8E5F002E2FCC100049F7AB /* BugsnagPerformanceNamedSpansTests */, 0122C25C29019C05002D243C /* BugsnagPerformanceTests */, + 8A80DA882966CE940035BDA9 /* BugsnagPerformanceTestsSwift */, ); path = Tests; sourceTree = ""; @@ -843,6 +920,8 @@ 094FA7322B10EDE600112ED4 /* BugsnagPerformanceSwift.framework */, 094FA73B2B10EDE700112ED4 /* BugsnagPerformanceSwiftTests.xctest */, 09D807F12B9756B000D01DF5 /* BugsnagPerformanceSwiftUI.framework */, + DA8E5ED62E2FC3E80049F7AB /* BugsnagPerformanceNamedSpans.framework */, + DA8E5EDF2E2FC3E80049F7AB /* BugsnagPerformanceNamedSpansTests.xctest */, ); name = Products; sourceTree = ""; @@ -903,6 +982,8 @@ 094FA7332B10EDE600112ED4 /* BugsnagPerformanceSwift */, 094FA7412B10EDE700112ED4 /* BugsnagPerformanceSwiftTests */, 09D807F22B9756B000D01DF5 /* BugsnagPerformanceSwiftUI */, + DA8E5ED72E2FC3E80049F7AB /* BugsnagPerformanceNamedSpans */, + DA8E5EE52E2FC3E80049F7AB /* BugsnagPerformanceNamedSpansTests */, 450CB5014AAED50046482044 /* Products */, 0122C25629019904002D243C /* Frameworks */, ); @@ -924,6 +1005,55 @@ path = NetworkInstrumentation; sourceTree = ""; }; + DA8E5ECD2E2FC32C0049F7AB /* BugsnagPerformanceNamedSpans */ = { + isa = PBXGroup; + children = ( + DA8E5ECE2E2FC3520049F7AB /* include */, + DA8E5ED02E2FC3710049F7AB /* Private */, + DA8E5ECF2E2FC36B0049F7AB /* Public */, + ); + path = BugsnagPerformanceNamedSpans; + sourceTree = ""; + }; + DA8E5ECE2E2FC3520049F7AB /* include */ = { + isa = PBXGroup; + children = ( + DA8E5EF92E2FC9AD0049F7AB /* BugsnagPerformanceNamedSpans */, + ); + path = include; + sourceTree = ""; + }; + DA8E5ECF2E2FC36B0049F7AB /* Public */ = { + isa = PBXGroup; + children = ( + DA87DAEE2E2FDFE60040F086 /* BugsnagPerformanceNamedSpansPlugin.m */, + ); + path = Public; + sourceTree = ""; + }; + DA8E5ED02E2FC3710049F7AB /* Private */ = { + isa = PBXGroup; + children = ( + ); + path = Private; + sourceTree = ""; + }; + DA8E5EF92E2FC9AD0049F7AB /* BugsnagPerformanceNamedSpans */ = { + isa = PBXGroup; + children = ( + DA8E5EFD2E2FCA020049F7AB /* BugsnagPerformanceNamedSpansPlugin.h */, + ); + path = BugsnagPerformanceNamedSpans; + sourceTree = ""; + }; + DA8E5F002E2FCC100049F7AB /* BugsnagPerformanceNamedSpansTests */ = { + isa = PBXGroup; + children = ( + DA8E5F0F2E2FCDED0049F7AB /* BugsnagPerformanceNamedSpansTests.m */, + ); + path = BugsnagPerformanceNamedSpansTests; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXHeadersBuildPhase section */ @@ -956,6 +1086,7 @@ 963726DE2DF0B43500C739E6 /* BugsnagPerformancePlugin.h in Headers */, 0122C24C29019770002D243C /* NetworkInstrumentation.h in Headers */, 963726C12DEA4E5700C739E6 /* BugsnagPerformancePriority.h in Headers */, + DA8E5EFE2E2FCA020049F7AB /* BugsnagPerformanceNamedSpansPlugin.h in Headers */, CB7FD930299D2EF200499E13 /* BugsnagPerformanceSpanOptions.h in Headers */, 965FBD152DF24D3300D6BACB /* Logging.h in Headers */, CB7881A129E698BF00A58906 /* PhasedStartup.h in Headers */, @@ -1027,6 +1158,14 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + DA8E5ED12E2FC3E80049F7AB /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + DA8E5EFF2E2FCA020049F7AB /* BugsnagPerformanceNamedSpansPlugin.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXHeadersBuildPhase section */ /* Begin PBXNativeTarget section */ @@ -1139,6 +1278,7 @@ dependencies = ( 966DD5042A211D7F002030B2 /* PBXTargetDependency */, 094FA7462B10EDE700112ED4 /* PBXTargetDependency */, + DA8E5EEA2E2FC3E80049F7AB /* PBXTargetDependency */, ); name = BugsnagPerformanceTestsApp; productName = BugsnagPerformanceTestsApp; @@ -1163,6 +1303,54 @@ productReference = 72E4BB63359EA30E80116E2A /* BugsnagPerformance.framework */; productType = "com.apple.product-type.framework"; }; + DA8E5ED52E2FC3E80049F7AB /* BugsnagPerformanceNamedSpans */ = { + isa = PBXNativeTarget; + buildConfigurationList = DA8E5EEE2E2FC3E80049F7AB /* Build configuration list for PBXNativeTarget "BugsnagPerformanceNamedSpans" */; + buildPhases = ( + DA8E5ED12E2FC3E80049F7AB /* Headers */, + DA8E5ED22E2FC3E80049F7AB /* Sources */, + DA8E5ED32E2FC3E80049F7AB /* Frameworks */, + DA8E5ED42E2FC3E80049F7AB /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + DA8E5EF72E2FC45B0049F7AB /* PBXTargetDependency */, + ); + fileSystemSynchronizedGroups = ( + DA8E5ED72E2FC3E80049F7AB /* BugsnagPerformanceNamedSpans */, + ); + name = BugsnagPerformanceNamedSpans; + packageProductDependencies = ( + ); + productName = BugsnagPerformanceNamedSpans; + productReference = DA8E5ED62E2FC3E80049F7AB /* BugsnagPerformanceNamedSpans.framework */; + productType = "com.apple.product-type.framework"; + }; + DA8E5EDE2E2FC3E80049F7AB /* BugsnagPerformanceNamedSpansTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = DA8E5EF12E2FC3E80049F7AB /* Build configuration list for PBXNativeTarget "BugsnagPerformanceNamedSpansTests" */; + buildPhases = ( + DA8E5EDB2E2FC3E80049F7AB /* Sources */, + DA8E5EDC2E2FC3E80049F7AB /* Frameworks */, + DA8E5EDD2E2FC3E80049F7AB /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + DA8E5EE22E2FC3E80049F7AB /* PBXTargetDependency */, + DA8E5EE42E2FC3E80049F7AB /* PBXTargetDependency */, + ); + fileSystemSynchronizedGroups = ( + DA8E5EE52E2FC3E80049F7AB /* BugsnagPerformanceNamedSpansTests */, + ); + name = BugsnagPerformanceNamedSpansTests; + packageProductDependencies = ( + ); + productName = BugsnagPerformanceNamedSpansTests; + productReference = DA8E5EDF2E2FC3E80049F7AB /* BugsnagPerformanceNamedSpansTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ @@ -1210,6 +1398,13 @@ B4A4A4DE1EA43C6C4D6D3894 = { LastSwiftMigration = 1540; }; + DA8E5ED52E2FC3E80049F7AB = { + CreatedOnToolsVersion = 16.2; + }; + DA8E5EDE2E2FC3E80049F7AB = { + CreatedOnToolsVersion = 16.2; + TestTargetID = 96D415EF29E6ADC500AEE435; + }; }; }; buildConfigurationList = 1B656A3BE38DEADBDE72618E /* Build configuration list for PBXProject "BugsnagPerformance" */; @@ -1232,6 +1427,8 @@ 96D415EF29E6ADC500AEE435 /* BugsnagPerformanceTestsApp */, 094FA73A2B10EDE700112ED4 /* BugsnagPerformanceSwiftTests */, 09D807F02B9756B000D01DF5 /* BugsnagPerformanceSwiftUI */, + DA8E5ED52E2FC3E80049F7AB /* BugsnagPerformanceNamedSpans */, + DA8E5EDE2E2FC3E80049F7AB /* BugsnagPerformanceNamedSpansTests */, ); }; /* End PBXProject section */ @@ -1289,6 +1486,20 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + DA8E5ED42E2FC3E80049F7AB /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + DA8E5EDD2E2FC3E80049F7AB /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ @@ -1384,6 +1595,7 @@ CBEC51DD2976F1F9009C0CE3 /* RetryQueue.mm in Sources */, 01A414CE2913C0F0003152A4 /* SpanAttributes.mm in Sources */, CB34771E29068C350033759C /* NSURLSession+Instrumentation.mm in Sources */, + DA87DAEF2E2FDFE60040F086 /* BugsnagPerformanceNamedSpansPlugin.m in Sources */, CBE8EA1B294B5AB800702950 /* Worker.mm in Sources */, CB78819C29E587CE00A58906 /* BugsnagPerformanceLibrary.mm in Sources */, 963726E02DF0B4AD00C739E6 /* BugsnagPerformancePluginContext.m in Sources */, @@ -1436,6 +1648,22 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + DA8E5ED22E2FC3E80049F7AB /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + DA87DAF02E2FDFE60040F086 /* BugsnagPerformanceNamedSpansPlugin.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + DA8E5EDB2E2FC3E80049F7AB /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + DA8E5F112E2FCDED0049F7AB /* BugsnagPerformanceNamedSpansTests.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ @@ -1489,6 +1717,26 @@ target = 96D415EF29E6ADC500AEE435 /* BugsnagPerformanceTestsApp */; targetProxy = 96D4160629E6F6B900AEE435 /* PBXContainerItemProxy */; }; + DA8E5EE22E2FC3E80049F7AB /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = DA8E5ED52E2FC3E80049F7AB /* BugsnagPerformanceNamedSpans */; + targetProxy = DA8E5EE12E2FC3E80049F7AB /* PBXContainerItemProxy */; + }; + DA8E5EE42E2FC3E80049F7AB /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 96D415EF29E6ADC500AEE435 /* BugsnagPerformanceTestsApp */; + targetProxy = DA8E5EE32E2FC3E80049F7AB /* PBXContainerItemProxy */; + }; + DA8E5EEA2E2FC3E80049F7AB /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = DA8E5ED52E2FC3E80049F7AB /* BugsnagPerformanceNamedSpans */; + targetProxy = DA8E5EE92E2FC3E80049F7AB /* PBXContainerItemProxy */; + }; + DA8E5EF72E2FC45B0049F7AB /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = B4A4A4DE1EA43C6C4D6D3894 /* BugsnagPerformance-iOS */; + targetProxy = DA8E5EF62E2FC45B0049F7AB /* PBXContainerItemProxy */; + }; /* End PBXTargetDependency section */ /* Begin PBXVariantGroup section */ @@ -1519,7 +1767,11 @@ DEVELOPMENT_TEAM = 7W9PZ27Y5F; GCC_WARN_PEDANTIC = NO; GENERATE_INFOPLIST_FILE = YES; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); MARKETING_VERSION = 1.14.0; PRODUCT_BUNDLE_IDENTIFIER = "com.bugsnag.BugsnagPerformance-iOSTests"; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -1541,7 +1793,11 @@ DEVELOPMENT_TEAM = 7W9PZ27Y5F; GCC_WARN_PEDANTIC = NO; GENERATE_INFOPLIST_FILE = YES; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); MARKETING_VERSION = 1.14.0; PRODUCT_BUNDLE_IDENTIFIER = "com.bugsnag.BugsnagPerformance-iOSTests"; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -1574,7 +1830,11 @@ INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2023 Bugsnag. All rights reserved."; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 13.0; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); LOCALIZATION_PREFERS_STRING_CATALOGS = YES; MARKETING_VERSION = 1.14.0; MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++"; @@ -1610,7 +1870,11 @@ INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2023 Bugsnag. All rights reserved."; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 13.0; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); LOCALIZATION_PREFERS_STRING_CATALOGS = YES; MARKETING_VERSION = 1.14.0; MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++"; @@ -1696,7 +1960,11 @@ INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2024 Bugsnag. All rights reserved."; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 17.2; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); LOCALIZATION_PREFERS_STRING_CATALOGS = YES; MARKETING_VERSION = 1.14.0; MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++"; @@ -1732,7 +2000,11 @@ INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2024 Bugsnag. All rights reserved."; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 17.2; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); LOCALIZATION_PREFERS_STRING_CATALOGS = YES; MARKETING_VERSION = 1.14.0; MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++"; @@ -1765,7 +2037,11 @@ GENERATE_INFOPLIST_FILE = YES; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 13.0; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); MARKETING_VERSION = 1.14.0; PRODUCT_BUNDLE_IDENTIFIER = com.bugsnag.BugsnagPerformance; PRODUCT_NAME = BugsnagPerformance; @@ -1795,7 +2071,11 @@ GENERATE_INFOPLIST_FILE = YES; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 13.0; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); MARKETING_VERSION = 1.14.0; PRODUCT_BUNDLE_IDENTIFIER = com.bugsnag.BugsnagPerformance; PRODUCT_NAME = BugsnagPerformance; @@ -1992,7 +2272,8 @@ MTL_FAST_MATH = YES; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = iphoneos; - SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; SWIFT_VERSION = 5.0; }; name = Release; @@ -2057,7 +2338,10 @@ INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; IPHONEOS_DEPLOYMENT_TARGET = 11.0; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); MARKETING_VERSION = 1.14.0; PRODUCT_BUNDLE_IDENTIFIER = com.bugsnag.BugsnagPerformanceTestsApp; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -2089,7 +2373,10 @@ INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; IPHONEOS_DEPLOYMENT_TARGET = 11.0; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); MARKETING_VERSION = 1.14.0; PRODUCT_BUNDLE_IDENTIFIER = com.bugsnag.BugsnagPerformanceTestsApp; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -2102,6 +2389,186 @@ }; name = Release; }; + DA8E5EEF2E2FC3E80049F7AB /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_ENABLE_OBJC_WEAK = NO; + CLANG_MODULES_AUTOLINK = NO; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES; + CLANG_WARN_FLOAT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = 7W9PZ27Y5F; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_MODULE_VERIFIER = NO; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_SYMBOLS_PRIVATE_EXTERN = YES; + GCC_TREAT_IMPLICIT_FUNCTION_DECLARATIONS_AS_ERRORS = YES; + GCC_TREAT_INCOMPATIBLE_POINTER_TYPE_WARNINGS_AS_ERRORS = YES; + GCC_WARN_INHIBIT_ALL_WARNINGS = NO; + GENERATE_INFOPLIST_FILE = YES; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + LOCALIZATION_PREFERS_STRING_CATALOGS = NO; + MARKETING_VERSION = 1.14.0; + MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c"; + MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = ""; + PRODUCT_BUNDLE_IDENTIFIER = com.bugsnag.BugsnagPerformanceNamedSpans; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SKIP_INSTALL = YES; + SWIFT_EMIT_LOC_STRINGS = NO; + TARGETED_DEVICE_FAMILY = "1,2"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + WARNING_CFLAGS = ( + "-Wcast-qual", + "-Wconditional-uninitialized", + "-Wcustom-atomic-properties", + "-Wdirect-ivar-access", + "-Wdocumentation-unknown-command", + "-Wformat-nonliteral", + "-Widiomatic-parentheses", + "-Wimplicit-int-float-conversion", + "-Wimport-preprocessor-directive-pedantic", + "-Wincomplete-implementation", + "-Wmissing-variable-declarations", + "-Wno-unknown-warning-option", + "-Wnonportable-include-path", + "-Wnullable-to-nonnull-conversion", + "-Woverriding-method-mismatch", + "-Wpointer-sign", + "-Wswitch-enum", + "-Wundef", + "-Wunused-macros", + ); + }; + name = Debug; + }; + DA8E5EF02E2FC3E80049F7AB /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_ENABLE_OBJC_WEAK = NO; + CLANG_MODULES_AUTOLINK = NO; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES; + CLANG_WARN_FLOAT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = 7W9PZ27Y5F; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_MODULE_VERIFIER = NO; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_SYMBOLS_PRIVATE_EXTERN = YES; + GCC_TREAT_IMPLICIT_FUNCTION_DECLARATIONS_AS_ERRORS = YES; + GCC_TREAT_INCOMPATIBLE_POINTER_TYPE_WARNINGS_AS_ERRORS = YES; + GCC_WARN_INHIBIT_ALL_WARNINGS = NO; + GENERATE_INFOPLIST_FILE = YES; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + LOCALIZATION_PREFERS_STRING_CATALOGS = NO; + MARKETING_VERSION = 1.14.0; + MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c"; + MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = ""; + PRODUCT_BUNDLE_IDENTIFIER = com.bugsnag.BugsnagPerformanceNamedSpans; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SKIP_INSTALL = YES; + SWIFT_EMIT_LOC_STRINGS = NO; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + WARNING_CFLAGS = ( + "-Wcast-qual", + "-Wconditional-uninitialized", + "-Wcustom-atomic-properties", + "-Wdirect-ivar-access", + "-Wdocumentation-unknown-command", + "-Wformat-nonliteral", + "-Widiomatic-parentheses", + "-Wimplicit-int-float-conversion", + "-Wimport-preprocessor-directive-pedantic", + "-Wincomplete-implementation", + "-Wmissing-variable-declarations", + "-Wno-unknown-warning-option", + "-Wnonportable-include-path", + "-Wnullable-to-nonnull-conversion", + "-Woverriding-method-mismatch", + "-Wpointer-sign", + "-Wswitch-enum", + "-Wundef", + "-Wunused-macros", + ); + }; + name = Release; + }; + DA8E5EF22E2FC3E80049F7AB /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = 7W9PZ27Y5F; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 18.2; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.bugsnag.BugsnagPerformanceNamedSpansTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = NO; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/BugsnagPerformanceTestsApp.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/BugsnagPerformanceTestsApp"; + }; + name = Debug; + }; + DA8E5EF32E2FC3E80049F7AB /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = 7W9PZ27Y5F; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 18.2; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.bugsnag.BugsnagPerformanceNamedSpansTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = NO; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/BugsnagPerformanceTestsApp.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/BugsnagPerformanceTestsApp"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ @@ -2177,6 +2644,24 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; + DA8E5EEE2E2FC3E80049F7AB /* Build configuration list for PBXNativeTarget "BugsnagPerformanceNamedSpans" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + DA8E5EEF2E2FC3E80049F7AB /* Debug */, + DA8E5EF02E2FC3E80049F7AB /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + DA8E5EF12E2FC3E80049F7AB /* Build configuration list for PBXNativeTarget "BugsnagPerformanceNamedSpansTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + DA8E5EF22E2FC3E80049F7AB /* Debug */, + DA8E5EF32E2FC3E80049F7AB /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; /* End XCConfigurationList section */ }; rootObject = 5DB9DB2E409A040615FFD4FE /* Project object */; diff --git a/BugsnagPerformanceNamedSpans.podspec.json b/BugsnagPerformanceNamedSpans.podspec.json new file mode 100644 index 00000000..b11a6919 --- /dev/null +++ b/BugsnagPerformanceNamedSpans.podspec.json @@ -0,0 +1,32 @@ +{ + "name": "BugsnagPerformanceNamedSpans", + "version": "1.14.0", + "summary": "The Bugsnag performance monitoring framework for iOS.", + "homepage": "https://github.com/bugsnag/bugsnag-cocoa-performance", + "license": { + "type": "MIT", + "file": "LICENSE" + }, + "authors": { + "Bugsnag": "notifiers@bugsnag.com" + }, + "source": { + "git": "https://github.com/bugsnag/bugsnag-cocoa-performance.git", + "tag": "v1.14.0" + }, + "platforms": { + "ios": "13.0" + }, + "requires_arc": true, + "source_files": "Sources/BugsnagPerformanceNamedSpans/{**/,}*.{m,h,mm,c,swift}", + "public_header_files": "Sources/BugsnagPerformanceNamedSpans/include/BugsnagPerformanceNamedSpans/*.h", + "prefix_header_file": false, + "dependencies": { + "BugsnagPerformance": "1.14.0" + }, + "libraries": "c++", + "pod_target_xcconfig": { + "CLANG_CXX_LIBRARY": "libc++", + "GCC_SYMBOLS_PRIVATE_EXTERN": "YES" + } +} diff --git a/BugsnagPerformanceNamedSpans/BugsnagPerformanceNamedSpans.docc/BugsnagPerformanceNamedSpans.md b/BugsnagPerformanceNamedSpans/BugsnagPerformanceNamedSpans.docc/BugsnagPerformanceNamedSpans.md new file mode 100644 index 00000000..2497511b --- /dev/null +++ b/BugsnagPerformanceNamedSpans/BugsnagPerformanceNamedSpans.docc/BugsnagPerformanceNamedSpans.md @@ -0,0 +1,13 @@ +# ``BugsnagPerformanceNamedSpans`` + +Summary + +## Overview + +Text + +## Topics + +### Group + +- ``Symbol`` \ No newline at end of file diff --git a/BugsnagPerformanceNamedSpans/BugsnagPerformanceNamedSpans.h b/BugsnagPerformanceNamedSpans/BugsnagPerformanceNamedSpans.h new file mode 100644 index 00000000..511f79a6 --- /dev/null +++ b/BugsnagPerformanceNamedSpans/BugsnagPerformanceNamedSpans.h @@ -0,0 +1,19 @@ +// +// BugsnagPerformanceNamedSpans.h +// BugsnagPerformanceNamedSpans +// +// Created by Yousif Ahmed on 22/07/2025. +// Copyright © 2025 Bugsnag. All rights reserved. +// + +#import + +//! Project version number for BugsnagPerformanceNamedSpans. +FOUNDATION_EXPORT double BugsnagPerformanceNamedSpansVersionNumber; + +//! Project version string for BugsnagPerformanceNamedSpans. +FOUNDATION_EXPORT const unsigned char BugsnagPerformanceNamedSpansVersionString[]; + +// In this header, you should import all the public headers of your framework using statements like #import + + diff --git a/BugsnagPerformanceNamedSpansTests/BugsnagPerformanceNamedSpansTests.m b/BugsnagPerformanceNamedSpansTests/BugsnagPerformanceNamedSpansTests.m new file mode 100644 index 00000000..29419806 --- /dev/null +++ b/BugsnagPerformanceNamedSpansTests/BugsnagPerformanceNamedSpansTests.m @@ -0,0 +1,37 @@ +// +// BugsnagPerformanceNamedSpansTests.m +// BugsnagPerformanceNamedSpansTests +// +// Created by Yousif Ahmed on 22/07/2025. +// Copyright © 2025 Bugsnag. All rights reserved. +// + +#import + +@interface BugsnagPerformanceNamedSpansTests : XCTestCase + +@end + +@implementation BugsnagPerformanceNamedSpansTests + +- (void)setUp { + // Put setup code here. This method is called before the invocation of each test method in the class. +} + +- (void)tearDown { + // Put teardown code here. This method is called after the invocation of each test method in the class. +} + +- (void)testExample { + // This is an example of a functional test case. + // Use XCTAssert and related functions to verify your tests produce the correct results. +} + +- (void)testPerformanceExample { + // This is an example of a performance test case. + [self measureBlock:^{ + // Put the code you want to measure the time of here. + }]; +} + +@end diff --git a/Makefile b/Makefile index fb4385a8..358aeb2b 100644 --- a/Makefile +++ b/Makefile @@ -189,6 +189,7 @@ endif # Workaround for CocoaPods/CocoaPods#8000 @EXPANDED_CODE_SIGN_IDENTITY="" EXPANDED_CODE_SIGN_IDENTITY_NAME="" EXPANDED_PROVISIONING_PROFILE="" pod trunk push --allow-warnings BugsnagPerformance.podspec.json @EXPANDED_CODE_SIGN_IDENTITY="" EXPANDED_CODE_SIGN_IDENTITY_NAME="" EXPANDED_PROVISIONING_PROFILE="" pod trunk push --allow-warnings --synchronous BugsnagPerformanceSwift.podspec.json + @EXPANDED_CODE_SIGN_IDENTITY="" EXPANDED_CODE_SIGN_IDENTITY_NAME="" EXPANDED_PROVISIONING_PROFILE="" pod trunk push --allow-warnings --synchronous BugsnagPerformanceNamedSpans.podspec.json bump: ## Bump the version numbers to $VERSION ifeq ($(VERSION),) @@ -202,6 +203,9 @@ endif @sed -i '' "s/\"version\": .*,/\"version\": \"$(VERSION)\",/" BugsnagPerformanceSwift.podspec.json @sed -i '' "s/\"tag\": .*/\"tag\": \"v$(VERSION)\"/" BugsnagPerformanceSwift.podspec.json @sed -i '' "s/\"BugsnagPerformance\": .*/\"BugsnagPerformance\": \"$(VERSION)\"/" BugsnagPerformanceSwift.podspec.json + @sed -i '' "s/\"version\": .*,/\"version\": \"$(VERSION)\",/" BugsnagPerformanceNamedSpans.podspec.json + @sed -i '' "s/\"tag\": .*/\"tag\": \"v$(VERSION)\"/" BugsnagPerformanceNamedSpans.podspec.json + @sed -i '' "s/\"BugsnagPerformance\": .*/\"BugsnagPerformance\": \"$(VERSION)\"/" BugsnagPerformanceNamedSpans.podspec.json @sed -i '' "s/## TBD/## $(VERSION) ($(DATE))/" CHANGELOG.md @sed -i '' -E "s/[0-9]+\.[0-9]+\.[0-9]+/$(VERSION)/g" .jazzy.yaml @sed -i '' -E "s/[0-9]+\.[0-9]+\.[0-9]+/$(VERSION)/g" Sources/BugsnagPerformance/Private/Version.h @@ -213,7 +217,7 @@ ifeq ($(VERSION),) @$(error VERSION is not defined. Run with `make VERSION=number prerelease`) endif @git checkout -b release-v$(VERSION) - @git add BugsnagPerformance.podspec.json VERSION CHANGELOG.md .jazzy.yaml Sources/BugsnagPerformance/Private/Version.h BugsnagPerformanceSwift.podspec.json BugsnagPerformance.xcodeproj/project.pbxproj + @git add BugsnagPerformance.podspec.json VERSION CHANGELOG.md .jazzy.yaml Sources/BugsnagPerformance/Private/Version.h BugsnagPerformanceSwift.podspec.json BugsnagPerformanceNamedSpans.podspec.json BugsnagPerformance.xcodeproj/project.pbxproj @git diff --exit-code || (echo "you have unstaged changes - Makefile may need updating to `git add` some more files"; exit 1) @git commit -m "Release v$(VERSION)" @git push origin release-v$(VERSION) diff --git a/Package.swift b/Package.swift index 81eeb296..e9e552d8 100644 --- a/Package.swift +++ b/Package.swift @@ -12,6 +12,9 @@ let package = Package( .library( name: "BugsnagPerformance", targets: ["BugsnagPerformance"]), + .library( + name: "BugsnagPerformanceNamedSpans", + targets: ["BugsnagPerformanceNamedSpans"]), .library( name: "BugsnagPerformanceSwift", targets: ["BugsnagPerformanceSwift"]), @@ -36,6 +39,15 @@ let package = Package( .linkedFramework("CoreTelephony"), ] ), + .target( + name: "BugsnagPerformanceNamedSpans", + dependencies: ["BugsnagPerformance"], + path: "Sources/BugsnagPerformanceNamedSpans", + cSettings: [ + .define("NS_BLOCK_ASSERTIONS", .when(configuration: .release)), + .define("NDEBUG", .when(configuration: .release)) + ] + ), .target( name: "BugsnagPerformanceSwift", dependencies: ["BugsnagPerformance"], @@ -56,6 +68,9 @@ let package = Package( .testTarget( name: "BugsnagPerformanceTestsSwift", dependencies: ["BugsnagPerformance"]), + .testTarget( + name: "BugsnagPerformanceNamedSpansTests", + dependencies: ["BugsnagPerformance"]), ], cxxLanguageStandard: .cxx14 ) diff --git a/Sources/BugsnagPerformanceNamedSpans/Public/BugsnagPerformanceNamedSpansPlugin.m b/Sources/BugsnagPerformanceNamedSpans/Public/BugsnagPerformanceNamedSpansPlugin.m new file mode 100644 index 00000000..979c127a --- /dev/null +++ b/Sources/BugsnagPerformanceNamedSpans/Public/BugsnagPerformanceNamedSpansPlugin.m @@ -0,0 +1,9 @@ +// +// BugsnagPerformanceNamedSpansPlugin.m +// BugsnagPerformanceNamedSpans +// +// Created by Yousif Ahmed on 22/07/2025. +// Copyright © 2025 Bugsnag. All rights reserved. +// + +#import diff --git a/Sources/BugsnagPerformanceNamedSpans/include/BugsnagPerformanceNamedSpans/BugsnagPerformanceNamedSpansPlugin.h b/Sources/BugsnagPerformanceNamedSpans/include/BugsnagPerformanceNamedSpans/BugsnagPerformanceNamedSpansPlugin.h new file mode 100644 index 00000000..09ba54fd --- /dev/null +++ b/Sources/BugsnagPerformanceNamedSpans/include/BugsnagPerformanceNamedSpans/BugsnagPerformanceNamedSpansPlugin.h @@ -0,0 +1,10 @@ +// +// BugsnagPerformanceNamedSpans.h +// BugsnagPerformanceNamedSpans +// +// Created by Yousif Ahmed on 22/07/2025. +// Copyright © 2025 Bugsnag. All rights reserved. +// + +NS_ASSUME_NONNULL_BEGIN +NS_ASSUME_NONNULL_END diff --git a/Tests/BugsnagPerformanceNamedSpansTests/BugsnagPerformanceNamedSpansTests.m b/Tests/BugsnagPerformanceNamedSpansTests/BugsnagPerformanceNamedSpansTests.m new file mode 100644 index 00000000..91723667 --- /dev/null +++ b/Tests/BugsnagPerformanceNamedSpansTests/BugsnagPerformanceNamedSpansTests.m @@ -0,0 +1,9 @@ +// +// BugsnagPerformanceNamedSpansTests.m +// BugsnagPerformanceNamedSpansTests +// +// Created by Yousif Ahmed on 22/07/2025. +// Copyright © 2025 Bugsnag. All rights reserved. +// + +#import diff --git a/scripts/build-xcframework.sh b/scripts/build-xcframework.sh index a508d58f..ac2a7e43 100755 --- a/scripts/build-xcframework.sh +++ b/scripts/build-xcframework.sh @@ -20,9 +20,10 @@ set -e -u -o pipefail # Scheme: The base name of the scheme to build with (minus any "-iOS" type suffix) # Namespaced: If TRUE, the schemes to load all have an ("-iOS", "-macOS") style namespaced suffix. TARGETS=( -# [Framework] [Project/WS Path] [Scheme] [NAMESPACED] - "BugsnagPerformance BugsnagPerformance.xcodeproj BugsnagPerformance TRUE" - "BugsnagPerformanceSwift BugsnagPerformance.xcodeproj BugsnagPerformanceSwift FALSE" +# [Framework] [Project/WS Path] [Scheme] [NAMESPACED] + "BugsnagPerformance BugsnagPerformance.xcodeproj BugsnagPerformance TRUE" + "BugsnagPerformanceSwift BugsnagPerformance.xcodeproj BugsnagPerformanceSwift FALSE" + "BugsnagPerformanceNamedSpans BugsnagPerformance.xcodeproj BugsnagPerformanceNamedSpans FALSE" ) # Platforms we are building for From 6d7b67b3b0f65768a8719555a1af8c4f75ec79ac Mon Sep 17 00:00:00 2001 From: Yousif Ahmed Date: Wed, 30 Jul 2025 13:08:41 +0100 Subject: [PATCH 02/53] Implemented BugsnagPerformanceNamedSpansPlugin --- BugsnagPerformance.xcodeproj/project.pbxproj | 20 ++-- .../Public/BugsnagPerformanceNamedSpanQuery.m | 20 ++++ .../BugsnagPerformanceNamedSpansPlugin.m | 9 -- .../BugsnagPerformanceNamedSpansPlugin.mm | 113 ++++++++++++++++++ .../BugsnagPerformanceNamedSpanQuery.h | 18 +++ .../BugsnagPerformanceNamedSpansPlugin.h | 9 +- 6 files changed, 171 insertions(+), 18 deletions(-) create mode 100644 Sources/BugsnagPerformanceNamedSpans/Public/BugsnagPerformanceNamedSpanQuery.m delete mode 100644 Sources/BugsnagPerformanceNamedSpans/Public/BugsnagPerformanceNamedSpansPlugin.m create mode 100644 Sources/BugsnagPerformanceNamedSpans/Public/BugsnagPerformanceNamedSpansPlugin.mm create mode 100644 Sources/BugsnagPerformanceNamedSpans/include/BugsnagPerformanceNamedSpans/BugsnagPerformanceNamedSpanQuery.h diff --git a/BugsnagPerformance.xcodeproj/project.pbxproj b/BugsnagPerformance.xcodeproj/project.pbxproj index d84e7088..eacf75d8 100644 --- a/BugsnagPerformance.xcodeproj/project.pbxproj +++ b/BugsnagPerformance.xcodeproj/project.pbxproj @@ -198,13 +198,13 @@ CBF7C5E1297A8E9100D47719 /* Gzip.h in Headers */ = {isa = PBXBuildFile; fileRef = CBF7C5DF297A8E9100D47719 /* Gzip.h */; }; CBF7C5E2297A8E9100D47719 /* Gzip.m in Sources */ = {isa = PBXBuildFile; fileRef = CBF7C5E0297A8E9100D47719 /* Gzip.m */; }; CBF7C5E4297A964900D47719 /* libz.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = CBF7C5E3297A963A00D47719 /* libz.tbd */; }; - DA87DAEF2E2FDFE60040F086 /* BugsnagPerformanceNamedSpansPlugin.m in Sources */ = {isa = PBXBuildFile; fileRef = DA87DAEE2E2FDFE60040F086 /* BugsnagPerformanceNamedSpansPlugin.m */; }; - DA87DAF02E2FDFE60040F086 /* BugsnagPerformanceNamedSpansPlugin.m in Sources */ = {isa = PBXBuildFile; fileRef = DA87DAEE2E2FDFE60040F086 /* BugsnagPerformanceNamedSpansPlugin.m */; }; + DA87DAF02E2FDFE60040F086 /* BugsnagPerformanceNamedSpansPlugin.mm in Sources */ = {isa = PBXBuildFile; fileRef = DA87DAEE2E2FDFE60040F086 /* BugsnagPerformanceNamedSpansPlugin.mm */; }; + DA87DAF92E3791CE0040F086 /* BugsnagPerformanceNamedSpanQuery.h in Headers */ = {isa = PBXBuildFile; fileRef = DA87DAF72E3791CE0040F086 /* BugsnagPerformanceNamedSpanQuery.h */; }; + DA87DAFB2E3792B10040F086 /* BugsnagPerformanceNamedSpanQuery.m in Sources */ = {isa = PBXBuildFile; fileRef = DA87DAFA2E3792B10040F086 /* BugsnagPerformanceNamedSpanQuery.m */; }; DA8E5EE02E2FC3E80049F7AB /* BugsnagPerformanceNamedSpans.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DA8E5ED62E2FC3E80049F7AB /* BugsnagPerformanceNamedSpans.framework */; }; DA8E5EEB2E2FC3E80049F7AB /* BugsnagPerformanceNamedSpans.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DA8E5ED62E2FC3E80049F7AB /* BugsnagPerformanceNamedSpans.framework */; }; DA8E5EEC2E2FC3E80049F7AB /* BugsnagPerformanceNamedSpans.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = DA8E5ED62E2FC3E80049F7AB /* BugsnagPerformanceNamedSpans.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; DA8E5EF42E2FC45B0049F7AB /* BugsnagPerformance.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 72E4BB63359EA30E80116E2A /* BugsnagPerformance.framework */; }; - DA8E5EFE2E2FCA020049F7AB /* BugsnagPerformanceNamedSpansPlugin.h in Headers */ = {isa = PBXBuildFile; fileRef = DA8E5EFD2E2FCA020049F7AB /* BugsnagPerformanceNamedSpansPlugin.h */; }; DA8E5EFF2E2FCA020049F7AB /* BugsnagPerformanceNamedSpansPlugin.h in Headers */ = {isa = PBXBuildFile; fileRef = DA8E5EFD2E2FCA020049F7AB /* BugsnagPerformanceNamedSpansPlugin.h */; }; DA8E5F112E2FCDED0049F7AB /* BugsnagPerformanceNamedSpansTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DA8E5F0F2E2FCDED0049F7AB /* BugsnagPerformanceNamedSpansTests.m */; }; /* End PBXBuildFile section */ @@ -527,7 +527,9 @@ CBF7C5DF297A8E9100D47719 /* Gzip.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Gzip.h; sourceTree = ""; }; CBF7C5E0297A8E9100D47719 /* Gzip.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = Gzip.m; sourceTree = ""; }; CBF7C5E3297A963A00D47719 /* libz.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libz.tbd; path = usr/lib/libz.tbd; sourceTree = SDKROOT; }; - DA87DAEE2E2FDFE60040F086 /* BugsnagPerformanceNamedSpansPlugin.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = BugsnagPerformanceNamedSpansPlugin.m; sourceTree = ""; }; + DA87DAEE2E2FDFE60040F086 /* BugsnagPerformanceNamedSpansPlugin.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = BugsnagPerformanceNamedSpansPlugin.mm; sourceTree = ""; }; + DA87DAF72E3791CE0040F086 /* BugsnagPerformanceNamedSpanQuery.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = BugsnagPerformanceNamedSpanQuery.h; sourceTree = ""; }; + DA87DAFA2E3792B10040F086 /* BugsnagPerformanceNamedSpanQuery.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = BugsnagPerformanceNamedSpanQuery.m; sourceTree = ""; }; DA8E5ED62E2FC3E80049F7AB /* BugsnagPerformanceNamedSpans.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = BugsnagPerformanceNamedSpans.framework; sourceTree = BUILT_PRODUCTS_DIR; }; DA8E5EDF2E2FC3E80049F7AB /* BugsnagPerformanceNamedSpansTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = BugsnagPerformanceNamedSpansTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; DA8E5EFD2E2FCA020049F7AB /* BugsnagPerformanceNamedSpansPlugin.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = BugsnagPerformanceNamedSpansPlugin.h; sourceTree = ""; }; @@ -1026,7 +1028,8 @@ DA8E5ECF2E2FC36B0049F7AB /* Public */ = { isa = PBXGroup; children = ( - DA87DAEE2E2FDFE60040F086 /* BugsnagPerformanceNamedSpansPlugin.m */, + DA87DAEE2E2FDFE60040F086 /* BugsnagPerformanceNamedSpansPlugin.mm */, + DA87DAFA2E3792B10040F086 /* BugsnagPerformanceNamedSpanQuery.m */, ); path = Public; sourceTree = ""; @@ -1042,6 +1045,7 @@ isa = PBXGroup; children = ( DA8E5EFD2E2FCA020049F7AB /* BugsnagPerformanceNamedSpansPlugin.h */, + DA87DAF72E3791CE0040F086 /* BugsnagPerformanceNamedSpanQuery.h */, ); path = BugsnagPerformanceNamedSpans; sourceTree = ""; @@ -1086,7 +1090,6 @@ 963726DE2DF0B43500C739E6 /* BugsnagPerformancePlugin.h in Headers */, 0122C24C29019770002D243C /* NetworkInstrumentation.h in Headers */, 963726C12DEA4E5700C739E6 /* BugsnagPerformancePriority.h in Headers */, - DA8E5EFE2E2FCA020049F7AB /* BugsnagPerformanceNamedSpansPlugin.h in Headers */, CB7FD930299D2EF200499E13 /* BugsnagPerformanceSpanOptions.h in Headers */, 965FBD152DF24D3300D6BACB /* Logging.h in Headers */, CB7881A129E698BF00A58906 /* PhasedStartup.h in Headers */, @@ -1162,6 +1165,7 @@ isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( + DA87DAF92E3791CE0040F086 /* BugsnagPerformanceNamedSpanQuery.h in Headers */, DA8E5EFF2E2FCA020049F7AB /* BugsnagPerformanceNamedSpansPlugin.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; @@ -1595,7 +1599,6 @@ CBEC51DD2976F1F9009C0CE3 /* RetryQueue.mm in Sources */, 01A414CE2913C0F0003152A4 /* SpanAttributes.mm in Sources */, CB34771E29068C350033759C /* NSURLSession+Instrumentation.mm in Sources */, - DA87DAEF2E2FDFE60040F086 /* BugsnagPerformanceNamedSpansPlugin.m in Sources */, CBE8EA1B294B5AB800702950 /* Worker.mm in Sources */, CB78819C29E587CE00A58906 /* BugsnagPerformanceLibrary.mm in Sources */, 963726E02DF0B4AD00C739E6 /* BugsnagPerformancePluginContext.m in Sources */, @@ -1652,7 +1655,8 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - DA87DAF02E2FDFE60040F086 /* BugsnagPerformanceNamedSpansPlugin.m in Sources */, + DA87DAF02E2FDFE60040F086 /* BugsnagPerformanceNamedSpansPlugin.mm in Sources */, + DA87DAFB2E3792B10040F086 /* BugsnagPerformanceNamedSpanQuery.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Sources/BugsnagPerformanceNamedSpans/Public/BugsnagPerformanceNamedSpanQuery.m b/Sources/BugsnagPerformanceNamedSpans/Public/BugsnagPerformanceNamedSpanQuery.m new file mode 100644 index 00000000..b9bcacb0 --- /dev/null +++ b/Sources/BugsnagPerformanceNamedSpans/Public/BugsnagPerformanceNamedSpanQuery.m @@ -0,0 +1,20 @@ +// +// BugsnagPerformanceNamedSpanQuery.m +// BugsnagPerformance +// +// Created by Yousif Ahmed on 28/07/2025. +// Copyright © 2025 Bugsnag. All rights reserved. +// +#import +#import + +static NSString * const spanNameAttributeKey = @"name"; + +@implementation BugsnagPerformanceNamedSpanQuery + ++ (instancetype)queryWithName:(NSString *)spanName { + NSDictionary *attributes = @{spanNameAttributeKey: spanName}; + return [self queryWithResultType:[BugsnagPerformanceSpan class] attributes:attributes]; +} + +@end diff --git a/Sources/BugsnagPerformanceNamedSpans/Public/BugsnagPerformanceNamedSpansPlugin.m b/Sources/BugsnagPerformanceNamedSpans/Public/BugsnagPerformanceNamedSpansPlugin.m deleted file mode 100644 index 979c127a..00000000 --- a/Sources/BugsnagPerformanceNamedSpans/Public/BugsnagPerformanceNamedSpansPlugin.m +++ /dev/null @@ -1,9 +0,0 @@ -// -// BugsnagPerformanceNamedSpansPlugin.m -// BugsnagPerformanceNamedSpans -// -// Created by Yousif Ahmed on 22/07/2025. -// Copyright © 2025 Bugsnag. All rights reserved. -// - -#import diff --git a/Sources/BugsnagPerformanceNamedSpans/Public/BugsnagPerformanceNamedSpansPlugin.mm b/Sources/BugsnagPerformanceNamedSpans/Public/BugsnagPerformanceNamedSpansPlugin.mm new file mode 100644 index 00000000..53cfb3de --- /dev/null +++ b/Sources/BugsnagPerformanceNamedSpans/Public/BugsnagPerformanceNamedSpansPlugin.mm @@ -0,0 +1,113 @@ +// +// BugsnagPerformanceNamedSpansPlugin.m +// BugsnagPerformanceNamedSpans +// +// Created by Yousif Ahmed on 22/07/2025. +// Copyright © 2025 Bugsnag. All rights reserved. +// + +#import +#import +#import +#import + +static const NSTimeInterval kSpanTimeoutInterval = 600; // 10 minutes + +@interface BugsnagPerformanceNamedSpansPlugin () { + std::map _spanTimeoutTimers; +} + +@property (nonatomic, strong) NSMutableDictionary *spansByName; + +@end + + +@implementation BugsnagPerformanceNamedSpansPlugin + +#pragma mark BugsnagPerformancePlugin + +#pragma clang diagnostic ignored "-Wdirect-ivar-access" +- (void)installWithContext:(BugsnagPerformancePluginContext *)context { + self.spansByName = [NSMutableDictionary new]; + + // Add spans to the cache when started + __block BugsnagPerformanceNamedSpansPlugin *blockSelf = self; + BugsnagPerformanceSpanStartCallback spanStartCallback = ^(BugsnagPerformanceSpan *span) { + @synchronized (blockSelf) { + blockSelf.spansByName[span.name] = span; + + // Add a 10 minute timeout to remove the span from the cache if not ended + void *key = (__bridge void *)span; + dispatch_source_t timer = [blockSelf createSpanTimeoutTimer:span]; + blockSelf->_spanTimeoutTimers[key] = timer; + } + }; + + // Remove spans from the cache when ended + BugsnagPerformanceSpanEndCallback spanEndCallback = ^(BugsnagPerformanceSpan *span) { + return [blockSelf endNativeSpan:span]; + }; + + [context addOnSpanStartCallback:spanStartCallback priority:BugsnagPerformancePriorityHigh]; + [context addOnSpanEndCallback:spanEndCallback priority:BugsnagPerformancePriorityLow]; + [context addSpanControlProvider:self]; +} + +- (void)start { +} + +#pragma mark BugsnagPerformanceSpanControlProvider + +- (id)getSpanControlsWithQuery:(BugsnagPerformanceSpanQuery *)query { + if ([query isKindOfClass:[BugsnagPerformanceNamedSpanQuery class]]) { + NSString *spanName = [query getAttributeWithName:@"name"]; + @synchronized (self) { + return self.spansByName[spanName]; + } + } + return nil; +} + +#pragma mark Private + +- (dispatch_source_t)createSpanTimeoutTimer:(BugsnagPerformanceSpan *)span { + __weak BugsnagPerformanceNamedSpansPlugin *weakSelf = self; + + dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, + dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0)); + + dispatch_source_set_timer(timer, + dispatch_time(DISPATCH_TIME_NOW, (int64_t)(kSpanTimeoutInterval * NSEC_PER_SEC)), + DISPATCH_TIME_FOREVER, + 0); + + dispatch_source_set_event_handler(timer, ^{ + [weakSelf endNativeSpan:span]; + }); + + dispatch_resume(timer); + return timer; +} + +#pragma clang diagnostic ignored "-Wdirect-ivar-access" +- (BOOL)endNativeSpan:(BugsnagPerformanceSpan *)span { + void *key = (__bridge void *)span; + + @synchronized (self) { + // Remove span from cache if it exists + if ([self.spansByName objectForKey:span.name] == span) { + [self.spansByName removeObjectForKey:span.name]; + } + + // Clean up timer for this span + auto& timerMap = _spanTimeoutTimers; + auto it = timerMap.find(key); + if (it != timerMap.end()) { + dispatch_source_cancel(it->second); + timerMap.erase(it); + } + } + return YES; +} + +@end diff --git a/Sources/BugsnagPerformanceNamedSpans/include/BugsnagPerformanceNamedSpans/BugsnagPerformanceNamedSpanQuery.h b/Sources/BugsnagPerformanceNamedSpans/include/BugsnagPerformanceNamedSpans/BugsnagPerformanceNamedSpanQuery.h new file mode 100644 index 00000000..66554594 --- /dev/null +++ b/Sources/BugsnagPerformanceNamedSpans/include/BugsnagPerformanceNamedSpans/BugsnagPerformanceNamedSpanQuery.h @@ -0,0 +1,18 @@ +// +// BugsnagPerformanceNamedSpanQuery.h +// BugsnagPerformanceNamedSpans +// +// Created by Yousif Ahmed on 28/07/2025. +// Copyright © 2025 Bugsnag. All rights reserved. +// +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface BugsnagPerformanceNamedSpanQuery : BugsnagPerformanceSpanQuery + ++ (instancetype)queryWithName:(nonnull NSString *)name; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Sources/BugsnagPerformanceNamedSpans/include/BugsnagPerformanceNamedSpans/BugsnagPerformanceNamedSpansPlugin.h b/Sources/BugsnagPerformanceNamedSpans/include/BugsnagPerformanceNamedSpans/BugsnagPerformanceNamedSpansPlugin.h index 09ba54fd..a8a55d95 100644 --- a/Sources/BugsnagPerformanceNamedSpans/include/BugsnagPerformanceNamedSpans/BugsnagPerformanceNamedSpansPlugin.h +++ b/Sources/BugsnagPerformanceNamedSpans/include/BugsnagPerformanceNamedSpans/BugsnagPerformanceNamedSpansPlugin.h @@ -1,10 +1,17 @@ // -// BugsnagPerformanceNamedSpans.h +// BugsnagPerformanceNamedSpansPlugin.h // BugsnagPerformanceNamedSpans // // Created by Yousif Ahmed on 22/07/2025. // Copyright © 2025 Bugsnag. All rights reserved. // +#import +#import NS_ASSUME_NONNULL_BEGIN + +@interface BugsnagPerformanceNamedSpansPlugin: NSObject + +@end + NS_ASSUME_NONNULL_END From 94624967f4679407b5731da574fae2024f044db9 Mon Sep 17 00:00:00 2001 From: Yousif Ahmed Date: Wed, 30 Jul 2025 13:08:55 +0100 Subject: [PATCH 03/53] Added BugsnagPerformanceNamedSpans to example app --- Example/Example.xcodeproj/project.pbxproj | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Example/Example.xcodeproj/project.pbxproj b/Example/Example.xcodeproj/project.pbxproj index 32aaaa4d..3f249f6e 100644 --- a/Example/Example.xcodeproj/project.pbxproj +++ b/Example/Example.xcodeproj/project.pbxproj @@ -21,6 +21,7 @@ 09BEAE002B9752C1002312C9 /* BugsnagPerformanceSwift in Frameworks */ = {isa = PBXBuildFile; productRef = 09BEADFF2B9752C1002312C9 /* BugsnagPerformanceSwift */; }; CB572EF429C1B96100FD7A2A /* AnotherViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 011C69DE28F57F1500C5BA3C /* AnotherViewController.swift */; }; CBC90C4029C45DCB00280884 /* ForceUBSan.m in Sources */ = {isa = PBXBuildFile; fileRef = CBC90C3F29C45DCB00280884 /* ForceUBSan.m */; }; + DA87DAD52E2FD7840040F086 /* BugsnagPerformanceNamedSpans in Frameworks */ = {isa = PBXBuildFile; productRef = DA87DAD42E2FD7840040F086 /* BugsnagPerformanceNamedSpans */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ @@ -50,6 +51,7 @@ 0983A1752B14966B00DDF4FF /* BugsnagPerformance in Frameworks */, 09BEADFE2B9752C1002312C9 /* BugsnagPerformance in Frameworks */, 09BEAE002B9752C1002312C9 /* BugsnagPerformanceSwift in Frameworks */, + DA87DAD52E2FD7840040F086 /* BugsnagPerformanceNamedSpans in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -156,6 +158,7 @@ 0983A1742B14966B00DDF4FF /* BugsnagPerformance */, 09BEADFD2B9752C1002312C9 /* BugsnagPerformance */, 09BEADFF2B9752C1002312C9 /* BugsnagPerformanceSwift */, + DA87DAD42E2FD7840040F086 /* BugsnagPerformanceNamedSpans */, ); productName = Example; productReference = 010656A428DB2E8C00F2A9A6 /* Example.app */; @@ -575,6 +578,10 @@ isa = XCSwiftPackageProductDependency; productName = BugsnagPerformanceSwift; }; + DA87DAD42E2FD7840040F086 /* BugsnagPerformanceNamedSpans */ = { + isa = XCSwiftPackageProductDependency; + productName = BugsnagPerformanceNamedSpans; + }; /* End XCSwiftPackageProductDependency section */ }; rootObject = 0106569C28DB2E8C00F2A9A6 /* Project object */; From 198e6a897838a42994c453fec5a5cf2f7ef2aa3b Mon Sep 17 00:00:00 2001 From: Yousif Ahmed Date: Wed, 30 Jul 2025 13:10:46 +0100 Subject: [PATCH 04/53] Added unit tests for named spans plugin --- .../BugsnagPerformanceNamedSpansTests.m | 298 +++++++++++++++++- 1 file changed, 297 insertions(+), 1 deletion(-) diff --git a/Tests/BugsnagPerformanceNamedSpansTests/BugsnagPerformanceNamedSpansTests.m b/Tests/BugsnagPerformanceNamedSpansTests/BugsnagPerformanceNamedSpansTests.m index 91723667..e790bb1b 100644 --- a/Tests/BugsnagPerformanceNamedSpansTests/BugsnagPerformanceNamedSpansTests.m +++ b/Tests/BugsnagPerformanceNamedSpansTests/BugsnagPerformanceNamedSpansTests.m @@ -6,4 +6,300 @@ // Copyright © 2025 Bugsnag. All rights reserved. // -#import +#import +#import +#import +#import +#import +#import + +@interface BugsnagPerformanceNamedSpansTests : XCTestCase +@property (nonatomic, strong) BugsnagPerformanceNamedSpansPlugin *plugin; +@property (nonatomic, strong) BugsnagPerformancePluginContext *mockContext; +@end + +@interface FakeSpan : NSObject +@property (nonatomic, strong) NSString *name; +@property (nonatomic, assign) BOOL isEnded; +- (instancetype)initWithName:(NSString *)name; +@end + +@interface FakePluginContext : NSObject +@property (nonatomic, copy) BugsnagPerformanceSpanStartCallback spanStartCallback; +@property (nonatomic, copy) BugsnagPerformanceSpanEndCallback spanEndCallback; +@property (nonatomic, strong) NSMutableArray> *spanControlProviders; +- (void)addOnSpanStartCallback:(BugsnagPerformanceSpanStartCallback)callback priority:(BugsnagPerformancePriority)priority; +- (void)addOnSpanEndCallback:(BugsnagPerformanceSpanEndCallback)callback priority:(BugsnagPerformancePriority)priority; +- (void)addSpanControlProvider:(id)provider; +@end + +@implementation FakeSpan + +- (instancetype)initWithName:(NSString *)name { + if (self = [super init]) { + _name = name; + _isEnded = NO; + } + return self; +} + +- (void)end { + self.isEnded = YES; +} + +- (void)endWithEndTime:(NSDate *)endTime { + self.isEnded = YES; +} + +@end + +@implementation FakePluginContext + +- (instancetype)init { + if (self = [super init]) { + _spanControlProviders = [NSMutableArray new]; + } + return self; +} + +- (void)addOnSpanStartCallback:(BugsnagPerformanceSpanStartCallback)callback priority:(BugsnagPerformancePriority)priority { + self.spanStartCallback = callback; +} + +- (void)addOnSpanEndCallback:(BugsnagPerformanceSpanEndCallback)callback priority:(BugsnagPerformancePriority)priority { + self.spanEndCallback = callback; +} + +- (void)addSpanControlProvider:(id)provider { + [self.spanControlProviders addObject:provider]; +} + +@end + +@implementation BugsnagPerformanceNamedSpansTests + +- (void)setUp { + [super setUp]; + self.plugin = [[BugsnagPerformanceNamedSpansPlugin alloc] init]; + self.mockContext = (BugsnagPerformancePluginContext *)[[FakePluginContext alloc] init]; +} + +- (void)tearDown { + self.plugin = nil; + self.mockContext = nil; + [super tearDown]; +} + +#pragma mark - Installation Tests + +- (void)testInstallWithContext { + [self.plugin installWithContext:self.mockContext]; + + FakePluginContext *fakeContext = (FakePluginContext *)self.mockContext; + XCTAssertNotNil(fakeContext.spanStartCallback); + XCTAssertNotNil(fakeContext.spanEndCallback); + XCTAssertEqual(fakeContext.spanControlProviders.count, 1); + XCTAssertIdentical(fakeContext.spanControlProviders.firstObject, self.plugin); +} + +- (void)testStartMethod { + // start method should not throw + XCTAssertNoThrow([self.plugin start]); +} + +#pragma mark - Span Caching Tests + +- (void)testSpanStartCallbackCachesSpan { + [self.plugin installWithContext:self.mockContext]; + + FakeSpan *span = [[FakeSpan alloc] initWithName:@"test-span"]; + FakePluginContext *fakeContext = (FakePluginContext *)self.mockContext; + + // Simulate span start + fakeContext.spanStartCallback(span); + + // Verify span is cached + BugsnagPerformanceNamedSpanQuery *query = [BugsnagPerformanceNamedSpanQuery queryWithName:@"test-span"]; + id result = [self.plugin getSpanControlsWithQuery:query]; + + XCTAssertIdentical(result, span); +} + +- (void)testSpanEndCallbackRemovesSpanFromCache { + [self.plugin installWithContext:self.mockContext]; + + FakeSpan *span = [[FakeSpan alloc] initWithName:@"test-span"]; + FakePluginContext *fakeContext = (FakePluginContext *)self.mockContext; + + // Simulate span start and end + fakeContext.spanStartCallback(span); + BOOL result = fakeContext.spanEndCallback(span); + + XCTAssertTrue(result); + + // Verify span is removed from cache + BugsnagPerformanceNamedSpanQuery *query = [BugsnagPerformanceNamedSpanQuery queryWithName:@"test-span"]; + id cachedSpan = [self.plugin getSpanControlsWithQuery:query]; + + XCTAssertNil(cachedSpan); +} + +- (void)testMultipleSpansWithSameName { + [self.plugin installWithContext:self.mockContext]; + + FakeSpan *span1 = [[FakeSpan alloc] initWithName:@"test-span"]; + FakeSpan *span2 = [[FakeSpan alloc] initWithName:@"test-span"]; + FakePluginContext *fakeContext = (FakePluginContext *)self.mockContext; + + // Start first span + fakeContext.spanStartCallback(span1); + + // Start second span with same name (should replace first) + fakeContext.spanStartCallback(span2); + + // Verify second span is cached + BugsnagPerformanceNamedSpanQuery *query = [BugsnagPerformanceNamedSpanQuery queryWithName:@"test-span"]; + id result = [self.plugin getSpanControlsWithQuery:query]; + + XCTAssertIdentical(result, span2); +} + +- (void)testSpanEndOnlyRemovesCorrectSpan { + [self.plugin installWithContext:self.mockContext]; + + FakeSpan *span1 = [[FakeSpan alloc] initWithName:@"test-span"]; + FakeSpan *span2 = [[FakeSpan alloc] initWithName:@"test-span"]; + FakePluginContext *fakeContext = (FakePluginContext *)self.mockContext; + + // Start first span + fakeContext.spanStartCallback(span1); + + // Start second span with same name + fakeContext.spanStartCallback(span2); + + // End first span (should not remove from cache since span2 is now cached) + BOOL result = fakeContext.spanEndCallback(span1); + XCTAssertTrue(result); + + // Verify second span is still cached + BugsnagPerformanceNamedSpanQuery *query = [BugsnagPerformanceNamedSpanQuery queryWithName:@"test-span"]; + id cachedSpan = [self.plugin getSpanControlsWithQuery:query]; + + XCTAssertIdentical(cachedSpan, span2); +} + +#pragma mark - Span Control Provider Tests + +- (void)testGetSpanControlsWithNamedSpanQuery { + [self.plugin installWithContext:self.mockContext]; + + FakeSpan *span = [[FakeSpan alloc] initWithName:@"my-span"]; + FakePluginContext *fakeContext = (FakePluginContext *)self.mockContext; + + fakeContext.spanStartCallback(span); + + BugsnagPerformanceNamedSpanQuery *query = [BugsnagPerformanceNamedSpanQuery queryWithName:@"my-span"]; + id result = [self.plugin getSpanControlsWithQuery:query]; + + XCTAssertIdentical(result, span); +} + +- (void)testGetSpanControlsWithNonNamedSpanQuery { + [self.plugin installWithContext:self.mockContext]; + + BugsnagPerformanceSpanQuery *query = [BugsnagPerformanceSpanQuery queryWithResultType:[FakeSpan class]]; + id result = [self.plugin getSpanControlsWithQuery:query]; + + XCTAssertNil(result); +} + +- (void)testGetSpanControlsWithNonExistentSpanName { + [self.plugin installWithContext:self.mockContext]; + + BugsnagPerformanceNamedSpanQuery *query = [BugsnagPerformanceNamedSpanQuery queryWithName:@"non-existent"]; + id result = [self.plugin getSpanControlsWithQuery:query]; + + XCTAssertNil(result); +} + +- (void)testGetSpanControlsWithEmptyCache { + [self.plugin installWithContext:self.mockContext]; + + BugsnagPerformanceNamedSpanQuery *query = [BugsnagPerformanceNamedSpanQuery queryWithName:@"any-span"]; + id result = [self.plugin getSpanControlsWithQuery:query]; + + XCTAssertNil(result); +} + +#pragma mark - Timeout Tests + +- (void)testSpanTimeoutRemovesSpanFromCache { + [self.plugin installWithContext:self.mockContext]; + + FakeSpan *span = [[FakeSpan alloc] initWithName:@"timeout-span"]; + FakePluginContext *fakeContext = (FakePluginContext *)self.mockContext; + + // Start span + fakeContext.spanStartCallback(span); + + // Verify span is cached + BugsnagPerformanceNamedSpanQuery *query = [BugsnagPerformanceNamedSpanQuery queryWithName:@"timeout-span"]; + id result = [self.plugin getSpanControlsWithQuery:query]; + XCTAssertIdentical(result, span); + + // Wait a short time and verify span is still there (not testing actual 10 minute timeout) + // This test mainly verifies the timeout timer is set up + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ + BugsnagPerformanceNamedSpanQuery *query = [BugsnagPerformanceNamedSpanQuery queryWithName:@"timeout-span"]; + id result = [self.plugin getSpanControlsWithQuery:query]; + XCTAssertIdentical(result, span); + }); +} + +#pragma mark - Thread Safety Tests + +- (void)testConcurrentSpanOperations { + [self.plugin installWithContext:self.mockContext]; + + FakePluginContext *fakeContext = (FakePluginContext *)self.mockContext; + NSMutableArray *spans = [NSMutableArray new]; + + // Create multiple spans concurrently + dispatch_group_t group = dispatch_group_create(); + for (int i = 0; i < 10; i++) { + dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + FakeSpan *span = [[FakeSpan alloc] initWithName:[NSString stringWithFormat:@"span-%d", i]]; + [spans addObject:span]; + fakeContext.spanStartCallback(span); + }); + } + + // Wait for all spans to be created + dispatch_group_wait(group, DISPATCH_TIME_FOREVER); + + // Verify all spans are cached + for (int i = 0; i < 10; i++) { + BugsnagPerformanceNamedSpanQuery *query = [BugsnagPerformanceNamedSpanQuery queryWithName:[NSString stringWithFormat:@"span-%d", i]]; + id result = [self.plugin getSpanControlsWithQuery:query]; + XCTAssertNotNil(result); + } + + // End all spans concurrently + for (FakeSpan *span in spans) { + dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + fakeContext.spanEndCallback(span); + }); + } + + // Wait for all spans to be ended + dispatch_group_wait(group, DISPATCH_TIME_FOREVER); + + // Verify all spans are removed from cache + for (int i = 0; i < 10; i++) { + BugsnagPerformanceNamedSpanQuery *query = [BugsnagPerformanceNamedSpanQuery queryWithName:[NSString stringWithFormat:@"span-%d", i]]; + id result = [self.plugin getSpanControlsWithQuery:query]; + XCTAssertNil(result); + } +} + +@end From b997a05278549e53c66a43f6cb935a05e57dd086 Mon Sep 17 00:00:00 2001 From: Yousif Ahmed Date: Wed, 30 Jul 2025 13:16:43 +0100 Subject: [PATCH 05/53] Add missing import for BugsnagPerformancePriority --- .../Public/BugsnagPerformanceNamedSpansPlugin.mm | 1 + 1 file changed, 1 insertion(+) diff --git a/Sources/BugsnagPerformanceNamedSpans/Public/BugsnagPerformanceNamedSpansPlugin.mm b/Sources/BugsnagPerformanceNamedSpans/Public/BugsnagPerformanceNamedSpansPlugin.mm index 53cfb3de..02343949 100644 --- a/Sources/BugsnagPerformanceNamedSpans/Public/BugsnagPerformanceNamedSpansPlugin.mm +++ b/Sources/BugsnagPerformanceNamedSpans/Public/BugsnagPerformanceNamedSpansPlugin.mm @@ -9,6 +9,7 @@ #import #import #import +#import #import static const NSTimeInterval kSpanTimeoutInterval = 600; // 10 minutes From 6dbcbb416345bce73133650093acb4abf1c19d82 Mon Sep 17 00:00:00 2001 From: Yousif Ahmed Date: Wed, 30 Jul 2025 17:32:16 +0100 Subject: [PATCH 06/53] Added maze runner scenario for named spans plugin --- features/default/named_spans_plugin.feature | 10 ++++++ .../ios/Fixture.xcodeproj/project.pbxproj | 12 ++++--- .../project.pbxproj | 10 ++++-- .../Scenarios/NamedSpansPluginScenario.swift | 32 +++++++++++++++++++ 4 files changed, 57 insertions(+), 7 deletions(-) create mode 100644 features/default/named_spans_plugin.feature create mode 100644 features/fixtures/ios/Scenarios/NamedSpansPluginScenario.swift diff --git a/features/default/named_spans_plugin.feature b/features/default/named_spans_plugin.feature new file mode 100644 index 00000000..ecabdbca --- /dev/null +++ b/features/default/named_spans_plugin.feature @@ -0,0 +1,10 @@ +Feature: Named spans plugin + + Scenario: Spans can be accessed by name using the named spans plugin + Given I run "NamedSpansPluginScenario" + And I wait to receive a sampling request + And I wait to receive a trace + + Then a span named "Test Span" contains the attributes: + | attribute | type | value | + | queried | boolValue | true | diff --git a/features/fixtures/ios/Fixture.xcodeproj/project.pbxproj b/features/fixtures/ios/Fixture.xcodeproj/project.pbxproj index 0c3f4954..8326a283 100644 --- a/features/fixtures/ios/Fixture.xcodeproj/project.pbxproj +++ b/features/fixtures/ios/Fixture.xcodeproj/project.pbxproj @@ -100,6 +100,7 @@ CBF62109291A4F47004BEE0B /* RetryScenario.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBF62108291A4F47004BEE0B /* RetryScenario.swift */; }; DA58B7D62DF87EC500CB80A4 /* PluginInstallErrorScenario.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA58B7D52DF87EA900CB80A4 /* PluginInstallErrorScenario.swift */; }; DA85961E2DF718D100905922 /* PluginScenario.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA85961D2DF718C800905922 /* PluginScenario.swift */; }; + DA95ABF32E3A7E52001E6B0E /* NamedSpansPluginScenario.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA95ABF22E3A7E52001E6B0E /* NamedSpansPluginScenario.swift */; }; DADB14F92DE08A030006F44F /* OnStartCallbackScenario.swift in Sources */ = {isa = PBXBuildFile; fileRef = DADB14F82DE08A030006F44F /* OnStartCallbackScenario.swift */; }; /* End PBXBuildFile section */ @@ -203,6 +204,7 @@ CBF62108291A4F47004BEE0B /* RetryScenario.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RetryScenario.swift; sourceTree = ""; }; DA58B7D52DF87EA900CB80A4 /* PluginInstallErrorScenario.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PluginInstallErrorScenario.swift; sourceTree = ""; }; DA85961D2DF718C800905922 /* PluginScenario.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PluginScenario.swift; sourceTree = ""; }; + DA95ABF22E3A7E52001E6B0E /* NamedSpansPluginScenario.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NamedSpansPluginScenario.swift; sourceTree = ""; }; DADB14F82DE08A030006F44F /* OnStartCallbackScenario.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnStartCallbackScenario.swift; sourceTree = ""; }; /* End PBXFileReference section */ @@ -281,9 +283,6 @@ 01FE4DC128E1AF0700D1F239 /* Scenarios */ = { isa = PBXGroup; children = ( - DA58B7D52DF87EA900CB80A4 /* PluginInstallErrorScenario.swift */, - DA85961D2DF718C800905922 /* PluginScenario.swift */, - DADB14F82DE08A030006F44F /* OnStartCallbackScenario.swift */, 96D528CB2C72B14300FEA2E2 /* AppDataOverrideScenario.swift */, 01D3A7DF28F0290D0063D79E /* AutoInstrumentAppStartsScenario.swift */, 099331FB2C11F6CF009EC92F /* AutoInstrumentAVAssetScenario.swift */, @@ -312,6 +311,7 @@ CBAAE2582912601D006D4AA0 /* BatchingScenario.swift */, CB0496932913CA300097E526 /* BatchingWithTimeoutScenario.swift */, 09301DC02B63A65A000A7C12 /* ComplexViewScenario.swift */, + 098FC87B2D40EAB8001B627D /* CPUMetricsScenario.swift */, 09DC62292C6DE242000AA8E1 /* EarlySpanOnEndScenario.swift */, CBC90CDD29CDCFF700280884 /* FirstClassNoScenario.swift */, CBC90CDF29CDD02800280884 /* FirstClassYesScenario.swift */, @@ -342,10 +342,14 @@ CBEC89442A4ED0590088A3CE /* MaxPayloadSizeScenario.swift */, 09F3F5272D6C728300BAA0A3 /* MemoryMetricsScenario.swift */, 09E9BDAE2C80907B00DC7C8E /* ModifyEarlySpansScenario.swift */, + DA95ABF22E3A7E52001E6B0E /* NamedSpansPluginScenario.swift */, 09F025052BA08804007D9F73 /* ObjCURLSession.h */, 09F025062BA08804007D9F73 /* ObjCURLSession.m */, 098C3B2C2C523EC5006F9886 /* OnEndCallbackScenario.swift */, + DADB14F82DE08A030006F44F /* OnStartCallbackScenario.swift */, CBE615F629A4C1F0000E72D8 /* ParentSpanScenario.swift */, + DA58B7D52DF87EA900CB80A4 /* PluginInstallErrorScenario.swift */, + DA85961D2DF718C800905922 /* PluginScenario.swift */, CBE43A9829A8EFA3000B4205 /* ProbabilityExpiryScenario.swift */, 967F6F1929C4AD300054EED8 /* ReleaseStageNotEnabledScenario.swift */, 09F3F52F2D6F17B300BAA0A3 /* RenderingMetricsScenario.swift */, @@ -359,7 +363,6 @@ 96986D9F2D50654500A44C34 /* SpanConditionsMultipleConditionsScenario.swift */, 96986D9D2D5060E600A44C34 /* SpanConditionsSimpleConditionScenario.swift */, 09F025142BAC50EC007D9F73 /* ViewDidLoadDoesntTriggerScenario.swift */, - 098FC87B2D40EAB8001B627D /* CPUMetricsScenario.swift */, ); path = Scenarios; sourceTree = ""; @@ -456,6 +459,7 @@ files = ( CBE43A9929A8EFA3000B4205 /* ProbabilityExpiryScenario.swift in Sources */, 09F23AC12CF0CBB500F0D769 /* AutoInstrumentGenericViewLoadScenario2.swift in Sources */, + DA95ABF32E3A7E52001E6B0E /* NamedSpansPluginScenario.swift in Sources */, CBE0872B29F81BBB007455F2 /* AutoInstrumentNetworkNoParentScenario.swift in Sources */, 96D528CC2C72B14300FEA2E2 /* AppDataOverrideScenario.swift in Sources */, 09E045612C98649D003882D3 /* SetAttributeCountLimitScenario.swift in Sources */, diff --git a/features/fixtures/ios/FixtureXcFramework.xcodeproj/project.pbxproj b/features/fixtures/ios/FixtureXcFramework.xcodeproj/project.pbxproj index edf4471f..9c7d6bc8 100644 --- a/features/fixtures/ios/FixtureXcFramework.xcodeproj/project.pbxproj +++ b/features/fixtures/ios/FixtureXcFramework.xcodeproj/project.pbxproj @@ -100,6 +100,7 @@ CBF62109291A4F47004BEE0B /* RetryScenario.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBF62108291A4F47004BEE0B /* RetryScenario.swift */; }; DA58B7D42DF8365E00CB80A4 /* PluginScenario.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA58B7D32DF8365E00CB80A4 /* PluginScenario.swift */; }; DA58B7D82DF97D7200CB80A4 /* PluginInstallErrorScenario.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA58B7D72DF97D7200CB80A4 /* PluginInstallErrorScenario.swift */; }; + DA95ABF52E3A7FBA001E6B0E /* NamedSpansPluginScenario.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA95ABF42E3A7FBA001E6B0E /* NamedSpansPluginScenario.swift */; }; DADB14F72DDF852B0006F44F /* OnStartCallbackScenario.swift in Sources */ = {isa = PBXBuildFile; fileRef = DADB14F62DDF85210006F44F /* OnStartCallbackScenario.swift */; }; /* End PBXBuildFile section */ @@ -218,6 +219,7 @@ CBF62108291A4F47004BEE0B /* RetryScenario.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RetryScenario.swift; sourceTree = ""; }; DA58B7D32DF8365E00CB80A4 /* PluginScenario.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PluginScenario.swift; sourceTree = ""; }; DA58B7D72DF97D7200CB80A4 /* PluginInstallErrorScenario.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PluginInstallErrorScenario.swift; sourceTree = ""; }; + DA95ABF42E3A7FBA001E6B0E /* NamedSpansPluginScenario.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NamedSpansPluginScenario.swift; sourceTree = ""; }; DADB14F62DDF85210006F44F /* OnStartCallbackScenario.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnStartCallbackScenario.swift; sourceTree = ""; }; /* End PBXFileReference section */ @@ -298,9 +300,6 @@ 01FE4DC128E1AF0700D1F239 /* Scenarios */ = { isa = PBXGroup; children = ( - DA58B7D72DF97D7200CB80A4 /* PluginInstallErrorScenario.swift */, - DA58B7D32DF8365E00CB80A4 /* PluginScenario.swift */, - DADB14F62DDF85210006F44F /* OnStartCallbackScenario.swift */, 96D528CB2C72B14300FEA2E2 /* AppDataOverrideScenario.swift */, 01D3A7DF28F0290D0063D79E /* AutoInstrumentAppStartsScenario.swift */, 099331FB2C11F6CF009EC92F /* AutoInstrumentAVAssetScenario.swift */, @@ -358,10 +357,14 @@ CBEC89442A4ED0590088A3CE /* MaxPayloadSizeScenario.swift */, 09F3F52B2D6C72BD00BAA0A3 /* MemoryMetricsScenario.swift */, 09E9BDAE2C80907B00DC7C8E /* ModifyEarlySpansScenario.swift */, + DA95ABF42E3A7FBA001E6B0E /* NamedSpansPluginScenario.swift */, 09F025052BA08804007D9F73 /* ObjCURLSession.h */, 09F025062BA08804007D9F73 /* ObjCURLSession.m */, 098C3B2C2C523EC5006F9886 /* OnEndCallbackScenario.swift */, + DADB14F62DDF85210006F44F /* OnStartCallbackScenario.swift */, CBE615F629A4C1F0000E72D8 /* ParentSpanScenario.swift */, + DA58B7D72DF97D7200CB80A4 /* PluginInstallErrorScenario.swift */, + DA58B7D32DF8365E00CB80A4 /* PluginScenario.swift */, CBE43A9829A8EFA3000B4205 /* ProbabilityExpiryScenario.swift */, 967F6F1929C4AD300054EED8 /* ReleaseStageNotEnabledScenario.swift */, 96F129322DCE0CDD00A6FB2B /* RenderingMetricsScenario.swift */, @@ -543,6 +546,7 @@ 09637A412B060E7C00F4F776 /* CommandReaderThread.swift in Sources */, CBC90CE029CDD02800280884 /* FirstClassYesScenario.swift in Sources */, 09F025072BA08804007D9F73 /* ObjCURLSession.m in Sources */, + DA95ABF52E3A7FBA001E6B0E /* NamedSpansPluginScenario.swift in Sources */, 0988B5392CAD36C500D131B1 /* InfraCheckMinimalBugsnagScenario.swift in Sources */, CBC90CDE29CDCFF700280884 /* FirstClassNoScenario.swift in Sources */, CBC90C4329C466BD00280884 /* ForceUBSan.m in Sources */, diff --git a/features/fixtures/ios/Scenarios/NamedSpansPluginScenario.swift b/features/fixtures/ios/Scenarios/NamedSpansPluginScenario.swift new file mode 100644 index 00000000..e1ab8899 --- /dev/null +++ b/features/fixtures/ios/Scenarios/NamedSpansPluginScenario.swift @@ -0,0 +1,32 @@ +// +// NamedSpansPluginScenario.swift +// Fixture +// +// Created by Yousif Ahmed on 30/07/2025. +// + +import BugsnagPerformance +import BugsnagPerformanceNamedSpans + +@objcMembers +class NamedSpansPluginScenario: Scenario { + + override func setInitialBugsnagConfiguration() { + super.setInitialBugsnagConfiguration() + bugsnagPerfConfig.add(BugsnagPerformanceNamedSpansPlugin()) + } + + override func run() { + BugsnagPerformance.startSpan(name: "Test Span") + + let query = BugsnagPerformanceNamedSpanQuery(name: "Test Span") + + guard let spanControl = BugsnagPerformance.getSpanControls(with: query) as? BugsnagPerformanceSpan else { + return + } + + spanControl.setAttribute("queried", withValue: true) + spanControl.end() + } +} + From f8046a297fda04f3d8e66401e9d215e1b41876e3 Mon Sep 17 00:00:00 2001 From: Yousif Ahmed Date: Thu, 31 Jul 2025 10:20:33 +0100 Subject: [PATCH 07/53] Update unit tests --- .../BugsnagPerformanceNamedSpansTests.m | 69 +++++++++---------- 1 file changed, 34 insertions(+), 35 deletions(-) diff --git a/Tests/BugsnagPerformanceNamedSpansTests/BugsnagPerformanceNamedSpansTests.m b/Tests/BugsnagPerformanceNamedSpansTests/BugsnagPerformanceNamedSpansTests.m index e790bb1b..de593fa4 100644 --- a/Tests/BugsnagPerformanceNamedSpansTests/BugsnagPerformanceNamedSpansTests.m +++ b/Tests/BugsnagPerformanceNamedSpansTests/BugsnagPerformanceNamedSpansTests.m @@ -7,22 +7,36 @@ // #import -#import -#import +#import "BugsnagPerformanceNamedSpansPlugin.h" +#import "BugsnagPerformanceNamedSpanQuery.h" #import #import #import +@interface TestSpan: BugsnagPerformanceSpan + +-(instancetype)initWithName:(NSString *)name; + +@end + +@implementation TestSpan + +- (instancetype)initWithName:(NSString *)name { + TraceId tid = {.value = 1}; + if ((self = [super initWithTraceId:traceId spanId:1])) { + _name = name; + } + + return self; +} + +@end + @interface BugsnagPerformanceNamedSpansTests : XCTestCase @property (nonatomic, strong) BugsnagPerformanceNamedSpansPlugin *plugin; @property (nonatomic, strong) BugsnagPerformancePluginContext *mockContext; @end -@interface FakeSpan : NSObject -@property (nonatomic, strong) NSString *name; -@property (nonatomic, assign) BOOL isEnded; -- (instancetype)initWithName:(NSString *)name; -@end @interface FakePluginContext : NSObject @property (nonatomic, copy) BugsnagPerformanceSpanStartCallback spanStartCallback; @@ -33,25 +47,10 @@ - (void)addOnSpanEndCallback:(BugsnagPerformanceSpanEndCallback)callback priorit - (void)addSpanControlProvider:(id)provider; @end -@implementation FakeSpan - -- (instancetype)initWithName:(NSString *)name { - if (self = [super init]) { - _name = name; - _isEnded = NO; - } - return self; -} - -- (void)end { - self.isEnded = YES; +static BugsnagPerformanceSpan *createSpan(NSString *name) { + return [[TestSpan alloc] initWithName:name]; } -- (void)endWithEndTime:(NSDate *)endTime { - self.isEnded = YES; -} - -@end @implementation FakePluginContext @@ -112,7 +111,7 @@ - (void)testStartMethod { - (void)testSpanStartCallbackCachesSpan { [self.plugin installWithContext:self.mockContext]; - FakeSpan *span = [[FakeSpan alloc] initWithName:@"test-span"]; + BugsnagPerformanceSpan *span = createSpan(@"test-span"); FakePluginContext *fakeContext = (FakePluginContext *)self.mockContext; // Simulate span start @@ -128,7 +127,7 @@ - (void)testSpanStartCallbackCachesSpan { - (void)testSpanEndCallbackRemovesSpanFromCache { [self.plugin installWithContext:self.mockContext]; - FakeSpan *span = [[FakeSpan alloc] initWithName:@"test-span"]; + BugsnagPerformanceSpan *span = createSpan(@"test-span"); FakePluginContext *fakeContext = (FakePluginContext *)self.mockContext; // Simulate span start and end @@ -147,8 +146,8 @@ - (void)testSpanEndCallbackRemovesSpanFromCache { - (void)testMultipleSpansWithSameName { [self.plugin installWithContext:self.mockContext]; - FakeSpan *span1 = [[FakeSpan alloc] initWithName:@"test-span"]; - FakeSpan *span2 = [[FakeSpan alloc] initWithName:@"test-span"]; + BugsnagPerformanceSpan *span1 = createSpan(@"test-span"); + BugsnagPerformanceSpan *span2 = createSpan(@"test-span"); FakePluginContext *fakeContext = (FakePluginContext *)self.mockContext; // Start first span @@ -167,8 +166,8 @@ - (void)testMultipleSpansWithSameName { - (void)testSpanEndOnlyRemovesCorrectSpan { [self.plugin installWithContext:self.mockContext]; - FakeSpan *span1 = [[FakeSpan alloc] initWithName:@"test-span"]; - FakeSpan *span2 = [[FakeSpan alloc] initWithName:@"test-span"]; + BugsnagPerformanceSpan *span1 = createSpan(@"test-span"); + BugsnagPerformanceSpan *span2 = createSpan(@"test-span"); FakePluginContext *fakeContext = (FakePluginContext *)self.mockContext; // Start first span @@ -193,7 +192,7 @@ - (void)testSpanEndOnlyRemovesCorrectSpan { - (void)testGetSpanControlsWithNamedSpanQuery { [self.plugin installWithContext:self.mockContext]; - FakeSpan *span = [[FakeSpan alloc] initWithName:@"my-span"]; + BugsnagPerformanceSpan *span = createSpan(@"my-span"); FakePluginContext *fakeContext = (FakePluginContext *)self.mockContext; fakeContext.spanStartCallback(span); @@ -207,7 +206,7 @@ - (void)testGetSpanControlsWithNamedSpanQuery { - (void)testGetSpanControlsWithNonNamedSpanQuery { [self.plugin installWithContext:self.mockContext]; - BugsnagPerformanceSpanQuery *query = [BugsnagPerformanceSpanQuery queryWithResultType:[FakeSpan class]]; + BugsnagPerformanceSpanQuery *query = [BugsnagPerformanceSpanQuery queryWithResultType:[BugsnagPerformanceSpan class]]; id result = [self.plugin getSpanControlsWithQuery:query]; XCTAssertNil(result); @@ -236,7 +235,7 @@ - (void)testGetSpanControlsWithEmptyCache { - (void)testSpanTimeoutRemovesSpanFromCache { [self.plugin installWithContext:self.mockContext]; - FakeSpan *span = [[FakeSpan alloc] initWithName:@"timeout-span"]; + BugsnagPerformanceSpan *span = createSpan("timeout-span"); FakePluginContext *fakeContext = (FakePluginContext *)self.mockContext; // Start span @@ -268,7 +267,7 @@ - (void)testConcurrentSpanOperations { dispatch_group_t group = dispatch_group_create(); for (int i = 0; i < 10; i++) { dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - FakeSpan *span = [[FakeSpan alloc] initWithName:[NSString stringWithFormat:@"span-%d", i]]; + BugsnagPerformanceSpan *span = createSpan([NSString stringWithFormat:@"span-%d", i]); [spans addObject:span]; fakeContext.spanStartCallback(span); }); @@ -285,7 +284,7 @@ - (void)testConcurrentSpanOperations { } // End all spans concurrently - for (FakeSpan *span in spans) { + for (BugsnagPerformanceSpan *span in spans) { dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ fakeContext.spanEndCallback(span); }); From 8633b6ebea27b5ef8abadbb99de6dddb97f75e2b Mon Sep 17 00:00:00 2001 From: Yousif Ahmed Date: Thu, 31 Jul 2025 10:20:48 +0100 Subject: [PATCH 08/53] Update min deployment target --- BugsnagPerformance.xcodeproj/project.pbxproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/BugsnagPerformance.xcodeproj/project.pbxproj b/BugsnagPerformance.xcodeproj/project.pbxproj index eacf75d8..9fdcc473 100644 --- a/BugsnagPerformance.xcodeproj/project.pbxproj +++ b/BugsnagPerformance.xcodeproj/project.pbxproj @@ -2539,7 +2539,7 @@ ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu17; GENERATE_INFOPLIST_FILE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 18.2; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; LOCALIZATION_PREFERS_STRING_CATALOGS = YES; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = com.bugsnag.BugsnagPerformanceNamedSpansTests; @@ -2561,7 +2561,7 @@ ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu17; GENERATE_INFOPLIST_FILE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 18.2; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; LOCALIZATION_PREFERS_STRING_CATALOGS = YES; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = com.bugsnag.BugsnagPerformanceNamedSpansTests; From eb28e9100a19544c599f0634c3a94c3c2ed75a6b Mon Sep 17 00:00:00 2001 From: Robert Bartoszewski Date: Thu, 31 Jul 2025 13:54:59 +0200 Subject: [PATCH 09/53] Fixed .pbxproj issues --- BugsnagPerformance.xcodeproj/project.pbxproj | 130 +++++++------------ 1 file changed, 46 insertions(+), 84 deletions(-) diff --git a/BugsnagPerformance.xcodeproj/project.pbxproj b/BugsnagPerformance.xcodeproj/project.pbxproj index 9fdcc473..9d9201c6 100644 --- a/BugsnagPerformance.xcodeproj/project.pbxproj +++ b/BugsnagPerformance.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 70; + objectVersion = 46; objects = { /* Begin PBXBuildFile section */ @@ -135,6 +135,9 @@ 96F1292D2DCD141700A6FB2B /* BugsnagPerformanceRemoteSpanContext.mm in Sources */ = {isa = PBXBuildFile; fileRef = 96F1292C2DCD141700A6FB2B /* BugsnagPerformanceRemoteSpanContext.mm */; }; 96F1292F2DCD1BE200A6FB2B /* BugsnagPerformanceRemoteSpanContextTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 96F1292E2DCD1BE200A6FB2B /* BugsnagPerformanceRemoteSpanContextTests.m */; }; 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 */; }; + 96F417192E3B8E7F00EABD8E /* BugsnagPerformanceNamedSpansTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 96F417172E3B8E7F00EABD8E /* BugsnagPerformanceNamedSpansTests.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 */; }; @@ -458,6 +461,9 @@ 96F1292C2DCD141700A6FB2B /* BugsnagPerformanceRemoteSpanContext.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = BugsnagPerformanceRemoteSpanContext.mm; sourceTree = ""; }; 96F1292E2DCD1BE200A6FB2B /* BugsnagPerformanceRemoteSpanContextTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = BugsnagPerformanceRemoteSpanContextTests.m; sourceTree = ""; }; 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 = ""; }; + 96F417172E3B8E7F00EABD8E /* BugsnagPerformanceNamedSpansTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = BugsnagPerformanceNamedSpansTests.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 = ""; }; @@ -537,21 +543,6 @@ EDE8339F2AF550E20042A78F /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; lastKnownFileType = text.xml; name = PrivacyInfo.xcprivacy; path = Sources/BugsnagPerformance/resources/PrivacyInfo.xcprivacy; sourceTree = ""; }; /* End PBXFileReference section */ -/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */ - DA8E5EED2E2FC3E80049F7AB /* PBXFileSystemSynchronizedBuildFileExceptionSet */ = { - isa = PBXFileSystemSynchronizedBuildFileExceptionSet; - publicHeaders = ( - BugsnagPerformanceNamedSpans.h, - ); - target = DA8E5ED52E2FC3E80049F7AB /* BugsnagPerformanceNamedSpans */; - }; -/* End PBXFileSystemSynchronizedBuildFileExceptionSet section */ - -/* Begin PBXFileSystemSynchronizedRootGroup section */ - DA8E5ED72E2FC3E80049F7AB /* BugsnagPerformanceNamedSpans */ = {isa = PBXFileSystemSynchronizedRootGroup; exceptions = (DA8E5EED2E2FC3E80049F7AB /* PBXFileSystemSynchronizedBuildFileExceptionSet */, ); explicitFileTypes = {}; explicitFolders = (); path = BugsnagPerformanceNamedSpans; sourceTree = ""; }; - DA8E5EE52E2FC3E80049F7AB /* BugsnagPerformanceNamedSpansTests */ = {isa = PBXFileSystemSynchronizedRootGroup; explicitFileTypes = {}; explicitFolders = (); path = BugsnagPerformanceNamedSpansTests; sourceTree = ""; }; -/* End PBXFileSystemSynchronizedRootGroup section */ - /* Begin PBXFrameworksBuildPhase section */ 0122C26229019CE1002D243C /* Frameworks */ = { isa = PBXFrameworksBuildPhase; @@ -973,6 +964,23 @@ path = BugsnagPerformanceTestsApp; sourceTree = ""; }; + 96F417142E3B8E7000EABD8E /* BugsnagPerformanceNamedSpans */ = { + isa = PBXGroup; + children = ( + 96F417122E3B8E7000EABD8E /* BugsnagPerformanceNamedSpans.h */, + 96F417132E3B8E7000EABD8E /* BugsnagPerformanceNamedSpans.docc */, + ); + path = BugsnagPerformanceNamedSpans; + sourceTree = ""; + }; + 96F417182E3B8E7F00EABD8E /* BugsnagPerformanceNamedSpansTests */ = { + isa = PBXGroup; + children = ( + 96F417172E3B8E7F00EABD8E /* BugsnagPerformanceNamedSpansTests.m */, + ); + path = BugsnagPerformanceNamedSpansTests; + sourceTree = ""; + }; C6D4AAD24D6356218C22BEBE = { isa = PBXGroup; children = ( @@ -984,8 +992,8 @@ 094FA7332B10EDE600112ED4 /* BugsnagPerformanceSwift */, 094FA7412B10EDE700112ED4 /* BugsnagPerformanceSwiftTests */, 09D807F22B9756B000D01DF5 /* BugsnagPerformanceSwiftUI */, - DA8E5ED72E2FC3E80049F7AB /* BugsnagPerformanceNamedSpans */, - DA8E5EE52E2FC3E80049F7AB /* BugsnagPerformanceNamedSpansTests */, + 96F417142E3B8E7000EABD8E /* BugsnagPerformanceNamedSpans */, + 96F417182E3B8E7F00EABD8E /* BugsnagPerformanceNamedSpansTests */, 450CB5014AAED50046482044 /* Products */, 0122C25629019904002D243C /* Frameworks */, ); @@ -1165,6 +1173,7 @@ isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( + 96F417152E3B8E7000EABD8E /* BugsnagPerformanceNamedSpans.h in Headers */, DA87DAF92E3791CE0040F086 /* BugsnagPerformanceNamedSpanQuery.h in Headers */, DA8E5EFF2E2FCA020049F7AB /* BugsnagPerformanceNamedSpansPlugin.h in Headers */, ); @@ -1321,9 +1330,6 @@ dependencies = ( DA8E5EF72E2FC45B0049F7AB /* PBXTargetDependency */, ); - fileSystemSynchronizedGroups = ( - DA8E5ED72E2FC3E80049F7AB /* BugsnagPerformanceNamedSpans */, - ); name = BugsnagPerformanceNamedSpans; packageProductDependencies = ( ); @@ -1345,9 +1351,6 @@ DA8E5EE22E2FC3E80049F7AB /* PBXTargetDependency */, DA8E5EE42E2FC3E80049F7AB /* PBXTargetDependency */, ); - fileSystemSynchronizedGroups = ( - DA8E5EE52E2FC3E80049F7AB /* BugsnagPerformanceNamedSpansTests */, - ); name = BugsnagPerformanceNamedSpansTests; packageProductDependencies = ( ); @@ -1404,9 +1407,13 @@ }; DA8E5ED52E2FC3E80049F7AB = { CreatedOnToolsVersion = 16.2; + DevelopmentTeam = 7W9PZ27Y5F; + ProvisioningStyle = Automatic; }; DA8E5EDE2E2FC3E80049F7AB = { CreatedOnToolsVersion = 16.2; + DevelopmentTeam = 7W9PZ27Y5F; + ProvisioningStyle = Automatic; TestTargetID = 96D415EF29E6ADC500AEE435; }; }; @@ -1655,6 +1662,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 96F417162E3B8E7000EABD8E /* BugsnagPerformanceNamedSpans.docc in Sources */, DA87DAF02E2FDFE60040F086 /* BugsnagPerformanceNamedSpansPlugin.mm in Sources */, DA87DAFB2E3792B10040F086 /* BugsnagPerformanceNamedSpanQuery.m in Sources */, ); @@ -1664,6 +1672,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 96F417192E3B8E7F00EABD8E /* BugsnagPerformanceNamedSpansTests.m in Sources */, DA8E5F112E2FCDED0049F7AB /* BugsnagPerformanceNamedSpansTests.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -1771,11 +1780,7 @@ DEVELOPMENT_TEAM = 7W9PZ27Y5F; GCC_WARN_PEDANTIC = NO; GENERATE_INFOPLIST_FILE = YES; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - "@loader_path/Frameworks", - ); + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; MARKETING_VERSION = 1.14.0; PRODUCT_BUNDLE_IDENTIFIER = "com.bugsnag.BugsnagPerformance-iOSTests"; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -1797,11 +1802,7 @@ DEVELOPMENT_TEAM = 7W9PZ27Y5F; GCC_WARN_PEDANTIC = NO; GENERATE_INFOPLIST_FILE = YES; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - "@loader_path/Frameworks", - ); + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; MARKETING_VERSION = 1.14.0; PRODUCT_BUNDLE_IDENTIFIER = "com.bugsnag.BugsnagPerformance-iOSTests"; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -1834,11 +1835,7 @@ INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2023 Bugsnag. All rights reserved."; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 13.0; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - "@loader_path/Frameworks", - ); + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; LOCALIZATION_PREFERS_STRING_CATALOGS = YES; MARKETING_VERSION = 1.14.0; MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++"; @@ -1874,11 +1871,7 @@ INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2023 Bugsnag. All rights reserved."; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 13.0; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - "@loader_path/Frameworks", - ); + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; LOCALIZATION_PREFERS_STRING_CATALOGS = YES; MARKETING_VERSION = 1.14.0; MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++"; @@ -1964,11 +1957,7 @@ INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2024 Bugsnag. All rights reserved."; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 17.2; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - "@loader_path/Frameworks", - ); + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; LOCALIZATION_PREFERS_STRING_CATALOGS = YES; MARKETING_VERSION = 1.14.0; MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++"; @@ -2004,11 +1993,7 @@ INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2024 Bugsnag. All rights reserved."; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 17.2; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - "@loader_path/Frameworks", - ); + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; LOCALIZATION_PREFERS_STRING_CATALOGS = YES; MARKETING_VERSION = 1.14.0; MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++"; @@ -2041,11 +2026,7 @@ GENERATE_INFOPLIST_FILE = YES; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 13.0; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - "@loader_path/Frameworks", - ); + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; MARKETING_VERSION = 1.14.0; PRODUCT_BUNDLE_IDENTIFIER = com.bugsnag.BugsnagPerformance; PRODUCT_NAME = BugsnagPerformance; @@ -2075,11 +2056,7 @@ GENERATE_INFOPLIST_FILE = YES; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 13.0; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - "@loader_path/Frameworks", - ); + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; MARKETING_VERSION = 1.14.0; PRODUCT_BUNDLE_IDENTIFIER = com.bugsnag.BugsnagPerformance; PRODUCT_NAME = BugsnagPerformance; @@ -2276,8 +2253,7 @@ MTL_FAST_MATH = YES; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = iphoneos; - SWIFT_COMPILATION_MODE = wholemodule; - SWIFT_OPTIMIZATION_LEVEL = "-O"; + SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; SWIFT_VERSION = 5.0; }; name = Release; @@ -2342,10 +2318,7 @@ INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; IPHONEOS_DEPLOYMENT_TARGET = 11.0; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - ); + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; MARKETING_VERSION = 1.14.0; PRODUCT_BUNDLE_IDENTIFIER = com.bugsnag.BugsnagPerformanceTestsApp; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -2377,10 +2350,7 @@ INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; IPHONEOS_DEPLOYMENT_TARGET = 11.0; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - ); + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; MARKETING_VERSION = 1.14.0; PRODUCT_BUNDLE_IDENTIFIER = com.bugsnag.BugsnagPerformanceTestsApp; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -2420,11 +2390,7 @@ GENERATE_INFOPLIST_FILE = YES; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 13.0; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - "@loader_path/Frameworks", - ); + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; LOCALIZATION_PREFERS_STRING_CATALOGS = NO; MARKETING_VERSION = 1.14.0; MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c"; @@ -2487,11 +2453,7 @@ GENERATE_INFOPLIST_FILE = YES; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 13.0; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - "@loader_path/Frameworks", - ); + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; LOCALIZATION_PREFERS_STRING_CATALOGS = NO; MARKETING_VERSION = 1.14.0; MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c"; From 3894030b592f5f59e93ff246c8289f4956c1e9b0 Mon Sep 17 00:00:00 2001 From: Robert Bartoszewski Date: Thu, 31 Jul 2025 14:03:15 +0200 Subject: [PATCH 10/53] Added BugsnagPerformanceNamedSpansTests to the test scheme --- .../xcschemes/BugsnagPerformance-iOS.xcscheme | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/BugsnagPerformance.xcodeproj/xcshareddata/xcschemes/BugsnagPerformance-iOS.xcscheme b/BugsnagPerformance.xcodeproj/xcshareddata/xcschemes/BugsnagPerformance-iOS.xcscheme index 6b307ea8..50e5c8ff 100644 --- a/BugsnagPerformance.xcodeproj/xcshareddata/xcschemes/BugsnagPerformance-iOS.xcscheme +++ b/BugsnagPerformance.xcodeproj/xcshareddata/xcschemes/BugsnagPerformance-iOS.xcscheme @@ -55,6 +55,16 @@ ReferencedContainer = "container:BugsnagPerformance.xcodeproj"> + + + + Date: Thu, 31 Jul 2025 23:49:50 +0200 Subject: [PATCH 11/53] Fixed BugsnagPerformanceNamedSpansTests --- BugsnagPerformance.xcodeproj/project.pbxproj | 283 +++++++++--------- .../xcschemes/BugsnagPerformance-iOS.xcscheme | 2 +- .../BugsnagPerformanceNamedSpansTests.m | 37 --- ...m => BugsnagPerformanceNamedSpansTests.mm} | 75 ++--- 4 files changed, 163 insertions(+), 234 deletions(-) delete mode 100644 BugsnagPerformanceNamedSpansTests/BugsnagPerformanceNamedSpansTests.m rename Tests/BugsnagPerformanceNamedSpansTests/{BugsnagPerformanceNamedSpansTests.m => BugsnagPerformanceNamedSpansTests.mm} (85%) diff --git a/BugsnagPerformance.xcodeproj/project.pbxproj b/BugsnagPerformance.xcodeproj/project.pbxproj index 9d9201c6..5791700a 100644 --- a/BugsnagPerformance.xcodeproj/project.pbxproj +++ b/BugsnagPerformance.xcodeproj/project.pbxproj @@ -103,6 +103,9 @@ 963726E42DF19D7F00C739E6 /* BugsnagPerformanceSpanControlProvider+Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 963726E32DF19D7B00C739E6 /* BugsnagPerformanceSpanControlProvider+Private.h */; }; 963726E62DF1DFC300C739E6 /* BSGPluginManager.h in Headers */ = {isa = PBXBuildFile; fileRef = 963726E52DF1DFBA00C739E6 /* BSGPluginManager.h */; }; 963726E82DF1DFD100C739E6 /* BSGPluginManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 963726E72DF1DFCB00C739E6 /* BSGPluginManager.m */; }; + 963828B92E3C0B9A00404F3A /* BugsnagPerformance.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 72E4BB63359EA30E80116E2A /* BugsnagPerformance.framework */; }; + 963828BF2E3C0BAC00404F3A /* BugsnagPerformanceNamedSpans.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DA8E5ED62E2FC3E80049F7AB /* BugsnagPerformanceNamedSpans.framework */; }; + 963828C42E3C175900404F3A /* BugsnagPerformanceNamedSpansTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = DA8E5F0F2E2FCDED0049F7AB /* BugsnagPerformanceNamedSpansTests.mm */; }; 964B78502D39D36900FF077D /* BugsnagPerformanceSpanCondition.h in Headers */ = {isa = PBXBuildFile; fileRef = 964B784F2D39D36900FF077D /* BugsnagPerformanceSpanCondition.h */; settings = {ATTRIBUTES = (Public, ); }; }; 964B78522D47D26F00FF077D /* ConditionTimeoutExecutor.h in Headers */ = {isa = PBXBuildFile; fileRef = 964B78512D47D26F00FF077D /* ConditionTimeoutExecutor.h */; }; 964B78562D4B924C00FF077D /* BugsnagPerformanceSpanCondition.mm in Sources */ = {isa = PBXBuildFile; fileRef = 964B78552D4B924C00FF077D /* BugsnagPerformanceSpanCondition.mm */; }; @@ -137,7 +140,6 @@ 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 */; }; - 96F417192E3B8E7F00EABD8E /* BugsnagPerformanceNamedSpansTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 96F417172E3B8E7F00EABD8E /* BugsnagPerformanceNamedSpansTests.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 */; }; @@ -204,12 +206,10 @@ DA87DAF02E2FDFE60040F086 /* BugsnagPerformanceNamedSpansPlugin.mm in Sources */ = {isa = PBXBuildFile; fileRef = DA87DAEE2E2FDFE60040F086 /* BugsnagPerformanceNamedSpansPlugin.mm */; }; DA87DAF92E3791CE0040F086 /* BugsnagPerformanceNamedSpanQuery.h in Headers */ = {isa = PBXBuildFile; fileRef = DA87DAF72E3791CE0040F086 /* BugsnagPerformanceNamedSpanQuery.h */; }; DA87DAFB2E3792B10040F086 /* BugsnagPerformanceNamedSpanQuery.m in Sources */ = {isa = PBXBuildFile; fileRef = DA87DAFA2E3792B10040F086 /* BugsnagPerformanceNamedSpanQuery.m */; }; - DA8E5EE02E2FC3E80049F7AB /* BugsnagPerformanceNamedSpans.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DA8E5ED62E2FC3E80049F7AB /* BugsnagPerformanceNamedSpans.framework */; }; DA8E5EEB2E2FC3E80049F7AB /* BugsnagPerformanceNamedSpans.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DA8E5ED62E2FC3E80049F7AB /* BugsnagPerformanceNamedSpans.framework */; }; DA8E5EEC2E2FC3E80049F7AB /* BugsnagPerformanceNamedSpans.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = DA8E5ED62E2FC3E80049F7AB /* BugsnagPerformanceNamedSpans.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; DA8E5EF42E2FC45B0049F7AB /* BugsnagPerformance.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 72E4BB63359EA30E80116E2A /* BugsnagPerformance.framework */; }; DA8E5EFF2E2FCA020049F7AB /* BugsnagPerformanceNamedSpansPlugin.h in Headers */ = {isa = PBXBuildFile; fileRef = DA8E5EFD2E2FCA020049F7AB /* BugsnagPerformanceNamedSpansPlugin.h */; }; - DA8E5F112E2FCDED0049F7AB /* BugsnagPerformanceNamedSpansTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DA8E5F0F2E2FCDED0049F7AB /* BugsnagPerformanceNamedSpansTests.m */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -262,35 +262,42 @@ remoteGlobalIDString = B4A4A4DE1EA43C6C4D6D3894; remoteInfo = "BugsnagPerformance-iOS"; }; - 966DD5032A211D7F002030B2 /* PBXContainerItemProxy */ = { + 963828982E3C0B9A00404F3A /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 5DB9DB2E409A040615FFD4FE /* Project object */; proxyType = 1; remoteGlobalIDString = B4A4A4DE1EA43C6C4D6D3894; remoteInfo = "BugsnagPerformance-iOS"; }; - 96D4160429E6F6B500AEE435 /* PBXContainerItemProxy */ = { + 9638289A2E3C0B9A00404F3A /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 5DB9DB2E409A040615FFD4FE /* Project object */; proxyType = 1; remoteGlobalIDString = 96D415EF29E6ADC500AEE435; remoteInfo = BugsnagPerformanceTestsApp; }; - 96D4160629E6F6B900AEE435 /* PBXContainerItemProxy */ = { + 963828C12E3C0BAC00404F3A /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 5DB9DB2E409A040615FFD4FE /* Project object */; proxyType = 1; - remoteGlobalIDString = 96D415EF29E6ADC500AEE435; - remoteInfo = BugsnagPerformanceTestsApp; + remoteGlobalIDString = DA8E5ED52E2FC3E80049F7AB; + remoteInfo = BugsnagPerformanceNamedSpans; }; - DA8E5EE12E2FC3E80049F7AB /* PBXContainerItemProxy */ = { + 966DD5032A211D7F002030B2 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 5DB9DB2E409A040615FFD4FE /* Project object */; proxyType = 1; - remoteGlobalIDString = DA8E5ED52E2FC3E80049F7AB; - remoteInfo = BugsnagPerformanceNamedSpans; + remoteGlobalIDString = B4A4A4DE1EA43C6C4D6D3894; + remoteInfo = "BugsnagPerformance-iOS"; }; - DA8E5EE32E2FC3E80049F7AB /* PBXContainerItemProxy */ = { + 96D4160429E6F6B500AEE435 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 5DB9DB2E409A040615FFD4FE /* Project object */; + proxyType = 1; + remoteGlobalIDString = 96D415EF29E6ADC500AEE435; + remoteInfo = BugsnagPerformanceTestsApp; + }; + 96D4160629E6F6B900AEE435 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 5DB9DB2E409A040615FFD4FE /* Project object */; proxyType = 1; @@ -428,6 +435,7 @@ 963726E32DF19D7B00C739E6 /* BugsnagPerformanceSpanControlProvider+Private.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "BugsnagPerformanceSpanControlProvider+Private.h"; sourceTree = ""; }; 963726E52DF1DFBA00C739E6 /* BSGPluginManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = BSGPluginManager.h; sourceTree = ""; }; 963726E72DF1DFCB00C739E6 /* BSGPluginManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = BSGPluginManager.m; sourceTree = ""; }; + 963828BE2E3C0B9A00404F3A /* BugsnagPerformanceNamedSpansTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = BugsnagPerformanceNamedSpansTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 964B784F2D39D36900FF077D /* BugsnagPerformanceSpanCondition.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = BugsnagPerformanceSpanCondition.h; sourceTree = ""; }; 964B78512D47D26F00FF077D /* ConditionTimeoutExecutor.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ConditionTimeoutExecutor.h; sourceTree = ""; }; 964B78552D4B924C00FF077D /* BugsnagPerformanceSpanCondition.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = BugsnagPerformanceSpanCondition.mm; sourceTree = ""; }; @@ -463,7 +471,6 @@ 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 = ""; }; - 96F417172E3B8E7F00EABD8E /* BugsnagPerformanceNamedSpansTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = BugsnagPerformanceNamedSpansTests.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 = ""; }; @@ -537,9 +544,8 @@ DA87DAF72E3791CE0040F086 /* BugsnagPerformanceNamedSpanQuery.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = BugsnagPerformanceNamedSpanQuery.h; sourceTree = ""; }; DA87DAFA2E3792B10040F086 /* BugsnagPerformanceNamedSpanQuery.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = BugsnagPerformanceNamedSpanQuery.m; sourceTree = ""; }; DA8E5ED62E2FC3E80049F7AB /* BugsnagPerformanceNamedSpans.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = BugsnagPerformanceNamedSpans.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - DA8E5EDF2E2FC3E80049F7AB /* BugsnagPerformanceNamedSpansTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = BugsnagPerformanceNamedSpansTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; DA8E5EFD2E2FCA020049F7AB /* BugsnagPerformanceNamedSpansPlugin.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = BugsnagPerformanceNamedSpansPlugin.h; sourceTree = ""; }; - DA8E5F0F2E2FCDED0049F7AB /* BugsnagPerformanceNamedSpansTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = BugsnagPerformanceNamedSpansTests.m; sourceTree = ""; }; + DA8E5F0F2E2FCDED0049F7AB /* BugsnagPerformanceNamedSpansTests.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = BugsnagPerformanceNamedSpansTests.mm; sourceTree = ""; }; EDE8339F2AF550E20042A78F /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; lastKnownFileType = text.xml; name = PrivacyInfo.xcprivacy; path = Sources/BugsnagPerformance/resources/PrivacyInfo.xcprivacy; sourceTree = ""; }; /* End PBXFileReference section */ @@ -596,29 +602,30 @@ ); runOnlyForDeploymentPostprocessing = 0; }; - 96D415ED29E6ADC500AEE435 /* Frameworks */ = { + 963828B82E3C0B9A00404F3A /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 966DD5012A211D7F002030B2 /* BugsnagPerformance.framework in Frameworks */, - DA8E5EEB2E2FC3E80049F7AB /* BugsnagPerformanceNamedSpans.framework in Frameworks */, - 094FA7472B10EDE700112ED4 /* BugsnagPerformanceSwift.framework in Frameworks */, + 963828B92E3C0B9A00404F3A /* BugsnagPerformance.framework in Frameworks */, + 963828BF2E3C0BAC00404F3A /* BugsnagPerformanceNamedSpans.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; - DA8E5ED32E2FC3E80049F7AB /* Frameworks */ = { + 96D415ED29E6ADC500AEE435 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - DA8E5EF42E2FC45B0049F7AB /* BugsnagPerformance.framework in Frameworks */, + 966DD5012A211D7F002030B2 /* BugsnagPerformance.framework in Frameworks */, + DA8E5EEB2E2FC3E80049F7AB /* BugsnagPerformanceNamedSpans.framework in Frameworks */, + 094FA7472B10EDE700112ED4 /* BugsnagPerformanceSwift.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; - DA8E5EDC2E2FC3E80049F7AB /* Frameworks */ = { + DA8E5ED32E2FC3E80049F7AB /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - DA8E5EE02E2FC3E80049F7AB /* BugsnagPerformanceNamedSpans.framework in Frameworks */, + DA8E5EF42E2FC45B0049F7AB /* BugsnagPerformance.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -914,7 +921,7 @@ 094FA73B2B10EDE700112ED4 /* BugsnagPerformanceSwiftTests.xctest */, 09D807F12B9756B000D01DF5 /* BugsnagPerformanceSwiftUI.framework */, DA8E5ED62E2FC3E80049F7AB /* BugsnagPerformanceNamedSpans.framework */, - DA8E5EDF2E2FC3E80049F7AB /* BugsnagPerformanceNamedSpansTests.xctest */, + 963828BE2E3C0B9A00404F3A /* BugsnagPerformanceNamedSpansTests.xctest */, ); name = Products; sourceTree = ""; @@ -973,14 +980,6 @@ path = BugsnagPerformanceNamedSpans; sourceTree = ""; }; - 96F417182E3B8E7F00EABD8E /* BugsnagPerformanceNamedSpansTests */ = { - isa = PBXGroup; - children = ( - 96F417172E3B8E7F00EABD8E /* BugsnagPerformanceNamedSpansTests.m */, - ); - path = BugsnagPerformanceNamedSpansTests; - sourceTree = ""; - }; C6D4AAD24D6356218C22BEBE = { isa = PBXGroup; children = ( @@ -993,7 +992,6 @@ 094FA7412B10EDE700112ED4 /* BugsnagPerformanceSwiftTests */, 09D807F22B9756B000D01DF5 /* BugsnagPerformanceSwiftUI */, 96F417142E3B8E7000EABD8E /* BugsnagPerformanceNamedSpans */, - 96F417182E3B8E7F00EABD8E /* BugsnagPerformanceNamedSpansTests */, 450CB5014AAED50046482044 /* Products */, 0122C25629019904002D243C /* Frameworks */, ); @@ -1061,7 +1059,7 @@ DA8E5F002E2FCC100049F7AB /* BugsnagPerformanceNamedSpansTests */ = { isa = PBXGroup; children = ( - DA8E5F0F2E2FCDED0049F7AB /* BugsnagPerformanceNamedSpansTests.m */, + DA8E5F0F2E2FCDED0049F7AB /* BugsnagPerformanceNamedSpansTests.mm */, ); path = BugsnagPerformanceNamedSpansTests; sourceTree = ""; @@ -1277,6 +1275,26 @@ productReference = 8A80DA872966CE940035BDA9 /* BugsnagPerformance-iOSTestsSwift.xctest */; productType = "com.apple.product-type.bundle.unit-test"; }; + 963828962E3C0B9A00404F3A /* BugsnagPerformanceNamedSpansTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 963828BB2E3C0B9A00404F3A /* Build configuration list for PBXNativeTarget "BugsnagPerformanceNamedSpansTests" */; + buildPhases = ( + 9638289B2E3C0B9A00404F3A /* Sources */, + 963828B82E3C0B9A00404F3A /* Frameworks */, + 963828BA2E3C0B9A00404F3A /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 963828972E3C0B9A00404F3A /* PBXTargetDependency */, + 963828992E3C0B9A00404F3A /* PBXTargetDependency */, + 963828C22E3C0BAC00404F3A /* PBXTargetDependency */, + ); + name = BugsnagPerformanceNamedSpansTests; + productName = "BugsnagPerformance-iOSTests"; + productReference = 963828BE2E3C0B9A00404F3A /* BugsnagPerformanceNamedSpansTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; 96D415EF29E6ADC500AEE435 /* BugsnagPerformanceTestsApp */ = { isa = PBXNativeTarget; buildConfigurationList = 96D4160129E6ADC600AEE435 /* Build configuration list for PBXNativeTarget "BugsnagPerformanceTestsApp" */; @@ -1337,27 +1355,6 @@ productReference = DA8E5ED62E2FC3E80049F7AB /* BugsnagPerformanceNamedSpans.framework */; productType = "com.apple.product-type.framework"; }; - DA8E5EDE2E2FC3E80049F7AB /* BugsnagPerformanceNamedSpansTests */ = { - isa = PBXNativeTarget; - buildConfigurationList = DA8E5EF12E2FC3E80049F7AB /* Build configuration list for PBXNativeTarget "BugsnagPerformanceNamedSpansTests" */; - buildPhases = ( - DA8E5EDB2E2FC3E80049F7AB /* Sources */, - DA8E5EDC2E2FC3E80049F7AB /* Frameworks */, - DA8E5EDD2E2FC3E80049F7AB /* Resources */, - ); - buildRules = ( - ); - dependencies = ( - DA8E5EE22E2FC3E80049F7AB /* PBXTargetDependency */, - DA8E5EE42E2FC3E80049F7AB /* PBXTargetDependency */, - ); - name = BugsnagPerformanceNamedSpansTests; - packageProductDependencies = ( - ); - productName = BugsnagPerformanceNamedSpansTests; - productReference = DA8E5EDF2E2FC3E80049F7AB /* BugsnagPerformanceNamedSpansTests.xctest */; - productType = "com.apple.product-type.bundle.unit-test"; - }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ @@ -1396,6 +1393,9 @@ ProvisioningStyle = Automatic; TestTargetID = 96D415EF29E6ADC500AEE435; }; + 963828962E3C0B9A00404F3A = { + DevelopmentTeam = 7W9PZ27Y5F; + }; 96D415EF29E6ADC500AEE435 = { CreatedOnToolsVersion = 14.2; DevelopmentTeam = 7W9PZ27Y5F; @@ -1410,12 +1410,6 @@ DevelopmentTeam = 7W9PZ27Y5F; ProvisioningStyle = Automatic; }; - DA8E5EDE2E2FC3E80049F7AB = { - CreatedOnToolsVersion = 16.2; - DevelopmentTeam = 7W9PZ27Y5F; - ProvisioningStyle = Automatic; - TestTargetID = 96D415EF29E6ADC500AEE435; - }; }; }; buildConfigurationList = 1B656A3BE38DEADBDE72618E /* Build configuration list for PBXProject "BugsnagPerformance" */; @@ -1439,7 +1433,7 @@ 094FA73A2B10EDE700112ED4 /* BugsnagPerformanceSwiftTests */, 09D807F02B9756B000D01DF5 /* BugsnagPerformanceSwiftUI */, DA8E5ED52E2FC3E80049F7AB /* BugsnagPerformanceNamedSpans */, - DA8E5EDE2E2FC3E80049F7AB /* BugsnagPerformanceNamedSpansTests */, + 963828962E3C0B9A00404F3A /* BugsnagPerformanceNamedSpansTests */, ); }; /* End PBXProject section */ @@ -1487,24 +1481,24 @@ ); runOnlyForDeploymentPostprocessing = 0; }; - 96D415EE29E6ADC500AEE435 /* Resources */ = { + 963828BA2E3C0B9A00404F3A /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( - 96D415FF29E6ADC600AEE435 /* LaunchScreen.storyboard in Resources */, - 96D415FC29E6ADC600AEE435 /* Assets.xcassets in Resources */, - 96D415FA29E6ADC500AEE435 /* Main.storyboard in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; - DA8E5ED42E2FC3E80049F7AB /* Resources */ = { + 96D415EE29E6ADC500AEE435 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + 96D415FF29E6ADC600AEE435 /* LaunchScreen.storyboard in Resources */, + 96D415FC29E6ADC600AEE435 /* Assets.xcassets in Resources */, + 96D415FA29E6ADC500AEE435 /* Main.storyboard in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; - DA8E5EDD2E2FC3E80049F7AB /* Resources */ = { + DA8E5ED42E2FC3E80049F7AB /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( @@ -1649,6 +1643,14 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 9638289B2E3C0B9A00404F3A /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 963828C42E3C175900404F3A /* BugsnagPerformanceNamedSpansTests.mm in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 96D415EC29E6ADC500AEE435 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -1668,15 +1670,6 @@ ); runOnlyForDeploymentPostprocessing = 0; }; - DA8E5EDB2E2FC3E80049F7AB /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 96F417192E3B8E7F00EABD8E /* BugsnagPerformanceNamedSpansTests.m in Sources */, - DA8E5F112E2FCDED0049F7AB /* BugsnagPerformanceNamedSpansTests.m in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ @@ -1715,6 +1708,21 @@ target = B4A4A4DE1EA43C6C4D6D3894 /* BugsnagPerformance-iOS */; targetProxy = 8A80DA8C2966CE940035BDA9 /* PBXContainerItemProxy */; }; + 963828972E3C0B9A00404F3A /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = B4A4A4DE1EA43C6C4D6D3894 /* BugsnagPerformance-iOS */; + targetProxy = 963828982E3C0B9A00404F3A /* PBXContainerItemProxy */; + }; + 963828992E3C0B9A00404F3A /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 96D415EF29E6ADC500AEE435 /* BugsnagPerformanceTestsApp */; + targetProxy = 9638289A2E3C0B9A00404F3A /* PBXContainerItemProxy */; + }; + 963828C22E3C0BAC00404F3A /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = DA8E5ED52E2FC3E80049F7AB /* BugsnagPerformanceNamedSpans */; + targetProxy = 963828C12E3C0BAC00404F3A /* PBXContainerItemProxy */; + }; 966DD5042A211D7F002030B2 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = B4A4A4DE1EA43C6C4D6D3894 /* BugsnagPerformance-iOS */; @@ -1730,16 +1738,6 @@ target = 96D415EF29E6ADC500AEE435 /* BugsnagPerformanceTestsApp */; targetProxy = 96D4160629E6F6B900AEE435 /* PBXContainerItemProxy */; }; - DA8E5EE22E2FC3E80049F7AB /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = DA8E5ED52E2FC3E80049F7AB /* BugsnagPerformanceNamedSpans */; - targetProxy = DA8E5EE12E2FC3E80049F7AB /* PBXContainerItemProxy */; - }; - DA8E5EE42E2FC3E80049F7AB /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = 96D415EF29E6ADC500AEE435 /* BugsnagPerformanceTestsApp */; - targetProxy = DA8E5EE32E2FC3E80049F7AB /* PBXContainerItemProxy */; - }; DA8E5EEA2E2FC3E80049F7AB /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = DA8E5ED52E2FC3E80049F7AB /* BugsnagPerformanceNamedSpans */; @@ -1891,7 +1889,6 @@ 094FA74B2B10EDE700112ED4 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { - ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CODE_SIGN_STYLE = Automatic; @@ -1916,7 +1913,6 @@ 094FA74C2B10EDE700112ED4 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { - ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CODE_SIGN_STYLE = Automatic; @@ -2299,10 +2295,53 @@ }; name = Release; }; + 963828BC2E3C0B9A00404F3A /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = 7W9PZ27Y5F; + GCC_WARN_PEDANTIC = NO; + GENERATE_INFOPLIST_FILE = YES; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + MARKETING_VERSION = 1.14.0; + PRODUCT_BUNDLE_IDENTIFIER = "com.bugsnag.BugsnagPerformance-iOSTests"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = iphoneos; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_OBJC_BRIDGING_HEADER = "BugsnagPerformanceTestsApp/BugsnagPerformanceTestsApp-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/BugsnagPerformanceTestsApp.app/BugsnagPerformanceTestsApp"; + }; + name = Debug; + }; + 963828BD2E3C0B9A00404F3A /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = 7W9PZ27Y5F; + GCC_WARN_PEDANTIC = NO; + GENERATE_INFOPLIST_FILE = YES; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + MARKETING_VERSION = 1.14.0; + PRODUCT_BUNDLE_IDENTIFIER = "com.bugsnag.BugsnagPerformance-iOSTests"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = iphoneos; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_OBJC_BRIDGING_HEADER = "BugsnagPerformanceTestsApp/BugsnagPerformanceTestsApp-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/BugsnagPerformanceTestsApp.app/BugsnagPerformanceTestsApp"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; 96D4160229E6ADC600AEE435 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { - ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; @@ -2334,7 +2373,6 @@ 96D4160329E6ADC600AEE435 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { - ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; @@ -2490,51 +2528,6 @@ }; name = Release; }; - DA8E5EF22E2FC3E80049F7AB /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; - CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = 7W9PZ27Y5F; - ENABLE_USER_SCRIPT_SANDBOXING = YES; - GCC_C_LANGUAGE_STANDARD = gnu17; - GENERATE_INFOPLIST_FILE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 13.0; - LOCALIZATION_PREFERS_STRING_CATALOGS = YES; - MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = com.bugsnag.BugsnagPerformanceNamedSpansTests; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_EMIT_LOC_STRINGS = NO; - TARGETED_DEVICE_FAMILY = "1,2"; - TEST_HOST = "$(BUILT_PRODUCTS_DIR)/BugsnagPerformanceTestsApp.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/BugsnagPerformanceTestsApp"; - }; - name = Debug; - }; - DA8E5EF32E2FC3E80049F7AB /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; - CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = 7W9PZ27Y5F; - ENABLE_USER_SCRIPT_SANDBOXING = YES; - GCC_C_LANGUAGE_STANDARD = gnu17; - GENERATE_INFOPLIST_FILE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 13.0; - LOCALIZATION_PREFERS_STRING_CATALOGS = YES; - MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = com.bugsnag.BugsnagPerformanceNamedSpansTests; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_EMIT_LOC_STRINGS = NO; - TARGETED_DEVICE_FAMILY = "1,2"; - TEST_HOST = "$(BUILT_PRODUCTS_DIR)/BugsnagPerformanceTestsApp.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/BugsnagPerformanceTestsApp"; - VALIDATE_PRODUCT = YES; - }; - name = Release; - }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ @@ -2601,29 +2594,29 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - 96D4160129E6ADC600AEE435 /* Build configuration list for PBXNativeTarget "BugsnagPerformanceTestsApp" */ = { + 963828BB2E3C0B9A00404F3A /* Build configuration list for PBXNativeTarget "BugsnagPerformanceNamedSpansTests" */ = { isa = XCConfigurationList; buildConfigurations = ( - 96D4160229E6ADC600AEE435 /* Debug */, - 96D4160329E6ADC600AEE435 /* Release */, + 963828BC2E3C0B9A00404F3A /* Debug */, + 963828BD2E3C0B9A00404F3A /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - DA8E5EEE2E2FC3E80049F7AB /* Build configuration list for PBXNativeTarget "BugsnagPerformanceNamedSpans" */ = { + 96D4160129E6ADC600AEE435 /* Build configuration list for PBXNativeTarget "BugsnagPerformanceTestsApp" */ = { isa = XCConfigurationList; buildConfigurations = ( - DA8E5EEF2E2FC3E80049F7AB /* Debug */, - DA8E5EF02E2FC3E80049F7AB /* Release */, + 96D4160229E6ADC600AEE435 /* Debug */, + 96D4160329E6ADC600AEE435 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - DA8E5EF12E2FC3E80049F7AB /* Build configuration list for PBXNativeTarget "BugsnagPerformanceNamedSpansTests" */ = { + DA8E5EEE2E2FC3E80049F7AB /* Build configuration list for PBXNativeTarget "BugsnagPerformanceNamedSpans" */ = { isa = XCConfigurationList; buildConfigurations = ( - DA8E5EF22E2FC3E80049F7AB /* Debug */, - DA8E5EF32E2FC3E80049F7AB /* Release */, + DA8E5EEF2E2FC3E80049F7AB /* Debug */, + DA8E5EF02E2FC3E80049F7AB /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; diff --git a/BugsnagPerformance.xcodeproj/xcshareddata/xcschemes/BugsnagPerformance-iOS.xcscheme b/BugsnagPerformance.xcodeproj/xcshareddata/xcschemes/BugsnagPerformance-iOS.xcscheme index 50e5c8ff..2f6ef825 100644 --- a/BugsnagPerformance.xcodeproj/xcshareddata/xcschemes/BugsnagPerformance-iOS.xcscheme +++ b/BugsnagPerformance.xcodeproj/xcshareddata/xcschemes/BugsnagPerformance-iOS.xcscheme @@ -59,7 +59,7 @@ skipped = "NO"> diff --git a/BugsnagPerformanceNamedSpansTests/BugsnagPerformanceNamedSpansTests.m b/BugsnagPerformanceNamedSpansTests/BugsnagPerformanceNamedSpansTests.m deleted file mode 100644 index 29419806..00000000 --- a/BugsnagPerformanceNamedSpansTests/BugsnagPerformanceNamedSpansTests.m +++ /dev/null @@ -1,37 +0,0 @@ -// -// BugsnagPerformanceNamedSpansTests.m -// BugsnagPerformanceNamedSpansTests -// -// Created by Yousif Ahmed on 22/07/2025. -// Copyright © 2025 Bugsnag. All rights reserved. -// - -#import - -@interface BugsnagPerformanceNamedSpansTests : XCTestCase - -@end - -@implementation BugsnagPerformanceNamedSpansTests - -- (void)setUp { - // Put setup code here. This method is called before the invocation of each test method in the class. -} - -- (void)tearDown { - // Put teardown code here. This method is called after the invocation of each test method in the class. -} - -- (void)testExample { - // This is an example of a functional test case. - // Use XCTAssert and related functions to verify your tests produce the correct results. -} - -- (void)testPerformanceExample { - // This is an example of a performance test case. - [self measureBlock:^{ - // Put the code you want to measure the time of here. - }]; -} - -@end diff --git a/Tests/BugsnagPerformanceNamedSpansTests/BugsnagPerformanceNamedSpansTests.m b/Tests/BugsnagPerformanceNamedSpansTests/BugsnagPerformanceNamedSpansTests.mm similarity index 85% rename from Tests/BugsnagPerformanceNamedSpansTests/BugsnagPerformanceNamedSpansTests.m rename to Tests/BugsnagPerformanceNamedSpansTests/BugsnagPerformanceNamedSpansTests.mm index de593fa4..5ae5cf3f 100644 --- a/Tests/BugsnagPerformanceNamedSpansTests/BugsnagPerformanceNamedSpansTests.m +++ b/Tests/BugsnagPerformanceNamedSpansTests/BugsnagPerformanceNamedSpansTests.mm @@ -1,5 +1,5 @@ // -// BugsnagPerformanceNamedSpansTests.m +// BugsnagPerformanceNamedSpansTests.mm // BugsnagPerformanceNamedSpansTests // // Created by Yousif Ahmed on 22/07/2025. @@ -10,33 +10,11 @@ #import "BugsnagPerformanceNamedSpansPlugin.h" #import "BugsnagPerformanceNamedSpanQuery.h" #import -#import #import +#import "BugsnagPerformanceSpan+Private.h" +#import "IdGenerator.h" -@interface TestSpan: BugsnagPerformanceSpan - --(instancetype)initWithName:(NSString *)name; - -@end - -@implementation TestSpan - -- (instancetype)initWithName:(NSString *)name { - TraceId tid = {.value = 1}; - if ((self = [super initWithTraceId:traceId spanId:1])) { - _name = name; - } - - return self; -} - -@end - -@interface BugsnagPerformanceNamedSpansTests : XCTestCase -@property (nonatomic, strong) BugsnagPerformanceNamedSpansPlugin *plugin; -@property (nonatomic, strong) BugsnagPerformancePluginContext *mockContext; -@end - +using namespace bugsnag; @interface FakePluginContext : NSObject @property (nonatomic, copy) BugsnagPerformanceSpanStartCallback spanStartCallback; @@ -48,7 +26,20 @@ - (void)addSpanControlProvider:(id)provid @end static BugsnagPerformanceSpan *createSpan(NSString *name) { - return [[TestSpan alloc] initWithName:name]; + MetricsOptions metricsOptions; + TraceId tid = {.value = 1}; + return [[BugsnagPerformanceSpan alloc] initWithName:name + traceId:tid + spanId:IdGenerator::generateSpanId() + parentId:IdGenerator::generateSpanId() + startTime:SpanOptions().startTime + firstClass:BSGTriStateNo + samplingProbability:1.0 + attributeCountLimit:128 + metricsOptions:metricsOptions + onSpanEndSet:^(BugsnagPerformanceSpan * _Nonnull) {} + onSpanClosed:^(BugsnagPerformanceSpan * _Nonnull) {} + onSpanBlocked:^BugsnagPerformanceSpanCondition * _Nullable(BugsnagPerformanceSpan * _Nonnull, NSTimeInterval) { return nil; }]; } @@ -75,6 +66,11 @@ - (void)addSpanControlProvider:(id)provid @end +@interface BugsnagPerformanceNamedSpansTests : XCTestCase +@property (nonatomic, strong) BugsnagPerformanceNamedSpansPlugin *plugin; +@property (nonatomic, strong) BugsnagPerformancePluginContext *mockContext; +@end + @implementation BugsnagPerformanceNamedSpansTests - (void)setUp { @@ -97,7 +93,7 @@ - (void)testInstallWithContext { FakePluginContext *fakeContext = (FakePluginContext *)self.mockContext; XCTAssertNotNil(fakeContext.spanStartCallback); XCTAssertNotNil(fakeContext.spanEndCallback); - XCTAssertEqual(fakeContext.spanControlProviders.count, 1); + XCTAssertEqual(fakeContext.spanControlProviders.count, 1ULL); XCTAssertIdentical(fakeContext.spanControlProviders.firstObject, self.plugin); } @@ -232,29 +228,6 @@ - (void)testGetSpanControlsWithEmptyCache { #pragma mark - Timeout Tests -- (void)testSpanTimeoutRemovesSpanFromCache { - [self.plugin installWithContext:self.mockContext]; - - BugsnagPerformanceSpan *span = createSpan("timeout-span"); - FakePluginContext *fakeContext = (FakePluginContext *)self.mockContext; - - // Start span - fakeContext.spanStartCallback(span); - - // Verify span is cached - BugsnagPerformanceNamedSpanQuery *query = [BugsnagPerformanceNamedSpanQuery queryWithName:@"timeout-span"]; - id result = [self.plugin getSpanControlsWithQuery:query]; - XCTAssertIdentical(result, span); - - // Wait a short time and verify span is still there (not testing actual 10 minute timeout) - // This test mainly verifies the timeout timer is set up - dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ - BugsnagPerformanceNamedSpanQuery *query = [BugsnagPerformanceNamedSpanQuery queryWithName:@"timeout-span"]; - id result = [self.plugin getSpanControlsWithQuery:query]; - XCTAssertIdentical(result, span); - }); -} - #pragma mark - Thread Safety Tests - (void)testConcurrentSpanOperations { From d99df97b940ee5c277f2f8683f4bf9587568dd5e Mon Sep 17 00:00:00 2001 From: Yousif Ahmed Date: Fri, 1 Aug 2025 10:18:10 +0100 Subject: [PATCH 12/53] Fixed priority symbol visibility --- .../BugsnagPerformance/Public/BugsnagPerformancePriority.m | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Sources/BugsnagPerformance/Public/BugsnagPerformancePriority.m b/Sources/BugsnagPerformance/Public/BugsnagPerformancePriority.m index 7d45c232..06017595 100644 --- a/Sources/BugsnagPerformance/Public/BugsnagPerformancePriority.m +++ b/Sources/BugsnagPerformance/Public/BugsnagPerformancePriority.m @@ -8,6 +8,6 @@ #import -const BugsnagPerformancePriority BugsnagPerformancePriorityHigh = 100000; -const BugsnagPerformancePriority BugsnagPerformancePriorityMedium = 50000; -const BugsnagPerformancePriority BugsnagPerformancePriorityLow = 0; +__attribute__((visibility("default"))) const BugsnagPerformancePriority BugsnagPerformancePriorityHigh = 100000; +__attribute__((visibility("default"))) const BugsnagPerformancePriority BugsnagPerformancePriorityMedium = 50000; +__attribute__((visibility("default"))) const BugsnagPerformancePriority BugsnagPerformancePriorityLow = 0; From 7527ed43324d4475e694f8efa12459ab89928d51 Mon Sep 17 00:00:00 2001 From: Yousif Ahmed Date: Fri, 1 Aug 2025 10:30:28 +0100 Subject: [PATCH 13/53] Update xcframework pipeline to include named spans module --- .buildkite/pipeline.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.buildkite/pipeline.yml b/.buildkite/pipeline.yml index f1ad4b62..29df7331 100644 --- a/.buildkite/pipeline.yml +++ b/.buildkite/pipeline.yml @@ -20,6 +20,7 @@ steps: upload: - "BugsnagPerformance.xcframework.zip" - "BugsnagPerformanceSwift.xcframework.zip" + - "BugsnagPerformanceNamedSpans.xcframework.zip" - label: "Carthage" timeout_in_minutes: 10 @@ -76,6 +77,7 @@ steps: download: - "BugsnagPerformance.xcframework.zip" - "BugsnagPerformanceSwift.xcframework.zip" + - "BugsnagPerformanceNamedSpans.xcframework.zip" artifact_paths: - features/fixtures/ios/output/bb_xcframework_ipa_url.txt - features/fixtures/ios/output/bs_xcframework_ipa_url.txt From 4c6f9348e0246e085a2ef55658d95196ef8fa571 Mon Sep 17 00:00:00 2001 From: Yousif Ahmed Date: Fri, 1 Aug 2025 10:30:50 +0100 Subject: [PATCH 14/53] Add named spans module to fixture project --- features/fixtures/ios/Fixture.xcodeproj/project.pbxproj | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/features/fixtures/ios/Fixture.xcodeproj/project.pbxproj b/features/fixtures/ios/Fixture.xcodeproj/project.pbxproj index 8326a283..b5284e86 100644 --- a/features/fixtures/ios/Fixture.xcodeproj/project.pbxproj +++ b/features/fixtures/ios/Fixture.xcodeproj/project.pbxproj @@ -101,6 +101,7 @@ DA58B7D62DF87EC500CB80A4 /* PluginInstallErrorScenario.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA58B7D52DF87EA900CB80A4 /* PluginInstallErrorScenario.swift */; }; DA85961E2DF718D100905922 /* PluginScenario.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA85961D2DF718C800905922 /* PluginScenario.swift */; }; DA95ABF32E3A7E52001E6B0E /* NamedSpansPluginScenario.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA95ABF22E3A7E52001E6B0E /* NamedSpansPluginScenario.swift */; }; + DAC7DD672E3CBED10091961A /* BugsnagPerformanceNamedSpans in Frameworks */ = {isa = PBXBuildFile; productRef = DAC7DD662E3CBED10091961A /* BugsnagPerformanceNamedSpans */; }; DADB14F92DE08A030006F44F /* OnStartCallbackScenario.swift in Sources */ = {isa = PBXBuildFile; fileRef = DADB14F82DE08A030006F44F /* OnStartCallbackScenario.swift */; }; /* End PBXBuildFile section */ @@ -216,6 +217,7 @@ 01A414C82912CA18003152A4 /* Bugsnag in Frameworks */, 0983A17B2B14BB2000DDF4FF /* BugsnagPerformanceSwift in Frameworks */, 01FE4DC028E1AEDF00D1F239 /* BugsnagPerformance in Frameworks */, + DAC7DD672E3CBED10091961A /* BugsnagPerformanceNamedSpans in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -397,6 +399,7 @@ 01FE4DBF28E1AEDF00D1F239 /* BugsnagPerformance */, 01A414C72912CA18003152A4 /* Bugsnag */, 0983A17A2B14BB2000DDF4FF /* BugsnagPerformanceSwift */, + DAC7DD662E3CBED10091961A /* BugsnagPerformanceNamedSpans */, ); productName = Fixture; productReference = 01FE4DA528E1AEBD00D1F239 /* Fixture.app */; @@ -796,6 +799,10 @@ isa = XCSwiftPackageProductDependency; productName = BugsnagPerformanceSwift; }; + DAC7DD662E3CBED10091961A /* BugsnagPerformanceNamedSpans */ = { + isa = XCSwiftPackageProductDependency; + productName = BugsnagPerformanceNamedSpans; + }; /* End XCSwiftPackageProductDependency section */ }; rootObject = 01FE4D9D28E1AEBD00D1F239 /* Project object */; From de4ab7e2ae06114a0fadf3298f0a781852a1700f Mon Sep 17 00:00:00 2001 From: Yousif Ahmed Date: Fri, 1 Aug 2025 11:49:15 +0100 Subject: [PATCH 15/53] Updated unit tests --- .../BugsnagPerformanceNamedSpansTests.mm | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/Tests/BugsnagPerformanceNamedSpansTests/BugsnagPerformanceNamedSpansTests.mm b/Tests/BugsnagPerformanceNamedSpansTests/BugsnagPerformanceNamedSpansTests.mm index 5ae5cf3f..2a07df4b 100644 --- a/Tests/BugsnagPerformanceNamedSpansTests/BugsnagPerformanceNamedSpansTests.mm +++ b/Tests/BugsnagPerformanceNamedSpansTests/BugsnagPerformanceNamedSpansTests.mm @@ -202,6 +202,11 @@ - (void)testGetSpanControlsWithNamedSpanQuery { - (void)testGetSpanControlsWithNonNamedSpanQuery { [self.plugin installWithContext:self.mockContext]; + BugsnagPerformanceSpan *span = createSpan(@"my-span"); + FakePluginContext *fakeContext = (FakePluginContext *)self.mockContext; + + fakeContext.spanStartCallback(span); + BugsnagPerformanceSpanQuery *query = [BugsnagPerformanceSpanQuery queryWithResultType:[BugsnagPerformanceSpan class]]; id result = [self.plugin getSpanControlsWithQuery:query]; @@ -211,6 +216,11 @@ - (void)testGetSpanControlsWithNonNamedSpanQuery { - (void)testGetSpanControlsWithNonExistentSpanName { [self.plugin installWithContext:self.mockContext]; + BugsnagPerformanceSpan *span = createSpan(@"my-span"); + FakePluginContext *fakeContext = (FakePluginContext *)self.mockContext; + + fakeContext.spanStartCallback(span); + BugsnagPerformanceNamedSpanQuery *query = [BugsnagPerformanceNamedSpanQuery queryWithName:@"non-existent"]; id result = [self.plugin getSpanControlsWithQuery:query]; From 919bf4d84f8e5627bb60c19852ef75a94e1e1891 Mon Sep 17 00:00:00 2001 From: Yousif Ahmed Date: Fri, 1 Aug 2025 12:21:55 +0100 Subject: [PATCH 16/53] Added named spans to xcframework fixture --- .../ios/FixtureXcFramework.xcodeproj/project.pbxproj | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/features/fixtures/ios/FixtureXcFramework.xcodeproj/project.pbxproj b/features/fixtures/ios/FixtureXcFramework.xcodeproj/project.pbxproj index 9c7d6bc8..16c16db9 100644 --- a/features/fixtures/ios/FixtureXcFramework.xcodeproj/project.pbxproj +++ b/features/fixtures/ios/FixtureXcFramework.xcodeproj/project.pbxproj @@ -101,6 +101,8 @@ DA58B7D42DF8365E00CB80A4 /* PluginScenario.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA58B7D32DF8365E00CB80A4 /* PluginScenario.swift */; }; DA58B7D82DF97D7200CB80A4 /* PluginInstallErrorScenario.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA58B7D72DF97D7200CB80A4 /* PluginInstallErrorScenario.swift */; }; DA95ABF52E3A7FBA001E6B0E /* NamedSpansPluginScenario.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA95ABF42E3A7FBA001E6B0E /* NamedSpansPluginScenario.swift */; }; + DAC7DD6C2E3CDA990091961A /* BugsnagPerformanceNamedSpans.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = DAC7DD6A2E3CDA590091961A /* BugsnagPerformanceNamedSpans.xcframework */; }; + DAC7DD6D2E3CDA990091961A /* BugsnagPerformanceNamedSpans.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = DAC7DD6A2E3CDA590091961A /* BugsnagPerformanceNamedSpans.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; DADB14F72DDF852B0006F44F /* OnStartCallbackScenario.swift in Sources */ = {isa = PBXBuildFile; fileRef = DADB14F62DDF85210006F44F /* OnStartCallbackScenario.swift */; }; /* End PBXBuildFile section */ @@ -112,6 +114,7 @@ dstSubfolderSpec = 10; files = ( 55A1B7CE2CBFF140009D68A7 /* BugsnagPerformanceSwift.xcframework in Embed Frameworks */, + DAC7DD6D2E3CDA990091961A /* BugsnagPerformanceNamedSpans.xcframework in Embed Frameworks */, 55A1B7CC2CBFF140009D68A7 /* BugsnagPerformance.xcframework in Embed Frameworks */, ); name = "Embed Frameworks"; @@ -220,6 +223,7 @@ DA58B7D32DF8365E00CB80A4 /* PluginScenario.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PluginScenario.swift; sourceTree = ""; }; DA58B7D72DF97D7200CB80A4 /* PluginInstallErrorScenario.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PluginInstallErrorScenario.swift; sourceTree = ""; }; DA95ABF42E3A7FBA001E6B0E /* NamedSpansPluginScenario.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NamedSpansPluginScenario.swift; sourceTree = ""; }; + DAC7DD6A2E3CDA590091961A /* BugsnagPerformanceNamedSpans.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = BugsnagPerformanceNamedSpans.xcframework; path = ../../../BugsnagPerformanceNamedSpans.xcframework; sourceTree = ""; }; DADB14F62DDF85210006F44F /* OnStartCallbackScenario.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnStartCallbackScenario.swift; sourceTree = ""; }; /* End PBXFileReference section */ @@ -230,6 +234,7 @@ files = ( 55A1B7CB2CBFF140009D68A7 /* BugsnagPerformance.xcframework in Frameworks */, 55A1B7CD2CBFF140009D68A7 /* BugsnagPerformanceSwift.xcframework in Frameworks */, + DAC7DD6C2E3CDA990091961A /* BugsnagPerformanceNamedSpans.xcframework in Frameworks */, 01A414C82912CA18003152A4 /* Bugsnag in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -291,6 +296,7 @@ 01FE4DBE28E1AEDF00D1F239 /* Frameworks */ = { isa = PBXGroup; children = ( + DAC7DD6A2E3CDA590091961A /* BugsnagPerformanceNamedSpans.xcframework */, 55A1B7C92CBFF140009D68A7 /* BugsnagPerformance.xcframework */, 55A1B7CA2CBFF140009D68A7 /* BugsnagPerformanceSwift.xcframework */, ); From bfdba37c3e8c649b6b0a74aaa26ad87fd450e698 Mon Sep 17 00:00:00 2001 From: Yousif Ahmed Date: Fri, 1 Aug 2025 12:29:01 +0100 Subject: [PATCH 17/53] Fixed xcframework fixture build script --- features/fixtures/ios/build_xcframework.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/features/fixtures/ios/build_xcframework.sh b/features/fixtures/ios/build_xcframework.sh index b0ae8b8b..4d1d0a39 100755 --- a/features/fixtures/ios/build_xcframework.sh +++ b/features/fixtures/ios/build_xcframework.sh @@ -31,6 +31,7 @@ done; unzip BugsnagPerformance.xcframework.zip unzip BugsnagPerformanceSwift.xcframework.zip +unzip BugsnagPerformanceNamedSpans.xcframework.zip cp $(dirname "${BASH_SOURCE[0]}")/Fixture/Info.template.plist $(dirname "${BASH_SOURCE[0]}")/Fixture/Info.plist From 630e9c403e8e58805d06c7a2502dd6fd24f0e39b Mon Sep 17 00:00:00 2001 From: Robert Bartoszewski Date: Fri, 1 Aug 2025 14:11:40 +0200 Subject: [PATCH 18/53] Added an xcscheme for BugsnagPerformanceNamedSpans --- .../BugsnagPerformanceNamedSpans.xcscheme | 66 +++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 BugsnagPerformance.xcodeproj/xcshareddata/xcschemes/BugsnagPerformanceNamedSpans.xcscheme diff --git a/BugsnagPerformance.xcodeproj/xcshareddata/xcschemes/BugsnagPerformanceNamedSpans.xcscheme b/BugsnagPerformance.xcodeproj/xcshareddata/xcschemes/BugsnagPerformanceNamedSpans.xcscheme new file mode 100644 index 00000000..93d1a721 --- /dev/null +++ b/BugsnagPerformance.xcodeproj/xcshareddata/xcschemes/BugsnagPerformanceNamedSpans.xcscheme @@ -0,0 +1,66 @@ + + + + + + + + + + + + + + + + + + + + + + + + + From e67d7463fa8028186db14dee8d2c2287c9cdd0c8 Mon Sep 17 00:00:00 2001 From: Robert Bartoszewski Date: Fri, 1 Aug 2025 15:02:58 +0200 Subject: [PATCH 19/53] Made BugsnagPerformanceNamedSpans headers public --- BugsnagPerformance.xcodeproj/project.pbxproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/BugsnagPerformance.xcodeproj/project.pbxproj b/BugsnagPerformance.xcodeproj/project.pbxproj index 5791700a..04107cf5 100644 --- a/BugsnagPerformance.xcodeproj/project.pbxproj +++ b/BugsnagPerformance.xcodeproj/project.pbxproj @@ -204,12 +204,12 @@ CBF7C5E2297A8E9100D47719 /* Gzip.m in Sources */ = {isa = PBXBuildFile; fileRef = CBF7C5E0297A8E9100D47719 /* Gzip.m */; }; CBF7C5E4297A964900D47719 /* libz.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = CBF7C5E3297A963A00D47719 /* libz.tbd */; }; DA87DAF02E2FDFE60040F086 /* BugsnagPerformanceNamedSpansPlugin.mm in Sources */ = {isa = PBXBuildFile; fileRef = DA87DAEE2E2FDFE60040F086 /* BugsnagPerformanceNamedSpansPlugin.mm */; }; - DA87DAF92E3791CE0040F086 /* BugsnagPerformanceNamedSpanQuery.h in Headers */ = {isa = PBXBuildFile; fileRef = DA87DAF72E3791CE0040F086 /* BugsnagPerformanceNamedSpanQuery.h */; }; + DA87DAF92E3791CE0040F086 /* BugsnagPerformanceNamedSpanQuery.h in Headers */ = {isa = PBXBuildFile; fileRef = DA87DAF72E3791CE0040F086 /* BugsnagPerformanceNamedSpanQuery.h */; settings = {ATTRIBUTES = (Public, ); }; }; DA87DAFB2E3792B10040F086 /* BugsnagPerformanceNamedSpanQuery.m in Sources */ = {isa = PBXBuildFile; fileRef = DA87DAFA2E3792B10040F086 /* BugsnagPerformanceNamedSpanQuery.m */; }; DA8E5EEB2E2FC3E80049F7AB /* BugsnagPerformanceNamedSpans.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DA8E5ED62E2FC3E80049F7AB /* BugsnagPerformanceNamedSpans.framework */; }; DA8E5EEC2E2FC3E80049F7AB /* BugsnagPerformanceNamedSpans.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = DA8E5ED62E2FC3E80049F7AB /* BugsnagPerformanceNamedSpans.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; DA8E5EF42E2FC45B0049F7AB /* BugsnagPerformance.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 72E4BB63359EA30E80116E2A /* BugsnagPerformance.framework */; }; - DA8E5EFF2E2FCA020049F7AB /* BugsnagPerformanceNamedSpansPlugin.h in Headers */ = {isa = PBXBuildFile; fileRef = DA8E5EFD2E2FCA020049F7AB /* BugsnagPerformanceNamedSpansPlugin.h */; }; + DA8E5EFF2E2FCA020049F7AB /* BugsnagPerformanceNamedSpansPlugin.h in Headers */ = {isa = PBXBuildFile; fileRef = DA8E5EFD2E2FCA020049F7AB /* BugsnagPerformanceNamedSpansPlugin.h */; settings = {ATTRIBUTES = (Public, ); }; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ From 6c4a003c17cfa230dc081ca2660f62842ae8469a Mon Sep 17 00:00:00 2001 From: Robert Bartoszewski Date: Fri, 1 Aug 2025 15:58:56 +0200 Subject: [PATCH 20/53] Created BugsnagPerformanceNamedSpans.h --- BugsnagPerformance.xcodeproj/project.pbxproj | 4 ++++ .../BugsnagPerformanceNamedSpanQuery.h | 1 + .../BugsnagPerformanceNamedSpans.h | 11 +++++++++++ .../BugsnagPerformanceNamedSpansPlugin.h | 1 + 4 files changed, 17 insertions(+) create mode 100644 Sources/BugsnagPerformanceNamedSpans/include/BugsnagPerformanceNamedSpans/BugsnagPerformanceNamedSpans.h diff --git a/BugsnagPerformance.xcodeproj/project.pbxproj b/BugsnagPerformance.xcodeproj/project.pbxproj index 04107cf5..af5f2b07 100644 --- a/BugsnagPerformance.xcodeproj/project.pbxproj +++ b/BugsnagPerformance.xcodeproj/project.pbxproj @@ -106,6 +106,7 @@ 963828B92E3C0B9A00404F3A /* BugsnagPerformance.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 72E4BB63359EA30E80116E2A /* BugsnagPerformance.framework */; }; 963828BF2E3C0BAC00404F3A /* BugsnagPerformanceNamedSpans.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DA8E5ED62E2FC3E80049F7AB /* BugsnagPerformanceNamedSpans.framework */; }; 963828C42E3C175900404F3A /* BugsnagPerformanceNamedSpansTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = DA8E5F0F2E2FCDED0049F7AB /* BugsnagPerformanceNamedSpansTests.mm */; }; + 963828C62E3CFE2400404F3A /* BugsnagPerformanceNamedSpans.h in Headers */ = {isa = PBXBuildFile; fileRef = 963828C52E3CFE1800404F3A /* BugsnagPerformanceNamedSpans.h */; settings = {ATTRIBUTES = (Public, ); }; }; 964B78502D39D36900FF077D /* BugsnagPerformanceSpanCondition.h in Headers */ = {isa = PBXBuildFile; fileRef = 964B784F2D39D36900FF077D /* BugsnagPerformanceSpanCondition.h */; settings = {ATTRIBUTES = (Public, ); }; }; 964B78522D47D26F00FF077D /* ConditionTimeoutExecutor.h in Headers */ = {isa = PBXBuildFile; fileRef = 964B78512D47D26F00FF077D /* ConditionTimeoutExecutor.h */; }; 964B78562D4B924C00FF077D /* BugsnagPerformanceSpanCondition.mm in Sources */ = {isa = PBXBuildFile; fileRef = 964B78552D4B924C00FF077D /* BugsnagPerformanceSpanCondition.mm */; }; @@ -436,6 +437,7 @@ 963726E52DF1DFBA00C739E6 /* BSGPluginManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = BSGPluginManager.h; sourceTree = ""; }; 963726E72DF1DFCB00C739E6 /* BSGPluginManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = BSGPluginManager.m; sourceTree = ""; }; 963828BE2E3C0B9A00404F3A /* BugsnagPerformanceNamedSpansTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = BugsnagPerformanceNamedSpansTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 963828C52E3CFE1800404F3A /* BugsnagPerformanceNamedSpans.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = BugsnagPerformanceNamedSpans.h; sourceTree = ""; }; 964B784F2D39D36900FF077D /* BugsnagPerformanceSpanCondition.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = BugsnagPerformanceSpanCondition.h; sourceTree = ""; }; 964B78512D47D26F00FF077D /* ConditionTimeoutExecutor.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ConditionTimeoutExecutor.h; sourceTree = ""; }; 964B78552D4B924C00FF077D /* BugsnagPerformanceSpanCondition.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = BugsnagPerformanceSpanCondition.mm; sourceTree = ""; }; @@ -1050,6 +1052,7 @@ DA8E5EF92E2FC9AD0049F7AB /* BugsnagPerformanceNamedSpans */ = { isa = PBXGroup; children = ( + 963828C52E3CFE1800404F3A /* BugsnagPerformanceNamedSpans.h */, DA8E5EFD2E2FCA020049F7AB /* BugsnagPerformanceNamedSpansPlugin.h */, DA87DAF72E3791CE0040F086 /* BugsnagPerformanceNamedSpanQuery.h */, ); @@ -1174,6 +1177,7 @@ 96F417152E3B8E7000EABD8E /* BugsnagPerformanceNamedSpans.h in Headers */, DA87DAF92E3791CE0040F086 /* BugsnagPerformanceNamedSpanQuery.h in Headers */, DA8E5EFF2E2FCA020049F7AB /* BugsnagPerformanceNamedSpansPlugin.h in Headers */, + 963828C62E3CFE2400404F3A /* BugsnagPerformanceNamedSpans.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Sources/BugsnagPerformanceNamedSpans/include/BugsnagPerformanceNamedSpans/BugsnagPerformanceNamedSpanQuery.h b/Sources/BugsnagPerformanceNamedSpans/include/BugsnagPerformanceNamedSpans/BugsnagPerformanceNamedSpanQuery.h index 66554594..cd7b4946 100644 --- a/Sources/BugsnagPerformanceNamedSpans/include/BugsnagPerformanceNamedSpans/BugsnagPerformanceNamedSpanQuery.h +++ b/Sources/BugsnagPerformanceNamedSpans/include/BugsnagPerformanceNamedSpans/BugsnagPerformanceNamedSpanQuery.h @@ -9,6 +9,7 @@ NS_ASSUME_NONNULL_BEGIN +OBJC_EXPORT @interface BugsnagPerformanceNamedSpanQuery : BugsnagPerformanceSpanQuery + (instancetype)queryWithName:(nonnull NSString *)name; diff --git a/Sources/BugsnagPerformanceNamedSpans/include/BugsnagPerformanceNamedSpans/BugsnagPerformanceNamedSpans.h b/Sources/BugsnagPerformanceNamedSpans/include/BugsnagPerformanceNamedSpans/BugsnagPerformanceNamedSpans.h new file mode 100644 index 00000000..eaa83a79 --- /dev/null +++ b/Sources/BugsnagPerformanceNamedSpans/include/BugsnagPerformanceNamedSpans/BugsnagPerformanceNamedSpans.h @@ -0,0 +1,11 @@ +// +// BugsnagPerformanceNamedSpans.h +// BugsnagPerformance +// +// Created by Robert Bartoszewski on 01/08/2025. +// Copyright © 2025 Bugsnag. All rights reserved. +// + + +#import +#import diff --git a/Sources/BugsnagPerformanceNamedSpans/include/BugsnagPerformanceNamedSpans/BugsnagPerformanceNamedSpansPlugin.h b/Sources/BugsnagPerformanceNamedSpans/include/BugsnagPerformanceNamedSpans/BugsnagPerformanceNamedSpansPlugin.h index a8a55d95..f33c8779 100644 --- a/Sources/BugsnagPerformanceNamedSpans/include/BugsnagPerformanceNamedSpans/BugsnagPerformanceNamedSpansPlugin.h +++ b/Sources/BugsnagPerformanceNamedSpans/include/BugsnagPerformanceNamedSpans/BugsnagPerformanceNamedSpansPlugin.h @@ -10,6 +10,7 @@ NS_ASSUME_NONNULL_BEGIN +OBJC_EXPORT @interface BugsnagPerformanceNamedSpansPlugin: NSObject @end From 6d53fab830a8fc5aa4e81be7ba54dcba017a61d2 Mon Sep 17 00:00:00 2001 From: Robert Bartoszewski Date: Fri, 1 Aug 2025 16:02:52 +0200 Subject: [PATCH 21/53] Fixed BugsnagPerformanceNamedSpans.h --- .../BugsnagPerformanceNamedSpans.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Sources/BugsnagPerformanceNamedSpans/include/BugsnagPerformanceNamedSpans/BugsnagPerformanceNamedSpans.h b/Sources/BugsnagPerformanceNamedSpans/include/BugsnagPerformanceNamedSpans/BugsnagPerformanceNamedSpans.h index eaa83a79..3c18c1d4 100644 --- a/Sources/BugsnagPerformanceNamedSpans/include/BugsnagPerformanceNamedSpans/BugsnagPerformanceNamedSpans.h +++ b/Sources/BugsnagPerformanceNamedSpans/include/BugsnagPerformanceNamedSpans/BugsnagPerformanceNamedSpans.h @@ -1,11 +1,11 @@ // // BugsnagPerformanceNamedSpans.h -// BugsnagPerformance +// BugsnagPerformanceNamedSpans // // Created by Robert Bartoszewski on 01/08/2025. // Copyright © 2025 Bugsnag. All rights reserved. // -#import -#import +#import +#import From 350014873ab9ff61f21219398aaa7884886708c7 Mon Sep 17 00:00:00 2001 From: Yousif Ahmed Date: Mon, 4 Aug 2025 10:56:30 +0100 Subject: [PATCH 22/53] Fixed concurrency test --- .../BugsnagPerformanceNamedSpansTests.mm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Tests/BugsnagPerformanceNamedSpansTests/BugsnagPerformanceNamedSpansTests.mm b/Tests/BugsnagPerformanceNamedSpansTests/BugsnagPerformanceNamedSpansTests.mm index 2a07df4b..006134ce 100644 --- a/Tests/BugsnagPerformanceNamedSpansTests/BugsnagPerformanceNamedSpansTests.mm +++ b/Tests/BugsnagPerformanceNamedSpansTests/BugsnagPerformanceNamedSpansTests.mm @@ -244,14 +244,12 @@ - (void)testConcurrentSpanOperations { [self.plugin installWithContext:self.mockContext]; FakePluginContext *fakeContext = (FakePluginContext *)self.mockContext; - NSMutableArray *spans = [NSMutableArray new]; // Create multiple spans concurrently dispatch_group_t group = dispatch_group_create(); for (int i = 0; i < 10; i++) { dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ BugsnagPerformanceSpan *span = createSpan([NSString stringWithFormat:@"span-%d", i]); - [spans addObject:span]; fakeContext.spanStartCallback(span); }); } @@ -260,10 +258,12 @@ - (void)testConcurrentSpanOperations { dispatch_group_wait(group, DISPATCH_TIME_FOREVER); // Verify all spans are cached + NSMutableArray *spans = [NSMutableArray new]; for (int i = 0; i < 10; i++) { BugsnagPerformanceNamedSpanQuery *query = [BugsnagPerformanceNamedSpanQuery queryWithName:[NSString stringWithFormat:@"span-%d", i]]; id result = [self.plugin getSpanControlsWithQuery:query]; XCTAssertNotNil(result); + [spans addObject:result]; } // End all spans concurrently From 36dc309d7b2cae1f185aa5af6357f549f98c367e Mon Sep 17 00:00:00 2001 From: Yousif Ahmed Date: Mon, 4 Aug 2025 10:57:27 +0100 Subject: [PATCH 23/53] Improved span timeout handling --- .../BugsnagPerformanceNamedSpansPlugin.mm | 42 +++++++++++-------- 1 file changed, 25 insertions(+), 17 deletions(-) diff --git a/Sources/BugsnagPerformanceNamedSpans/Public/BugsnagPerformanceNamedSpansPlugin.mm b/Sources/BugsnagPerformanceNamedSpans/Public/BugsnagPerformanceNamedSpansPlugin.mm index 02343949..f281d342 100644 --- a/Sources/BugsnagPerformanceNamedSpans/Public/BugsnagPerformanceNamedSpansPlugin.mm +++ b/Sources/BugsnagPerformanceNamedSpans/Public/BugsnagPerformanceNamedSpansPlugin.mm @@ -35,6 +35,12 @@ - (void)installWithContext:(BugsnagPerformancePluginContext *)context { __block BugsnagPerformanceNamedSpansPlugin *blockSelf = self; BugsnagPerformanceSpanStartCallback spanStartCallback = ^(BugsnagPerformanceSpan *span) { @synchronized (blockSelf) { + BugsnagPerformanceSpan *existingSpan = blockSelf.spansByName[span.name]; + if (existingSpan) { + // If a span with the same name already exists, cancel the associated timeout + [blockSelf cancelSpanTimeout:existingSpan]; + } + blockSelf.spansByName[span.name] = span; // Add a 10 minute timeout to remove the span from the cache if not ended @@ -71,6 +77,18 @@ - (void)start { #pragma mark Private +- (BOOL)endNativeSpan:(BugsnagPerformanceSpan *)span { + @synchronized (self) { + // Remove span from cache if it exists + if ([self.spansByName objectForKey:span.name] == span) { + [self.spansByName removeObjectForKey:span.name]; + } + + [self cancelSpanTimeout:span]; + } + return YES; +} + - (dispatch_source_t)createSpanTimeoutTimer:(BugsnagPerformanceSpan *)span { __weak BugsnagPerformanceNamedSpansPlugin *weakSelf = self; @@ -90,25 +108,15 @@ - (dispatch_source_t)createSpanTimeoutTimer:(BugsnagPerformanceSpan *)span { return timer; } -#pragma clang diagnostic ignored "-Wdirect-ivar-access" -- (BOOL)endNativeSpan:(BugsnagPerformanceSpan *)span { +- (void)cancelSpanTimeout:(BugsnagPerformanceSpan *)span { void *key = (__bridge void *)span; - - @synchronized (self) { - // Remove span from cache if it exists - if ([self.spansByName objectForKey:span.name] == span) { - [self.spansByName removeObjectForKey:span.name]; - } - - // Clean up timer for this span - auto& timerMap = _spanTimeoutTimers; - auto it = timerMap.find(key); - if (it != timerMap.end()) { - dispatch_source_cancel(it->second); - timerMap.erase(it); - } + auto& timerMap = _spanTimeoutTimers; + auto it = timerMap.find(key); + if (it != timerMap.end()) { + dispatch_source_cancel(it->second); + timerMap.erase(it); } - return YES; } + @end From 9da42c80aeef1038f05ab8199d97b1c3eda4c523 Mon Sep 17 00:00:00 2001 From: Yousif Ahmed Date: Mon, 4 Aug 2025 13:39:35 +0100 Subject: [PATCH 24/53] Refactored plugin to allow better testing of timeout handling --- BugsnagPerformance.xcodeproj/project.pbxproj | 20 +- ...gsnagPerformanceNamedSpansPlugin+Private.h | 26 +++ .../BugsnagPerformanceNamedSpansPlugin.mm | 24 ++- .../BugsnagPerformanceNamedSpansTests.mm | 172 +++++++++++++++++- 4 files changed, 225 insertions(+), 17 deletions(-) create mode 100644 Sources/BugsnagPerformanceNamedSpans/Private/BugsnagPerformanceNamedSpansPlugin+Private.h diff --git a/BugsnagPerformance.xcodeproj/project.pbxproj b/BugsnagPerformance.xcodeproj/project.pbxproj index af5f2b07..902f445e 100644 --- a/BugsnagPerformance.xcodeproj/project.pbxproj +++ b/BugsnagPerformance.xcodeproj/project.pbxproj @@ -211,6 +211,7 @@ DA8E5EEC2E2FC3E80049F7AB /* BugsnagPerformanceNamedSpans.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = DA8E5ED62E2FC3E80049F7AB /* BugsnagPerformanceNamedSpans.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; DA8E5EF42E2FC45B0049F7AB /* BugsnagPerformance.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 72E4BB63359EA30E80116E2A /* BugsnagPerformance.framework */; }; DA8E5EFF2E2FCA020049F7AB /* BugsnagPerformanceNamedSpansPlugin.h in Headers */ = {isa = PBXBuildFile; fileRef = DA8E5EFD2E2FCA020049F7AB /* BugsnagPerformanceNamedSpansPlugin.h */; settings = {ATTRIBUTES = (Public, ); }; }; + DA9BB1CB2E40CC19009A7D25 /* BugsnagPerformanceNamedSpansPlugin+Private.h in Headers */ = {isa = PBXBuildFile; fileRef = DA9BB1CA2E40CC19009A7D25 /* BugsnagPerformanceNamedSpansPlugin+Private.h */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -548,6 +549,7 @@ DA8E5ED62E2FC3E80049F7AB /* BugsnagPerformanceNamedSpans.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = BugsnagPerformanceNamedSpans.framework; sourceTree = BUILT_PRODUCTS_DIR; }; DA8E5EFD2E2FCA020049F7AB /* BugsnagPerformanceNamedSpansPlugin.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = BugsnagPerformanceNamedSpansPlugin.h; sourceTree = ""; }; DA8E5F0F2E2FCDED0049F7AB /* BugsnagPerformanceNamedSpansTests.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = BugsnagPerformanceNamedSpansTests.mm; sourceTree = ""; }; + DA9BB1CA2E40CC19009A7D25 /* BugsnagPerformanceNamedSpansPlugin+Private.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "BugsnagPerformanceNamedSpansPlugin+Private.h"; sourceTree = ""; }; EDE8339F2AF550E20042A78F /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; lastKnownFileType = text.xml; name = PrivacyInfo.xcprivacy; path = Sources/BugsnagPerformance/resources/PrivacyInfo.xcprivacy; sourceTree = ""; }; /* End PBXFileReference section */ @@ -1018,8 +1020,8 @@ DA8E5ECD2E2FC32C0049F7AB /* BugsnagPerformanceNamedSpans */ = { isa = PBXGroup; children = ( + DA9BB1C92E40CBE4009A7D25 /* Private */, DA8E5ECE2E2FC3520049F7AB /* include */, - DA8E5ED02E2FC3710049F7AB /* Private */, DA8E5ECF2E2FC36B0049F7AB /* Public */, ); path = BugsnagPerformanceNamedSpans; @@ -1042,13 +1044,6 @@ path = Public; sourceTree = ""; }; - DA8E5ED02E2FC3710049F7AB /* Private */ = { - isa = PBXGroup; - children = ( - ); - path = Private; - sourceTree = ""; - }; DA8E5EF92E2FC9AD0049F7AB /* BugsnagPerformanceNamedSpans */ = { isa = PBXGroup; children = ( @@ -1067,6 +1062,14 @@ path = BugsnagPerformanceNamedSpansTests; sourceTree = ""; }; + DA9BB1C92E40CBE4009A7D25 /* Private */ = { + isa = PBXGroup; + children = ( + DA9BB1CA2E40CC19009A7D25 /* BugsnagPerformanceNamedSpansPlugin+Private.h */, + ); + path = Private; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXHeadersBuildPhase section */ @@ -1175,6 +1178,7 @@ buildActionMask = 2147483647; files = ( 96F417152E3B8E7000EABD8E /* BugsnagPerformanceNamedSpans.h in Headers */, + DA9BB1CB2E40CC19009A7D25 /* BugsnagPerformanceNamedSpansPlugin+Private.h in Headers */, DA87DAF92E3791CE0040F086 /* BugsnagPerformanceNamedSpanQuery.h in Headers */, DA8E5EFF2E2FCA020049F7AB /* BugsnagPerformanceNamedSpansPlugin.h in Headers */, 963828C62E3CFE2400404F3A /* BugsnagPerformanceNamedSpans.h in Headers */, diff --git a/Sources/BugsnagPerformanceNamedSpans/Private/BugsnagPerformanceNamedSpansPlugin+Private.h b/Sources/BugsnagPerformanceNamedSpans/Private/BugsnagPerformanceNamedSpansPlugin+Private.h new file mode 100644 index 00000000..2d6b0e4e --- /dev/null +++ b/Sources/BugsnagPerformanceNamedSpans/Private/BugsnagPerformanceNamedSpansPlugin+Private.h @@ -0,0 +1,26 @@ +// +// BugsnagPerformanceNamedSpansPlugin+Private.h +// BugsnagPerformanceNamedSpans +// +// Created by Yousif Ahmed on 04/08/2025. +// Copyright © 2025 Bugsnag. All rights reserved. +// +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface BugsnagPerformanceNamedSpansPlugin () + +/** + * Creates a new plugin instance with a custom timeout interval (for testing purposes). + */ +- (instancetype)initWithTimeoutInterval:(NSTimeInterval)timeoutInterval NS_DESIGNATED_INITIALIZER; + +@property (nonatomic, assign, readonly) NSTimeInterval timeoutInterval; + +@property (nonatomic, assign, readonly) std::map spanTimeoutTimers; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Sources/BugsnagPerformanceNamedSpans/Public/BugsnagPerformanceNamedSpansPlugin.mm b/Sources/BugsnagPerformanceNamedSpans/Public/BugsnagPerformanceNamedSpansPlugin.mm index f281d342..7e5cd7a3 100644 --- a/Sources/BugsnagPerformanceNamedSpans/Public/BugsnagPerformanceNamedSpansPlugin.mm +++ b/Sources/BugsnagPerformanceNamedSpans/Public/BugsnagPerformanceNamedSpansPlugin.mm @@ -10,21 +10,31 @@ #import #import #import -#import +#import "../Private/BugsnagPerformanceNamedSpansPlugin+Private.h" static const NSTimeInterval kSpanTimeoutInterval = 600; // 10 minutes -@interface BugsnagPerformanceNamedSpansPlugin () { - std::map _spanTimeoutTimers; -} +@interface BugsnagPerformanceNamedSpansPlugin () @property (nonatomic, strong) NSMutableDictionary *spansByName; @end - @implementation BugsnagPerformanceNamedSpansPlugin +#pragma mark - Initialization + +- (instancetype)init { + return [self initWithTimeoutInterval:kSpanTimeoutInterval]; +} + +- (instancetype)initWithTimeoutInterval:(NSTimeInterval)timeoutInterval { + if ((self = [super init])) { + _timeoutInterval = timeoutInterval; + } + return self; +} + #pragma mark BugsnagPerformancePlugin #pragma clang diagnostic ignored "-Wdirect-ivar-access" @@ -43,7 +53,7 @@ - (void)installWithContext:(BugsnagPerformancePluginContext *)context { blockSelf.spansByName[span.name] = span; - // Add a 10 minute timeout to remove the span from the cache if not ended + // Add timeout to remove the span from the cache if not ended void *key = (__bridge void *)span; dispatch_source_t timer = [blockSelf createSpanTimeoutTimer:span]; blockSelf->_spanTimeoutTimers[key] = timer; @@ -96,7 +106,7 @@ - (dispatch_source_t)createSpanTimeoutTimer:(BugsnagPerformanceSpan *)span { dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0)); dispatch_source_set_timer(timer, - dispatch_time(DISPATCH_TIME_NOW, (int64_t)(kSpanTimeoutInterval * NSEC_PER_SEC)), + dispatch_time(DISPATCH_TIME_NOW, (int64_t)(self.timeoutInterval * NSEC_PER_SEC)), DISPATCH_TIME_FOREVER, 0); diff --git a/Tests/BugsnagPerformanceNamedSpansTests/BugsnagPerformanceNamedSpansTests.mm b/Tests/BugsnagPerformanceNamedSpansTests/BugsnagPerformanceNamedSpansTests.mm index 006134ce..2376500a 100644 --- a/Tests/BugsnagPerformanceNamedSpansTests/BugsnagPerformanceNamedSpansTests.mm +++ b/Tests/BugsnagPerformanceNamedSpansTests/BugsnagPerformanceNamedSpansTests.mm @@ -7,7 +7,7 @@ // #import -#import "BugsnagPerformanceNamedSpansPlugin.h" +#import "BugsnagPerformanceNamedSpansPlugin+Private.h" #import "BugsnagPerformanceNamedSpanQuery.h" #import #import @@ -75,7 +75,7 @@ @implementation BugsnagPerformanceNamedSpansTests - (void)setUp { [super setUp]; - self.plugin = [[BugsnagPerformanceNamedSpansPlugin alloc] init]; + self.plugin = [[BugsnagPerformanceNamedSpansPlugin alloc] initWithTimeoutInterval:0.2]; // Use a short timeout for testing self.mockContext = (BugsnagPerformancePluginContext *)[[FakePluginContext alloc] init]; } @@ -102,6 +102,16 @@ - (void)testStartMethod { XCTAssertNoThrow([self.plugin start]); } +- (void)testInitWithCustomTimeoutInterval { + BugsnagPerformanceNamedSpansPlugin *customPlugin = [[BugsnagPerformanceNamedSpansPlugin alloc] initWithTimeoutInterval:30.0]; + XCTAssertEqual(customPlugin.timeoutInterval, 30.0); +} + +- (void)testDefaultInitUsesDefaultTimeout { + BugsnagPerformanceNamedSpansPlugin *defaultPlugin = [[BugsnagPerformanceNamedSpansPlugin alloc] init]; + XCTAssertEqual(defaultPlugin.timeoutInterval, 600.0); // 10 minutes +} + #pragma mark - Span Caching Tests - (void)testSpanStartCallbackCachesSpan { @@ -238,6 +248,164 @@ - (void)testGetSpanControlsWithEmptyCache { #pragma mark - Timeout Tests +- (void)testSpanTimeoutTimerRemovesSpanFromCache { + [self.plugin installWithContext:self.mockContext]; + + BugsnagPerformanceSpan *span = createSpan(@"timeout-span"); + FakePluginContext *fakeContext = (FakePluginContext *)self.mockContext; + + // Start span (creates timeout timer) + fakeContext.spanStartCallback(span); + + // Verify span is cached + BugsnagPerformanceNamedSpanQuery *query = [BugsnagPerformanceNamedSpanQuery queryWithName:@"timeout-span"]; + id cachedSpan = [self.plugin getSpanControlsWithQuery:query]; + XCTAssertIdentical(cachedSpan, span); + XCTAssertEqual(self.plugin.spanTimeoutTimers.size(), 1UL); + + // Wait for timeout to trigger + XCTestExpectation *timeoutExpectation = [self expectationWithDescription:@"Span timeout should remove span from cache"]; + + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ + // Check span is removed from cache after timeout interval + id stillCachedSpan = [self.plugin getSpanControlsWithQuery:query]; + XCTAssertNil(stillCachedSpan); + XCTAssertEqual(self.plugin.spanTimeoutTimers.size(), 0UL); + [timeoutExpectation fulfill]; + }); + + [self waitForExpectations:@[timeoutExpectation] timeout:0.5]; +} + +- (void)testTimeoutTimerCancelledWhenSpanEnds { + [self.plugin installWithContext:self.mockContext]; + + BugsnagPerformanceSpan *span = createSpan(@"timer-span"); + FakePluginContext *fakeContext = (FakePluginContext *)self.mockContext; + + // Start span (creates timeout timer) + fakeContext.spanStartCallback(span); + + // Verify span is cached + BugsnagPerformanceNamedSpanQuery *query = [BugsnagPerformanceNamedSpanQuery queryWithName:@"timer-span"]; + id cachedSpan = [self.plugin getSpanControlsWithQuery:query]; + XCTAssertIdentical(cachedSpan, span); + XCTAssertEqual(self.plugin.spanTimeoutTimers.size(), 1UL); + + // End span (should cancel timeout timer and remove from cache) + BOOL result = fakeContext.spanEndCallback(span); + XCTAssertTrue(result); + + // Verify span is removed from cache + id removedSpan = [self.plugin getSpanControlsWithQuery:query]; + XCTAssertNil(removedSpan); + XCTAssertEqual(self.plugin.spanTimeoutTimers.size(), 0UL); +} + +- (void)testTimeoutTimerCancelledWhenNewSpanWithSameNameStarted { + [self.plugin installWithContext:self.mockContext]; + + BugsnagPerformanceSpan *span1 = createSpan(@"test-span"); + BugsnagPerformanceSpan *span2 = createSpan(@"test-span"); + FakePluginContext *fakeContext = (FakePluginContext *)self.mockContext; + + // Start first span (creates timeout timer) + fakeContext.spanStartCallback(span1); + + // Verify first span is cached + BugsnagPerformanceNamedSpanQuery *query = [BugsnagPerformanceNamedSpanQuery queryWithName:@"test-span"]; + id cachedSpan = [self.plugin getSpanControlsWithQuery:query]; + XCTAssertIdentical(cachedSpan, span1); + + XCTAssertEqual(self.plugin.spanTimeoutTimers.size(), 1UL); + + // Wait for first span's timeout to almost elapse (0.15 seconds of 0.2 second timeout) + XCTestExpectation *stagingExpectation = [self expectationWithDescription:@"Second span with same name should replace first span"]; + + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.15 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ + // Verify first span's timer is still active + XCTAssertEqual(self.plugin.spanTimeoutTimers.size(), 1UL); + + // Start second span with same name (should cancel first timer and create a new one) + fakeContext.spanStartCallback(span2); + + // Verify second span replaced first span + id newCachedSpan = [self.plugin getSpanControlsWithQuery:query]; + XCTAssertIdentical(newCachedSpan, span2); + XCTAssertEqual(self.plugin.spanTimeoutTimers.size(), 1UL); + + [stagingExpectation fulfill]; + }); + + [self waitForExpectations:@[stagingExpectation] timeout:0.3]; + + // Now wait for the original first span's timeout period to fully elapse (0.1 more seconds) + XCTestExpectation *timeoutExpectation = [self expectationWithDescription:@"Second span with same name should create a new timeout"]; + + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ + // Verify second span is still cached + id stillCachedSpan = [self.plugin getSpanControlsWithQuery:query]; + XCTAssertIdentical(stillCachedSpan, span2); + XCTAssertEqual(self.plugin.spanTimeoutTimers.size(), 1UL); + [timeoutExpectation fulfill]; + }); + + [self waitForExpectations:@[timeoutExpectation] timeout:0.3]; +} + +- (void)testMultipleSpansWithDifferentNamesHaveSeparateTimeoutTimers { + [self.plugin installWithContext:self.mockContext]; + + BugsnagPerformanceSpan *span1 = createSpan(@"span-one"); + BugsnagPerformanceSpan *span2 = createSpan(@"span-two"); + BugsnagPerformanceSpan *span3 = createSpan(@"span-three"); + FakePluginContext *fakeContext = (FakePluginContext *)self.mockContext; + + // Start first span + fakeContext.spanStartCallback(span1); + XCTAssertEqual(self.plugin.spanTimeoutTimers.size(), 1UL); + + // Start second span with different name + fakeContext.spanStartCallback(span2); + XCTAssertEqual(self.plugin.spanTimeoutTimers.size(), 2UL); + + // Start third span with different name + fakeContext.spanStartCallback(span3); + XCTAssertEqual(self.plugin.spanTimeoutTimers.size(), 3UL); + + // Verify all spans are cached + BugsnagPerformanceNamedSpanQuery *query1 = [BugsnagPerformanceNamedSpanQuery queryWithName:@"span-one"]; + BugsnagPerformanceNamedSpanQuery *query2 = [BugsnagPerformanceNamedSpanQuery queryWithName:@"span-two"]; + BugsnagPerformanceNamedSpanQuery *query3 = [BugsnagPerformanceNamedSpanQuery queryWithName:@"span-three"]; + + XCTAssertIdentical([self.plugin getSpanControlsWithQuery:query1], span1); + XCTAssertIdentical([self.plugin getSpanControlsWithQuery:query2], span2); + XCTAssertIdentical([self.plugin getSpanControlsWithQuery:query3], span3); + + // End middle span - should only remove its timer + BOOL result = fakeContext.spanEndCallback(span2); + XCTAssertTrue(result); + XCTAssertEqual(self.plugin.spanTimeoutTimers.size(), 2UL); + + // Verify span2 is removed but span1 and span3 remain + XCTAssertIdentical([self.plugin getSpanControlsWithQuery:query1], span1); + XCTAssertNil([self.plugin getSpanControlsWithQuery:query2]); + XCTAssertIdentical([self.plugin getSpanControlsWithQuery:query3], span3); + + // Wait for timeout to trigger on remaining spans + XCTestExpectation *timeoutExpectation = [self expectationWithDescription:@"Remaining spans should timeout"]; + + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ + // Both remaining spans should be removed by timeout + XCTAssertNil([self.plugin getSpanControlsWithQuery:query1]); + XCTAssertNil([self.plugin getSpanControlsWithQuery:query3]); + XCTAssertEqual(self.plugin.spanTimeoutTimers.size(), 0UL); + [timeoutExpectation fulfill]; + }); + + [self waitForExpectations:@[timeoutExpectation] timeout:0.5]; +} + #pragma mark - Thread Safety Tests - (void)testConcurrentSpanOperations { From 0209149cbdc332235b3e9f85e6ff2168e051ed2a Mon Sep 17 00:00:00 2001 From: Yousif <74918474+yousif-bugsnag@users.noreply.github.com> Date: Wed, 6 Aug 2025 11:18:08 +0100 Subject: [PATCH 25/53] Use std::unordered_map for span timeout timers Co-authored-by: Jason --- .../Private/BugsnagPerformanceNamedSpansPlugin+Private.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/BugsnagPerformanceNamedSpans/Private/BugsnagPerformanceNamedSpansPlugin+Private.h b/Sources/BugsnagPerformanceNamedSpans/Private/BugsnagPerformanceNamedSpansPlugin+Private.h index 2d6b0e4e..de186418 100644 --- a/Sources/BugsnagPerformanceNamedSpans/Private/BugsnagPerformanceNamedSpansPlugin+Private.h +++ b/Sources/BugsnagPerformanceNamedSpans/Private/BugsnagPerformanceNamedSpansPlugin+Private.h @@ -6,7 +6,7 @@ // Copyright © 2025 Bugsnag. All rights reserved. // #import -#import +#import NS_ASSUME_NONNULL_BEGIN @@ -19,7 +19,7 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic, assign, readonly) NSTimeInterval timeoutInterval; -@property (nonatomic, assign, readonly) std::map spanTimeoutTimers; +@property (nonatomic, assign, readonly) std::unordered_map spanTimeoutTimers; @end From a10b0dfecba5794a87bd037942f862e69548b966 Mon Sep 17 00:00:00 2001 From: Yousif <74918474+yousif-bugsnag@users.noreply.github.com> Date: Wed, 6 Aug 2025 11:19:41 +0100 Subject: [PATCH 26/53] Rename endNativeSpan method to didEndNativeSpan Co-authored-by: Robert Bartoszewski <126675445+robert-smartbear@users.noreply.github.com> --- .../Public/BugsnagPerformanceNamedSpansPlugin.mm | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Sources/BugsnagPerformanceNamedSpans/Public/BugsnagPerformanceNamedSpansPlugin.mm b/Sources/BugsnagPerformanceNamedSpans/Public/BugsnagPerformanceNamedSpansPlugin.mm index 7e5cd7a3..f160f575 100644 --- a/Sources/BugsnagPerformanceNamedSpans/Public/BugsnagPerformanceNamedSpansPlugin.mm +++ b/Sources/BugsnagPerformanceNamedSpans/Public/BugsnagPerformanceNamedSpansPlugin.mm @@ -62,7 +62,7 @@ - (void)installWithContext:(BugsnagPerformancePluginContext *)context { // Remove spans from the cache when ended BugsnagPerformanceSpanEndCallback spanEndCallback = ^(BugsnagPerformanceSpan *span) { - return [blockSelf endNativeSpan:span]; + return [blockSelf didEndNativeSpan:span]; }; [context addOnSpanStartCallback:spanStartCallback priority:BugsnagPerformancePriorityHigh]; @@ -87,7 +87,7 @@ - (void)start { #pragma mark Private -- (BOOL)endNativeSpan:(BugsnagPerformanceSpan *)span { +- (BOOL)didEndNativeSpan:(BugsnagPerformanceSpan *)span { @synchronized (self) { // Remove span from cache if it exists if ([self.spansByName objectForKey:span.name] == span) { @@ -111,7 +111,7 @@ - (dispatch_source_t)createSpanTimeoutTimer:(BugsnagPerformanceSpan *)span { 0); dispatch_source_set_event_handler(timer, ^{ - [weakSelf endNativeSpan:span]; + [weakSelf didEndNativeSpan:span]; }); dispatch_resume(timer); From 14c76181c464d78bcce63928229c614433f75c39 Mon Sep 17 00:00:00 2001 From: Yousif Ahmed Date: Wed, 6 Aug 2025 11:23:13 +0100 Subject: [PATCH 27/53] Move spansByName initialisation to init method --- .../Public/BugsnagPerformanceNamedSpansPlugin.mm | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Sources/BugsnagPerformanceNamedSpans/Public/BugsnagPerformanceNamedSpansPlugin.mm b/Sources/BugsnagPerformanceNamedSpans/Public/BugsnagPerformanceNamedSpansPlugin.mm index f160f575..081f7930 100644 --- a/Sources/BugsnagPerformanceNamedSpans/Public/BugsnagPerformanceNamedSpansPlugin.mm +++ b/Sources/BugsnagPerformanceNamedSpans/Public/BugsnagPerformanceNamedSpansPlugin.mm @@ -31,6 +31,7 @@ - (instancetype)init { - (instancetype)initWithTimeoutInterval:(NSTimeInterval)timeoutInterval { if ((self = [super init])) { _timeoutInterval = timeoutInterval; + _spansByName = [NSMutableDictionary new]; } return self; } @@ -39,8 +40,6 @@ - (instancetype)initWithTimeoutInterval:(NSTimeInterval)timeoutInterval { #pragma clang diagnostic ignored "-Wdirect-ivar-access" - (void)installWithContext:(BugsnagPerformancePluginContext *)context { - self.spansByName = [NSMutableDictionary new]; - // Add spans to the cache when started __block BugsnagPerformanceNamedSpansPlugin *blockSelf = self; BugsnagPerformanceSpanStartCallback spanStartCallback = ^(BugsnagPerformanceSpan *span) { From 6980667ea85801dcddd1bcee4663d0909729cb9f Mon Sep 17 00:00:00 2001 From: Yousif <74918474+yousif-bugsnag@users.noreply.github.com> Date: Thu, 7 Aug 2025 14:16:48 +0100 Subject: [PATCH 28/53] Remove extra line Co-authored-by: Robert Bartoszewski <126675445+robert-smartbear@users.noreply.github.com> --- .../Public/BugsnagPerformanceNamedSpansPlugin.mm | 1 - 1 file changed, 1 deletion(-) diff --git a/Sources/BugsnagPerformanceNamedSpans/Public/BugsnagPerformanceNamedSpansPlugin.mm b/Sources/BugsnagPerformanceNamedSpans/Public/BugsnagPerformanceNamedSpansPlugin.mm index 081f7930..2e595792 100644 --- a/Sources/BugsnagPerformanceNamedSpans/Public/BugsnagPerformanceNamedSpansPlugin.mm +++ b/Sources/BugsnagPerformanceNamedSpans/Public/BugsnagPerformanceNamedSpansPlugin.mm @@ -127,5 +127,4 @@ - (void)cancelSpanTimeout:(BugsnagPerformanceSpan *)span { } } - @end From 747390a5f56b80236e84fc9e52e01e58ee4d8aa4 Mon Sep 17 00:00:00 2001 From: Yousif Ahmed Date: Mon, 11 Aug 2025 09:41:09 +0100 Subject: [PATCH 29/53] Fixed zero padding of span and trace IDs in Crosstalk API --- .../Private/BugsnagPerformanceCrossTalkAPI.mm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/BugsnagPerformance/Private/BugsnagPerformanceCrossTalkAPI.mm b/Sources/BugsnagPerformance/Private/BugsnagPerformanceCrossTalkAPI.mm index 5d86d5e1..04ccff22 100644 --- a/Sources/BugsnagPerformance/Private/BugsnagPerformanceCrossTalkAPI.mm +++ b/Sources/BugsnagPerformance/Private/BugsnagPerformanceCrossTalkAPI.mm @@ -69,8 +69,8 @@ - (NSArray * _Nullable)getCurrentTraceAndSpanIdV1 { return nil; } return @[ - [NSString stringWithFormat:@"%llx%llx", span.traceIdHi, span.traceIdLo], - [NSString stringWithFormat:@"%llx", span.spanId] + [NSString stringWithFormat:@"%016llx%016llx", span.traceIdHi, span.traceIdLo], + [NSString stringWithFormat:@"%016llx", span.spanId] ]; } From ec0a4a3457f39d8313720176d0a9ed47f8f4dc70 Mon Sep 17 00:00:00 2001 From: Yousif Ahmed Date: Mon, 11 Aug 2025 10:22:26 +0100 Subject: [PATCH 30/53] Added changelog entry --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a134077c..aecffe5c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,9 @@ Changelog * Fixed issue where some Network spans were categorized as custom. [448](https://github.com/bugsnag/bugsnag-cocoa-performance/pull/448) +* Fixed zero padding of span and trace IDs in Crosstalk API. + [460](https://github.com/bugsnag/bugsnag-cocoa-performance/pull/460) + ## 1.14.0 (2025-06-16) ### Bug fixes From aed928f58961eb07be0061e164fcfcef34319230 Mon Sep 17 00:00:00 2001 From: Yousif Ahmed Date: Tue, 12 Aug 2025 14:55:29 +0100 Subject: [PATCH 31/53] Moved onSpanStart block to separate method --- .../BugsnagPerformanceNamedSpansPlugin.mm | 38 ++++++++++--------- 1 file changed, 21 insertions(+), 17 deletions(-) diff --git a/Sources/BugsnagPerformanceNamedSpans/Public/BugsnagPerformanceNamedSpansPlugin.mm b/Sources/BugsnagPerformanceNamedSpans/Public/BugsnagPerformanceNamedSpansPlugin.mm index 2e595792..3386b074 100644 --- a/Sources/BugsnagPerformanceNamedSpans/Public/BugsnagPerformanceNamedSpansPlugin.mm +++ b/Sources/BugsnagPerformanceNamedSpans/Public/BugsnagPerformanceNamedSpansPlugin.mm @@ -43,25 +43,12 @@ - (void)installWithContext:(BugsnagPerformancePluginContext *)context { // Add spans to the cache when started __block BugsnagPerformanceNamedSpansPlugin *blockSelf = self; BugsnagPerformanceSpanStartCallback spanStartCallback = ^(BugsnagPerformanceSpan *span) { - @synchronized (blockSelf) { - BugsnagPerformanceSpan *existingSpan = blockSelf.spansByName[span.name]; - if (existingSpan) { - // If a span with the same name already exists, cancel the associated timeout - [blockSelf cancelSpanTimeout:existingSpan]; - } - - blockSelf.spansByName[span.name] = span; - - // Add timeout to remove the span from the cache if not ended - void *key = (__bridge void *)span; - dispatch_source_t timer = [blockSelf createSpanTimeoutTimer:span]; - blockSelf->_spanTimeoutTimers[key] = timer; - } + [blockSelf didStartSpan:span]; }; // Remove spans from the cache when ended BugsnagPerformanceSpanEndCallback spanEndCallback = ^(BugsnagPerformanceSpan *span) { - return [blockSelf didEndNativeSpan:span]; + return [blockSelf didEndSpan:span]; }; [context addOnSpanStartCallback:spanStartCallback priority:BugsnagPerformancePriorityHigh]; @@ -86,7 +73,24 @@ - (void)start { #pragma mark Private -- (BOOL)didEndNativeSpan:(BugsnagPerformanceSpan *)span { +- (void)didStartSpan:(BugsnagPerformanceSpan *)span { + @synchronized (self) { + BugsnagPerformanceSpan *existingSpan = self.spansByName[span.name]; + if (existingSpan) { + // If a span with the same name already exists, cancel the associated timeout + [self cancelSpanTimeout:existingSpan]; + } + + self.spansByName[span.name] = span; + + // Add timeout to remove the span from the cache if not ended + void *key = (__bridge void *)span; + dispatch_source_t timer = [self createSpanTimeoutTimer:span]; + self->_spanTimeoutTimers[key] = timer; + } +} + +- (BOOL)didEndSpan:(BugsnagPerformanceSpan *)span { @synchronized (self) { // Remove span from cache if it exists if ([self.spansByName objectForKey:span.name] == span) { @@ -110,7 +114,7 @@ - (dispatch_source_t)createSpanTimeoutTimer:(BugsnagPerformanceSpan *)span { 0); dispatch_source_set_event_handler(timer, ^{ - [weakSelf didEndNativeSpan:span]; + [weakSelf didEndSpan:span]; }); dispatch_resume(timer); From 1b465d5436db92e4787c217829ea006519e8c476 Mon Sep 17 00:00:00 2001 From: Daria Bialobrzeska Date: Wed, 13 Aug 2025 13:59:39 +0200 Subject: [PATCH 32/53] Wrap spanTimeoutTimers map in a shared_ptr --- ...gsnagPerformanceNamedSpansPlugin+Private.h | 2 +- .../BugsnagPerformanceNamedSpansPlugin.mm | 11 +++-- .../BugsnagPerformanceNamedSpansTests.mm | 44 +++++++++---------- 3 files changed, 28 insertions(+), 29 deletions(-) diff --git a/Sources/BugsnagPerformanceNamedSpans/Private/BugsnagPerformanceNamedSpansPlugin+Private.h b/Sources/BugsnagPerformanceNamedSpans/Private/BugsnagPerformanceNamedSpansPlugin+Private.h index de186418..f5fcd41f 100644 --- a/Sources/BugsnagPerformanceNamedSpans/Private/BugsnagPerformanceNamedSpansPlugin+Private.h +++ b/Sources/BugsnagPerformanceNamedSpans/Private/BugsnagPerformanceNamedSpansPlugin+Private.h @@ -19,7 +19,7 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic, assign, readonly) NSTimeInterval timeoutInterval; -@property (nonatomic, assign, readonly) std::unordered_map spanTimeoutTimers; +@property (nonatomic, assign, readonly) std::shared_ptr> spanTimeoutTimers; @end diff --git a/Sources/BugsnagPerformanceNamedSpans/Public/BugsnagPerformanceNamedSpansPlugin.mm b/Sources/BugsnagPerformanceNamedSpans/Public/BugsnagPerformanceNamedSpansPlugin.mm index 3386b074..551687d7 100644 --- a/Sources/BugsnagPerformanceNamedSpans/Public/BugsnagPerformanceNamedSpansPlugin.mm +++ b/Sources/BugsnagPerformanceNamedSpans/Public/BugsnagPerformanceNamedSpansPlugin.mm @@ -32,13 +32,13 @@ - (instancetype)initWithTimeoutInterval:(NSTimeInterval)timeoutInterval { if ((self = [super init])) { _timeoutInterval = timeoutInterval; _spansByName = [NSMutableDictionary new]; + _spanTimeoutTimers = std::make_shared>(); } return self; } #pragma mark BugsnagPerformancePlugin -#pragma clang diagnostic ignored "-Wdirect-ivar-access" - (void)installWithContext:(BugsnagPerformancePluginContext *)context { // Add spans to the cache when started __block BugsnagPerformanceNamedSpansPlugin *blockSelf = self; @@ -86,7 +86,7 @@ - (void)didStartSpan:(BugsnagPerformanceSpan *)span { // Add timeout to remove the span from the cache if not ended void *key = (__bridge void *)span; dispatch_source_t timer = [self createSpanTimeoutTimer:span]; - self->_spanTimeoutTimers[key] = timer; + (*self.spanTimeoutTimers)[key] = timer; } } @@ -123,11 +123,10 @@ - (dispatch_source_t)createSpanTimeoutTimer:(BugsnagPerformanceSpan *)span { - (void)cancelSpanTimeout:(BugsnagPerformanceSpan *)span { void *key = (__bridge void *)span; - auto& timerMap = _spanTimeoutTimers; - auto it = timerMap.find(key); - if (it != timerMap.end()) { + auto it = self.spanTimeoutTimers->find(key); + if (it != self.spanTimeoutTimers->end()) { dispatch_source_cancel(it->second); - timerMap.erase(it); + self.spanTimeoutTimers->erase(it); } } diff --git a/Tests/BugsnagPerformanceNamedSpansTests/BugsnagPerformanceNamedSpansTests.mm b/Tests/BugsnagPerformanceNamedSpansTests/BugsnagPerformanceNamedSpansTests.mm index 2376500a..dc298188 100644 --- a/Tests/BugsnagPerformanceNamedSpansTests/BugsnagPerformanceNamedSpansTests.mm +++ b/Tests/BugsnagPerformanceNamedSpansTests/BugsnagPerformanceNamedSpansTests.mm @@ -261,8 +261,8 @@ - (void)testSpanTimeoutTimerRemovesSpanFromCache { BugsnagPerformanceNamedSpanQuery *query = [BugsnagPerformanceNamedSpanQuery queryWithName:@"timeout-span"]; id cachedSpan = [self.plugin getSpanControlsWithQuery:query]; XCTAssertIdentical(cachedSpan, span); - XCTAssertEqual(self.plugin.spanTimeoutTimers.size(), 1UL); - + XCTAssertEqual(self.plugin.spanTimeoutTimers->size(), 1UL); + // Wait for timeout to trigger XCTestExpectation *timeoutExpectation = [self expectationWithDescription:@"Span timeout should remove span from cache"]; @@ -270,7 +270,7 @@ - (void)testSpanTimeoutTimerRemovesSpanFromCache { // Check span is removed from cache after timeout interval id stillCachedSpan = [self.plugin getSpanControlsWithQuery:query]; XCTAssertNil(stillCachedSpan); - XCTAssertEqual(self.plugin.spanTimeoutTimers.size(), 0UL); + XCTAssertEqual(self.plugin.spanTimeoutTimers->size(), 0UL); [timeoutExpectation fulfill]; }); @@ -290,8 +290,8 @@ - (void)testTimeoutTimerCancelledWhenSpanEnds { BugsnagPerformanceNamedSpanQuery *query = [BugsnagPerformanceNamedSpanQuery queryWithName:@"timer-span"]; id cachedSpan = [self.plugin getSpanControlsWithQuery:query]; XCTAssertIdentical(cachedSpan, span); - XCTAssertEqual(self.plugin.spanTimeoutTimers.size(), 1UL); - + XCTAssertEqual(self.plugin.spanTimeoutTimers->size(), 1UL); + // End span (should cancel timeout timer and remove from cache) BOOL result = fakeContext.spanEndCallback(span); XCTAssertTrue(result); @@ -299,7 +299,7 @@ - (void)testTimeoutTimerCancelledWhenSpanEnds { // Verify span is removed from cache id removedSpan = [self.plugin getSpanControlsWithQuery:query]; XCTAssertNil(removedSpan); - XCTAssertEqual(self.plugin.spanTimeoutTimers.size(), 0UL); + XCTAssertEqual(self.plugin.spanTimeoutTimers->size(), 0UL); } - (void)testTimeoutTimerCancelledWhenNewSpanWithSameNameStarted { @@ -317,23 +317,23 @@ - (void)testTimeoutTimerCancelledWhenNewSpanWithSameNameStarted { id cachedSpan = [self.plugin getSpanControlsWithQuery:query]; XCTAssertIdentical(cachedSpan, span1); - XCTAssertEqual(self.plugin.spanTimeoutTimers.size(), 1UL); - + XCTAssertEqual(self.plugin.spanTimeoutTimers->size(), 1UL); + // Wait for first span's timeout to almost elapse (0.15 seconds of 0.2 second timeout) XCTestExpectation *stagingExpectation = [self expectationWithDescription:@"Second span with same name should replace first span"]; dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.15 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ // Verify first span's timer is still active - XCTAssertEqual(self.plugin.spanTimeoutTimers.size(), 1UL); - + XCTAssertEqual(self.plugin.spanTimeoutTimers->size(), 1UL); + // Start second span with same name (should cancel first timer and create a new one) fakeContext.spanStartCallback(span2); // Verify second span replaced first span id newCachedSpan = [self.plugin getSpanControlsWithQuery:query]; XCTAssertIdentical(newCachedSpan, span2); - XCTAssertEqual(self.plugin.spanTimeoutTimers.size(), 1UL); - + XCTAssertEqual(self.plugin.spanTimeoutTimers->size(), 1UL); + [stagingExpectation fulfill]; }); @@ -346,7 +346,7 @@ - (void)testTimeoutTimerCancelledWhenNewSpanWithSameNameStarted { // Verify second span is still cached id stillCachedSpan = [self.plugin getSpanControlsWithQuery:query]; XCTAssertIdentical(stillCachedSpan, span2); - XCTAssertEqual(self.plugin.spanTimeoutTimers.size(), 1UL); + XCTAssertEqual(self.plugin.spanTimeoutTimers->size(), 1UL); [timeoutExpectation fulfill]; }); @@ -363,16 +363,16 @@ - (void)testMultipleSpansWithDifferentNamesHaveSeparateTimeoutTimers { // Start first span fakeContext.spanStartCallback(span1); - XCTAssertEqual(self.plugin.spanTimeoutTimers.size(), 1UL); - + XCTAssertEqual(self.plugin.spanTimeoutTimers->size(), 1UL); + // Start second span with different name fakeContext.spanStartCallback(span2); - XCTAssertEqual(self.plugin.spanTimeoutTimers.size(), 2UL); - + XCTAssertEqual(self.plugin.spanTimeoutTimers->size(), 2UL); + // Start third span with different name fakeContext.spanStartCallback(span3); - XCTAssertEqual(self.plugin.spanTimeoutTimers.size(), 3UL); - + XCTAssertEqual(self.plugin.spanTimeoutTimers->size(), 3UL); + // Verify all spans are cached BugsnagPerformanceNamedSpanQuery *query1 = [BugsnagPerformanceNamedSpanQuery queryWithName:@"span-one"]; BugsnagPerformanceNamedSpanQuery *query2 = [BugsnagPerformanceNamedSpanQuery queryWithName:@"span-two"]; @@ -385,8 +385,8 @@ - (void)testMultipleSpansWithDifferentNamesHaveSeparateTimeoutTimers { // End middle span - should only remove its timer BOOL result = fakeContext.spanEndCallback(span2); XCTAssertTrue(result); - XCTAssertEqual(self.plugin.spanTimeoutTimers.size(), 2UL); - + XCTAssertEqual(self.plugin.spanTimeoutTimers->size(), 2UL); + // Verify span2 is removed but span1 and span3 remain XCTAssertIdentical([self.plugin getSpanControlsWithQuery:query1], span1); XCTAssertNil([self.plugin getSpanControlsWithQuery:query2]); @@ -399,7 +399,7 @@ - (void)testMultipleSpansWithDifferentNamesHaveSeparateTimeoutTimers { // Both remaining spans should be removed by timeout XCTAssertNil([self.plugin getSpanControlsWithQuery:query1]); XCTAssertNil([self.plugin getSpanControlsWithQuery:query3]); - XCTAssertEqual(self.plugin.spanTimeoutTimers.size(), 0UL); + XCTAssertEqual(self.plugin.spanTimeoutTimers->size(), 0UL); [timeoutExpectation fulfill]; }); From c0c0d4d378761c2bce197ca70899bfe0ae590841 Mon Sep 17 00:00:00 2001 From: Yousif Ahmed Date: Wed, 13 Aug 2025 15:00:31 +0100 Subject: [PATCH 33/53] Use __weak reference for span start/end blocks --- .../Public/BugsnagPerformanceNamedSpansPlugin.mm | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/Sources/BugsnagPerformanceNamedSpans/Public/BugsnagPerformanceNamedSpansPlugin.mm b/Sources/BugsnagPerformanceNamedSpans/Public/BugsnagPerformanceNamedSpansPlugin.mm index 551687d7..88c2ae66 100644 --- a/Sources/BugsnagPerformanceNamedSpans/Public/BugsnagPerformanceNamedSpansPlugin.mm +++ b/Sources/BugsnagPerformanceNamedSpans/Public/BugsnagPerformanceNamedSpansPlugin.mm @@ -41,14 +41,24 @@ - (instancetype)initWithTimeoutInterval:(NSTimeInterval)timeoutInterval { - (void)installWithContext:(BugsnagPerformancePluginContext *)context { // Add spans to the cache when started - __block BugsnagPerformanceNamedSpansPlugin *blockSelf = self; + __weak BugsnagPerformanceNamedSpansPlugin *weakSelf = self; BugsnagPerformanceSpanStartCallback spanStartCallback = ^(BugsnagPerformanceSpan *span) { - [blockSelf didStartSpan:span]; + __strong BugsnagPerformanceNamedSpansPlugin *strongSelf = weakSelf; + if (strongSelf == nil) { + return; + } + + [strongSelf didStartSpan:span]; }; // Remove spans from the cache when ended BugsnagPerformanceSpanEndCallback spanEndCallback = ^(BugsnagPerformanceSpan *span) { - return [blockSelf didEndSpan:span]; + __strong BugsnagPerformanceNamedSpansPlugin *strongSelf = weakSelf; + if (strongSelf == nil) { + return YES; + } + + return [strongSelf didEndSpan:span];; }; [context addOnSpanStartCallback:spanStartCallback priority:BugsnagPerformancePriorityHigh]; From da982eb3a8b8bc583b13db777958f18723f54504 Mon Sep 17 00:00:00 2001 From: Daria Bialobrzeska Date: Thu, 14 Aug 2025 12:16:36 +0200 Subject: [PATCH 34/53] Release v1.15.0 --- .jazzy.yaml | 4 +-- BugsnagPerformance.podspec.json | 4 +-- BugsnagPerformance.xcodeproj/project.pbxproj | 36 ++++++++++---------- BugsnagPerformanceNamedSpans.podspec.json | 6 ++-- BugsnagPerformanceSwift.podspec.json | 6 ++-- CHANGELOG.md | 10 +++++- Sources/BugsnagPerformance/Private/Version.h | 2 +- VERSION | 2 +- 8 files changed, 39 insertions(+), 31 deletions(-) diff --git a/.jazzy.yaml b/.jazzy.yaml index 31422b9f..03f42c5b 100644 --- a/.jazzy.yaml +++ b/.jazzy.yaml @@ -2,11 +2,11 @@ author_url: "https://www.bugsnag.com" author: "Bugsnag Inc" clean: false # avoid deleting docs/.git framework_root: "BugsnagPerformance" -github_file_prefix: "https://github.com/bugsnag/bugsnag-cocoa-performance/tree/v1.14.0/Bugsnag" +github_file_prefix: "https://github.com/bugsnag/bugsnag-cocoa-performance/tree/v1.15.0/Bugsnag" github_url: "https://github.com/bugsnag/bugsnag-cocoa-performance" hide_documentation_coverage: true module: "BugsnagPerformance" -module_version: "1.14.0" +module_version: "1.15.0" objc: true output: "docs" readme: "README.md" diff --git a/BugsnagPerformance.podspec.json b/BugsnagPerformance.podspec.json index a712b077..f9f688b2 100644 --- a/BugsnagPerformance.podspec.json +++ b/BugsnagPerformance.podspec.json @@ -1,6 +1,6 @@ { "name": "BugsnagPerformance", - "version": "1.14.0", + "version": "1.15.0", "summary": "The Bugsnag performance monitoring framework for iOS.", "homepage": "https://github.com/bugsnag/bugsnag-cocoa-performance", "license": { @@ -12,7 +12,7 @@ }, "source": { "git": "https://github.com/bugsnag/bugsnag-cocoa-performance.git", - "tag": "v1.14.0" + "tag": "v1.15.0" }, "platforms": { "ios": "13.0" diff --git a/BugsnagPerformance.xcodeproj/project.pbxproj b/BugsnagPerformance.xcodeproj/project.pbxproj index 902f445e..b350553e 100644 --- a/BugsnagPerformance.xcodeproj/project.pbxproj +++ b/BugsnagPerformance.xcodeproj/project.pbxproj @@ -1787,7 +1787,7 @@ GCC_WARN_PEDANTIC = NO; GENERATE_INFOPLIST_FILE = YES; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - MARKETING_VERSION = 1.14.0; + MARKETING_VERSION = 1.15.0; PRODUCT_BUNDLE_IDENTIFIER = "com.bugsnag.BugsnagPerformance-iOSTests"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = iphoneos; @@ -1809,7 +1809,7 @@ GCC_WARN_PEDANTIC = NO; GENERATE_INFOPLIST_FILE = YES; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - MARKETING_VERSION = 1.14.0; + MARKETING_VERSION = 1.15.0; PRODUCT_BUNDLE_IDENTIFIER = "com.bugsnag.BugsnagPerformance-iOSTests"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = iphoneos; @@ -1843,7 +1843,7 @@ IPHONEOS_DEPLOYMENT_TARGET = 13.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; LOCALIZATION_PREFERS_STRING_CATALOGS = YES; - MARKETING_VERSION = 1.14.0; + MARKETING_VERSION = 1.15.0; MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++"; MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu17 gnu++20"; PRODUCT_BUNDLE_IDENTIFIER = com.bugsnag.BugsnagPerformanceSwiftUI; @@ -1879,7 +1879,7 @@ IPHONEOS_DEPLOYMENT_TARGET = 13.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; LOCALIZATION_PREFERS_STRING_CATALOGS = YES; - MARKETING_VERSION = 1.14.0; + MARKETING_VERSION = 1.15.0; MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++"; MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu17 gnu++20"; PRODUCT_BUNDLE_IDENTIFIER = com.bugsnag.BugsnagPerformanceSwiftUI; @@ -1907,7 +1907,7 @@ GENERATE_INFOPLIST_FILE = YES; IPHONEOS_DEPLOYMENT_TARGET = 17.0; LOCALIZATION_PREFERS_STRING_CATALOGS = YES; - MARKETING_VERSION = 1.14.0; + MARKETING_VERSION = 1.15.0; PRODUCT_BUNDLE_IDENTIFIER = com.bugsnag.BugsnagPerformanceSwiftUITests; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; @@ -1931,7 +1931,7 @@ GENERATE_INFOPLIST_FILE = YES; IPHONEOS_DEPLOYMENT_TARGET = 17.0; LOCALIZATION_PREFERS_STRING_CATALOGS = YES; - MARKETING_VERSION = 1.14.0; + MARKETING_VERSION = 1.15.0; PRODUCT_BUNDLE_IDENTIFIER = com.bugsnag.BugsnagPerformanceSwiftUITests; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = NO; @@ -1963,7 +1963,7 @@ IPHONEOS_DEPLOYMENT_TARGET = 17.2; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; LOCALIZATION_PREFERS_STRING_CATALOGS = YES; - MARKETING_VERSION = 1.14.0; + MARKETING_VERSION = 1.15.0; MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++"; MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu17 gnu++20"; PRODUCT_BUNDLE_IDENTIFIER = com.bugsnag.BugsnagPerformanceSwiftUI; @@ -1999,7 +1999,7 @@ IPHONEOS_DEPLOYMENT_TARGET = 17.2; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; LOCALIZATION_PREFERS_STRING_CATALOGS = YES; - MARKETING_VERSION = 1.14.0; + MARKETING_VERSION = 1.15.0; MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++"; MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu17 gnu++20"; PRODUCT_BUNDLE_IDENTIFIER = com.bugsnag.BugsnagPerformanceSwiftUI; @@ -2031,7 +2031,7 @@ INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 13.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - MARKETING_VERSION = 1.14.0; + MARKETING_VERSION = 1.15.0; PRODUCT_BUNDLE_IDENTIFIER = com.bugsnag.BugsnagPerformance; PRODUCT_NAME = BugsnagPerformance; SDKROOT = iphoneos; @@ -2061,7 +2061,7 @@ INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 13.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - MARKETING_VERSION = 1.14.0; + MARKETING_VERSION = 1.15.0; PRODUCT_BUNDLE_IDENTIFIER = com.bugsnag.BugsnagPerformance; PRODUCT_NAME = BugsnagPerformance; SDKROOT = iphoneos; @@ -2270,7 +2270,7 @@ CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = 7W9PZ27Y5F; GENERATE_INFOPLIST_FILE = YES; - MARKETING_VERSION = 1.14.0; + MARKETING_VERSION = 1.15.0; PRODUCT_BUNDLE_IDENTIFIER = com.bugsnag.BugsnagPerformanceTestsSwift; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = iphoneos; @@ -2290,7 +2290,7 @@ CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = 7W9PZ27Y5F; GENERATE_INFOPLIST_FILE = YES; - MARKETING_VERSION = 1.14.0; + MARKETING_VERSION = 1.15.0; PRODUCT_BUNDLE_IDENTIFIER = com.bugsnag.BugsnagPerformanceTestsSwift; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = iphoneos; @@ -2312,7 +2312,7 @@ GCC_WARN_PEDANTIC = NO; GENERATE_INFOPLIST_FILE = YES; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - MARKETING_VERSION = 1.14.0; + MARKETING_VERSION = 1.15.0; PRODUCT_BUNDLE_IDENTIFIER = "com.bugsnag.BugsnagPerformance-iOSTests"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = iphoneos; @@ -2334,7 +2334,7 @@ GCC_WARN_PEDANTIC = NO; GENERATE_INFOPLIST_FILE = YES; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - MARKETING_VERSION = 1.14.0; + MARKETING_VERSION = 1.15.0; PRODUCT_BUNDLE_IDENTIFIER = "com.bugsnag.BugsnagPerformance-iOSTests"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = iphoneos; @@ -2366,7 +2366,7 @@ INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; IPHONEOS_DEPLOYMENT_TARGET = 11.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; - MARKETING_VERSION = 1.14.0; + MARKETING_VERSION = 1.15.0; PRODUCT_BUNDLE_IDENTIFIER = com.bugsnag.BugsnagPerformanceTestsApp; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = iphoneos; @@ -2397,7 +2397,7 @@ INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; IPHONEOS_DEPLOYMENT_TARGET = 11.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; - MARKETING_VERSION = 1.14.0; + MARKETING_VERSION = 1.15.0; PRODUCT_BUNDLE_IDENTIFIER = com.bugsnag.BugsnagPerformanceTestsApp; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = iphoneos; @@ -2438,7 +2438,7 @@ IPHONEOS_DEPLOYMENT_TARGET = 13.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; LOCALIZATION_PREFERS_STRING_CATALOGS = NO; - MARKETING_VERSION = 1.14.0; + MARKETING_VERSION = 1.15.0; MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c"; MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = ""; PRODUCT_BUNDLE_IDENTIFIER = com.bugsnag.BugsnagPerformanceNamedSpans; @@ -2501,7 +2501,7 @@ IPHONEOS_DEPLOYMENT_TARGET = 13.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; LOCALIZATION_PREFERS_STRING_CATALOGS = NO; - MARKETING_VERSION = 1.14.0; + MARKETING_VERSION = 1.15.0; MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c"; MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = ""; PRODUCT_BUNDLE_IDENTIFIER = com.bugsnag.BugsnagPerformanceNamedSpans; diff --git a/BugsnagPerformanceNamedSpans.podspec.json b/BugsnagPerformanceNamedSpans.podspec.json index b11a6919..e06f469d 100644 --- a/BugsnagPerformanceNamedSpans.podspec.json +++ b/BugsnagPerformanceNamedSpans.podspec.json @@ -1,6 +1,6 @@ { "name": "BugsnagPerformanceNamedSpans", - "version": "1.14.0", + "version": "1.15.0", "summary": "The Bugsnag performance monitoring framework for iOS.", "homepage": "https://github.com/bugsnag/bugsnag-cocoa-performance", "license": { @@ -12,7 +12,7 @@ }, "source": { "git": "https://github.com/bugsnag/bugsnag-cocoa-performance.git", - "tag": "v1.14.0" + "tag": "v1.15.0" }, "platforms": { "ios": "13.0" @@ -22,7 +22,7 @@ "public_header_files": "Sources/BugsnagPerformanceNamedSpans/include/BugsnagPerformanceNamedSpans/*.h", "prefix_header_file": false, "dependencies": { - "BugsnagPerformance": "1.14.0" + "BugsnagPerformance": "1.15.0" }, "libraries": "c++", "pod_target_xcconfig": { diff --git a/BugsnagPerformanceSwift.podspec.json b/BugsnagPerformanceSwift.podspec.json index ba6a0002..4a19db80 100644 --- a/BugsnagPerformanceSwift.podspec.json +++ b/BugsnagPerformanceSwift.podspec.json @@ -1,6 +1,6 @@ { "name": "BugsnagPerformanceSwift", - "version": "1.14.0", + "version": "1.15.0", "summary": "The Bugsnag performance monitoring framework for Swift-only functionality in iOS.", "homepage": "https://github.com/bugsnag/bugsnag-cocoa-performance", "license": { @@ -13,7 +13,7 @@ "swift_version": "4.2", "source": { "git": "https://github.com/bugsnag/bugsnag-cocoa-performance.git", - "tag": "v1.14.0" + "tag": "v1.15.0" }, "platforms": { "ios": "13.0" @@ -24,6 +24,6 @@ "SwiftUI" ], "dependencies": { - "BugsnagPerformance": "1.14.0" + "BugsnagPerformance": "1.15.0" } } diff --git a/CHANGELOG.md b/CHANGELOG.md index aecffe5c..d4170ec3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,13 +1,21 @@ Changelog ========= -## TBD +## 1.15.0 (2025-08-14) + +### Enhancements + +* Added BugsnagPerformanceNamedSpans module and plugin. + [457](https://github.com/bugsnag/bugsnag-cocoa-performance/pull/457) ### Bug fixes * Fixed issue where some Network spans were categorized as custom. [448](https://github.com/bugsnag/bugsnag-cocoa-performance/pull/448) +* Don't send CPU data if cpu_mean_total is not available. + [451](https://github.com/bugsnag/bugsnag-cocoa-performance/pull/451) + * Fixed zero padding of span and trace IDs in Crosstalk API. [460](https://github.com/bugsnag/bugsnag-cocoa-performance/pull/460) diff --git a/Sources/BugsnagPerformance/Private/Version.h b/Sources/BugsnagPerformance/Private/Version.h index 54cbca3b..df5a461e 100644 --- a/Sources/BugsnagPerformance/Private/Version.h +++ b/Sources/BugsnagPerformance/Private/Version.h @@ -9,4 +9,4 @@ #pragma once #define TELEMETRY_SDK_NAME "bugsnag.performance.cocoa" -#define TELEMETRY_SDK_VERSION "1.14.0" +#define TELEMETRY_SDK_VERSION "1.15.0" diff --git a/VERSION b/VERSION index 850e7424..141f2e80 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.14.0 +1.15.0 From f90a1091b24350a16d0b626ac484e3d68cc9ac0e Mon Sep 17 00:00:00 2001 From: Steve Kirkland Date: Fri, 29 Aug 2025 16:02:35 +0100 Subject: [PATCH 35/53] Bump Maze Runner to v10 --- .gitignore | 1 + Gemfile | 2 +- docker-compose.yml | 4 ++-- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index 80ae3bdd..e3b11e40 100644 --- a/.gitignore +++ b/.gitignore @@ -105,3 +105,4 @@ features/fixtures/ios/Fixture/Info.plist maze-runner.log Gemfile.lock .idea/ +bugsnag-cocoa-performance.iml \ No newline at end of file diff --git a/Gemfile b/Gemfile index 8d20878c..2c4cfe97 100644 --- a/Gemfile +++ b/Gemfile @@ -1,6 +1,6 @@ source 'https://rubygems.org' -gem 'bugsnag-maze-runner', '~>9.0' +gem 'bugsnag-maze-runner', '~>10.0' gem 'cocoapods' gem 'xcpretty', '~>0.4.0' diff --git a/docker-compose.yml b/docker-compose.yml index add18c30..871fb4fb 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -24,7 +24,7 @@ x-common-environment: &common-environment services: maze-runner-bb: - image: 855461928731.dkr.ecr.us-west-1.amazonaws.com/maze-runner-releases:latest-v9-cli + image: 855461928731.dkr.ecr.us-west-1.amazonaws.com/maze-runner-releases:latest-v10-cli environment: <<: *common-environment BITBAR_USERNAME: @@ -40,7 +40,7 @@ services: - ./reports/:/app/reports/ maze-runner-bs: - image: 855461928731.dkr.ecr.us-west-1.amazonaws.com/maze-runner-releases:latest-v9-cli + image: 855461928731.dkr.ecr.us-west-1.amazonaws.com/maze-runner-releases:latest-v10-cli environment: <<: *common-environment BROWSER_STACK_USERNAME: From dc5baceaa24446ae8106b21ae965509f6cdbf241 Mon Sep 17 00:00:00 2001 From: Steve Kirkland Date: Fri, 29 Aug 2025 16:15:03 +0100 Subject: [PATCH 36/53] Use v10 steps --- .../default/00_infrastructure_check.feature | 2 +- features/default/automatic_spans.feature | 46 +++++++-------- .../automatic_spans_generic_view_load.feature | 4 +- features/default/configuration.feature | 6 +- features/default/initial-p-value.feature | 4 +- features/default/manual_spans.feature | 56 +++++++++---------- features/default/metrics.feature | 44 +++++++-------- features/default/network.feature | 2 +- features/default/sampling.feature | 2 +- features/default/triggers.feature | 2 +- 10 files changed, 84 insertions(+), 84 deletions(-) diff --git a/features/default/00_infrastructure_check.feature b/features/default/00_infrastructure_check.feature index b88a07e3..6a9266ea 100644 --- a/features/default/00_infrastructure_check.feature +++ b/features/default/00_infrastructure_check.feature @@ -7,4 +7,4 @@ Feature: Infrastructure checks Scenario: Check infrastructure with minimal Bugsnag activity Given I run "InfraCheckMinimalBugsnagScenario" And I wait to receive a reflection - And I wait for 1 span + And I wait to receive at least 1 span diff --git a/features/default/automatic_spans.feature b/features/default/automatic_spans.feature index c899823f..78437539 100644 --- a/features/default/automatic_spans.feature +++ b/features/default/automatic_spans.feature @@ -2,7 +2,7 @@ Feature: Automatic instrumentation spans Scenario: AutoInstrumentAppStartsScenario Given I run "AutoInstrumentAppStartsScenario" - And I wait for 4 spans + And I wait to receive at least 4 spans Then the trace "Content-Type" header equals "application/json" * the trace "Bugsnag-Span-Sampling" header equals "1:4" * 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$" @@ -29,7 +29,7 @@ Feature: Automatic instrumentation spans Scenario: AutoInstrumentViewLoadScenario Given I run "AutoInstrumentViewLoadScenario" - And I wait for 18 spans + And I wait to receive at least 18 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$" * a span field "name" equals "[ViewLoad/UIKit]/Fixture.ViewController" @@ -67,7 +67,7 @@ Feature: Automatic instrumentation spans Scenario: AutoInstrumentSubViewLoadScenario Given I run "AutoInstrumentSubViewLoadScenario" And I wait for 2 seconds - And I wait for 27 spans + And I wait to receive at least 27 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$" * a span field "name" equals "[ViewLoad/UIKit]/Fixture.ViewController" @@ -156,7 +156,7 @@ Feature: Automatic instrumentation spans Scenario: AutoInstrumentTabViewLoadScenario Given I run "AutoInstrumentTabViewLoadScenario" And I wait for 2 seconds - And I wait for 18 spans + And I wait to receive at least 18 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$" * a span field "name" equals "[ViewLoad/UIKit]/Fixture.ViewController" @@ -204,7 +204,7 @@ Feature: Automatic instrumentation spans Scenario: AutoInstrumentNavigationViewLoadScenario Given I run "AutoInstrumentNavigationViewLoadScenario" And I wait for 2 seconds - And I wait for 18 spans + And I wait to receive at least 18 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$" * a span field "name" equals "[ViewLoad/UIKit]/Fixture.ViewController" @@ -251,7 +251,7 @@ Feature: Automatic instrumentation spans Scenario: AutoInstrumentPreLoadedViewLoadScenario Given I run "AutoInstrumentPreLoadedViewLoadScenario" - And I wait for 18 spans + And I wait to receive at least 18 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$" * a span field "name" equals "[ViewLoad/UIKit]/Fixture.ViewController" @@ -291,7 +291,7 @@ Feature: Automatic instrumentation spans And I configure scenario "keep_view_controller_alive" to "true" And I start bugsnag And I run the loaded scenario - And I wait for 17 spans + And I wait to receive at least 17 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$" * a span field "name" equals "[ViewLoad/UIKit]/Fixture.ViewController" @@ -331,7 +331,7 @@ Feature: Automatic instrumentation spans And I configure scenario "keep_view_controller_alive" to "false" And I start bugsnag And I run the loaded scenario - And I wait for 18 spans + And I wait to receive at least 18 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$" * a span field "name" equals "[ViewLoad/UIKit]/Fixture.ViewController" @@ -369,7 +369,7 @@ Feature: Automatic instrumentation spans Scenario: AutoInstrumentSwiftUIScenario no change Given I run "AutoInstrumentSwiftUIScenario" - And I wait for 3 spans + And I wait to receive at least 3 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$" * a span field "name" equals "[ViewLoad/SwiftUI]/My VStack view" @@ -393,7 +393,7 @@ Feature: Automatic instrumentation spans Scenario: AutoInstrumentSwiftUIScenario with change Given I run "AutoInstrumentSwiftUIScenario" - And I wait for 3 spans + And I wait to receive at least 3 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$" * a span field "name" equals "[ViewLoad/SwiftUI]/My VStack view" @@ -415,7 +415,7 @@ Feature: Automatic instrumentation spans * the trace payload field "resourceSpans.0.resource" string attribute "telemetry.sdk.version" matches the regex "[0-9]+\.[0-9]+\.[0-9]+" And I discard every trace And I invoke "switchView" - Then I wait for 2 spans + Then I wait to receive at least 2 spans * a span field "name" equals "[ViewLoad/SwiftUI]/Text" * a span field "name" equals "[ViewLoadPhase/body]/Text" * every span field "spanId" matches the regex "^[A-Fa-f0-9]{16}$" @@ -431,7 +431,7 @@ Feature: Automatic instrumentation spans Scenario: AutoInstrumentSwiftUIDeferredScenario toggleEndSpanDefer Given I run "AutoInstrumentSwiftUIDeferredScenario" - And I wait for 2 spans + And I wait to receive at least 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$" * a span field "name" equals "[ViewLoadPhase/body]/vstack1" @@ -449,7 +449,7 @@ Feature: Automatic instrumentation spans * the trace payload field "resourceSpans.0.resource" string attribute "telemetry.sdk.version" matches the regex "[0-9]+\.[0-9]+\.[0-9]+" Then I discard every trace And I invoke "toggleEndSpanDefer" - And I wait for 2 spans + And I wait to receive at least 2 spans * a span field "name" equals "[ViewLoadPhase/body]/vstack1" * a span field "name" equals "[ViewLoadPhase/body]/text1" * every span field "spanId" matches the regex "^[A-Fa-f0-9]{16}$" @@ -463,7 +463,7 @@ Feature: Automatic instrumentation spans Scenario: AutoInstrumentSwiftUIDeferredScenario toggle everything Given I run "AutoInstrumentSwiftUIDeferredScenario" - And I wait for 2 spans + And I wait to receive at least 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$" * a span field "name" equals "[ViewLoadPhase/body]/vstack1" @@ -481,7 +481,7 @@ Feature: Automatic instrumentation spans * the trace payload field "resourceSpans.0.resource" string attribute "telemetry.sdk.version" matches the regex "[0-9]+\.[0-9]+\.[0-9]+" Then I discard every trace And I invoke "toggleEndSpanDefer" - And I wait for 2 spans + And I wait to receive at least 2 spans * a span field "name" equals "[ViewLoadPhase/body]/vstack1" * a span field "name" equals "[ViewLoadPhase/body]/text1" * every span field "spanId" matches the regex "^[A-Fa-f0-9]{16}$" @@ -494,7 +494,7 @@ Feature: Automatic instrumentation spans * a span string attribute "bugsnag.view.name" equals "text1" Then I discard every trace And I invoke "toggleHideText1" - And I wait for 2 spans + And I wait to receive at least 2 spans * a span field "name" equals "[ViewLoad/SwiftUI]/vstack1" * a span field "name" equals "[ViewLoadPhase/body]/vstack1" * every span field "spanId" matches the regex "^[A-Fa-f0-9]{16}$" @@ -509,7 +509,7 @@ Feature: Automatic instrumentation spans Scenario: Automatically start a network span that has a parent Given I run "AutoInstrumentNetworkWithParentScenario" And I wait for 2 seconds - And I wait for 2 spans + And I wait to receive at least 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$" * a span field "parentSpanId" exists @@ -537,7 +537,7 @@ Feature: Automatic instrumentation spans Scenario: Automatically start a network span that has no parent Given I run "AutoInstrumentNetworkNoParentScenario" And I wait for 2 seconds - And I wait for 2 spans + And I wait to receive at least 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 "parentSpanId" does not exist @@ -562,7 +562,7 @@ Feature: Automatic instrumentation spans Scenario: Auto-capture multiple network spans Given I run "AutoInstrumentNetworkMultiple" - And I wait for 10 spans + And I wait to receive at least 10 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 "parentSpanId" does not exist @@ -588,11 +588,11 @@ Feature: Automatic instrumentation spans Scenario: Don't send an auto network span that failed to send Given I run "AutoInstrumentNetworkBadAddressScenario" # Only the initial command request should be captured. - Then I wait for 1 span + Then I wait to receive at least 1 span Scenario: Automatically start a network span that has a null URL Given I run "AutoInstrumentNetworkNullURLScenario" - And I wait for 1 span + And I wait to receive at least 1 span 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 "parentSpanId" does not exist @@ -615,7 +615,7 @@ Feature: Automatic instrumentation spans Scenario: Automatically start a network span triggered by AVAssetDownloadURLSession (must not crash) Given I run "AutoInstrumentAVAssetScenario" And I wait for 2 seconds - And I wait for 2 spans + And I wait to receive at least 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 "parentSpanId" does not exist @@ -708,7 +708,7 @@ Feature: Automatic instrumentation spans Scenario: ComplexViewScenario Given I run "ComplexViewScenario" - And I wait for 27 spans + And I wait to receive at least 27 spans Then the trace "Content-Type" header equals "application/json" * the trace "Bugsnag-Span-Sampling" header equals "1:27" * 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$" diff --git a/features/default/automatic_spans_generic_view_load.feature b/features/default/automatic_spans_generic_view_load.feature index 1541865a..63f77589 100644 --- a/features/default/automatic_spans_generic_view_load.feature +++ b/features/default/automatic_spans_generic_view_load.feature @@ -3,7 +3,7 @@ Feature: Automatic instrumentation spans AutoInstrumentGenericViewLoadScenario Scenario: AutoInstrumentGenericViewLoadScenario Given I run "AutoInstrumentGenericViewLoadScenario" - And I wait for 18 spans + And I wait to receive at least 18 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$" * a span field "name" equals "[ViewLoad/UIKit]/Fixture.ViewController" @@ -40,7 +40,7 @@ Feature: Automatic instrumentation spans AutoInstrumentGenericViewLoadScenario Scenario: AutoInstrumentGenericViewLoadScenario2 Given I run "AutoInstrumentGenericViewLoadScenario2" - And I wait for 18 spans + And I wait to receive at least 18 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$" * a span field "name" equals "[ViewLoad/UIKit]/Fixture.ViewController" diff --git a/features/default/configuration.feature b/features/default/configuration.feature index 3c7c65d7..d45fc411 100644 --- a/features/default/configuration.feature +++ b/features/default/configuration.feature @@ -2,7 +2,7 @@ Feature: Configuration overrides Scenario: Override service name, bundle version and service version Given I run "AppDataOverrideScenario" - And I wait for 1 span + And I wait to receive at least 1 span 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-Span-Sampling" header equals "1:1" @@ -30,7 +30,7 @@ Feature: Configuration overrides Given I set the sampling probability for the next traces to "0" And I enter unmanaged traces mode And I run "FixedSamplingProbabilityOneScenario" - And I wait for 1 span + And I wait to receive at least 1 span 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-Span-Sampling" header is not present @@ -44,7 +44,7 @@ Feature: Configuration overrides Then I discard the oldest trace Then I set the sampling probability for the next traces to "0" And I invoke "step2" - And I wait for 1 span + And I wait to receive at least 1 span * the trace "Bugsnag-Span-Sampling" header is not present * 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 "FixedSamplingProbabilitySpan2" diff --git a/features/default/initial-p-value.feature b/features/default/initial-p-value.feature index 90f1b467..91eea554 100644 --- a/features/default/initial-p-value.feature +++ b/features/default/initial-p-value.feature @@ -19,7 +19,7 @@ Feature: Initial P values Then the sampling request "Bugsnag-Span-Sampling" header equals "1:0" * the sampling request "Bugsnag-Sent-At" header matches the regex "^\d\d\d\d-\d\d-\d\dT\d\d:\d\d:\d\d\.\d\d\dZ$" - Then I wait for 1 span + Then I wait to receive at least 1 span * the trace "Bugsnag-Span-Sampling" header equals "1:1" * 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 "First" @@ -30,7 +30,7 @@ Feature: Initial P values * every span field "endTimeUnixNano" matches the regex "^[0-9]+$" Then I discard the oldest trace And I invoke "step2" - And I wait for 1 span + And I wait to receive at least 1 span * the trace "Bugsnag-Span-Sampling" header equals "1:1" * 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 "Second" diff --git a/features/default/manual_spans.feature b/features/default/manual_spans.feature index ccad2930..d2a65ef1 100644 --- a/features/default/manual_spans.feature +++ b/features/default/manual_spans.feature @@ -5,13 +5,13 @@ Feature: Manual creation of spans Scenario: Retry a manual span Given I set the HTTP status code for the next requests to 200,500,200,200 And I run "RetryScenario" - And I wait for 1 span + And I wait to receive at least 1 span * the trace "Bugsnag-Span-Sampling" header equals "1:1" * 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 "WillRetry" Then I discard the oldest trace And I invoke "step2" - And I wait for 2 spans + And I wait to receive at least 2 spans * a span field "name" equals "WillRetry" * a span field "name" equals "Success" * every span bool attribute "bugsnag.span.first_class" is true @@ -21,7 +21,7 @@ Feature: Manual creation of spans Given I run "ManualSpanScenario" And I wait to receive an error And the error payload field "events.0.device.id" is stored as the value "bugsnag_device_id" - And I wait for 1 span + And I wait to receive at least 1 span 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-Span-Sampling" header equals "1:1" @@ -52,7 +52,7 @@ Feature: Manual creation of spans Scenario: Starting and ending a span before starting the SDK Given I run "ManualSpanBeforeStartScenario" - And I wait for 1 span + And I wait to receive at least 1 span * the trace "Bugsnag-Span-Sampling" header equals "1:1" * 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 "BeforeStart" @@ -72,7 +72,7 @@ Feature: Manual creation of spans Scenario: Manually report a view load span Given I run "ManualViewLoadScenario" - And I wait for 2 spans + And I wait to receive at least 2 spans * the trace "Bugsnag-Span-Sampling" header is present * 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 "[ViewLoad/UIKit]/ManualViewController" @@ -89,7 +89,7 @@ Feature: Manual creation of spans Scenario: Manually report a SwiftUI view load phase span Given I run "ManualViewLoadPhaseScenario" - And I wait for 2 spans + And I wait to receive at least 2 spans * the trace "Bugsnag-Span-Sampling" header is present * 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 "[ViewLoad/SwiftUI]/ManualViewLoadPhaseScenario" @@ -106,7 +106,7 @@ Feature: Manual creation of spans Scenario: Manually report a UIViewController load span Given I run "ManualUIViewLoadScenario" - And I wait for 1 span + And I wait to receive at least 1 span * the trace "Bugsnag-Span-Sampling" header equals "1:1" * 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 "[ViewLoad/UIKit]/UIViewController" @@ -120,7 +120,7 @@ Feature: Manual creation of spans Scenario: Manually start a network span Given I run "ManualNetworkSpanScenario" - And I wait for 1 span + And I wait to receive at least 1 span Then the trace "Content-Type" header equals "application/json" * the trace "Bugsnag-Span-Sampling" header equals "1:1" * 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$" @@ -143,7 +143,7 @@ Feature: Manual creation of spans Scenario: Manually start a network span with callback set to nil Given I run "ManualNetworkSpanCallbackSetToNilScenario" - And I wait for 1 span + And I wait to receive at least 1 span Then the trace "Content-Type" header equals "application/json" * the trace "Bugsnag-Span-Sampling" header is present * 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$" @@ -165,7 +165,7 @@ Feature: Manual creation of spans Scenario: Manually start and end a span field "with" batching Given I run "BatchingScenario" - And I wait for 2 spans + And I wait to receive at least 2 spans Then the trace "Content-Type" header equals "application/json" * the trace "Bugsnag-Span-Sampling" header is present * 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$" @@ -183,7 +183,7 @@ Feature: Manual creation of spans Scenario: Manually start and end a span field "with" batching Given I run "BatchingScenario" - And I wait for 2 spans + And I wait to receive at least 2 spans Then the trace "Content-Type" header equals "application/json" * the trace "Bugsnag-Span-Sampling" header is present * 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$" @@ -201,7 +201,7 @@ Feature: Manual creation of spans Scenario: Manually start and end parent and child spans Given I run "ParentSpanScenario" - And I wait for 2 spans + And I wait to receive at least 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$" * the trace payload field "resourceSpans.0.resource" string attribute "service.name" matches the regex "com.bugsnag.fixtures.cocoaperformance(xcframework)?" @@ -220,7 +220,7 @@ Feature: Manual creation of spans Scenario: Manually start and end child span with a manually defined parent Given I run "ManualParentSpanScenario" - And I wait for 1 span + And I wait to receive at least 1 span 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$" * the trace payload field "resourceSpans.0.resource" string attribute "service.name" matches the regex "com.bugsnag.fixtures.cocoaperformance(xcframework)?" @@ -238,7 +238,7 @@ Feature: Manual creation of spans Scenario: Manually start and end first-class = yes span Given I run "FirstClassYesScenario" - And I wait for 1 span + And I wait to receive at least 1 span 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$" * the trace payload field "resourceSpans.0.resource" string attribute "service.name" matches the regex "com.bugsnag.fixtures.cocoaperformance(xcframework)?" @@ -254,7 +254,7 @@ Feature: Manual creation of spans Scenario: Manually start and end first-class = no span Given I run "FirstClassNoScenario" - And I wait for 1 span + And I wait to receive at least 1 span 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$" * the trace payload field "resourceSpans.0.resource" string attribute "service.name" matches the regex "com.bugsnag.fixtures.cocoaperformance(xcframework)?" @@ -302,7 +302,7 @@ Feature: Manual creation of spans Scenario: Set attributes in a span Given I run "SetAttributesScenario" - And I wait for 1 span + And I wait to receive at least 1 span * the trace "Bugsnag-Span-Sampling" header equals "1:1" * 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 "SetAttributesScenario" @@ -319,7 +319,7 @@ Feature: Manual creation of spans Scenario: Set attributes in a span with limits set Given I run "SetAttributesWithLimitsScenario" - And I wait for 1 span + And I wait to receive at least 1 span * the trace "Bugsnag-Span-Sampling" header equals "1:1" * 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 "SetAttributesWithLimitsScenario" @@ -331,7 +331,7 @@ Feature: Manual creation of spans Scenario: Set attributes in a span with an attribute count limit set Given I run "SetAttributeCountLimitScenario" - And I wait for 1 span + And I wait to receive at least 1 span * the trace "Bugsnag-Span-Sampling" header equals "1:1" * 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 "SetAttributeCountLimitScenario" @@ -355,7 +355,7 @@ Feature: Manual creation of spans Scenario: Frame metrics - no slow frames Given I run "FrameMetricsNoSlowFramesScenario" - And I wait for 1 span + And I wait to receive at least 1 span Then the trace "Content-Type" header equals "application/json" * the trace "Bugsnag-Span-Sampling" header equals "1:1" * 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$" @@ -372,7 +372,7 @@ Feature: Manual creation of spans Scenario: Frame metrics - slow frames Given I run "FrameMetricsSlowFramesScenario" - And I wait for 1 span + And I wait to receive at least 1 span Then the trace "Content-Type" header equals "application/json" * the trace "Bugsnag-Span-Sampling" header equals "1:1" * 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$" @@ -389,7 +389,7 @@ Feature: Manual creation of spans Scenario: Frame metrics - frozen frames Given I run "FrameMetricsFronzenFramesScenario" - And I wait for 3 spans + And I wait to receive at least 3 spans Then the trace "Content-Type" header equals "application/json" * the trace "Bugsnag-Span-Sampling" header equals "1:3" * 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$" @@ -407,7 +407,7 @@ Feature: Manual creation of spans Scenario: Frame metrics - autoInstrumentRendering off Given I run "FrameMetricsAutoInstrumentRenderingOffScenario" - And I wait for 1 span + And I wait to receive at least 1 span Then the trace "Content-Type" header equals "application/json" * the trace "Bugsnag-Span-Sampling" header equals "1:1" * 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$" @@ -424,7 +424,7 @@ Feature: Manual creation of spans Scenario: Frame metrics - span instrumentRendering off Given I run "FrameMetricsSpanInstrumentRenderingOffScenario" - And I wait for 1 span + And I wait to receive at least 1 span Then the trace "Content-Type" header equals "application/json" * the trace "Bugsnag-Span-Sampling" header equals "1:1" * 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$" @@ -441,7 +441,7 @@ Feature: Manual creation of spans Scenario: Frame metrics - non firstClass span with instrumentRendering off Given I run "FrameMetricsNonFirstClassSpanInstrumentRenderingOnScenario" - And I wait for 1 span + And I wait to receive at least 1 span Then the trace "Content-Type" header equals "application/json" * the trace "Bugsnag-Span-Sampling" header equals "1:1" * 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$" @@ -458,7 +458,7 @@ Feature: Manual creation of spans Scenario: Span Conditions - condition closed Given I run "SpanConditionsSimpleConditionScenario" - And I wait for 2 spans + And I wait to receive at least 2 spans Then the trace "Content-Type" header equals "application/json" * the trace "Bugsnag-Span-Sampling" header equals "1:2" * 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$" @@ -474,7 +474,7 @@ Feature: Manual creation of spans Scenario: Span Conditions - condition timed out Given I run "SpanConditionsConditionTimedOutScenario" - And I wait for 2 spans + And I wait to receive at least 2 spans Then the trace "Content-Type" header equals "application/json" * the trace "Bugsnag-Span-Sampling" header equals "1:2" * 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$" @@ -489,7 +489,7 @@ Feature: Manual creation of spans Scenario: Span Conditions - multiple conditions Given I run "SpanConditionsMultipleConditionsScenario" - And I wait for 3 spans + And I wait to receive at least 3 spans Then the trace "Content-Type" header equals "application/json" * the trace "Bugsnag-Span-Sampling" header equals "1:3" * 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$" @@ -506,7 +506,7 @@ Feature: Manual creation of spans Scenario: Manually start and end a span with remote parent context Given I run "ManualSpanWithRemoteContextParentScenario" - And I wait for 1 span + And I wait to receive at least 1 span 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-Span-Sampling" header equals "1:1" diff --git a/features/default/metrics.feature b/features/default/metrics.feature index c4e6a0e7..11fee07b 100644 --- a/features/default/metrics.feature +++ b/features/default/metrics.feature @@ -7,7 +7,7 @@ Feature: Spans with collected metrics And I configure scenario "variant_name" to "NoSlow" And I start bugsnag And I run the loaded scenario - And I wait for 1 span + And I wait to receive at least 1 span Then the trace "Content-Type" header equals "application/json" * the trace "Bugsnag-Span-Sampling" header equals "1:1" * 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$" @@ -30,7 +30,7 @@ Feature: Spans with collected metrics And I set the sampling probability to "1.0" And I start bugsnag And I run the loaded scenario - And I wait for 1 span + And I wait to receive at least 1 span Then the trace "Content-Type" header equals "application/json" * the trace "Bugsnag-Span-Sampling" header equals "1:1" * 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$" @@ -47,7 +47,7 @@ Feature: Spans with collected metrics Scenario: Frame metrics - slow frames Given I run "FrameMetricsSlowFramesScenario" - And I wait for 1 span + And I wait to receive at least 1 span Then the trace "Content-Type" header equals "application/json" * the trace "Bugsnag-Span-Sampling" header equals "1:1" * 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$" @@ -64,7 +64,7 @@ Feature: Spans with collected metrics Scenario: Frame metrics - frozen frames Given I run "FrameMetricsFronzenFramesScenario" - And I wait for 3 spans + And I wait to receive at least 3 spans Then the trace "Content-Type" header equals "application/json" * the trace "Bugsnag-Span-Sampling" header equals "1:3" * 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$" @@ -82,7 +82,7 @@ Feature: Spans with collected metrics Scenario: Frame metrics - autoInstrumentRendering off Given I run "FrameMetricsAutoInstrumentRenderingOffScenario" - And I wait for 1 span + And I wait to receive at least 1 span Then the trace "Content-Type" header equals "application/json" * the trace "Bugsnag-Span-Sampling" header equals "1:1" * 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$" @@ -99,7 +99,7 @@ Feature: Spans with collected metrics Scenario: Frame metrics - span instrumentRendering off Given I run "FrameMetricsSpanInstrumentRenderingOffScenario" - And I wait for 1 span + And I wait to receive at least 1 span Then the trace "Content-Type" header equals "application/json" * the trace "Bugsnag-Span-Sampling" header equals "1:1" * 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$" @@ -116,7 +116,7 @@ Feature: Spans with collected metrics Scenario: Frame metrics - non firstClass span with instrumentRendering off Given I run "FrameMetricsNonFirstClassSpanInstrumentRenderingOnScenario" - And I wait for 1 span + And I wait to receive at least 1 span Then the trace "Content-Type" header equals "application/json" * the trace "Bugsnag-Span-Sampling" header equals "1:1" * 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$" @@ -139,7 +139,7 @@ Feature: Spans with collected metrics And I configure scenario "variant_name" to "DefaultSettingsCPUMetricsDisabled" And I start bugsnag And I run the loaded scenario - And I wait for 1 span + And I wait to receive at least 1 span Then the trace "Content-Type" header equals "application/json" * the trace "Bugsnag-Span-Sampling" header equals "1:1" * 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$" @@ -170,7 +170,7 @@ Feature: Spans with collected metrics And I configure scenario "variant_name" to "NoMetrics" And I start bugsnag And I run the loaded scenario - And I wait for 1 span + And I wait to receive at least 1 span Then the trace "Content-Type" header equals "application/json" * the trace "Bugsnag-Span-Sampling" header equals "1:1" * 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$" @@ -201,7 +201,7 @@ Feature: Spans with collected metrics And I configure scenario "variant_name" to "FirstClass" And I start bugsnag And I run the loaded scenario - And I wait for 1 span + And I wait to receive at least 1 span Then the trace "Content-Type" header equals "application/json" * the trace "Bugsnag-Span-Sampling" header equals "1:1" * 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$" @@ -232,7 +232,7 @@ Feature: Spans with collected metrics And I configure scenario "variant_name" to "NonFirstClass" And I start bugsnag And I run the loaded scenario - And I wait for 1 span + And I wait to receive at least 1 span Then the trace "Content-Type" header equals "application/json" * the trace "Bugsnag-Span-Sampling" header equals "1:1" * 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$" @@ -264,7 +264,7 @@ Feature: Spans with collected metrics And I configure scenario "variant_name" to "NonFirstClassWithMetrics" And I start bugsnag And I run the loaded scenario - And I wait for 1 span + And I wait to receive at least 1 span Then the trace "Content-Type" header equals "application/json" * the trace "Bugsnag-Span-Sampling" header equals "1:1" * 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$" @@ -295,7 +295,7 @@ Feature: Spans with collected metrics And I configure scenario "variant_name" to "LongerSpanDuration" And I start bugsnag And I run the loaded scenario - And I wait for 1 span + And I wait to receive at least 1 span Then the trace "Content-Type" header equals "application/json" * the trace "Bugsnag-Span-Sampling" header equals "1:1" * 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$" @@ -326,7 +326,7 @@ Feature: Spans with collected metrics And I configure scenario "variant_name" to "GenerateSpanLater" And I start bugsnag And I run the loaded scenario - And I wait for 1 span + And I wait to receive at least 1 span Then the trace "Content-Type" header equals "application/json" * the trace "Bugsnag-Span-Sampling" header equals "1:1" * 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$" @@ -361,7 +361,7 @@ Feature: Spans with collected metrics And I configure scenario "variant_name" to "MainThreadHeavyWork" And I start bugsnag And I run the loaded scenario - And I wait for 1 span + And I wait to receive at least 1 span Then the trace "Content-Type" header equals "application/json" * the trace "Bugsnag-Span-Sampling" header equals "1:1" * 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$" @@ -394,7 +394,7 @@ Feature: Spans with collected metrics And I configure scenario "variant_name" to "BgThreadHeavyWork" And I start bugsnag And I run the loaded scenario - And I wait for 1 span + And I wait to receive at least 1 span Then the trace "Content-Type" header equals "application/json" * the trace "Bugsnag-Span-Sampling" header equals "1:1" * 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$" @@ -425,7 +425,7 @@ Feature: Spans with collected metrics And I configure scenario "variant_name" to "DefaultSettings" And I start bugsnag And I run the loaded scenario - And I wait for 1 span + And I wait to receive at least 1 span Then the trace "Content-Type" header equals "application/json" * the trace "Bugsnag-Span-Sampling" header equals "1:1" * 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$" @@ -453,7 +453,7 @@ Feature: Spans with collected metrics And I configure scenario "variant_name" to "FirstClass" And I start bugsnag And I run the loaded scenario - And I wait for 1 span + And I wait to receive at least 1 span Then the trace "Content-Type" header equals "application/json" * the trace "Bugsnag-Span-Sampling" header equals "1:1" * 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$" @@ -481,7 +481,7 @@ Feature: Spans with collected metrics And I configure scenario "variant_name" to "NonFirstClass" And I start bugsnag And I run the loaded scenario - And I wait for 1 span + And I wait to receive at least 1 span Then the trace "Content-Type" header equals "application/json" * the trace "Bugsnag-Span-Sampling" header equals "1:1" * 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$" @@ -510,7 +510,7 @@ Feature: Spans with collected metrics And I configure scenario "variant_name" to "NonFirstClassEnabled" And I start bugsnag And I run the loaded scenario - And I wait for 1 span + And I wait to receive at least 1 span Then the trace "Content-Type" header equals "application/json" * the trace "Bugsnag-Span-Sampling" header equals "1:1" * 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$" @@ -538,7 +538,7 @@ Feature: Spans with collected metrics And I configure scenario "variant_name" to "LongerDuration" And I start bugsnag And I run the loaded scenario - And I wait for 1 span + And I wait to receive at least 1 span Then the trace "Content-Type" header equals "application/json" * the trace "Bugsnag-Span-Sampling" header equals "1:1" * 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$" @@ -566,7 +566,7 @@ Feature: Spans with collected metrics And I configure scenario "variant_name" to "GenerateLater" And I start bugsnag And I run the loaded scenario - And I wait for 1 span + And I wait to receive at least 1 span Then the trace "Content-Type" header equals "application/json" * the trace "Bugsnag-Span-Sampling" header equals "1:1" * 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$" diff --git a/features/default/network.feature b/features/default/network.feature index 4ecff6c4..be2ce508 100644 --- a/features/default/network.feature +++ b/features/default/network.feature @@ -19,7 +19,7 @@ Feature: Automatic instrumentation spans # Wait for a long time because there can be a LOT of maze-runner related URL requests before the scenario starts. And I wait for 20 seconds # There will actually be any number of requests by this point since we're not filtering at all. - And I wait for 1 span + And I wait to receive at least 1 span 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$" * a span field "name" equals "[HTTP/GET]" diff --git a/features/default/sampling.feature b/features/default/sampling.feature index 8d44c370..6cdf56a1 100644 --- a/features/default/sampling.feature +++ b/features/default/sampling.feature @@ -8,7 +8,7 @@ Feature: Sampling Scenario: But if the server changes the probability, we must honor that Given I set the sampling probability to "1.0" And I run "SamplingProbabilityZeroScenario" - And I wait for 2 spans + And I wait to receive at least 2 spans * the trace "Bugsnag-Span-Sampling" header equals "1:2" * 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 "Pre-start" diff --git a/features/default/triggers.feature b/features/default/triggers.feature index 72ed3d0b..4d2d9d8c 100644 --- a/features/default/triggers.feature +++ b/features/default/triggers.feature @@ -3,7 +3,7 @@ Feature: Automatic send triggers Scenario: BackgroundForegroundScenario Given I run "BackgroundForegroundScenario" And I switch to the web browser for 2 seconds - And I wait for 1 span + And I wait to receive at least 1 span Then the trace "Content-Type" header equals "application/json" * the trace "Bugsnag-Span-Sampling" header equals "1:1" * 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$" From b045f424995fb9875b280362e28a8ddf54a62cfc Mon Sep 17 00:00:00 2001 From: Josh Date: Tue, 2 Sep 2025 09:19:52 +0100 Subject: [PATCH 37/53] add retry to appium steps --- .buildkite/pipeline.full.yml | 64 ++++++++++++++++++++++++++++++++++++ .buildkite/pipeline.yml | 32 ++++++++++++++++++ 2 files changed, 96 insertions(+) diff --git a/.buildkite/pipeline.full.yml b/.buildkite/pipeline.full.yml index d0f151ff..18fa2329 100644 --- a/.buildkite/pipeline.full.yml +++ b/.buildkite/pipeline.full.yml @@ -82,6 +82,10 @@ steps: concurrency: 25 concurrency_group: bitbar-app concurrency_method: eager + retry: + automatic: + - exit_status: 103 # Appium session failed + limit: 2 - label: ":bitbar: iOS 16 E2E Tests" depends_on: @@ -115,6 +119,10 @@ steps: concurrency: 25 concurrency_group: bitbar-app concurrency_method: eager + retry: + automatic: + - exit_status: 103 # Appium session failed + limit: 2 - label: ":bitbar: iOS 15 E2E Tests" depends_on: @@ -148,6 +156,10 @@ steps: concurrency: 25 concurrency_group: bitbar-app concurrency_method: eager + retry: + automatic: + - exit_status: 103 # Appium session failed + limit: 2 - label: ":bitbar: iOS 14 E2E Tests" depends_on: @@ -181,6 +193,10 @@ steps: concurrency: 25 concurrency_group: bitbar-app concurrency_method: eager + retry: + automatic: + - exit_status: 103 # Appium session failed + limit: 2 - group: "XcFramework E2E Tests" steps: @@ -216,6 +232,10 @@ steps: concurrency: 25 concurrency_group: bitbar-app concurrency_method: eager + retry: + automatic: + - exit_status: 103 # Appium session failed + limit: 2 - label: ":bitbar: iOS XcFramework 16 E2E Tests" depends_on: @@ -249,6 +269,10 @@ steps: concurrency: 25 concurrency_group: bitbar-app concurrency_method: eager + retry: + automatic: + - exit_status: 103 # Appium session failed + limit: 2 - label: ":bitbar: iOS XcFramework 15 E2E Tests" depends_on: @@ -282,6 +306,10 @@ steps: concurrency: 25 concurrency_group: bitbar-app concurrency_method: eager + retry: + automatic: + - exit_status: 103 # Appium session failed + limit: 2 - label: ":bitbar: iOS XcFramework 14 E2E Tests" depends_on: @@ -315,6 +343,10 @@ steps: concurrency: 25 concurrency_group: bitbar-app concurrency_method: eager + retry: + automatic: + - exit_status: 103 # Appium session failed + limit: 2 - group: "Swizzling disabled E2E" steps: @@ -349,6 +381,10 @@ steps: concurrency: 25 concurrency_group: bitbar-app concurrency_method: eager + retry: + automatic: + - exit_status: 103 # Appium session failed + limit: 2 - label: ":bitbar: iOS 16 E2E Tests swizzling disabled" depends_on: @@ -381,6 +417,10 @@ steps: concurrency: 25 concurrency_group: bitbar-app concurrency_method: eager + retry: + automatic: + - exit_status: 103 # Appium session failed + limit: 2 - label: ":bitbar: iOS 15 E2E Tests swizzling disabled" depends_on: @@ -413,6 +453,10 @@ steps: concurrency: 25 concurrency_group: bitbar-app concurrency_method: eager + retry: + automatic: + - exit_status: 103 # Appium session failed + limit: 2 - label: ":bitbar: iOS 14 E2E Tests swizzling disabled" depends_on: @@ -445,6 +489,10 @@ steps: concurrency: 25 concurrency_group: bitbar-app concurrency_method: eager + retry: + automatic: + - exit_status: 103 # Appium session failed + limit: 2 - group: "Swizzling premain E2E" steps: @@ -480,6 +528,10 @@ steps: concurrency: 25 concurrency_group: bitbar-app concurrency_method: eager + retry: + automatic: + - exit_status: 103 # Appium session failed + limit: 2 - label: ":bitbar: iOS 16 E2E Tests swizzling premain" depends_on: @@ -513,6 +565,10 @@ steps: concurrency: 25 concurrency_group: bitbar-app concurrency_method: eager + retry: + automatic: + - exit_status: 103 # Appium session failed + limit: 2 - label: ":bitbar: iOS 15 E2E Tests swizzling premain" depends_on: @@ -546,6 +602,10 @@ steps: concurrency: 25 concurrency_group: bitbar-app concurrency_method: eager + retry: + automatic: + - exit_status: 103 # Appium session failed + limit: 2 - label: ":bitbar: iOS 14 E2E Tests swizzling premain" depends_on: @@ -579,3 +639,7 @@ steps: concurrency: 25 concurrency_group: bitbar-app concurrency_method: eager + retry: + automatic: + - exit_status: 103 # Appium session failed + limit: 2 diff --git a/.buildkite/pipeline.yml b/.buildkite/pipeline.yml index 29df7331..ac0eb7bf 100644 --- a/.buildkite/pipeline.yml +++ b/.buildkite/pipeline.yml @@ -168,6 +168,10 @@ steps: concurrency: 5 concurrency_group: browserstack-app concurrency_method: eager + retry: + automatic: + - exit_status: 103 # Appium session failed + limit: 2 - label: ":bitbar: iOS 13 E2E Tests" depends_on: @@ -201,6 +205,10 @@ steps: concurrency: 25 concurrency_group: bitbar-app concurrency_method: eager + retry: + automatic: + - exit_status: 103 # Appium session failed + limit: 2 - group: "XcFramework E2E Tests" steps: @@ -234,6 +242,10 @@ steps: concurrency: 5 concurrency_group: browserstack-app concurrency_method: eager + retry: + automatic: + - exit_status: 103 # Appium session failed + limit: 2 - label: ":bitbar: iOS XcFramework 13 E2E Tests" depends_on: @@ -267,6 +279,10 @@ steps: concurrency: 25 concurrency_group: bitbar-app concurrency_method: eager + retry: + automatic: + - exit_status: 103 # Appium session failed + limit: 2 - group: "Swizzling disabled E2E Tests" steps: @@ -300,6 +316,10 @@ steps: concurrency: 5 concurrency_group: browserstack-app concurrency_method: eager + retry: + automatic: + - exit_status: 103 # Appium session failed + limit: 2 - label: ":bitbar: iOS 13 E2E Tests swizzling disabled" depends_on: @@ -332,6 +352,10 @@ steps: concurrency: 25 concurrency_group: bitbar-app concurrency_method: eager + retry: + automatic: + - exit_status: 103 # Appium session failed + limit: 2 - group: "Swizzling premain E2E Tests" steps: @@ -366,6 +390,10 @@ steps: concurrency: 5 concurrency_group: browserstack-app concurrency_method: eager + retry: + automatic: + - exit_status: 103 # Appium session failed + limit: 2 - label: ":bitbar: iOS 13 E2E Tests swizzling premain" depends_on: @@ -399,6 +427,10 @@ steps: concurrency: 25 concurrency_group: bitbar-app concurrency_method: eager + retry: + automatic: + - exit_status: 103 # Appium session failed + limit: 2 - label: 'Conditionally trigger full set of tests' agents: From e846790f2c192c4144429f09a62e713d43c5c3bf Mon Sep 17 00:00:00 2001 From: Daria Bialobrzeska <88393714+DariaKunoichi@users.noreply.github.com> Date: Fri, 5 Sep 2025 11:45:29 +0200 Subject: [PATCH 38/53] Setting parent context to nil in the span options should prevent the span from becoming a child (#469) * Set default parent context for span that is not nil * Add e2e test for span's parent context set to nil --- BugsnagPerformance.xcodeproj/project.pbxproj | 8 ++++ .../BugsnagPerformanceSpanContext+Private.h | 2 + .../BugsnagPerformance/Private/SpanContext.h | 20 ++++++++++ .../BugsnagPerformance/Private/SpanContext.mm | 19 +++++++++ .../BugsnagPerformance/Private/SpanOptions.h | 3 +- Sources/BugsnagPerformance/Private/Tracer.mm | 3 ++ .../Public/BugsnagPerformanceSpanContext.mm | 6 +++ .../Public/BugsnagPerformanceSpanOptions.m | 3 +- .../SpanOptionsTests.mm | 20 +++++++++- features/default/manual_spans.feature | 14 +++++++ .../ios/Fixture.xcodeproj/project.pbxproj | 4 ++ ...nualSpanWithContextParentNilScenario.swift | 39 +++++++++++++++++++ 12 files changed, 137 insertions(+), 4 deletions(-) create mode 100644 Sources/BugsnagPerformance/Private/SpanContext.h create mode 100644 Sources/BugsnagPerformance/Private/SpanContext.mm create mode 100644 features/fixtures/ios/Scenarios/ManualSpanWithContextParentNilScenario.swift diff --git a/BugsnagPerformance.xcodeproj/project.pbxproj b/BugsnagPerformance.xcodeproj/project.pbxproj index b350553e..63cf170d 100644 --- a/BugsnagPerformance.xcodeproj/project.pbxproj +++ b/BugsnagPerformance.xcodeproj/project.pbxproj @@ -76,6 +76,8 @@ 09F23A8F2CE3521D00F0D769 /* BugsnagSwiftToolsImpl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09F23A8E2CE3521D00F0D769 /* BugsnagSwiftToolsImpl.swift */; }; 09FFD4402BEE3DE2009B0E04 /* BugsnagPerformanceCrossTalkAPI.h in Headers */ = {isa = PBXBuildFile; fileRef = 09FFD43E2BEE3DE2009B0E04 /* BugsnagPerformanceCrossTalkAPI.h */; }; 09FFD4412BEE3DE2009B0E04 /* BugsnagPerformanceCrossTalkAPI.mm in Sources */ = {isa = PBXBuildFile; fileRef = 09FFD43F2BEE3DE2009B0E04 /* BugsnagPerformanceCrossTalkAPI.mm */; }; + 1C7852A22E5F5B3000BB8E2D /* SpanContext.h in Headers */ = {isa = PBXBuildFile; fileRef = 1C7852A12E5F5B2E00BB8E2D /* SpanContext.h */; }; + 1C7852A42E5F698300BB8E2D /* SpanContext.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1C7852A32E5F697D00BB8E2D /* SpanContext.mm */; }; 8A80DA8B2966CE940035BDA9 /* BugsnagPerformance.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 72E4BB63359EA30E80116E2A /* BugsnagPerformance.framework */; }; 8A80DA912966CEB30035BDA9 /* ConfigurationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A80DA80296588840035BDA9 /* ConfigurationTests.swift */; }; 960EECF12B23DF9B009FAA11 /* BugsnagPerformanceTrackedViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 960EECED2B237561009FAA11 /* BugsnagPerformanceTrackedViewController.swift */; }; @@ -409,6 +411,8 @@ 09F23A8E2CE3521D00F0D769 /* BugsnagSwiftToolsImpl.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BugsnagSwiftToolsImpl.swift; sourceTree = ""; }; 09FFD43E2BEE3DE2009B0E04 /* BugsnagPerformanceCrossTalkAPI.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = BugsnagPerformanceCrossTalkAPI.h; sourceTree = ""; }; 09FFD43F2BEE3DE2009B0E04 /* BugsnagPerformanceCrossTalkAPI.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = BugsnagPerformanceCrossTalkAPI.mm; sourceTree = ""; }; + 1C7852A12E5F5B2E00BB8E2D /* SpanContext.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SpanContext.h; sourceTree = ""; }; + 1C7852A32E5F697D00BB8E2D /* SpanContext.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = SpanContext.mm; sourceTree = ""; }; 72E4BB63359EA30E80116E2A /* BugsnagPerformance.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = BugsnagPerformance.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 8A80DA80296588840035BDA9 /* ConfigurationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigurationTests.swift; sourceTree = ""; }; 8A80DA872966CE940035BDA9 /* BugsnagPerformance-iOSTestsSwift.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "BugsnagPerformance-iOSTestsSwift.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -709,6 +713,8 @@ 0122C21F29019770002D243C /* Private */ = { isa = PBXGroup; children = ( + 1C7852A32E5F697D00BB8E2D /* SpanContext.mm */, + 1C7852A12E5F5B2E00BB8E2D /* SpanContext.h */, CB572EA829BB783200FD7A2A /* AppStateTracker.h */, CB572EA929BB783200FD7A2A /* AppStateTracker.m */, CBE8EA1C294B5E1500702950 /* Batch.h */, @@ -1133,6 +1139,7 @@ 963726CA2DEAB24900C739E6 /* BugsnagPerformancePriority+Private.h in Headers */, 964B785F2D504FD800FF077D /* BugsnagPerformanceSpanCondition+Private.h in Headers */, 963726C62DEAB14D00C739E6 /* BSGCompositeSpanControlProvider.h in Headers */, + 1C7852A22E5F5B3000BB8E2D /* SpanContext.h in Headers */, 0122C24A29019770002D243C /* AppStartupInstrumentation.h in Headers */, CB04969B2915194E0097E526 /* OtlpUploader.h in Headers */, 966634D12C8909BB004A934D /* FrameMetricsCollector.h in Headers */, @@ -1630,6 +1637,7 @@ 0122C24829019770002D243C /* NetworkInstrumentation.mm in Sources */, 0122C24D29019770002D243C /* ViewLoadInstrumentation.mm in Sources */, 015836C229125E7A002F54C8 /* ResourceAttributes.mm in Sources */, + 1C7852A42E5F698300BB8E2D /* SpanContext.mm in Sources */, 01A414D22913C238003152A4 /* Reachability.mm in Sources */, 96D55C802A1EA5C6006D1F29 /* SpanStackingHandler.mm in Sources */, CB34771F29068C350033759C /* BSGURLSessionPerformanceProxy.mm in Sources */, diff --git a/Sources/BugsnagPerformance/Private/BugsnagPerformanceSpanContext+Private.h b/Sources/BugsnagPerformance/Private/BugsnagPerformanceSpanContext+Private.h index 2d2b2d72..79045069 100644 --- a/Sources/BugsnagPerformance/Private/BugsnagPerformanceSpanContext+Private.h +++ b/Sources/BugsnagPerformance/Private/BugsnagPerformanceSpanContext+Private.h @@ -16,6 +16,8 @@ NS_ASSUME_NONNULL_BEGIN - (NSString *)encodedAsTraceParentWithSampled:(BOOL)sampled; ++ (BugsnagPerformanceSpanContext*)defaultContext; + @end NS_ASSUME_NONNULL_END diff --git a/Sources/BugsnagPerformance/Private/SpanContext.h b/Sources/BugsnagPerformance/Private/SpanContext.h new file mode 100644 index 00000000..3b86344b --- /dev/null +++ b/Sources/BugsnagPerformance/Private/SpanContext.h @@ -0,0 +1,20 @@ +// +// SpanContext.h +// BugsnagPerformance +// +// Created by Daria Bialobrzeska on 27/08/2025. +// Copyright © 2025 Bugsnag. All rights reserved. +// +#ifndef BUGSNAGPERFORMANCE_SPANCONTEXT_H +#define BUGSNAGPERFORMANCE_SPANCONTEXT_H + +#import + +namespace bugsnag { + +static BugsnagPerformanceSpanContext *defaultContext = nil; + +BugsnagPerformanceSpanContext *getDefaultSpanContext(); +} + +#endif // BUGSNAGPERFORMANCE_SPANCONTEXT_H diff --git a/Sources/BugsnagPerformance/Private/SpanContext.mm b/Sources/BugsnagPerformance/Private/SpanContext.mm new file mode 100644 index 00000000..93a3830a --- /dev/null +++ b/Sources/BugsnagPerformance/Private/SpanContext.mm @@ -0,0 +1,19 @@ +// +// SpanContext.m +// BugsnagPerformance +// +// Created by Daria Bialobrzeska on 27/08/2025. +// Copyright © 2025 Bugsnag. All rights reserved. +// +#import "SpanContext.h" + +namespace bugsnag { + + BugsnagPerformanceSpanContext *getDefaultSpanContext() { + if (defaultContext == nil) { + defaultContext = [[BugsnagPerformanceSpanContext alloc] initWithTraceId:{.hi = 0, .lo = 0 } spanId:0]; + } + + return defaultContext; + } +} diff --git a/Sources/BugsnagPerformance/Private/SpanOptions.h b/Sources/BugsnagPerformance/Private/SpanOptions.h index fd6e37e8..236262d8 100644 --- a/Sources/BugsnagPerformance/Private/SpanOptions.h +++ b/Sources/BugsnagPerformance/Private/SpanOptions.h @@ -10,6 +10,7 @@ #import #import "Utils.h" #import "Metrics.h" +#import "SpanContext.h" namespace bugsnag { @@ -37,7 +38,7 @@ class SpanOptions { SpanOptions() // These defaults must match the defaults in BugsnagPerformanceSpanOptions.m - : SpanOptions(nil, + : SpanOptions(getDefaultSpanContext(), CFABSOLUTETIME_INVALID, true, BSGTriStateUnset, diff --git a/Sources/BugsnagPerformance/Private/Tracer.mm b/Sources/BugsnagPerformance/Private/Tracer.mm index 7b33bffb..03ddc442 100644 --- a/Sources/BugsnagPerformance/Private/Tracer.mm +++ b/Sources/BugsnagPerformance/Private/Tracer.mm @@ -14,6 +14,7 @@ #import "Instrumentation/ViewLoadInstrumentation.h" #import "BugsnagPerformanceLibrary.h" #import "FrameRateMetrics/FrameMetricsCollector.h" +#import "BugsnagPerformanceSpanContext+Private.h" #import using namespace bugsnag; @@ -103,6 +104,8 @@ __block auto blockThis = this; auto parentSpan = options.parentContext; if (parentSpan == nil) { + BSGLogTrace(@"Tracer::startSpan: Parent is intentionally set to nil"); + } else if (parentSpan == [BugsnagPerformanceSpanContext defaultContext]) { BSGLogTrace(@"Tracer::startSpan: No parent specified; using current span"); parentSpan = spanStackingHandler_->currentSpan(); } diff --git a/Sources/BugsnagPerformance/Public/BugsnagPerformanceSpanContext.mm b/Sources/BugsnagPerformance/Public/BugsnagPerformanceSpanContext.mm index 9fd18a32..d8d62065 100644 --- a/Sources/BugsnagPerformance/Public/BugsnagPerformanceSpanContext.mm +++ b/Sources/BugsnagPerformance/Public/BugsnagPerformanceSpanContext.mm @@ -7,9 +7,15 @@ // #import +#import "../Private/BugsnagPerformanceSpanContext+Private.h" +#import "../Private/SpanContext.h" @implementation BugsnagPerformanceSpanContext ++ (BugsnagPerformanceSpanContext*)defaultContext { + return bugsnag::getDefaultSpanContext(); +} + - (instancetype) initWithTraceId:(TraceId) traceId spanId:(SpanId) spanId { if ((self = [super init])) { _traceId = traceId; diff --git a/Sources/BugsnagPerformance/Public/BugsnagPerformanceSpanOptions.m b/Sources/BugsnagPerformance/Public/BugsnagPerformanceSpanOptions.m index fc4541db..25ad2203 100644 --- a/Sources/BugsnagPerformance/Public/BugsnagPerformanceSpanOptions.m +++ b/Sources/BugsnagPerformance/Public/BugsnagPerformanceSpanOptions.m @@ -9,6 +9,7 @@ #import #import #import +#import "../Private/BugsnagPerformanceSpanContext+Private.h" @implementation BugsnagPerformanceSpanMetricsOptions @@ -52,7 +53,7 @@ @implementation BugsnagPerformanceSpanOptions - (instancetype)init { // These defaults must match the defaults in SpanOptions.h return [self initWithStartTime:nil - parentContext:nil + parentContext:[BugsnagPerformanceSpanContext defaultContext] makeCurrentContext:true firstClass:BSGTriStateUnset metricsOptions:[BugsnagPerformanceSpanMetricsOptions new]]; diff --git a/Tests/BugsnagPerformanceTests/SpanOptionsTests.mm b/Tests/BugsnagPerformanceTests/SpanOptionsTests.mm index b40f55c6..6c6304ca 100644 --- a/Tests/BugsnagPerformanceTests/SpanOptionsTests.mm +++ b/Tests/BugsnagPerformanceTests/SpanOptionsTests.mm @@ -13,6 +13,7 @@ #import "BugsnagPerformanceSpan+Private.h" #import "IdGenerator.h" #import "BugsnagPerformanceSpanCondition+Private.h" +#import "BugsnagPerformanceSpanContext+Private.h" #import using namespace bugsnag; @@ -26,7 +27,7 @@ @implementation SpanOptionsTests - (void)testUnset { BugsnagPerformanceSpanOptions *objcOptions = [BugsnagPerformanceSpanOptions new]; - XCTAssertNil(objcOptions.parentContext); + XCTAssertEqual(objcOptions.parentContext, [BugsnagPerformanceSpanContext defaultContext]); XCTAssertNil(objcOptions.startTime); XCTAssertTrue(objcOptions.makeCurrentContext); XCTAssertEqual(objcOptions.firstClass, BSGTriStateUnset); @@ -35,12 +36,27 @@ - (void)testUnset { - (void)testConversionDefaults { BugsnagPerformanceSpanOptions *objcOptions = [BugsnagPerformanceSpanOptions new]; SpanOptions cOptions(objcOptions); - XCTAssertNil(cOptions.parentContext); + XCTAssertEqual(cOptions.parentContext, [BugsnagPerformanceSpanContext defaultContext]); XCTAssertTrue(isnan(cOptions.startTime)); XCTAssertTrue(cOptions.makeCurrentContext); XCTAssertEqual(cOptions.firstClass, BSGTriStateUnset); } +- (void)testSettingParentContext { + BugsnagPerformanceSpanOptions *objcOptions = [BugsnagPerformanceSpanOptions new]; + XCTAssertEqual(objcOptions.parentContext, [BugsnagPerformanceSpanContext defaultContext]); + [objcOptions setParentContext:nil]; + XCTAssertNil(objcOptions.parentContext); + + SpanOptions cOptions(objcOptions); + XCTAssertNil(objcOptions.parentContext); + + SpanOptions cOptions2; + XCTAssertEqual(cOptions2.parentContext, [BugsnagPerformanceSpanContext defaultContext]); + cOptions2.parentContext = nil; + XCTAssertNil(cOptions2.parentContext); +} + - (void)testConversion { MetricsOptions metricsOptions; metricsOptions.rendering = BSGTriStateNo; diff --git a/features/default/manual_spans.feature b/features/default/manual_spans.feature index d2a65ef1..7ed7b82c 100644 --- a/features/default/manual_spans.feature +++ b/features/default/manual_spans.feature @@ -519,3 +519,17 @@ Feature: Manual creation of spans * every span field "startTimeUnixNano" matches the regex "^[0-9]+$" * every span field "endTimeUnixNano" matches the regex "^[0-9]+$" * every span string attribute "bugsnag.span.category" equals "custom" + + Scenario: Manually start and end a span with nil parent context + Given I run "ManualSpanWithContextParentNilScenario" + And I wait for 3 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-Span-Sampling" header equals "1:3" + * 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$" + * the trace payload field "resourceSpans.0.scopeSpans.0.spans.0.name" equals "ManualSpanWithContextParentNilScenario" + * the trace payload field "resourceSpans.0.scopeSpans.0.spans.1.name" equals "ManualSpanWithContextParentSet" + * the trace payload field "resourceSpans.0.scopeSpans.0.spans.2.name" equals "ShouldNotBeParentSpan" + * 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 diff --git a/features/fixtures/ios/Fixture.xcodeproj/project.pbxproj b/features/fixtures/ios/Fixture.xcodeproj/project.pbxproj index b5284e86..db8e54bb 100644 --- a/features/fixtures/ios/Fixture.xcodeproj/project.pbxproj +++ b/features/fixtures/ios/Fixture.xcodeproj/project.pbxproj @@ -59,6 +59,7 @@ 09F23AC12CF0CBB500F0D769 /* AutoInstrumentGenericViewLoadScenario2.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09F23AC02CF0CBB500F0D769 /* AutoInstrumentGenericViewLoadScenario2.swift */; }; 09F3F5282D6C728300BAA0A3 /* MemoryMetricsScenario.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09F3F5272D6C728300BAA0A3 /* MemoryMetricsScenario.swift */; }; 09F3F5302D6F17B300BAA0A3 /* RenderingMetricsScenario.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09F3F52F2D6F17B300BAA0A3 /* RenderingMetricsScenario.swift */; }; + 1C7852A62E5FA7DF00BB8E2D /* ManualSpanWithContextParentNilScenario.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C7852A52E5FA7C700BB8E2D /* ManualSpanWithContextParentNilScenario.swift */; }; 960EECE92B2316E1009FAA11 /* AutoInstrumentGenericViewLoadScenario.swift in Sources */ = {isa = PBXBuildFile; fileRef = 960EECE82B2316E1009FAA11 /* AutoInstrumentGenericViewLoadScenario.swift */; }; 96284DCE2B626B6F00025070 /* AutoInstrumentPreLoadedViewLoadScenario.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96284DCD2B626B6F00025070 /* AutoInstrumentPreLoadedViewLoadScenario.swift */; }; 964735CB2CCF137A00759ED9 /* AutoInstrumentNetworkSharedSessionInvalidateScenario.swift in Sources */ = {isa = PBXBuildFile; fileRef = 964735CA2CCF137A00759ED9 /* AutoInstrumentNetworkSharedSessionInvalidateScenario.swift */; }; @@ -161,6 +162,7 @@ 09F23AC02CF0CBB500F0D769 /* AutoInstrumentGenericViewLoadScenario2.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutoInstrumentGenericViewLoadScenario2.swift; sourceTree = ""; }; 09F3F5272D6C728300BAA0A3 /* MemoryMetricsScenario.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MemoryMetricsScenario.swift; sourceTree = ""; }; 09F3F52F2D6F17B300BAA0A3 /* RenderingMetricsScenario.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RenderingMetricsScenario.swift; sourceTree = ""; }; + 1C7852A52E5FA7C700BB8E2D /* ManualSpanWithContextParentNilScenario.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ManualSpanWithContextParentNilScenario.swift; sourceTree = ""; }; 960EECE82B2316E1009FAA11 /* AutoInstrumentGenericViewLoadScenario.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutoInstrumentGenericViewLoadScenario.swift; sourceTree = ""; }; 96284DCD2B626B6F00025070 /* AutoInstrumentPreLoadedViewLoadScenario.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutoInstrumentPreLoadedViewLoadScenario.swift; sourceTree = ""; }; 964735CA2CCF137A00759ED9 /* AutoInstrumentNetworkSharedSessionInvalidateScenario.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutoInstrumentNetworkSharedSessionInvalidateScenario.swift; sourceTree = ""; }; @@ -338,6 +340,7 @@ 01E7918928EC7B5E00855993 /* ManualSpanBeforeStartScenario.swift */, 01FE4DC228E1AF3700D1F239 /* ManualSpanScenario.swift */, 96F129342DCE0CFE00A6FB2B /* ManualSpanWithRemoteContextParentScenario.swift */, + 1C7852A52E5FA7C700BB8E2D /* ManualSpanWithContextParentNilScenario.swift */, CB7FD92A299BB4E300499E13 /* ManualUIViewLoadScenario.swift */, 098808DF2B10A6E400DC1677 /* ManualViewLoadPhaseScenario.swift */, 01E3F99028F46B6700003F44 /* ManualViewLoadScenario.swift */, @@ -485,6 +488,7 @@ 96284DCE2B626B6F00025070 /* AutoInstrumentPreLoadedViewLoadScenario.swift in Sources */, 09637A3C2B0607F300F4F776 /* Logging.swift in Sources */, 9657A89B2A3D06EB001CEF5D /* AutoInstrumentNavigationViewLoadScenario.swift in Sources */, + 1C7852A62E5FA7DF00BB8E2D /* ManualSpanWithContextParentNilScenario.swift in Sources */, 96986DA02D50654500A44C34 /* SpanConditionsMultipleConditionsScenario.swift in Sources */, 01D3A7E028F0290D0063D79E /* AutoInstrumentAppStartsScenario.swift in Sources */, 96986D9E2D5060E600A44C34 /* SpanConditionsSimpleConditionScenario.swift in Sources */, diff --git a/features/fixtures/ios/Scenarios/ManualSpanWithContextParentNilScenario.swift b/features/fixtures/ios/Scenarios/ManualSpanWithContextParentNilScenario.swift new file mode 100644 index 00000000..90918fc5 --- /dev/null +++ b/features/fixtures/ios/Scenarios/ManualSpanWithContextParentNilScenario.swift @@ -0,0 +1,39 @@ +// +// ManualSpanWithContextParentNilScenario.swift +// Fixture +// +// Created by Daria Bialobrzeska on 27/08/2025. +// + +import Bugsnag +import BugsnagPerformance + +@objcMembers +class ManualSpanWithContextParentNilScenario: Scenario { + + override func run() { + let emptyOpts = BugsnagPerformanceSpanOptions() + let shouldNotBeParentSpan = BugsnagPerformance.startSpan( + name: "ShouldNotBeParentSpan", + options: emptyOpts + ) + + let opts = BugsnagPerformanceSpanOptions() + opts.setParentContext(nil) + let span = BugsnagPerformance.startSpan( + name: "ManualSpanWithContextParentNilScenario", + options: opts + ) + span.end() + + + let spanWithParent = BugsnagPerformance.startSpan( + name: "ManualSpanWithContextParentSet", + options: emptyOpts + ) + spanWithParent.end() + + + shouldNotBeParentSpan.end() + } +} From 8371de3ad2fe04eee1029ff5a9f99127afc2f74a Mon Sep 17 00:00:00 2001 From: Daria Bialobrzeska <88393714+DariaKunoichi@users.noreply.github.com> Date: Fri, 5 Sep 2025 12:13:41 +0200 Subject: [PATCH 39/53] Amend secondary instance URL to bugsnag.smartbear.com (#473) * Amend secondary instance URL to bugsnag.smartbear.com * Update define's name --- .../Public/BugsnagPerformanceConfiguration.mm | 4 ++-- .../BugsnagPerformanceConfigurationTests.mm | 14 +++++++------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/Sources/BugsnagPerformance/Public/BugsnagPerformanceConfiguration.mm b/Sources/BugsnagPerformance/Public/BugsnagPerformanceConfiguration.mm index ba28f0ff..da993787 100644 --- a/Sources/BugsnagPerformance/Public/BugsnagPerformanceConfiguration.mm +++ b/Sources/BugsnagPerformance/Public/BugsnagPerformanceConfiguration.mm @@ -25,13 +25,13 @@ #define DEFAULT_ATTRIBUTE_COUNT_LIMIT 128 #define DEFAULT_URL_FORMAT @"https://%@.otlp.bugsnag.com/v1/traces" -#define DEFAULT_HUB_URL_FORMAT @"https://%@.otlp.insighthub.smartbear.com/v1/traces" +#define DEFAULT_SECONDARY_URL_FORMAT @"https://%@.otlp.bugsnag.smartbear.com/v1/traces" #define HUB_API_PREFIX @"00000" static inline NSURL *DefaultEndpointForKey(NSString *apiKey) { #pragma clang diagnostic ignored "-Wformat-nonliteral" NSString *fmt = [apiKey hasPrefix:HUB_API_PREFIX] - ? DEFAULT_HUB_URL_FORMAT + ? DEFAULT_SECONDARY_URL_FORMAT : DEFAULT_URL_FORMAT; return nsurlWithString([NSString stringWithFormat:fmt, apiKey], nil); } diff --git a/Tests/BugsnagPerformanceTests/BugsnagPerformanceConfigurationTests.mm b/Tests/BugsnagPerformanceTests/BugsnagPerformanceConfigurationTests.mm index e436b9e3..d965ab75 100644 --- a/Tests/BugsnagPerformanceTests/BugsnagPerformanceConfigurationTests.mm +++ b/Tests/BugsnagPerformanceTests/BugsnagPerformanceConfigurationTests.mm @@ -64,21 +64,21 @@ - (void)testShouldPassValidationWithValidApiKeyAndValidCustomEndpoint { } - (void)testShouldSetIncludeApiKeyInTheHubEndpoint { - NSString *hubKey = @"00000abcdef1234567890abcdef12345"; - auto config = [[BugsnagPerformanceConfiguration alloc] initWithApiKey:hubKey]; + NSString *secondaryKey = @"00000abcdef1234567890abcdef12345"; + auto config = [[BugsnagPerformanceConfiguration alloc] initWithApiKey:secondaryKey]; XCTAssertEqualObjects(config.endpoint.absoluteString, - @"https://00000abcdef1234567890abcdef12345.otlp.insighthub.smartbear.com/v1/traces"); + @"https://00000abcdef1234567890abcdef12345.otlp.bugsnag.smartbear.com/v1/traces"); } - (void)testUpdatingAPIKeyUpdatesEndpointToHubURL { auto config = [[BugsnagPerformanceConfiguration alloc] initWithApiKey:@"0123456789abcdef0123456789abcdef"]; XCTAssertEqualObjects(config.endpoint.absoluteString, @"https://0123456789abcdef0123456789abcdef.otlp.bugsnag.com/v1/traces"); - NSString *hubKey = @"00000abcdef1234567890abcdef12345"; - [config setApiKey:hubKey]; - XCTAssertEqualObjects(config.apiKey, hubKey); + NSString *secondaryKey = @"00000abcdef1234567890abcdef12345"; + [config setApiKey:secondaryKey]; + XCTAssertEqualObjects(config.apiKey, secondaryKey); XCTAssertEqualObjects(config.endpoint.absoluteString, - @"https://00000abcdef1234567890abcdef12345.otlp.insighthub.smartbear.com/v1/traces"); + @"https://00000abcdef1234567890abcdef12345.otlp.bugsnag.smartbear.com/v1/traces"); } - (void)testCustomEndpointStillOverridesHubLogic { From 636d26a0f9971ac516261132b0961ef4ca58e0aa Mon Sep 17 00:00:00 2001 From: Robert Bartoszewski <126675445+robert-smartbear@users.noreply.github.com> Date: Thu, 11 Sep 2025 20:09:57 +0200 Subject: [PATCH 40/53] Merge pull request #476 from bugsnag/robert/plat-14716_benchmarking Benchmarking --- .buildkite/pipeline.yml | 89 +++ docker-compose.yml | 2 + features/benchmarks/spans.feature | 17 + features/default/manual_spans.feature | 2 +- features/fixtures/benchmarks/.gitignore | 2 + .../fixtures/benchmarks/ExportOptions.plist | 18 + .../Fixture.xcodeproj/project.pbxproj | 513 ++++++++++++++++++ .../contents.xcworkspacedata | 7 + .../xcshareddata/IDEWorkspaceChecks.plist | 8 + .../xcshareddata/xcschemes/Fixture.xcscheme | 92 ++++ .../benchmarks/Fixture/AppDelegate.swift | 37 ++ .../AccentColor.colorset/Contents.json | 11 + .../AppIcon.appiconset/Contents.json | 13 + .../Fixture/Assets.xcassets/Contents.json | 6 + .../Base.lproj/LaunchScreen.storyboard | 25 + .../Fixture/Base.lproj/Main.storyboard | 33 ++ .../Fixture/BenchmarkInstrumentation.swift | 91 ++++ .../Fixture/BenchmarkMeasurement.swift | 14 + .../benchmarks/Fixture/BenchmarkRunner.swift | 139 +++++ .../fixtures/benchmarks/Fixture/CPUSampler.h | 23 + .../fixtures/benchmarks/Fixture/CPUSampler.mm | 70 +++ .../Fixture/CommandReaderThread.swift | 126 +++++ .../Fixture/Fixture-Bridging-Header.h | 9 + .../fixtures/benchmarks/Fixture/Fixture.swift | 169 ++++++ .../benchmarks/Fixture/FixtureConfig.swift | 51 ++ .../fixtures/benchmarks/Fixture/Info.plist | 57 ++ .../Fixture/MazeRunnerCommand.swift | 29 + .../benchmarks/Fixture/SceneDelegate.swift | 53 ++ .../benchmarks/Fixture/ViewController.swift | 17 + .../benchmarks/Suite/SpanOpenCloseSuite.swift | 20 + .../fixtures/benchmarks/Suite/Suite.swift | 125 +++++ .../benchmarks/Suite/SuiteConfig.swift | 10 + features/fixtures/benchmarks/build.sh | 26 + features/fixtures/benchmarks/utils/Logging.h | 25 + features/fixtures/benchmarks/utils/Logging.m | 51 ++ .../fixtures/benchmarks/utils/Logging.swift | 45 ++ features/steps/app_steps.rb | 4 + 37 files changed, 2028 insertions(+), 1 deletion(-) create mode 100644 features/benchmarks/spans.feature create mode 100644 features/fixtures/benchmarks/.gitignore create mode 100644 features/fixtures/benchmarks/ExportOptions.plist create mode 100644 features/fixtures/benchmarks/Fixture.xcodeproj/project.pbxproj create mode 100644 features/fixtures/benchmarks/Fixture.xcodeproj/project.xcworkspace/contents.xcworkspacedata create mode 100644 features/fixtures/benchmarks/Fixture.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist create mode 100644 features/fixtures/benchmarks/Fixture.xcodeproj/xcshareddata/xcschemes/Fixture.xcscheme create mode 100644 features/fixtures/benchmarks/Fixture/AppDelegate.swift create mode 100644 features/fixtures/benchmarks/Fixture/Assets.xcassets/AccentColor.colorset/Contents.json create mode 100644 features/fixtures/benchmarks/Fixture/Assets.xcassets/AppIcon.appiconset/Contents.json create mode 100644 features/fixtures/benchmarks/Fixture/Assets.xcassets/Contents.json create mode 100644 features/fixtures/benchmarks/Fixture/Base.lproj/LaunchScreen.storyboard create mode 100644 features/fixtures/benchmarks/Fixture/Base.lproj/Main.storyboard create mode 100644 features/fixtures/benchmarks/Fixture/BenchmarkInstrumentation.swift create mode 100644 features/fixtures/benchmarks/Fixture/BenchmarkMeasurement.swift create mode 100644 features/fixtures/benchmarks/Fixture/BenchmarkRunner.swift create mode 100644 features/fixtures/benchmarks/Fixture/CPUSampler.h create mode 100644 features/fixtures/benchmarks/Fixture/CPUSampler.mm create mode 100644 features/fixtures/benchmarks/Fixture/CommandReaderThread.swift create mode 100644 features/fixtures/benchmarks/Fixture/Fixture-Bridging-Header.h create mode 100644 features/fixtures/benchmarks/Fixture/Fixture.swift create mode 100644 features/fixtures/benchmarks/Fixture/FixtureConfig.swift create mode 100644 features/fixtures/benchmarks/Fixture/Info.plist create mode 100644 features/fixtures/benchmarks/Fixture/MazeRunnerCommand.swift create mode 100644 features/fixtures/benchmarks/Fixture/SceneDelegate.swift create mode 100644 features/fixtures/benchmarks/Fixture/ViewController.swift create mode 100644 features/fixtures/benchmarks/Suite/SpanOpenCloseSuite.swift create mode 100644 features/fixtures/benchmarks/Suite/Suite.swift create mode 100644 features/fixtures/benchmarks/Suite/SuiteConfig.swift create mode 100755 features/fixtures/benchmarks/build.sh create mode 100644 features/fixtures/benchmarks/utils/Logging.h create mode 100644 features/fixtures/benchmarks/utils/Logging.m create mode 100644 features/fixtures/benchmarks/utils/Logging.swift diff --git a/.buildkite/pipeline.yml b/.buildkite/pipeline.yml index ac0eb7bf..b5992068 100644 --- a/.buildkite/pipeline.yml +++ b/.buildkite/pipeline.yml @@ -109,6 +109,21 @@ steps: artifact_paths: - features/fixtures/ios/output/bb_url_swizzling_premain.txt - features/fixtures/ios/output/bs_url_swizzling_premain.txt + + - label: "Benchmarks Fixture" + key: ios_fixture_benchmarks + timeout_in_minutes: 10 + commands: + - ./features/fixtures/benchmarks/build.sh --fixtureName FixtureBenchmarks + - bundle install + - bundle exec upload-app --farm=bb --app=./features/fixtures/benchmarks/output/FixtureBenchmarks.ipa --app-id-file=./features/fixtures/benchmarks/output/bb_ipa_url_benchmarks.txt + - bundle exec upload-app --farm=bs --app=./features/fixtures/benchmarks/output/FixtureBenchmarks.ipa --app-id-file=./features/fixtures/benchmarks/output/bs_ipa_url_benchmarks.txt + env: + XCODE_VERSION: "16.3.0" + artifact_paths: + - features/fixtures/benchmarks/output/bb_ipa_url_benchmarks.txt + - features/fixtures/benchmarks/output/bs_ipa_url_benchmarks.txt + - features/fixtures/benchmarks/output/FixtureBenchmarks.ipa - group: ":xcode_simulator: Unit Tests" steps: @@ -431,6 +446,80 @@ steps: automatic: - exit_status: 103 # Appium session failed limit: 2 + + - group: "Benchmark Tests" + steps: + - label: ":browserstack: iOS 18 Benchmark Tests" + depends_on: + - ios_fixture_benchmarks + timeout_in_minutes: 30 + agents: + queue: opensource + plugins: + artifacts#v1.9.3: + download: "features/fixtures/benchmarks/output/bs_ipa_url_benchmarks.txt" + upload: + - "maze_output/failed/**/*" + - "maze_output/metrics.csv" + - "maze_output/maze_output.zip" + docker-compose#v4.8.0: + pull: maze-runner-bs + run: maze-runner-bs + service-ports: true + command: + - "--app=@build_benchmarks/bs_ipa_url_benchmarks.txt" + - "--device=IOS_18" + - "--fail-fast" + - "--farm=bs" + - "features/benchmarks" + test-collector#v1.10.2: + files: "reports/TEST-*.xml" + format: "junit" + branch: "^main|next$$" + concurrency: 5 + concurrency_group: browserstack-app + concurrency_method: eager + retry: + automatic: + - exit_status: 103 # Appium session failed + limit: 2 + + - label: ":bitbar: iOS 13 Benchmark Tests" + depends_on: + - ios_fixture_benchmarks + timeout_in_minutes: 30 + agents: + queue: opensource + plugins: + artifacts#v1.9.3: + download: "features/fixtures/benchmarks/output/bb_ipa_url_benchmarks.txt" + upload: + - "maze_output/failed/**/*" + - "maze_output/metrics.csv" + - "maze_output/maze_output.zip" + docker-compose#v4.8.0: + pull: maze-runner-bb + run: maze-runner-bb + service-ports: true + command: + - "--app=@build_benchmarks/bb_ipa_url_benchmarks.txt" + - "--device=IOS_13" + - "--fail-fast" + - "--farm=bb" + - "--no-tunnel" + - "--aws-public-ip" + - "features/benchmarks" + test-collector#v1.10.2: + files: "reports/TEST-*.xml" + format: "junit" + branch: "^main|next$$" + concurrency: 25 + concurrency_group: bitbar-app + concurrency_method: eager + retry: + automatic: + - exit_status: 103 # Appium session failed + limit: 2 - label: 'Conditionally trigger full set of tests' agents: diff --git a/docker-compose.yml b/docker-compose.yml index 871fb4fb..e6d94e20 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -34,6 +34,7 @@ services: - "9000-9499:9340" volumes: - ./features/fixtures/ios/output:/app/build + - ./features/fixtures/benchmarks/output:/app/build_benchmarks - ./features/:/app/features/ - ./maze_output:/app/maze_output - /var/run/docker.sock:/var/run/docker.sock @@ -47,6 +48,7 @@ services: BROWSER_STACK_ACCESS_KEY: volumes: - ./features/fixtures/ios/output:/app/build + - ./features/fixtures/benchmarks/output:/app/build_benchmarks - ./features/:/app/features/ - ./maze_output:/app/maze_output - ./reports/:/app/reports/ diff --git a/features/benchmarks/spans.feature b/features/benchmarks/spans.feature new file mode 100644 index 00000000..b0cb21e6 --- /dev/null +++ b/features/benchmarks/spans.feature @@ -0,0 +1,17 @@ +Feature: Device Metrics + + Scenario Outline: + When I run "SpanOpenCloseSuite" configured as + And I wait for 30 seconds + And I wait to receive at least 1 metrics + And I discard the oldest metric + + Examples: + | options | + | "" | + | "rendering" | + | "cpu" | + | "memory" | + | "NamedSpan" | + | "rendering cpu memory" | + | "rendering cpu memory NamedSpan" | \ No newline at end of file diff --git a/features/default/manual_spans.feature b/features/default/manual_spans.feature index 7ed7b82c..20bf831b 100644 --- a/features/default/manual_spans.feature +++ b/features/default/manual_spans.feature @@ -522,7 +522,7 @@ Feature: Manual creation of spans Scenario: Manually start and end a span with nil parent context Given I run "ManualSpanWithContextParentNilScenario" - And I wait for 3 spans + And I wait to receive at least 3 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-Span-Sampling" header equals "1:3" diff --git a/features/fixtures/benchmarks/.gitignore b/features/fixtures/benchmarks/.gitignore new file mode 100644 index 00000000..7e48d904 --- /dev/null +++ b/features/fixtures/benchmarks/.gitignore @@ -0,0 +1,2 @@ +Fixture.xcarchive +output diff --git a/features/fixtures/benchmarks/ExportOptions.plist b/features/fixtures/benchmarks/ExportOptions.plist new file mode 100644 index 00000000..c8002a2b --- /dev/null +++ b/features/fixtures/benchmarks/ExportOptions.plist @@ -0,0 +1,18 @@ + + + + + compileBitcode + + method + development + signingStyle + automatic + stripSwiftSymbols + + teamID + 7W9PZ27Y5F + thinning + <none> + + diff --git a/features/fixtures/benchmarks/Fixture.xcodeproj/project.pbxproj b/features/fixtures/benchmarks/Fixture.xcodeproj/project.pbxproj new file mode 100644 index 00000000..a48699ef --- /dev/null +++ b/features/fixtures/benchmarks/Fixture.xcodeproj/project.pbxproj @@ -0,0 +1,513 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 55; + objects = { + +/* Begin PBXBuildFile section */ + 01A414C82912CA18003152A4 /* Bugsnag in Frameworks */ = {isa = PBXBuildFile; productRef = 01A414C72912CA18003152A4 /* Bugsnag */; }; + 01FE4DA928E1AEBD00D1F239 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01FE4DA828E1AEBD00D1F239 /* AppDelegate.swift */; }; + 01FE4DAB28E1AEBD00D1F239 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01FE4DAA28E1AEBD00D1F239 /* SceneDelegate.swift */; }; + 01FE4DAD28E1AEBD00D1F239 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01FE4DAC28E1AEBD00D1F239 /* ViewController.swift */; }; + 01FE4DB028E1AEBD00D1F239 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 01FE4DAE28E1AEBD00D1F239 /* Main.storyboard */; }; + 01FE4DB228E1AEBF00D1F239 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 01FE4DB128E1AEBF00D1F239 /* Assets.xcassets */; }; + 01FE4DB528E1AEBF00D1F239 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 01FE4DB328E1AEBF00D1F239 /* LaunchScreen.storyboard */; }; + 01FE4DC028E1AEDF00D1F239 /* BugsnagPerformance in Frameworks */ = {isa = PBXBuildFile; productRef = 01FE4DBF28E1AEDF00D1F239 /* BugsnagPerformance */; }; + 01FE4DC528E1AF9600D1F239 /* Suite.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01FE4DC428E1AF9600D1F239 /* Suite.swift */; }; + 01FE4DC728E1D5A400D1F239 /* Fixture.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01FE4DC628E1D5A400D1F239 /* Fixture.swift */; }; + 09637A3C2B0607F300F4F776 /* Logging.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09637A3B2B0607F300F4F776 /* Logging.swift */; }; + 09637A3F2B06082200F4F776 /* Logging.m in Sources */ = {isa = PBXBuildFile; fileRef = 09637A3E2B06082200F4F776 /* Logging.m */; }; + 09637A412B060E7C00F4F776 /* CommandReaderThread.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09637A402B060E7C00F4F776 /* CommandReaderThread.swift */; }; + 09637A432B0617FE00F4F776 /* MazeRunnerCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09637A422B0617FE00F4F776 /* MazeRunnerCommand.swift */; }; + 09637A452B0B883B00F4F776 /* FixtureConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09637A442B0B883B00F4F776 /* FixtureConfig.swift */; }; + 0983A17B2B14BB2000DDF4FF /* BugsnagPerformanceSwift in Frameworks */ = {isa = PBXBuildFile; productRef = 0983A17A2B14BB2000DDF4FF /* BugsnagPerformanceSwift */; }; + 966AEDE02E70C487002F12A6 /* BugsnagPerformanceNamedSpans in Frameworks */ = {isa = PBXBuildFile; productRef = 966AEDDF2E70C487002F12A6 /* BugsnagPerformanceNamedSpans */; }; + 966AEDE22E70C978002F12A6 /* BenchmarkMeasurement.swift in Sources */ = {isa = PBXBuildFile; fileRef = 966AEDE12E70C972002F12A6 /* BenchmarkMeasurement.swift */; }; + 966AEDE42E70CD33002F12A6 /* BenchmarkInstrumentation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 966AEDE32E70CD2D002F12A6 /* BenchmarkInstrumentation.swift */; }; + 966AEDE72E70D689002F12A6 /* CPUSampler.mm in Sources */ = {isa = PBXBuildFile; fileRef = 966AEDE62E70D687002F12A6 /* CPUSampler.mm */; }; + 966AEDE92E70E11A002F12A6 /* SpanOpenCloseSuite.swift in Sources */ = {isa = PBXBuildFile; fileRef = 966AEDE82E70E10D002F12A6 /* SpanOpenCloseSuite.swift */; }; + 9682AA3C2E70BA250033D9D8 /* BenchmarkRunner.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9682AA3B2E70BA210033D9D8 /* BenchmarkRunner.swift */; }; + 9682AA3E2E70BECF0033D9D8 /* SuiteConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9682AA3D2E70BECC0033D9D8 /* SuiteConfig.swift */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 01FE4DA528E1AEBD00D1F239 /* Fixture.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Fixture.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 01FE4DA828E1AEBD00D1F239 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 01FE4DAA28E1AEBD00D1F239 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; + 01FE4DAC28E1AEBD00D1F239 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; + 01FE4DAF28E1AEBD00D1F239 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + 01FE4DB128E1AEBF00D1F239 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 01FE4DB428E1AEBF00D1F239 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 01FE4DB628E1AEBF00D1F239 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 01FE4DBD28E1AED000D1F239 /* bugsnag-cocoa-performance */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = "bugsnag-cocoa-performance"; path = ../../..; sourceTree = ""; }; + 01FE4DC428E1AF9600D1F239 /* Suite.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Suite.swift; sourceTree = ""; }; + 01FE4DC628E1D5A400D1F239 /* Fixture.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Fixture.swift; sourceTree = ""; }; + 09637A3B2B0607F300F4F776 /* Logging.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Logging.swift; sourceTree = ""; }; + 09637A3D2B06082200F4F776 /* Logging.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Logging.h; sourceTree = ""; }; + 09637A3E2B06082200F4F776 /* Logging.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = Logging.m; sourceTree = ""; }; + 09637A402B060E7C00F4F776 /* CommandReaderThread.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CommandReaderThread.swift; sourceTree = ""; }; + 09637A422B0617FE00F4F776 /* MazeRunnerCommand.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MazeRunnerCommand.swift; sourceTree = ""; }; + 09637A442B0B883B00F4F776 /* FixtureConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FixtureConfig.swift; sourceTree = ""; }; + 966AEDE12E70C972002F12A6 /* BenchmarkMeasurement.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BenchmarkMeasurement.swift; sourceTree = ""; }; + 966AEDE32E70CD2D002F12A6 /* BenchmarkInstrumentation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BenchmarkInstrumentation.swift; sourceTree = ""; }; + 966AEDE52E70D67B002F12A6 /* CPUSampler.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CPUSampler.h; sourceTree = ""; }; + 966AEDE62E70D687002F12A6 /* CPUSampler.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = CPUSampler.mm; sourceTree = ""; }; + 966AEDE82E70E10D002F12A6 /* SpanOpenCloseSuite.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SpanOpenCloseSuite.swift; sourceTree = ""; }; + 9682AA3B2E70BA210033D9D8 /* BenchmarkRunner.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BenchmarkRunner.swift; sourceTree = ""; }; + 9682AA3D2E70BECC0033D9D8 /* SuiteConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SuiteConfig.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 = ""; }; + CBAAE25429125F5F006D4AA0 /* Fixture-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Fixture-Bridging-Header.h"; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 01FE4DA228E1AEBD00D1F239 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 01A414C82912CA18003152A4 /* Bugsnag in Frameworks */, + 0983A17B2B14BB2000DDF4FF /* BugsnagPerformanceSwift in Frameworks */, + 01FE4DC028E1AEDF00D1F239 /* BugsnagPerformance in Frameworks */, + 966AEDE02E70C487002F12A6 /* BugsnagPerformanceNamedSpans in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 01FE4D9C28E1AEBD00D1F239 = { + isa = PBXGroup; + children = ( + 09637A3A2B0607DA00F4F776 /* utils */, + 01FE4DA728E1AEBD00D1F239 /* Fixture */, + 01FE4DC128E1AF0700D1F239 /* Suite */, + 01FE4DBC28E1AED000D1F239 /* Packages */, + 01FE4DA628E1AEBD00D1F239 /* Products */, + 01FE4DBE28E1AEDF00D1F239 /* Frameworks */, + ); + sourceTree = ""; + }; + 01FE4DA628E1AEBD00D1F239 /* Products */ = { + isa = PBXGroup; + children = ( + 01FE4DA528E1AEBD00D1F239 /* Fixture.app */, + ); + name = Products; + sourceTree = ""; + }; + 01FE4DA728E1AEBD00D1F239 /* Fixture */ = { + isa = PBXGroup; + children = ( + 01FE4DA828E1AEBD00D1F239 /* AppDelegate.swift */, + 01FE4DB128E1AEBF00D1F239 /* Assets.xcassets */, + 966AEDE32E70CD2D002F12A6 /* BenchmarkInstrumentation.swift */, + 966AEDE12E70C972002F12A6 /* BenchmarkMeasurement.swift */, + 9682AA3B2E70BA210033D9D8 /* BenchmarkRunner.swift */, + CB211D0629EEB615008F748D /* BugsnagPerformanceConfiguration+Private.h */, + 09637A402B060E7C00F4F776 /* CommandReaderThread.swift */, + 966AEDE52E70D67B002F12A6 /* CPUSampler.h */, + 966AEDE62E70D687002F12A6 /* CPUSampler.mm */, + 01FE4DC628E1D5A400D1F239 /* Fixture.swift */, + CBAAE25429125F5F006D4AA0 /* Fixture-Bridging-Header.h */, + 09637A442B0B883B00F4F776 /* FixtureConfig.swift */, + 01FE4DB628E1AEBF00D1F239 /* Info.plist */, + 01FE4DB328E1AEBF00D1F239 /* LaunchScreen.storyboard */, + 01FE4DAE28E1AEBD00D1F239 /* Main.storyboard */, + 09637A422B0617FE00F4F776 /* MazeRunnerCommand.swift */, + 01FE4DAA28E1AEBD00D1F239 /* SceneDelegate.swift */, + 01FE4DAC28E1AEBD00D1F239 /* ViewController.swift */, + ); + path = Fixture; + sourceTree = ""; + }; + 01FE4DBC28E1AED000D1F239 /* Packages */ = { + isa = PBXGroup; + children = ( + 01FE4DBD28E1AED000D1F239 /* bugsnag-cocoa-performance */, + ); + name = Packages; + sourceTree = ""; + }; + 01FE4DBE28E1AEDF00D1F239 /* Frameworks */ = { + isa = PBXGroup; + children = ( + ); + name = Frameworks; + sourceTree = ""; + }; + 01FE4DC128E1AF0700D1F239 /* Suite */ = { + isa = PBXGroup; + children = ( + 966AEDE82E70E10D002F12A6 /* SpanOpenCloseSuite.swift */, + 9682AA3D2E70BECC0033D9D8 /* SuiteConfig.swift */, + 01FE4DC428E1AF9600D1F239 /* Suite.swift */, + ); + path = Suite; + sourceTree = ""; + }; + 09637A3A2B0607DA00F4F776 /* utils */ = { + isa = PBXGroup; + children = ( + 09637A3B2B0607F300F4F776 /* Logging.swift */, + 09637A3D2B06082200F4F776 /* Logging.h */, + 09637A3E2B06082200F4F776 /* Logging.m */, + ); + path = utils; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 01FE4DA428E1AEBD00D1F239 /* Fixture */ = { + isa = PBXNativeTarget; + buildConfigurationList = 01FE4DB928E1AEBF00D1F239 /* Build configuration list for PBXNativeTarget "Fixture" */; + buildPhases = ( + 01FE4DA128E1AEBD00D1F239 /* Sources */, + 01FE4DA228E1AEBD00D1F239 /* Frameworks */, + 01FE4DA328E1AEBD00D1F239 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Fixture; + packageProductDependencies = ( + 01FE4DBF28E1AEDF00D1F239 /* BugsnagPerformance */, + 01A414C72912CA18003152A4 /* Bugsnag */, + 0983A17A2B14BB2000DDF4FF /* BugsnagPerformanceSwift */, + 966AEDDF2E70C487002F12A6 /* BugsnagPerformanceNamedSpans */, + ); + productName = Fixture; + productReference = 01FE4DA528E1AEBD00D1F239 /* Fixture.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 01FE4D9D28E1AEBD00D1F239 /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = 1; + LastSwiftUpdateCheck = 1400; + LastUpgradeCheck = 1400; + TargetAttributes = { + 01FE4DA428E1AEBD00D1F239 = { + CreatedOnToolsVersion = 14.0; + LastSwiftMigration = 1400; + }; + }; + }; + buildConfigurationList = 01FE4DA028E1AEBD00D1F239 /* Build configuration list for PBXProject "Fixture" */; + compatibilityVersion = "Xcode 13.0"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 01FE4D9C28E1AEBD00D1F239; + packageReferences = ( + 01A414C62912CA18003152A4 /* XCRemoteSwiftPackageReference "bugsnag-cocoa" */, + ); + productRefGroup = 01FE4DA628E1AEBD00D1F239 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 01FE4DA428E1AEBD00D1F239 /* Fixture */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 01FE4DA328E1AEBD00D1F239 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 01FE4DB528E1AEBF00D1F239 /* LaunchScreen.storyboard in Resources */, + 01FE4DB228E1AEBF00D1F239 /* Assets.xcassets in Resources */, + 01FE4DB028E1AEBD00D1F239 /* Main.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 01FE4DA128E1AEBD00D1F239 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 09637A432B0617FE00F4F776 /* MazeRunnerCommand.swift in Sources */, + 01FE4DAD28E1AEBD00D1F239 /* ViewController.swift in Sources */, + 966AEDE72E70D689002F12A6 /* CPUSampler.mm in Sources */, + 966AEDE22E70C978002F12A6 /* BenchmarkMeasurement.swift in Sources */, + 01FE4DA928E1AEBD00D1F239 /* AppDelegate.swift in Sources */, + 09637A3C2B0607F300F4F776 /* Logging.swift in Sources */, + 9682AA3E2E70BECF0033D9D8 /* SuiteConfig.swift in Sources */, + 01FE4DAB28E1AEBD00D1F239 /* SceneDelegate.swift in Sources */, + 9682AA3C2E70BA250033D9D8 /* BenchmarkRunner.swift in Sources */, + 09637A3F2B06082200F4F776 /* Logging.m in Sources */, + 01FE4DC528E1AF9600D1F239 /* Suite.swift in Sources */, + 966AEDE42E70CD33002F12A6 /* BenchmarkInstrumentation.swift in Sources */, + 966AEDE92E70E11A002F12A6 /* SpanOpenCloseSuite.swift in Sources */, + 01FE4DC728E1D5A400D1F239 /* Fixture.swift in Sources */, + 09637A412B060E7C00F4F776 /* CommandReaderThread.swift in Sources */, + 09637A452B0B883B00F4F776 /* FixtureConfig.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXVariantGroup section */ + 01FE4DAE28E1AEBD00D1F239 /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 01FE4DAF28E1AEBD00D1F239 /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; + 01FE4DB328E1AEBF00D1F239 /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 01FE4DB428E1AEBF00D1F239 /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 01FE4DB728E1AEBF00D1F239 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 16.0; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 01FE4DB828E1AEBF00D1F239 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 16.0; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 01FE4DBA28E1AEBF00D1F239 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = 7W9PZ27Y5F; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = Fixture/Info.plist; + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; + INFOPLIST_KEY_UIMainStoryboardFile = Main; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.bugsnag.fixtures.cocoaperformance; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_OBJC_BRIDGING_HEADER = "Fixture/Fixture-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 01FE4DBB28E1AEBF00D1F239 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = 7W9PZ27Y5F; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = Fixture/Info.plist; + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; + INFOPLIST_KEY_UIMainStoryboardFile = Main; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.bugsnag.fixtures.cocoaperformance; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_OBJC_BRIDGING_HEADER = "Fixture/Fixture-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 01FE4DA028E1AEBD00D1F239 /* Build configuration list for PBXProject "Fixture" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 01FE4DB728E1AEBF00D1F239 /* Debug */, + 01FE4DB828E1AEBF00D1F239 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 01FE4DB928E1AEBF00D1F239 /* Build configuration list for PBXNativeTarget "Fixture" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 01FE4DBA28E1AEBF00D1F239 /* Debug */, + 01FE4DBB28E1AEBF00D1F239 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + +/* Begin XCRemoteSwiftPackageReference section */ + 01A414C62912CA18003152A4 /* XCRemoteSwiftPackageReference "bugsnag-cocoa" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/bugsnag/bugsnag-cocoa"; + requirement = { + branch = master; + kind = branch; + }; + }; +/* End XCRemoteSwiftPackageReference section */ + +/* Begin XCSwiftPackageProductDependency section */ + 01A414C72912CA18003152A4 /* Bugsnag */ = { + isa = XCSwiftPackageProductDependency; + package = 01A414C62912CA18003152A4 /* XCRemoteSwiftPackageReference "bugsnag-cocoa" */; + productName = Bugsnag; + }; + 01FE4DBF28E1AEDF00D1F239 /* BugsnagPerformance */ = { + isa = XCSwiftPackageProductDependency; + productName = BugsnagPerformance; + }; + 0983A17A2B14BB2000DDF4FF /* BugsnagPerformanceSwift */ = { + isa = XCSwiftPackageProductDependency; + productName = BugsnagPerformanceSwift; + }; + 966AEDDF2E70C487002F12A6 /* BugsnagPerformanceNamedSpans */ = { + isa = XCSwiftPackageProductDependency; + productName = BugsnagPerformanceNamedSpans; + }; +/* End XCSwiftPackageProductDependency section */ + }; + rootObject = 01FE4D9D28E1AEBD00D1F239 /* Project object */; +} diff --git a/features/fixtures/benchmarks/Fixture.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/features/fixtures/benchmarks/Fixture.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 00000000..919434a6 --- /dev/null +++ b/features/fixtures/benchmarks/Fixture.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/features/fixtures/benchmarks/Fixture.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/features/fixtures/benchmarks/Fixture.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 00000000..18d98100 --- /dev/null +++ b/features/fixtures/benchmarks/Fixture.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/features/fixtures/benchmarks/Fixture.xcodeproj/xcshareddata/xcschemes/Fixture.xcscheme b/features/fixtures/benchmarks/Fixture.xcodeproj/xcshareddata/xcschemes/Fixture.xcscheme new file mode 100644 index 00000000..4d00364c --- /dev/null +++ b/features/fixtures/benchmarks/Fixture.xcodeproj/xcshareddata/xcschemes/Fixture.xcscheme @@ -0,0 +1,92 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/features/fixtures/benchmarks/Fixture/AppDelegate.swift b/features/fixtures/benchmarks/Fixture/AppDelegate.swift new file mode 100644 index 00000000..1f2f0a8d --- /dev/null +++ b/features/fixtures/benchmarks/Fixture/AppDelegate.swift @@ -0,0 +1,37 @@ +// +// AppDelegate.swift +// Fixture +// +// Created by Nick Dowell on 26/09/2022. +// + +import UIKit + +@main +class AppDelegate: UIResponder, UIApplicationDelegate { + + var window: UIWindow? + + func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { + logInfo(">>>>>>>>>> Fixture app has launched <<<<<<<<<<") + return true + } +} + +@available(iOS 13.0, *) +extension AppDelegate { + + // MARK: UISceneSession Lifecycle + + func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { + // Called when a new scene session is being created. + // Use this method to select a configuration to create the new scene with. + return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) + } + + func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) { + // Called when the user discards a scene session. + // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions. + // Use this method to release any resources that were specific to the discarded scenes, as they will not return. + } +} diff --git a/features/fixtures/benchmarks/Fixture/Assets.xcassets/AccentColor.colorset/Contents.json b/features/fixtures/benchmarks/Fixture/Assets.xcassets/AccentColor.colorset/Contents.json new file mode 100644 index 00000000..eb878970 --- /dev/null +++ b/features/fixtures/benchmarks/Fixture/Assets.xcassets/AccentColor.colorset/Contents.json @@ -0,0 +1,11 @@ +{ + "colors" : [ + { + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/features/fixtures/benchmarks/Fixture/Assets.xcassets/AppIcon.appiconset/Contents.json b/features/fixtures/benchmarks/Fixture/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 00000000..13613e3e --- /dev/null +++ b/features/fixtures/benchmarks/Fixture/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,13 @@ +{ + "images" : [ + { + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/features/fixtures/benchmarks/Fixture/Assets.xcassets/Contents.json b/features/fixtures/benchmarks/Fixture/Assets.xcassets/Contents.json new file mode 100644 index 00000000..73c00596 --- /dev/null +++ b/features/fixtures/benchmarks/Fixture/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/features/fixtures/benchmarks/Fixture/Base.lproj/LaunchScreen.storyboard b/features/fixtures/benchmarks/Fixture/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 00000000..865e9329 --- /dev/null +++ b/features/fixtures/benchmarks/Fixture/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/features/fixtures/benchmarks/Fixture/Base.lproj/Main.storyboard b/features/fixtures/benchmarks/Fixture/Base.lproj/Main.storyboard new file mode 100644 index 00000000..477c04ee --- /dev/null +++ b/features/fixtures/benchmarks/Fixture/Base.lproj/Main.storyboard @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/features/fixtures/benchmarks/Fixture/BenchmarkInstrumentation.swift b/features/fixtures/benchmarks/Fixture/BenchmarkInstrumentation.swift new file mode 100644 index 00000000..5320706a --- /dev/null +++ b/features/fixtures/benchmarks/Fixture/BenchmarkInstrumentation.swift @@ -0,0 +1,91 @@ +// +// BenchmarkInstrumentation.swift +// Fixture +// +// Created by Robert Bartoszewski on 09/09/2025. +// + +import Foundation + + + +class BenchmarkInstrumentation { + + private enum Phase { + case idle, excluded, measured + } + + private let cpuSampler = CPUSampler() + + private var phase: Phase = .idle { + didSet { + currentPhaseStart = clock_gettime_nsec_np(CLOCK_MONOTONIC) + } + } + + private var currentPhaseStart: UInt64 = 0 + private var currentCpuSample: CPUSample? + + private var excludedTime: UInt64 = 0 + private var measuredTime: UInt64 = 0 + private var iterations = 0 + private var cpuUse = 0.0 + + func startExcludedTime() { + guard phase != .excluded else { return } + endCurrentPhase() + phase = .excluded + } + + func endExcludedTime() { + guard phase == .excluded else { return } + excludedTime += clock_gettime_nsec_np(CLOCK_MONOTONIC) - currentPhaseStart + phase = .idle + } + + func startMeasuredTime() { + guard phase != .measured else { return } + endCurrentPhase() + currentCpuSample = cpuSampler.recordSample() + phase = .measured + } + + func endMeasuredTime() { + guard phase == .measured else { return } + measuredTime += clock_gettime_nsec_np(CLOCK_MONOTONIC) - currentPhaseStart + if let cpuSample = currentCpuSample { + cpuUse += cpuSampler.recordSample()?.usage(since: cpuSample) ?? 0 + } + phase = .idle + } + + func recordIteration() { + iterations += 1 + } + + func finalMeasurement() -> BenchmarkMeasurement { + BenchmarkMeasurement( + timeTaken: Int(measuredTime + excludedTime), + excludedTime: Int(excludedTime), + measuredTime: Int(measuredTime), + iterations: iterations, + cpuUse: cpuUse + ) + } + + private func endCurrentPhase() { + switch phase { + case .excluded: + endExcludedTime() + case .measured: + endMeasuredTime() + default: break + } + } + + private func nanoseconds(from fromDate: Date, to toDate: Date) -> Int { + let calendar = Calendar.current + let duration = calendar.dateComponents([.nanosecond], from: fromDate, to: toDate) + return duration.nanosecond ?? 0 + } +} diff --git a/features/fixtures/benchmarks/Fixture/BenchmarkMeasurement.swift b/features/fixtures/benchmarks/Fixture/BenchmarkMeasurement.swift new file mode 100644 index 00000000..eed1115d --- /dev/null +++ b/features/fixtures/benchmarks/Fixture/BenchmarkMeasurement.swift @@ -0,0 +1,14 @@ +// +// BenchmarkMeasurement.swift +// Fixture +// +// Created by Robert Bartoszewski on 09/09/2025. +// + +struct BenchmarkMeasurement { + let timeTaken: Int + let excludedTime: Int + let measuredTime: Int + let iterations: Int + let cpuUse: Double +} diff --git a/features/fixtures/benchmarks/Fixture/BenchmarkRunner.swift b/features/fixtures/benchmarks/Fixture/BenchmarkRunner.swift new file mode 100644 index 00000000..a3ccfee6 --- /dev/null +++ b/features/fixtures/benchmarks/Fixture/BenchmarkRunner.swift @@ -0,0 +1,139 @@ +// +// BenchmarkRunner.swift +// Fixture +// +// Created by Robert Bartoszewski on 09/09/2025. +// + +class BenchmarkRunner { + + private enum Constants { + static let numberOfWarmupIterations = 1000 + static let numberOfMeasurementIterations = 100000 + static let numberOfRuns = 5 + } + + var fixtureConfig: FixtureConfig? + var measurements: [BenchmarkMeasurement] = [] + var suite: Suite! + var totalIterations = 0 + var totalMeasuredTime = 0 + var totalExcludedTime = 0 + var startTime: Date? + var args: [String]? + + func run(suite: Suite, args argsString: String, completion: @escaping () -> ()) { + self.suite = suite + totalIterations = 0 + totalExcludedTime = 0 + measurements = [] + startTime = Date() + args = splitArgs(args: argsString) + + logInfo("Starting suite \"\(String(describing: suite))\" with args \"\(args)\"") + warmUp() + coolDown() + + performMeasuredRuns() + reportMetrics(completion: completion) + } + + func warmUp() { + logInfo("Starting warmUp for suite \"\(String(describing: suite))\"") + suite.startBugsnag(args: args ?? []) + let warmupConfig = SuiteConfig(numberOfIterations: Constants.numberOfWarmupIterations) + suite.configure(warmupConfig) + suite.run() + logInfo("Finished warmUp for suite \"\(String(describing: suite))\"") + } + + func performMeasuredRuns() { + logInfo("Starting measured runs for suite \"\(String(describing: suite))\"") + let measurementConfig = SuiteConfig( + numberOfIterations: Constants.numberOfMeasurementIterations + ) + suite.configure(measurementConfig) + + for _ in 0.. Void) { + logInfo("Reporting metrics for suite \"\(String(describing: suite))\"") + guard let startTime = startTime else { return } + var metrics: [String: String] = [:] + metrics["timestamp"] = formatTimestamp(startTime) + metrics["benchmark"] = String(describing: type(of: suite!)) + .replacingOccurrences(of: "Fixture.", with: "") + args?.forEach { metrics["\($0)"] = "true" } + + metrics["totalTimeTaken"] = "\(totalMeasuredTime + totalExcludedTime)" + metrics["totalExcludedTime"] = "\(totalExcludedTime)" + metrics["totalMeasuredTime"] = "\(totalMeasuredTime)" + metrics["totalIterations"] = "\(totalIterations)" + + measurements.enumerated().forEach { index, measurement in + let runNr = index + 1 + metrics["timeTaken.\(runNr)"] = "\(measurement.timeTaken)" + metrics["excludedTime.\(runNr)"] = "\(measurement.excludedTime)" + metrics["measuredTime.\(runNr)"] = "\(measurement.measuredTime)" + metrics["iterations.\(runNr)"] = "\(measurement.iterations)" + metrics["cpuUse.\(runNr)"] = "\(measurement.cpuUse)" + } + + logInfo("Measurements recorded \(measurements)") + + var request = URLRequest(url: fixtureConfig!.metricsURL) + request.httpMethod = "POST" + request.setValue("application/json", forHTTPHeaderField: "Content-Type") + + let jsonData = try? JSONSerialization.data(withJSONObject: metrics) + guard let jsonData = jsonData else { + return + } + request.httpBody = jsonData + + logInfo("Sending measurements to url \(fixtureConfig!.metricsURL.absoluteString)") + URLSession.shared.dataTask(with: request, completionHandler: { _, _, error in + if let error { + logInfo("Sending measurements failed with error \(error)") + } else { + logInfo("Measurements sent successfully") + } + completion() + }).resume() + } + + func splitArgs(args: String) -> [String] { + return args.split(separator: " ").map(String.init) + } + + func formatTimestamp(_ date: Date) -> String { + var options = ISO8601DateFormatter.Options.withInternetDateTime.union(ISO8601DateFormatter.Options.withFractionalSeconds) + return ISO8601DateFormatter + .string( + from: date, + timeZone: TimeZone(secondsFromGMT: 0)!, + formatOptions: options + ) + } +} diff --git a/features/fixtures/benchmarks/Fixture/CPUSampler.h b/features/fixtures/benchmarks/Fixture/CPUSampler.h new file mode 100644 index 00000000..b57f5372 --- /dev/null +++ b/features/fixtures/benchmarks/Fixture/CPUSampler.h @@ -0,0 +1,23 @@ +// +// CPUSampler.h +// Fixture +// +// Created by Robert Bartoszewski on 09/09/2025. +// + +#import + +@interface CPUSample: NSObject + +@property (nonatomic, readonly) CFAbsoluteTime sampledAt; +@property (nonatomic, readonly) NSTimeInterval cpuTime; + +- (double)usageSince:(CPUSample *)other; + +@end + +@interface CPUSampler: NSObject + +- (CPUSample *)recordSample; + +@end diff --git a/features/fixtures/benchmarks/Fixture/CPUSampler.mm b/features/fixtures/benchmarks/Fixture/CPUSampler.mm new file mode 100644 index 00000000..c1451ee7 --- /dev/null +++ b/features/fixtures/benchmarks/Fixture/CPUSampler.mm @@ -0,0 +1,70 @@ +// +// CPUSampler.mm +// Fixture +// +// Created by Robert Bartoszewski on 09/09/2025. +// + +#import "CPUSampler.h" + +#import +#import +#import + +static inline NSTimeInterval timeValToTimeInterval(time_value_t value) { + return (NSTimeInterval)value.seconds + ((NSTimeInterval)value.microseconds / TIME_MICROS_MAX); +} + +NSTimeInterval taskTime() { + mach_port_t thread = mach_thread_self(); + thread_basic_info_data_t threadBasicInfo; + mach_msg_type_number_t count = THREAD_BASIC_INFO_COUNT; + auto status = thread_info(thread, THREAD_BASIC_INFO, (thread_info_t)&threadBasicInfo, &count); + if (status != KERN_SUCCESS) { + return -1; + } + return timeValToTimeInterval(threadBasicInfo.user_time); +} + +static double calcCPUUsagePct(CFAbsoluteTime earlierSampledAtTime, + NSTimeInterval earlierTimeValue, + CFAbsoluteTime currentSampledAtTime, + NSTimeInterval currentTimeValue) { + auto diffClockSec = currentSampledAtTime - earlierSampledAtTime; + if (diffClockSec <= 0) { + return 0; + } + auto diffCPUTimeSec = currentTimeValue - earlierTimeValue; + auto result = diffCPUTimeSec / diffClockSec * 100; + return result; +} + + +@interface CPUSample () +@property (nonatomic) CFAbsoluteTime sampledAt; +@property (nonatomic) NSTimeInterval cpuTime; +@end + +@implementation CPUSample + +- (double)usageSince:(CPUSample *)other { + return calcCPUUsagePct(other.sampledAt, other.cpuTime, self.sampledAt, self.cpuTime); +} + +@end + +@implementation CPUSampler + +- (CPUSample *)recordSample { + CFAbsoluteTime sampledAt = CFAbsoluteTimeGetCurrent(); + NSTimeInterval cpuTime = taskTime(); + if (cpuTime < 0) { + return nil; + } + CPUSample *sample = [CPUSample new]; + sample.sampledAt = sampledAt; + sample.cpuTime = cpuTime; + return sample; +} + +@end diff --git a/features/fixtures/benchmarks/Fixture/CommandReaderThread.swift b/features/fixtures/benchmarks/Fixture/CommandReaderThread.swift new file mode 100644 index 00000000..173db934 --- /dev/null +++ b/features/fixtures/benchmarks/Fixture/CommandReaderThread.swift @@ -0,0 +1,126 @@ +// +// CommandReaderThread.swift +// iOSTestApp +// +// Created by Karl Stenerud on 16.11.23. +// Copyright © 2023 Bugsnag. All rights reserved. +// + +import UIKit +import os + +class CommandReaderThread: Thread { + var fixtureConfig: FixtureConfig + var commandReceiver: CommandReceiver + var lastCommandID: String = "" + + init(fixtureConfig: FixtureConfig, commandReceiver: CommandReceiver) { + self.fixtureConfig = fixtureConfig + self.commandReceiver = commandReceiver + } + + override func main() { + while true { + if self.commandReceiver.canReceiveCommand() { + receiveNextCommand() + } else { + logDebug("A command is already in progress, waiting 1 second more...") + } + Thread.sleep(forTimeInterval: 1) + } + } + + func newStartedFetchTask() -> CommandFetchTask { + let fetchTask = CommandFetchTask(url: fixtureConfig.commandURL, afterCommandID: lastCommandID) + fetchTask.start() + return fetchTask + } + + func receiveNextCommand() { + let maxWaitTime = 5.0 + let pollingInterval = 1.0 + + var fetchTask = newStartedFetchTask() + let startTime = Date() + + while true { + Thread.sleep(forTimeInterval: pollingInterval) + switch fetchTask.state { + case CommandFetchState.success: + logDebug("Command fetch: Request succeeded") + let command = fetchTask.command! + if (command.uuid != "") { + lastCommandID = command.uuid + } + commandReceiver.receiveCommand(command: command) + return + case CommandFetchState.fetching: + let duration = Date() - startTime + if duration < maxWaitTime { + logDebug("Command fetch: Server hasn't responded in \(duration)s (max \(maxWaitTime)). Waiting \(pollingInterval)s more...") + } else { + fetchTask.cancel() + logInfo("Command fetch: Server hasn't responded in \(duration)s (max \(maxWaitTime)). Trying again...") + fetchTask = newStartedFetchTask() + } + break + case CommandFetchState.failed: + logInfo("Command fetch: Request failed. Trying again...") + fetchTask = newStartedFetchTask() + break + } + } + } +} + +extension Date { + static func - (lhs: Date, rhs: Date) -> TimeInterval { + return lhs.timeIntervalSinceReferenceDate - rhs.timeIntervalSinceReferenceDate + } +} + +enum CommandFetchState { + case failed, fetching, success +} + +class CommandFetchTask { + var url: URL + var state = CommandFetchState.failed + var command: MazeRunnerCommand? + var task: URLSessionTask? + + init(url: URL, afterCommandID: String) { + self.url = URL(string: "\(url.absoluteString)?after=\(afterCommandID)")! + } + + func cancel() { + task?.cancel() + } + + func start() { + logInfo("Fetching next command from \(url)") + state = CommandFetchState.fetching + let request = URLRequest(url: url) + task = URLSession.shared.dataTask(with: request) { data, response, error in + if let data = data { + do { + let asString = String(data: data, encoding: .utf8)! + logInfo("Received command \(asString)") + let decoded = try JSONSerialization.jsonObject(with: data, options: []) as! [String: Any] + let command = MazeRunnerCommand.init(fromJSONDict: decoded) + logInfo("Command fetched and decoded") + self.command = command; + self.state = CommandFetchState.success + } catch { + self.state = CommandFetchState.failed + let dataAsString = String(data: data, encoding: .utf8) + logError("Failed to fetch command: Invalid Response from \(String(describing: self.url)): [\(String(describing: dataAsString))]: Error is: \(error)") + } + } else if let error = error { + self.state = CommandFetchState.failed + logError("Failed to fetch command: HTTP Request to \(String(describing: self.url)) failed: \(error)") + } + } + task?.resume() + } +} diff --git a/features/fixtures/benchmarks/Fixture/Fixture-Bridging-Header.h b/features/fixtures/benchmarks/Fixture/Fixture-Bridging-Header.h new file mode 100644 index 00000000..b64ce354 --- /dev/null +++ b/features/fixtures/benchmarks/Fixture/Fixture-Bridging-Header.h @@ -0,0 +1,9 @@ +// +// Use this file to import your target's public headers that you would like to expose to Swift. +// + +#import +#import +#import "BugsnagPerformanceConfiguration+Private.h" +#import "Logging.h" +#import "CPUSampler.h" diff --git a/features/fixtures/benchmarks/Fixture/Fixture.swift b/features/fixtures/benchmarks/Fixture/Fixture.swift new file mode 100644 index 00000000..348cd85c --- /dev/null +++ b/features/fixtures/benchmarks/Fixture/Fixture.swift @@ -0,0 +1,169 @@ +// +// Fixture.swift +// Fixture +// +// Created by Nick Dowell on 26/09/2022. +// + +import Foundation + +protocol CommandReceiver { + func canReceiveCommand() -> Bool + func receiveCommand(command: MazeRunnerCommand) +} + +func copyDirectory(src: URL, dst: URL) { + logInfo("Attempting to copy \"\(src)\" to \"\(dst)\"") + let fm = FileManager.default + if fm.fileExists(atPath: src.path) { + do { + try fm.copyItem(at: src, to: dst) + } catch { + logError("Failed to copy \"\(src)\" to \"\(dst)\": \(error)") + } + } +} + +func copyCachesToDocumentsFolder() { + let fm = FileManager.default + let identifier = Bundle.main.bundleIdentifier ?? ProcessInfo.processInfo.processName + let cachesUrl = fm.urls(for: .cachesDirectory, in: .userDomainMask)[0] + let documentsUrl = fm.urls(for: .documentDirectory, in: .userDomainMask)[0] + let rootPathComponent = "bugsnag-performance-\(identifier)" + let sharedPathComponent = "bugsnag-shared-\(identifier)" + let dstCache = documentsUrl.appendingPathComponent("BugsnagCachesCopy") + if fm.fileExists(atPath: dstCache.path) { + do { + try fm.removeItem(at: dstCache) + } catch { + logError("Failed to remove \"\(dstCache)\": \(error)") + } + } + do { + try fm.createDirectory(atPath: dstCache.path, withIntermediateDirectories: true) + } catch { + logError("Failed to create \"\(dstCache)\": \(error)") + } + + copyDirectory(src: cachesUrl.appendingPathComponent(rootPathComponent), dst: dstCache.appendingPathComponent(rootPathComponent)) + copyDirectory(src: cachesUrl.appendingPathComponent(sharedPathComponent), dst: dstCache.appendingPathComponent(sharedPathComponent)) +} + +class Fixture: NSObject, CommandReceiver { + static let defaultMazeRunnerURL = URL(string: "http://bs-local.com:9339")! + + var readyToReceiveCommand = false + var commandReaderThread: CommandReaderThread? + var fixtureConfig: FixtureConfig = FixtureConfig(mazeRunnerBaseAddress: defaultMazeRunnerURL) + let benchmarkRunner = BenchmarkRunner() + var suite: Suite? = nil + var args = "" + + override init() { + super.init() + copyCachesToDocumentsFolder() + } + + func start() { + DispatchQueue.global(qos: .userInitiated).async { + self.loadMazeRunnerAddress { address in + self.fixtureConfig = FixtureConfig(mazeRunnerBaseAddress: address) + self.benchmarkRunner.fixtureConfig = self.fixtureConfig + self.beginReceivingCommands(fixtureConfig: self.fixtureConfig) + } + } + } + + func beginReceivingCommands(fixtureConfig: FixtureConfig) { + readyToReceiveCommand = true + commandReaderThread = CommandReaderThread(fixtureConfig: fixtureConfig, commandReceiver: self) + commandReaderThread!.start() + } + + func canReceiveCommand() -> Bool { + return readyToReceiveCommand + } + + func receiveCommand(command: MazeRunnerCommand) { + readyToReceiveCommand = false + DispatchQueue.main.async { + logInfo("Executing command [\(command.action)] with args \(command.args)") + switch command.action { + case "run_suite": + self.loadSuite(suiteName: command.args["suite"] as! String) + self.args = command.args["arguments"] as? String ?? "" + self.runLoadedSuite { + self.terminateApp() + } + self.readyToReceiveCommand = true + break + case "noop": + self.readyToReceiveCommand = true + break + default: + assertionFailure("\(command.action): Unknown command") + self.readyToReceiveCommand = true + } + } + } + + private func loadSuite(suiteName: String) { + logInfo("========== Loading suite \(suiteName) ==========") + let suiteClass: AnyClass = NSClassFromString("Fixture.\(suiteName)")! + logInfo("Loaded suite class: \(suiteClass)") + suite = (suiteClass as! Suite.Type).init(fixtureConfig: fixtureConfig) as Suite? + } + + private func runLoadedSuite(completion: @escaping () -> ()) { + logInfo("Starting suite \(String(describing: suite))") + benchmarkRunner.run(suite: suite!, args: args, completion: completion) + } + + private func terminateApp() { + exit(0) + } + + func loadMazeRunnerAddress(completion: (URL)->()) { + let defaultUrl = Fixture.defaultMazeRunnerURL + + // Only iOS 12 and above will run on BitBar for now + if #available(iOS 12.0, *) {} else { + completion(defaultUrl) + return + } + + for n in 1...60 { + let documentsUrl = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0] + + logInfo("Reading Maze Runner address from fixture_config.json") + do { + let fileUrl = URL(fileURLWithPath: "fixture_config", + relativeTo: documentsUrl).appendingPathExtension("json") + let savedData = try Data(contentsOf: fileUrl) + if let contents = String(data: savedData, encoding: .utf8) { + logInfo(String(format: "Found fixture_config.json after %d seconds", n)) + let decoder = JSONDecoder() + let jsonData = contents.data(using: .utf8) + let config = try decoder.decode(FixtureConfigJSON.self, from: jsonData!) + let address = "http://" + config.maze_address + logInfo("Using Maze Runner address: \(address)") + completion(URL(string: address)!) + return + } + } + catch let error as NSError { + logWarn("Failed to read fixture_config.json: \(error)") + } + logInfo("Waiting for fixture_config.json to appear") + sleep(1) + } + + logError("Unable to read from fixture_config.json, defaulting to BrowserStack environment") + completion(defaultUrl) + return + } + + private struct FixtureConfigJSON: Decodable { + var maze_address: String + } +} diff --git a/features/fixtures/benchmarks/Fixture/FixtureConfig.swift b/features/fixtures/benchmarks/Fixture/FixtureConfig.swift new file mode 100644 index 00000000..f35b550f --- /dev/null +++ b/features/fixtures/benchmarks/Fixture/FixtureConfig.swift @@ -0,0 +1,51 @@ +// +// FixtureConfig.swift +// Fixture +// +// Created by Karl Stenerud on 20.11.23. +// + +import Foundation + +class FixtureConfig { + // Base MazeRunner URL + let mazeRunnerURL: URL + + // Admin URLs we normally don't want to capture + let docsURL: URL + let tracesURL: URL + let commandURL: URL + let metricsURL: URL + let notifyURL: URL + let sessionsURL: URL + + // URLs explicitly used in tests + let reflectURL: URL + + // Convenience URL sets + let adminMazeRunnerURLs: [URL] + let allMazeRunnerURLs: [URL] + + init(mazeRunnerBaseAddress: URL) { + mazeRunnerURL = mazeRunnerBaseAddress + + docsURL = mazeRunnerBaseAddress.appendingPathComponent("docs") + tracesURL = mazeRunnerBaseAddress.appendingPathComponent("traces") + commandURL = mazeRunnerBaseAddress.appendingPathComponent("command") + metricsURL = mazeRunnerBaseAddress.appendingPathComponent("metrics") + notifyURL = mazeRunnerBaseAddress.appendingPathComponent("notify") + sessionsURL = mazeRunnerBaseAddress.appendingPathComponent("sessions") + + reflectURL = mazeRunnerBaseAddress.appendingPathComponent("reflect") + + adminMazeRunnerURLs = [ + docsURL, + tracesURL, + commandURL, + metricsURL, + notifyURL, + sessionsURL, + ] + allMazeRunnerURLs = [mazeRunnerURL] + } +} diff --git a/features/fixtures/benchmarks/Fixture/Info.plist b/features/fixtures/benchmarks/Fixture/Info.plist new file mode 100644 index 00000000..3705bbde --- /dev/null +++ b/features/fixtures/benchmarks/Fixture/Info.plist @@ -0,0 +1,57 @@ + + + + + NSAppTransportSecurity + + NSAllowsArbitraryLoads + + + UIApplicationSceneManifest + + UIApplicationSupportsMultipleScenes + + UISceneConfigurations + + UIWindowSceneSessionRoleApplication + + + UISceneConfigurationName + Default Configuration + UISceneDelegateClassName + $(PRODUCT_MODULE_NAME).SceneDelegate + UISceneStoryboardFile + Main + + + + + bugsnag + + performance + + bundleVersion + 30 + autoInstrumentNetworkRequests + + autoInstrumentViewControllers + + autoInstrumentAppStarts + + + enabledReleaseStages + + release + staging + + releaseStage + benchmarks + bundleVersion + 40 + appVersion + 10.0 + apiKey + 0123456789abcdef0123456789abcdef + + + diff --git a/features/fixtures/benchmarks/Fixture/MazeRunnerCommand.swift b/features/fixtures/benchmarks/Fixture/MazeRunnerCommand.swift new file mode 100644 index 00000000..6fa8093a --- /dev/null +++ b/features/fixtures/benchmarks/Fixture/MazeRunnerCommand.swift @@ -0,0 +1,29 @@ +// +// MazeRunnerCommand.swift +// Fixture +// +// Created by Karl Stenerud on 16.11.23. +// + +import Foundation + +class MazeRunnerCommand { + let message: String + let action: String + let uuid: String + let args: Dictionary + + init(uuid: String, action: String, args: Dictionary, message: String) { + self.uuid = uuid + self.message = message + self.action = action + self.args = args + } + + init(fromJSONDict: Dictionary) { + self.uuid = fromJSONDict["uuid"] as? String ?? "" + self.action = fromJSONDict["action"] as! String + self.args = fromJSONDict["args"] as? Dictionary ?? [:] + self.message = fromJSONDict["message"] as? String ?? "" + } +} diff --git a/features/fixtures/benchmarks/Fixture/SceneDelegate.swift b/features/fixtures/benchmarks/Fixture/SceneDelegate.swift new file mode 100644 index 00000000..c89da8d3 --- /dev/null +++ b/features/fixtures/benchmarks/Fixture/SceneDelegate.swift @@ -0,0 +1,53 @@ +// +// SceneDelegate.swift +// Fixture +// +// Created by Nick Dowell on 26/09/2022. +// + +import UIKit + +@available(iOS 13.0, *) +class SceneDelegate: UIResponder, UIWindowSceneDelegate { + + var window: UIWindow? + + + func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { + // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`. + // If using a storyboard, the `window` property will automatically be initialized and attached to the scene. + // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead). + guard let _ = (scene as? UIWindowScene) else { return } + } + + func sceneDidDisconnect(_ scene: UIScene) { + // Called as the scene is being released by the system. + // This occurs shortly after the scene enters the background, or when its session is discarded. + // Release any resources associated with this scene that can be re-created the next time the scene connects. + // The scene may re-connect later, as its session was not necessarily discarded (see `application:didDiscardSceneSessions` instead). + } + + func sceneDidBecomeActive(_ scene: UIScene) { + // Called when the scene has moved from an inactive state to an active state. + // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive. + } + + func sceneWillResignActive(_ scene: UIScene) { + // Called when the scene will move from an active state to an inactive state. + // This may occur due to temporary interruptions (ex. an incoming phone call). + } + + func sceneWillEnterForeground(_ scene: UIScene) { + // Called as the scene transitions from the background to the foreground. + // Use this method to undo the changes made on entering the background. + } + + func sceneDidEnterBackground(_ scene: UIScene) { + // Called as the scene transitions from the foreground to the background. + // Use this method to save data, release shared resources, and store enough scene-specific state information + // to restore the scene back to its current state. + } + + +} + diff --git a/features/fixtures/benchmarks/Fixture/ViewController.swift b/features/fixtures/benchmarks/Fixture/ViewController.swift new file mode 100644 index 00000000..333bff8e --- /dev/null +++ b/features/fixtures/benchmarks/Fixture/ViewController.swift @@ -0,0 +1,17 @@ +// +// ViewController.swift +// Fixture +// +// Created by Nick Dowell on 26/09/2022. +// + +import UIKit + +class ViewController: UIViewController { + var fixture: Fixture = Fixture() + + required init?(coder: NSCoder) { + super.init(coder: coder) + fixture.start() + } +} diff --git a/features/fixtures/benchmarks/Suite/SpanOpenCloseSuite.swift b/features/fixtures/benchmarks/Suite/SpanOpenCloseSuite.swift new file mode 100644 index 00000000..ebecaf58 --- /dev/null +++ b/features/fixtures/benchmarks/Suite/SpanOpenCloseSuite.swift @@ -0,0 +1,20 @@ +// +// SpanOpenCloseSuite.swift +// Fixture +// +// Created by Robert Bartoszewski on 09/09/2025. +// + +class SpanOpenCloseSuite: Suite { + override func run() { + let options = BugsnagPerformanceSpanOptions() + .setMakeCurrentContext(false) + .setFirstClass(.no) + + measureRepeated { i in + BugsnagPerformance + .startSpan(name: "SpanOpenCloseSuite_\(i)", options: options) + .end() + } + } +} diff --git a/features/fixtures/benchmarks/Suite/Suite.swift b/features/fixtures/benchmarks/Suite/Suite.swift new file mode 100644 index 00000000..3b7f86c2 --- /dev/null +++ b/features/fixtures/benchmarks/Suite/Suite.swift @@ -0,0 +1,125 @@ +// +// Suite.swift +// Fixture +// +// Created by Robert Bartoszewski on 09/09/2025. +// + +import BugsnagPerformance +import BugsnagPerformanceNamedSpans +import Foundation + +class Suite: NSObject { + let fixtureConfig: FixtureConfig + var suiteConfig: SuiteConfig? + var instrumentation: BenchmarkInstrumentation? + + private override init() { + fatalError("do not use the default init of Scenario") + } + + required init(fixtureConfig: FixtureConfig) { + self.fixtureConfig = fixtureConfig + } + + func startBugsnag(args: [String]) { + let bugsnagPerfConfig = BugsnagPerformanceConfiguration.loadConfig() + bugsnagPerfConfig.internal.clearPersistenceOnStart = true + bugsnagPerfConfig.internal.autoTriggerExportOnBatchSize = 50 + bugsnagPerfConfig.apiKey = "12312312312312312312312312312312" + bugsnagPerfConfig.autoInstrumentAppStarts = false + bugsnagPerfConfig.autoInstrumentNetworkRequests = false + bugsnagPerfConfig.autoInstrumentViewControllers = false + if (args.contains(where: { $0 == "rendering" })) { + bugsnagPerfConfig.enabledMetrics.rendering = true + } + if (args.contains(where: { $0 == "cpu" })) { + bugsnagPerfConfig.enabledMetrics.cpu = true + } + if (args.contains(where: { $0 == "memory" })) { + bugsnagPerfConfig.enabledMetrics.memory = true + } + if (args.contains(where: { $0 == "NamedSpan" })) { + bugsnagPerfConfig.add(BugsnagPerformanceNamedSpansPlugin()) + } + bugsnagPerfConfig.endpoint = fixtureConfig.tracesURL + bugsnagPerfConfig.networkRequestCallback = filterAdminMazeRunnerNetRequests + bugsnagPerfConfig.releaseStage = "benchmarks" + bugsnagPerfConfig.enabledReleaseStages = ["release"] + BugsnagPerformance.start(configuration: bugsnagPerfConfig) + } + + func configure(_ config: SuiteConfig) { + suiteConfig = config + } + + func instrument(_ instrumentation: BenchmarkInstrumentation) { + self.instrumentation = instrumentation + } + + func urlHasAnyPrefixIn(url: URL, prefixes: [URL]) -> Bool { + for prefix in prefixes { + if url.absoluteString.hasPrefix(prefix.absoluteString) { + return true + } + } + return false + } + + func filterNetRequestsContainingPrefixes(info: BugsnagPerformanceNetworkRequestInfo, + prefixes: [URL]) -> BugsnagPerformanceNetworkRequestInfo { + if info.url == nil { + return info + } + + if urlHasAnyPrefixIn(url: info.url!, prefixes: prefixes) { + info.url = nil + } + return info + } + + func filterAllMazeRunnerNetRequests(info: BugsnagPerformanceNetworkRequestInfo) -> BugsnagPerformanceNetworkRequestInfo { + return filterNetRequestsContainingPrefixes(info: info, prefixes: fixtureConfig.allMazeRunnerURLs) + } + + func filterAdminMazeRunnerNetRequests(info: BugsnagPerformanceNetworkRequestInfo) -> BugsnagPerformanceNetworkRequestInfo { + // Everything except reflectURL + return filterNetRequestsContainingPrefixes(info: info, prefixes: fixtureConfig.adminMazeRunnerURLs) + } + + func clearPersistentData() { + logDebug("Sute.clearPersistentData()") + UserDefaults.standard.removePersistentDomain( + forName: Bundle.main.bundleIdentifier!) + let cachesUrl = FileManager.default.urls(for: .cachesDirectory, in: .allDomainsMask).first! + for file in try! FileManager.default.contentsOfDirectory(at: cachesUrl, includingPropertiesForKeys: nil) { + try! FileManager.default.removeItem(at: file) + } + } + + func run() { + logError("Suite.run() has not been overridden!") + fatalError("To be implemented by subclass") + } + + func measureRepeated(_ body: (Int) -> Void) { + instrumentation?.startMeasuredTime() + for i in 0.. + +void logDebugObjC(NSString *format, ...); +void logInfoObjC(NSString *format, ...); +void logWarnObjC(NSString *format, ...); +void logErrorObjC(NSString *format, ...); + +//#define logInfo(FMT, ...) logInfoObjC(FMT, __VA_ARGS__) +//#define logWarn(FMT, ...) logWarnObjC(FMT, __VA_ARGS__) +//#define logError(FMT, ...) logErrorObjC(FMT, __VA_ARGS__) + +#define logDebug logDebugObjC +#define logInfo logInfoObjC +#define logWarn logWarnObjC +#define logError logErrorObjC + +void logInternal(const char* level, NSString *format, va_list args); diff --git a/features/fixtures/benchmarks/utils/Logging.m b/features/fixtures/benchmarks/utils/Logging.m new file mode 100644 index 00000000..742201d7 --- /dev/null +++ b/features/fixtures/benchmarks/utils/Logging.m @@ -0,0 +1,51 @@ +// +// Logging.m +// iOSTestApp +// +// Created by Karl Stenerud on 02.11.23. +// Copyright © 2023 Bugsnag. All rights reserved. +// + +#import "Logging.h" + +extern void bsg_i_kslog_logCBasic(const char *fmt, ...) __printflike(1, 2); + +void logInternal(const char* level, NSString *format, va_list args) { + NSString *formatted = [[NSString alloc] initWithFormat:format arguments:args]; + NSString *fullMessage = [NSString stringWithFormat:@"bugsnagci %s: %@", level, formatted]; + + NSLog(@"%@", fullMessage); + bsg_i_kslog_logCBasic("%s", + [[NSString stringWithFormat:@"%@ %@", + [NSDate date], fullMessage] + cStringUsingEncoding:NSUTF8StringEncoding]); + +} + +void logDebugObjC(NSString *format, ...) { + va_list args; + va_start(args, format); + logInternal("debug", format, args); + va_end(args); +} + +void logInfoObjC(NSString *format, ...) { + va_list args; + va_start(args, format); + logInternal("info", format, args); + va_end(args); +} + +void logWarnObjC(NSString *format, ...) { + va_list args; + va_start(args, format); + logInternal("warn", format, args); + va_end(args); +} + +void logErrorObjC(NSString *format, ...) { + va_list args; + va_start(args, format); + logInternal("error", format, args); + va_end(args); +} diff --git a/features/fixtures/benchmarks/utils/Logging.swift b/features/fixtures/benchmarks/utils/Logging.swift new file mode 100644 index 00000000..92538e87 --- /dev/null +++ b/features/fixtures/benchmarks/utils/Logging.swift @@ -0,0 +1,45 @@ +// +// logging.swift +// Fixture +// +// Created by Karl Stenerud on 02.11.23. +// Copyright © 2023 Bugsnag. All rights reserved. +// + +import Foundation + +//public func logDebug(_ format: String) { +// logDebug(format: format, args: 0 as Int) +//} +public func logDebug(_ format: String, _ args: CVarArg...) { + withVaList(args) { + logInternal("debug", format, $0) + } +} + +//public func logInfo(_ format: String) { +// logInfo(format: format, args: 0 as Int) +//} +public func logInfo(_ format: String, _ args: CVarArg...) { + withVaList(args) { + logInternal("info", format, $0) + } +} + +//public func logWarn(_ format: String) { +// logWarn(format: format, args: 0 as Int) +//} +public func logWarn(_ format: String, _ args: CVarArg...) { + withVaList(args) { + logInternal("warn", format, $0) + } +} + +//public func logError(_ format: String) { +// logError(format: format, args: 0 as Int) +//} +public func logError(_ format: String, _ args: CVarArg...) { + withVaList(args) { + logInternal("error", format, $0) + } +} diff --git a/features/steps/app_steps.rb b/features/steps/app_steps.rb index 23aef062..29ef0aaf 100644 --- a/features/steps/app_steps.rb +++ b/features/steps/app_steps.rb @@ -34,6 +34,10 @@ def skip_between(os, version_lo, version_hi) run_command("run_scenario", { scenario: scenario_name }) end +When('I run {string} configured as {string}') do |suite_name, args| + run_command("run_suite", { suite: suite_name, arguments: args}) +end + When('I load scenario {string}') do |scenario_name| run_command("load_scenario", { scenario: scenario_name }) end From 022316902e0bdc2f55e33a8c2ada42eac233f373 Mon Sep 17 00:00:00 2001 From: Robert Bartoszewski <126675445+robert-smartbear@users.noreply.github.com> Date: Wed, 24 Sep 2025 18:22:05 +0200 Subject: [PATCH 41/53] BugsnagPerformanceNamedSpans optimization (#481) * BugsnagPerformanceNamedSpans optimization * Fixed span name comparison method * Switched to NSMutableDictionary * Removed unused code --------- Co-authored-by: Robert Bartoszewski --- BugsnagPerformance.xcodeproj/project.pbxproj | 20 +++- .../Private/BSGNamedSpanState.h | 19 ++++ .../Private/BSGNamedSpanState.mm | 12 ++ ...gsnagPerformanceNamedSpansPlugin+Private.h | 7 +- .../Private/NamedSpansStore.h | 46 ++++++++ .../Private/NamedSpansStore.mm | 105 ++++++++++++++++++ .../BugsnagPerformanceNamedSpansPlugin.mm | 77 +++---------- .../BugsnagPerformanceNamedSpansTests.mm | 70 ++++-------- 8 files changed, 240 insertions(+), 116 deletions(-) create mode 100644 Sources/BugsnagPerformanceNamedSpans/Private/BSGNamedSpanState.h create mode 100644 Sources/BugsnagPerformanceNamedSpans/Private/BSGNamedSpanState.mm create mode 100644 Sources/BugsnagPerformanceNamedSpans/Private/NamedSpansStore.h create mode 100644 Sources/BugsnagPerformanceNamedSpans/Private/NamedSpansStore.mm diff --git a/BugsnagPerformance.xcodeproj/project.pbxproj b/BugsnagPerformance.xcodeproj/project.pbxproj index 63cf170d..15abad6a 100644 --- a/BugsnagPerformance.xcodeproj/project.pbxproj +++ b/BugsnagPerformance.xcodeproj/project.pbxproj @@ -126,6 +126,10 @@ 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 */; }; + 96A143C22E80D3CB009138C3 /* NamedSpansStore.h in Headers */ = {isa = PBXBuildFile; fileRef = 96A143C12E80D3BA009138C3 /* NamedSpansStore.h */; }; + 96A143C42E80D3DD009138C3 /* NamedSpansStore.mm in Sources */ = {isa = PBXBuildFile; fileRef = 96A143C32E80D3D8009138C3 /* NamedSpansStore.mm */; }; + 96A143C62E80DDEB009138C3 /* BSGNamedSpanState.h in Headers */ = {isa = PBXBuildFile; fileRef = 96A143C52E80DDE6009138C3 /* BSGNamedSpanState.h */; }; + 96A143CA2E81BEB5009138C3 /* BSGNamedSpanState.mm in Sources */ = {isa = PBXBuildFile; fileRef = 96A143C92E81BEB0009138C3 /* BSGNamedSpanState.mm */; }; 96D415F329E6ADC500AEE435 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96D415F229E6ADC500AEE435 /* AppDelegate.swift */; }; 96D415F729E6ADC500AEE435 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96D415F629E6ADC500AEE435 /* ViewController.swift */; }; 96D415FA29E6ADC500AEE435 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 96D415F829E6ADC500AEE435 /* Main.storyboard */; }; @@ -458,6 +462,10 @@ 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 = ""; }; + 96A143C12E80D3BA009138C3 /* NamedSpansStore.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = NamedSpansStore.h; sourceTree = ""; }; + 96A143C32E80D3D8009138C3 /* NamedSpansStore.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = NamedSpansStore.mm; sourceTree = ""; }; + 96A143C52E80DDE6009138C3 /* BSGNamedSpanState.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = BSGNamedSpanState.h; sourceTree = ""; }; + 96A143C92E81BEB0009138C3 /* BSGNamedSpanState.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = BSGNamedSpanState.mm; sourceTree = ""; }; 96D415F029E6ADC500AEE435 /* BugsnagPerformanceTestsApp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = BugsnagPerformanceTestsApp.app; sourceTree = BUILT_PRODUCTS_DIR; }; 96D415F229E6ADC500AEE435 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 96D415F629E6ADC500AEE435 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; @@ -1044,8 +1052,8 @@ DA8E5ECF2E2FC36B0049F7AB /* Public */ = { isa = PBXGroup; children = ( - DA87DAEE2E2FDFE60040F086 /* BugsnagPerformanceNamedSpansPlugin.mm */, DA87DAFA2E3792B10040F086 /* BugsnagPerformanceNamedSpanQuery.m */, + DA87DAEE2E2FDFE60040F086 /* BugsnagPerformanceNamedSpansPlugin.mm */, ); path = Public; sourceTree = ""; @@ -1053,9 +1061,9 @@ DA8E5EF92E2FC9AD0049F7AB /* BugsnagPerformanceNamedSpans */ = { isa = PBXGroup; children = ( + DA87DAF72E3791CE0040F086 /* BugsnagPerformanceNamedSpanQuery.h */, 963828C52E3CFE1800404F3A /* BugsnagPerformanceNamedSpans.h */, DA8E5EFD2E2FCA020049F7AB /* BugsnagPerformanceNamedSpansPlugin.h */, - DA87DAF72E3791CE0040F086 /* BugsnagPerformanceNamedSpanQuery.h */, ); path = BugsnagPerformanceNamedSpans; sourceTree = ""; @@ -1071,7 +1079,11 @@ DA9BB1C92E40CBE4009A7D25 /* Private */ = { isa = PBXGroup; children = ( + 96A143C52E80DDE6009138C3 /* BSGNamedSpanState.h */, + 96A143C92E81BEB0009138C3 /* BSGNamedSpanState.mm */, DA9BB1CA2E40CC19009A7D25 /* BugsnagPerformanceNamedSpansPlugin+Private.h */, + 96A143C12E80D3BA009138C3 /* NamedSpansStore.h */, + 96A143C32E80D3D8009138C3 /* NamedSpansStore.mm */, ); path = Private; sourceTree = ""; @@ -1187,6 +1199,8 @@ 96F417152E3B8E7000EABD8E /* BugsnagPerformanceNamedSpans.h in Headers */, DA9BB1CB2E40CC19009A7D25 /* BugsnagPerformanceNamedSpansPlugin+Private.h in Headers */, DA87DAF92E3791CE0040F086 /* BugsnagPerformanceNamedSpanQuery.h in Headers */, + 96A143C62E80DDEB009138C3 /* BSGNamedSpanState.h in Headers */, + 96A143C22E80D3CB009138C3 /* NamedSpansStore.h in Headers */, DA8E5EFF2E2FCA020049F7AB /* BugsnagPerformanceNamedSpansPlugin.h in Headers */, 963828C62E3CFE2400404F3A /* BugsnagPerformanceNamedSpans.h in Headers */, ); @@ -1680,6 +1694,8 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 96A143CA2E81BEB5009138C3 /* BSGNamedSpanState.mm in Sources */, + 96A143C42E80D3DD009138C3 /* NamedSpansStore.mm in Sources */, 96F417162E3B8E7000EABD8E /* BugsnagPerformanceNamedSpans.docc in Sources */, DA87DAF02E2FDFE60040F086 /* BugsnagPerformanceNamedSpansPlugin.mm in Sources */, DA87DAFB2E3792B10040F086 /* BugsnagPerformanceNamedSpanQuery.m in Sources */, diff --git a/Sources/BugsnagPerformanceNamedSpans/Private/BSGNamedSpanState.h b/Sources/BugsnagPerformanceNamedSpans/Private/BSGNamedSpanState.h new file mode 100644 index 00000000..d39f1263 --- /dev/null +++ b/Sources/BugsnagPerformanceNamedSpans/Private/BSGNamedSpanState.h @@ -0,0 +1,19 @@ +// +// BSGNamedSpanState.h +// BugsnagPerformance +// +// Created by Robert Bartoszewski on 22/09/2025. +// Copyright © 2025 Bugsnag. All rights reserved. +// + +#import + +@interface BSGNamedSpanState : NSObject + +@property (nonatomic, strong) BugsnagPerformanceSpan *span; +@property (nonatomic) CFAbsoluteTime expireTime; + +@property (nonatomic, strong) BSGNamedSpanState *next; +@property (nonatomic, strong) BSGNamedSpanState *previous; + +@end diff --git a/Sources/BugsnagPerformanceNamedSpans/Private/BSGNamedSpanState.mm b/Sources/BugsnagPerformanceNamedSpans/Private/BSGNamedSpanState.mm new file mode 100644 index 00000000..55c4aa63 --- /dev/null +++ b/Sources/BugsnagPerformanceNamedSpans/Private/BSGNamedSpanState.mm @@ -0,0 +1,12 @@ +// +// BSGNamedSpanState.mm +// BugsnagPerformance +// +// Created by Robert Bartoszewski on 22/09/2025. +// Copyright © 2025 Bugsnag. All rights reserved. +// + +#import "BSGNamedSpanState.h" + +@implementation BSGNamedSpanState +@end diff --git a/Sources/BugsnagPerformanceNamedSpans/Private/BugsnagPerformanceNamedSpansPlugin+Private.h b/Sources/BugsnagPerformanceNamedSpans/Private/BugsnagPerformanceNamedSpansPlugin+Private.h index f5fcd41f..7d3c07bd 100644 --- a/Sources/BugsnagPerformanceNamedSpans/Private/BugsnagPerformanceNamedSpansPlugin+Private.h +++ b/Sources/BugsnagPerformanceNamedSpans/Private/BugsnagPerformanceNamedSpansPlugin+Private.h @@ -15,11 +15,8 @@ NS_ASSUME_NONNULL_BEGIN /** * Creates a new plugin instance with a custom timeout interval (for testing purposes). */ -- (instancetype)initWithTimeoutInterval:(NSTimeInterval)timeoutInterval NS_DESIGNATED_INITIALIZER; - -@property (nonatomic, assign, readonly) NSTimeInterval timeoutInterval; - -@property (nonatomic, assign, readonly) std::shared_ptr> spanTimeoutTimers; +- (instancetype)initWithTimeoutInterval:(NSTimeInterval)timeoutInterval + sweepInterval:(NSTimeInterval)sweepInterval NS_DESIGNATED_INITIALIZER; @end diff --git a/Sources/BugsnagPerformanceNamedSpans/Private/NamedSpansStore.h b/Sources/BugsnagPerformanceNamedSpans/Private/NamedSpansStore.h new file mode 100644 index 00000000..440c53ee --- /dev/null +++ b/Sources/BugsnagPerformanceNamedSpans/Private/NamedSpansStore.h @@ -0,0 +1,46 @@ +// +// NamedSpansStore.h +// BugsnagPerformance +// +// Created by Robert Bartoszewski on 22/09/2025. +// Copyright © 2025 Bugsnag. All rights reserved. +// + +#import +#import "BSGNamedSpanState.h" +#import + +namespace bugsnag { + +class NamedSpansStore { +public: + NamedSpansStore(NSTimeInterval timeout, + NSTimeInterval sweepInterval) noexcept + : timeout_(timeout) + , sweepInterval_(sweepInterval) + , nameToSpan_([NSMutableDictionary dictionary]) {} + + ~NamedSpansStore() noexcept { + if (timer_ != nullptr) { + dispatch_cancel(timer_); + } + }; + + void start() noexcept; + void add(BugsnagPerformanceSpan *span) noexcept; + void remove(BugsnagPerformanceSpan *span) noexcept; + BugsnagPerformanceSpan *getSpan(NSString *name) noexcept; +private: + NSMutableDictionary *nameToSpan_; + std::mutex mutex_; + dispatch_source_t timer_; + NSTimeInterval timeout_; + NSTimeInterval sweepInterval_; + BSGNamedSpanState *first_; + BSGNamedSpanState *last_; + + void add(BSGNamedSpanState *state) noexcept; + void erase(BSGNamedSpanState *state) noexcept; + void sweepExpiredSpans() noexcept; +}; +} diff --git a/Sources/BugsnagPerformanceNamedSpans/Private/NamedSpansStore.mm b/Sources/BugsnagPerformanceNamedSpans/Private/NamedSpansStore.mm new file mode 100644 index 00000000..ecd87b27 --- /dev/null +++ b/Sources/BugsnagPerformanceNamedSpans/Private/NamedSpansStore.mm @@ -0,0 +1,105 @@ +// +// NamedSpansStore.mm +// BugsnagPerformance +// +// Created by Robert Bartoszewski on 22/09/2025. +// Copyright © 2025 Bugsnag. All rights reserved. +// + +#import "NamedSpansStore.h" + +using namespace bugsnag; + +void +NamedSpansStore::start() noexcept { + std::lock_guard guard(mutex_); + + __block auto blockThis = this; + dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, + dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0)); + + dispatch_source_set_timer(timer, + dispatch_time(DISPATCH_TIME_NOW, + (int64_t)(sweepInterval_ * NSEC_PER_SEC)), + (uint64_t)(sweepInterval_ * NSEC_PER_SEC), + 0); + + dispatch_source_set_event_handler(timer, ^{ + blockThis->sweepExpiredSpans(); + }); + + dispatch_resume(timer); + timer_ = timer; +} + +void +NamedSpansStore::add(BugsnagPerformanceSpan *span) noexcept { + std::lock_guard guard(mutex_); + auto existingState = nameToSpan_[span.name]; + if (existingState != nil) { + erase(existingState); + } + auto spanState = [BSGNamedSpanState new]; + spanState.span = span; + spanState.expireTime = CFAbsoluteTimeGetCurrent() + timeout_; + add(spanState); +} + +void +NamedSpansStore::remove(BugsnagPerformanceSpan *span) noexcept { + std::lock_guard guard(mutex_); + auto state = nameToSpan_[span.name]; + if (state.span == span) { + erase(state); + } +} + +BugsnagPerformanceSpan * +NamedSpansStore::getSpan(NSString *name) noexcept { + std::lock_guard guard(mutex_); + return nameToSpan_[name].span; +} + +#pragma mark Private + +void +NamedSpansStore::add(BSGNamedSpanState *state) noexcept { + state.previous = last_; + last_.next = state; + if (first_ == nil) { + first_ = state; + } + last_ = state; + nameToSpan_[state.span.name] = state; +} + +void +NamedSpansStore::erase(BSGNamedSpanState *state) noexcept { + auto next = state.next; + auto previous = state.previous; + next.previous = previous; + previous.next = next; + if (state == first_) { + first_ = next; + } + if (state == last_) { + last_ = previous; + } + nameToSpan_[state.span.name] = nil; +} + +void +NamedSpansStore::sweepExpiredSpans() noexcept { + std::lock_guard guard(mutex_); + CFAbsoluteTime now = CFAbsoluteTimeGetCurrent(); + + auto state = first_; + while (state != nil) { + if (now > state.expireTime) { + erase(state); + state = state.next; + } else { + return; + } + } +} diff --git a/Sources/BugsnagPerformanceNamedSpans/Public/BugsnagPerformanceNamedSpansPlugin.mm b/Sources/BugsnagPerformanceNamedSpans/Public/BugsnagPerformanceNamedSpansPlugin.mm index 88c2ae66..173e647c 100644 --- a/Sources/BugsnagPerformanceNamedSpans/Public/BugsnagPerformanceNamedSpansPlugin.mm +++ b/Sources/BugsnagPerformanceNamedSpans/Public/BugsnagPerformanceNamedSpansPlugin.mm @@ -11,12 +11,20 @@ #import #import #import "../Private/BugsnagPerformanceNamedSpansPlugin+Private.h" +#import "../Private/NamedSpansStore.h" static const NSTimeInterval kSpanTimeoutInterval = 600; // 10 minutes +static const NSTimeInterval kSpanSweepInterval = 60; // 1 minute + +using namespace bugsnag; + +@interface BugsnagPerformanceSpan () + +@end @interface BugsnagPerformanceNamedSpansPlugin () -@property (nonatomic, strong) NSMutableDictionary *spansByName; +@property (nonatomic, assign, readonly) std::shared_ptr store; @end @@ -25,14 +33,14 @@ @implementation BugsnagPerformanceNamedSpansPlugin #pragma mark - Initialization - (instancetype)init { - return [self initWithTimeoutInterval:kSpanTimeoutInterval]; + return [self initWithTimeoutInterval:kSpanTimeoutInterval sweepInterval:kSpanSweepInterval]; } -- (instancetype)initWithTimeoutInterval:(NSTimeInterval)timeoutInterval { +- (instancetype)initWithTimeoutInterval:(NSTimeInterval)timeoutInterval + sweepInterval:(NSTimeInterval)sweepInterval { if ((self = [super init])) { - _timeoutInterval = timeoutInterval; - _spansByName = [NSMutableDictionary new]; - _spanTimeoutTimers = std::make_shared>(); + _store = std::make_shared(timeoutInterval, + sweepInterval); } return self; } @@ -64,6 +72,7 @@ - (void)installWithContext:(BugsnagPerformancePluginContext *)context { [context addOnSpanStartCallback:spanStartCallback priority:BugsnagPerformancePriorityHigh]; [context addOnSpanEndCallback:spanEndCallback priority:BugsnagPerformancePriorityLow]; [context addSpanControlProvider:self]; + self.store->start(); } - (void)start { @@ -74,9 +83,7 @@ - (void)start { - (id)getSpanControlsWithQuery:(BugsnagPerformanceSpanQuery *)query { if ([query isKindOfClass:[BugsnagPerformanceNamedSpanQuery class]]) { NSString *spanName = [query getAttributeWithName:@"name"]; - @synchronized (self) { - return self.spansByName[spanName]; - } + return self.store->getSpan(spanName); } return nil; } @@ -84,60 +91,12 @@ - (void)start { #pragma mark Private - (void)didStartSpan:(BugsnagPerformanceSpan *)span { - @synchronized (self) { - BugsnagPerformanceSpan *existingSpan = self.spansByName[span.name]; - if (existingSpan) { - // If a span with the same name already exists, cancel the associated timeout - [self cancelSpanTimeout:existingSpan]; - } - - self.spansByName[span.name] = span; - - // Add timeout to remove the span from the cache if not ended - void *key = (__bridge void *)span; - dispatch_source_t timer = [self createSpanTimeoutTimer:span]; - (*self.spanTimeoutTimers)[key] = timer; - } + self.store->add(span); } - (BOOL)didEndSpan:(BugsnagPerformanceSpan *)span { - @synchronized (self) { - // Remove span from cache if it exists - if ([self.spansByName objectForKey:span.name] == span) { - [self.spansByName removeObjectForKey:span.name]; - } - - [self cancelSpanTimeout:span]; - } + self.store->remove(span); return YES; } -- (dispatch_source_t)createSpanTimeoutTimer:(BugsnagPerformanceSpan *)span { - __weak BugsnagPerformanceNamedSpansPlugin *weakSelf = self; - - dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, - dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0)); - - dispatch_source_set_timer(timer, - dispatch_time(DISPATCH_TIME_NOW, (int64_t)(self.timeoutInterval * NSEC_PER_SEC)), - DISPATCH_TIME_FOREVER, - 0); - - dispatch_source_set_event_handler(timer, ^{ - [weakSelf didEndSpan:span]; - }); - - dispatch_resume(timer); - return timer; -} - -- (void)cancelSpanTimeout:(BugsnagPerformanceSpan *)span { - void *key = (__bridge void *)span; - auto it = self.spanTimeoutTimers->find(key); - if (it != self.spanTimeoutTimers->end()) { - dispatch_source_cancel(it->second); - self.spanTimeoutTimers->erase(it); - } -} - @end diff --git a/Tests/BugsnagPerformanceNamedSpansTests/BugsnagPerformanceNamedSpansTests.mm b/Tests/BugsnagPerformanceNamedSpansTests/BugsnagPerformanceNamedSpansTests.mm index dc298188..160f7e5b 100644 --- a/Tests/BugsnagPerformanceNamedSpansTests/BugsnagPerformanceNamedSpansTests.mm +++ b/Tests/BugsnagPerformanceNamedSpansTests/BugsnagPerformanceNamedSpansTests.mm @@ -75,7 +75,7 @@ @implementation BugsnagPerformanceNamedSpansTests - (void)setUp { [super setUp]; - self.plugin = [[BugsnagPerformanceNamedSpansPlugin alloc] initWithTimeoutInterval:0.2]; // Use a short timeout for testing + self.plugin = [[BugsnagPerformanceNamedSpansPlugin alloc] initWithTimeoutInterval:0.2 sweepInterval:0.01]; // Use a short timeout for testing self.mockContext = (BugsnagPerformancePluginContext *)[[FakePluginContext alloc] init]; } @@ -102,16 +102,6 @@ - (void)testStartMethod { XCTAssertNoThrow([self.plugin start]); } -- (void)testInitWithCustomTimeoutInterval { - BugsnagPerformanceNamedSpansPlugin *customPlugin = [[BugsnagPerformanceNamedSpansPlugin alloc] initWithTimeoutInterval:30.0]; - XCTAssertEqual(customPlugin.timeoutInterval, 30.0); -} - -- (void)testDefaultInitUsesDefaultTimeout { - BugsnagPerformanceNamedSpansPlugin *defaultPlugin = [[BugsnagPerformanceNamedSpansPlugin alloc] init]; - XCTAssertEqual(defaultPlugin.timeoutInterval, 600.0); // 10 minutes -} - #pragma mark - Span Caching Tests - (void)testSpanStartCallbackCachesSpan { @@ -209,6 +199,23 @@ - (void)testGetSpanControlsWithNamedSpanQuery { XCTAssertIdentical(result, span); } +- (void)testGetSpanControlsWithNonConstantStringNamedSpanQuery { + [self.plugin installWithContext:self.mockContext]; + + BugsnagPerformanceSpan *span = createSpan(@"my-span"); + FakePluginContext *fakeContext = (FakePluginContext *)self.mockContext; + + fakeContext.spanStartCallback(span); + NSMutableString *name = [NSMutableString string]; + [name appendString:@"my-"]; + [name appendString:@"span"]; + + BugsnagPerformanceNamedSpanQuery *query = [BugsnagPerformanceNamedSpanQuery queryWithName:name]; + id result = [self.plugin getSpanControlsWithQuery:query]; + + XCTAssertIdentical(result, span); +} + - (void)testGetSpanControlsWithNonNamedSpanQuery { [self.plugin installWithContext:self.mockContext]; @@ -261,7 +268,6 @@ - (void)testSpanTimeoutTimerRemovesSpanFromCache { BugsnagPerformanceNamedSpanQuery *query = [BugsnagPerformanceNamedSpanQuery queryWithName:@"timeout-span"]; id cachedSpan = [self.plugin getSpanControlsWithQuery:query]; XCTAssertIdentical(cachedSpan, span); - XCTAssertEqual(self.plugin.spanTimeoutTimers->size(), 1UL); // Wait for timeout to trigger XCTestExpectation *timeoutExpectation = [self expectationWithDescription:@"Span timeout should remove span from cache"]; @@ -270,39 +276,14 @@ - (void)testSpanTimeoutTimerRemovesSpanFromCache { // Check span is removed from cache after timeout interval id stillCachedSpan = [self.plugin getSpanControlsWithQuery:query]; XCTAssertNil(stillCachedSpan); - XCTAssertEqual(self.plugin.spanTimeoutTimers->size(), 0UL); [timeoutExpectation fulfill]; }); [self waitForExpectations:@[timeoutExpectation] timeout:0.5]; } -- (void)testTimeoutTimerCancelledWhenSpanEnds { - [self.plugin installWithContext:self.mockContext]; - - BugsnagPerformanceSpan *span = createSpan(@"timer-span"); - FakePluginContext *fakeContext = (FakePluginContext *)self.mockContext; - - // Start span (creates timeout timer) - fakeContext.spanStartCallback(span); - - // Verify span is cached - BugsnagPerformanceNamedSpanQuery *query = [BugsnagPerformanceNamedSpanQuery queryWithName:@"timer-span"]; - id cachedSpan = [self.plugin getSpanControlsWithQuery:query]; - XCTAssertIdentical(cachedSpan, span); - XCTAssertEqual(self.plugin.spanTimeoutTimers->size(), 1UL); - - // End span (should cancel timeout timer and remove from cache) - BOOL result = fakeContext.spanEndCallback(span); - XCTAssertTrue(result); - - // Verify span is removed from cache - id removedSpan = [self.plugin getSpanControlsWithQuery:query]; - XCTAssertNil(removedSpan); - XCTAssertEqual(self.plugin.spanTimeoutTimers->size(), 0UL); -} -- (void)testTimeoutTimerCancelledWhenNewSpanWithSameNameStarted { +- (void)testTimeoutIsRenewedWhenNewSpanWithSameNameStarted { [self.plugin installWithContext:self.mockContext]; BugsnagPerformanceSpan *span1 = createSpan(@"test-span"); @@ -316,15 +297,11 @@ - (void)testTimeoutTimerCancelledWhenNewSpanWithSameNameStarted { BugsnagPerformanceNamedSpanQuery *query = [BugsnagPerformanceNamedSpanQuery queryWithName:@"test-span"]; id cachedSpan = [self.plugin getSpanControlsWithQuery:query]; XCTAssertIdentical(cachedSpan, span1); - - XCTAssertEqual(self.plugin.spanTimeoutTimers->size(), 1UL); // Wait for first span's timeout to almost elapse (0.15 seconds of 0.2 second timeout) XCTestExpectation *stagingExpectation = [self expectationWithDescription:@"Second span with same name should replace first span"]; dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.15 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ - // Verify first span's timer is still active - XCTAssertEqual(self.plugin.spanTimeoutTimers->size(), 1UL); // Start second span with same name (should cancel first timer and create a new one) fakeContext.spanStartCallback(span2); @@ -332,7 +309,6 @@ - (void)testTimeoutTimerCancelledWhenNewSpanWithSameNameStarted { // Verify second span replaced first span id newCachedSpan = [self.plugin getSpanControlsWithQuery:query]; XCTAssertIdentical(newCachedSpan, span2); - XCTAssertEqual(self.plugin.spanTimeoutTimers->size(), 1UL); [stagingExpectation fulfill]; }); @@ -346,14 +322,13 @@ - (void)testTimeoutTimerCancelledWhenNewSpanWithSameNameStarted { // Verify second span is still cached id stillCachedSpan = [self.plugin getSpanControlsWithQuery:query]; XCTAssertIdentical(stillCachedSpan, span2); - XCTAssertEqual(self.plugin.spanTimeoutTimers->size(), 1UL); [timeoutExpectation fulfill]; }); [self waitForExpectations:@[timeoutExpectation] timeout:0.3]; } -- (void)testMultipleSpansWithDifferentNamesHaveSeparateTimeoutTimers { +- (void)testMultipleSpansWithDifferentNamesAreRemovedFromCacheOnTheirTimeout { [self.plugin installWithContext:self.mockContext]; BugsnagPerformanceSpan *span1 = createSpan(@"span-one"); @@ -363,15 +338,12 @@ - (void)testMultipleSpansWithDifferentNamesHaveSeparateTimeoutTimers { // Start first span fakeContext.spanStartCallback(span1); - XCTAssertEqual(self.plugin.spanTimeoutTimers->size(), 1UL); // Start second span with different name fakeContext.spanStartCallback(span2); - XCTAssertEqual(self.plugin.spanTimeoutTimers->size(), 2UL); // Start third span with different name fakeContext.spanStartCallback(span3); - XCTAssertEqual(self.plugin.spanTimeoutTimers->size(), 3UL); // Verify all spans are cached BugsnagPerformanceNamedSpanQuery *query1 = [BugsnagPerformanceNamedSpanQuery queryWithName:@"span-one"]; @@ -385,7 +357,6 @@ - (void)testMultipleSpansWithDifferentNamesHaveSeparateTimeoutTimers { // End middle span - should only remove its timer BOOL result = fakeContext.spanEndCallback(span2); XCTAssertTrue(result); - XCTAssertEqual(self.plugin.spanTimeoutTimers->size(), 2UL); // Verify span2 is removed but span1 and span3 remain XCTAssertIdentical([self.plugin getSpanControlsWithQuery:query1], span1); @@ -399,7 +370,6 @@ - (void)testMultipleSpansWithDifferentNamesHaveSeparateTimeoutTimers { // Both remaining spans should be removed by timeout XCTAssertNil([self.plugin getSpanControlsWithQuery:query1]); XCTAssertNil([self.plugin getSpanControlsWithQuery:query3]); - XCTAssertEqual(self.plugin.spanTimeoutTimers->size(), 0UL); [timeoutExpectation fulfill]; }); From c407989331f0b528c6f1252bd2a6890b57974f75 Mon Sep 17 00:00:00 2001 From: Robert Bartoszewski <126675445+robert-smartbear@users.noreply.github.com> Date: Fri, 17 Oct 2025 14:23:48 +0200 Subject: [PATCH 42/53] Tracer refactor: removed dead code and excessive logging (#485) Co-authored-by: Robert Bartoszewski --- .../Private/EarlyConfiguration.h | 1 - .../Private/EarlyConfiguration.mm | 5 - .../ViewLoad/ViewLoadSpanFactoryCallbacks.h | 3 +- .../ViewLoad/ViewLoadSpanFactoryImpl.mm | 5 +- Sources/BugsnagPerformance/Private/Tracer.h | 12 +- Sources/BugsnagPerformance/Private/Tracer.mm | 62 +----- Tests/BugsnagPerformanceTests/TracerTests.mm | 180 ------------------ 7 files changed, 5 insertions(+), 263 deletions(-) diff --git a/Sources/BugsnagPerformance/Private/EarlyConfiguration.h b/Sources/BugsnagPerformance/Private/EarlyConfiguration.h index 676f6c3c..13ada9c9 100644 --- a/Sources/BugsnagPerformance/Private/EarlyConfiguration.h +++ b/Sources/BugsnagPerformance/Private/EarlyConfiguration.h @@ -30,7 +30,6 @@ NS_ASSUME_NONNULL_BEGIN @property(nonatomic, readonly) BOOL enableSwizzling; @property(nonatomic, readonly) BOOL swizzleViewLoadPreMain; -@property(nonatomic, readwrite) BOOL appWasLaunchedPreWarmed; @end diff --git a/Sources/BugsnagPerformance/Private/EarlyConfiguration.mm b/Sources/BugsnagPerformance/Private/EarlyConfiguration.mm index 4bf01daf..61f9f011 100644 --- a/Sources/BugsnagPerformance/Private/EarlyConfiguration.mm +++ b/Sources/BugsnagPerformance/Private/EarlyConfiguration.mm @@ -18,13 +18,8 @@ - (instancetype) initWithBundleDictionary:(NSDictionary *)dict { } id swizzleViewLoadPreMain = [dict valueForKeyPath:@"bugsnag.performance.swizzleViewLoadPreMain"]; _swizzleViewLoadPreMain = swizzleViewLoadPreMain != nil && [swizzleViewLoadPreMain boolValue]; - _appWasLaunchedPreWarmed = [NSProcessInfo.processInfo.environment[@"ActivePrewarm"] isEqualToString:@"1"]; } - BSGLogDebug(@"BSGEarlyConfiguration.enableSwizzling = %d", self.enableSwizzling); - BSGLogDebug(@"BSGEarlyConfiguration.swizzleViewLoadPreMain = %d", self.swizzleViewLoadPreMain); - BSGLogDebug(@"BSGEarlyConfiguration.appWasLaunchedPreWarmed = %d", self.appWasLaunchedPreWarmed); - return self; } diff --git a/Sources/BugsnagPerformance/Private/SpanFactory/ViewLoad/ViewLoadSpanFactoryCallbacks.h b/Sources/BugsnagPerformance/Private/SpanFactory/ViewLoad/ViewLoadSpanFactoryCallbacks.h index 6c1043c1..af11698f 100644 --- a/Sources/BugsnagPerformance/Private/SpanFactory/ViewLoad/ViewLoadSpanFactoryCallbacks.h +++ b/Sources/BugsnagPerformance/Private/SpanFactory/ViewLoad/ViewLoadSpanFactoryCallbacks.h @@ -11,13 +11,12 @@ typedef BugsnagPerformanceSpan *_Nullable(^GetViewLoadParentSpanCallback)(void); typedef BOOL (^IsViewLoadInProgressCallback)(void); -typedef void (^OnViewLoadSpanStarted)(BugsnagPerformanceSpan * _Nonnull, NSString * _Nonnull); +typedef void (^OnViewLoadSpanStarted)(NSString * _Nonnull); @interface ViewLoadSpanFactoryCallbacks: NSObject @property (nonatomic, nullable) GetViewLoadParentSpanCallback getViewLoadParentSpan; @property (nonatomic, nullable) IsViewLoadInProgressCallback isViewLoadInProgress; @property (nonatomic, nullable) OnViewLoadSpanStarted onViewLoadSpanStarted; -@property (nonatomic, nullable) SpanLifecycleCallback onViewLoadPhaseSpanStarted; @end diff --git a/Sources/BugsnagPerformance/Private/SpanFactory/ViewLoad/ViewLoadSpanFactoryImpl.mm b/Sources/BugsnagPerformance/Private/SpanFactory/ViewLoad/ViewLoadSpanFactoryImpl.mm index dc3143a9..c87c70f4 100644 --- a/Sources/BugsnagPerformance/Private/SpanFactory/ViewLoad/ViewLoadSpanFactoryImpl.mm +++ b/Sources/BugsnagPerformance/Private/SpanFactory/ViewLoad/ViewLoadSpanFactoryImpl.mm @@ -116,7 +116,7 @@ spanAttributes, conditionsToEndOnClose); if (callbacks_.onViewLoadSpanStarted != nil) { - callbacks_.onViewLoadSpanStarted(span, className); + callbacks_.onViewLoadSpanStarted(className); } return span; } @@ -136,9 +136,6 @@ BSGTriStateUnset, attributes, conditionsToEndOnClose); - if (callbacks_.onViewLoadPhaseSpanStarted != nil) { - callbacks_.onViewLoadPhaseSpanStarted(span); - } return span; } diff --git a/Sources/BugsnagPerformance/Private/Tracer.h b/Sources/BugsnagPerformance/Private/Tracer.h index 6e403b1c..1cea532a 100644 --- a/Sources/BugsnagPerformance/Private/Tracer.h +++ b/Sources/BugsnagPerformance/Private/Tracer.h @@ -49,7 +49,6 @@ class Tracer: public PhasedStartup { void (^onSpanStarted)()) noexcept : spanStackingHandler_(spanStackingHandler) , sampler_(sampler) - , prewarmSpans_([NSMutableArray new]) , blockedSpans_([NSMutableArray new]) , potentiallyOpenSpans_(std::make_shared()) , batch_(batch) @@ -69,7 +68,7 @@ class Tracer: public PhasedStartup { ~Tracer() {}; - void earlyConfigure(BSGEarlyConfiguration *) noexcept; + void earlyConfigure(BSGEarlyConfiguration *) noexcept {} void earlySetup() noexcept {} void configure(BugsnagPerformanceConfiguration *config) noexcept { plainSpanFactory_->setAttributeCountLimit(config.attributeCountLimit); @@ -102,16 +101,12 @@ class Tracer: public PhasedStartup { NSArray *conditionsToEndOnClose) noexcept; void cancelQueuedSpan(BugsnagPerformanceSpan *span) noexcept; - - void onPrewarmPhaseEnded(void) noexcept; void abortAllOpenSpans() noexcept; // Sweep must be called periodically to avoid a buildup of dead pointers. void sweep() noexcept; - void callOnSpanEndCallbacks(BugsnagPerformanceSpan *span); - private: Tracer() = delete; std::shared_ptr sampler_; @@ -122,10 +117,7 @@ class Tracer: public PhasedStartup { std::shared_ptr viewLoadSpanFactory_; std::shared_ptr networkSpanFactory_; - std::atomic willDiscardPrewarmSpans_{false}; BugsnagPerformanceEnabledMetrics *enabledMetrics_{[BugsnagPerformanceEnabledMetrics withAllEnabled]}; - std::mutex prewarmSpansMutex_; - NSMutableArray *prewarmSpans_; NSMutableArray *blockedSpans_; BSGPrioritizedStore *onSpanStartCallbacks_; BSGPrioritizedStore *onSpanEndCallbacks_; @@ -142,7 +134,6 @@ class Tracer: public PhasedStartup { PlainSpanFactoryCallbacks *createPlainSpanFactoryCallbacks() noexcept; ViewLoadSpanFactoryCallbacks *createViewLoadSpanFactoryCallbacks() noexcept; void createFrozenFrameSpan(NSTimeInterval startTime, NSTimeInterval endTime, BugsnagPerformanceSpanContext *parentContext) noexcept; - void markPrewarmSpan(BugsnagPerformanceSpan *span) noexcept; void onSpanEndSet(BugsnagPerformanceSpan *span); void onSpanClosed(BugsnagPerformanceSpan *span); BugsnagPerformanceSpanCondition *onSpanBlocked(BugsnagPerformanceSpan *blocked, NSTimeInterval timeout); @@ -150,5 +141,6 @@ class Tracer: public PhasedStartup { void processFrameMetrics(BugsnagPerformanceSpan *span) noexcept; bool shouldInstrumentRendering(BugsnagPerformanceSpan *span) noexcept; void callOnSpanStartCallbacks(BugsnagPerformanceSpan *span); + void callOnSpanEndCallbacks(BugsnagPerformanceSpan *span); }; } diff --git a/Sources/BugsnagPerformance/Private/Tracer.mm b/Sources/BugsnagPerformance/Private/Tracer.mm index f4360aa2..5e2b4893 100644 --- a/Sources/BugsnagPerformance/Private/Tracer.mm +++ b/Sources/BugsnagPerformance/Private/Tracer.mm @@ -18,31 +18,20 @@ using namespace bugsnag; -void -Tracer::earlyConfigure(BSGEarlyConfiguration *config) noexcept { - willDiscardPrewarmSpans_ = config.appWasLaunchedPreWarmed; -} - void Tracer::preStartSetup() noexcept { - BSGLogDebug(@"Tracer::preStartSetup()"); reprocessEarlySpans(); } void Tracer::reprocessEarlySpans(void) { - BSGLogDebug(@"Tracer::reprocessEarlySpans()"); // Up until now nothing was configured, so all early spans have been kept. // Now that configuration is complete, force-drain all early spans and re-process them. auto toReprocess = batch_->drain(true); - BSGLogDebug(@"Tracer::reprocessEarlySpans: Reprocessing %zu early spans", toReprocess.count); for (BugsnagPerformanceSpan *span in toReprocess) { - BSGLogDebug(@"Tracer::reprocessEarlySpans: Try to re-add span (%@) to batch", span.name); if (span.state != SpanStateEnded) { - BSGLogDebug(@"Tracer::reprocessEarlySpans: span %@ has state %d, so ignoring", span.name, span.state); continue; } if (!sampler_->sampled(span)) { - BSGLogDebug(@"Tracer::reprocessEarlySpans: span %@ was not sampled (P=%f), so dropping", span.name, sampler_->getProbability()); [span abortUnconditionally]; continue; } @@ -50,7 +39,6 @@ callOnSpanEndCallbacks(span); }]; if (span.state == SpanStateAborted) { - BSGLogDebug(@"Tracer::reprocessEarlySpans: span %@ was rejected in the OnEnd callbacks, so dropping", span.name); [span abortUnconditionally]; continue; } @@ -61,13 +49,11 @@ void Tracer::abortAllOpenSpans() noexcept { - BSGLogDebug(@"Tracer::abortAllOpenSpans()"); potentiallyOpenSpans_->abortAllOpen(); } void Tracer::sweep() noexcept { - BSGLogDebug(@"Tracer::sweep()"); constexpr unsigned minEntriesBeforeCompacting = 10000; if (potentiallyOpenSpans_->count() >= minEntriesBeforeCompacting) { potentiallyOpenSpans_->compact(); @@ -83,16 +69,12 @@ } void Tracer::onSpanEndSet(BugsnagPerformanceSpan *span) { - BSGLogTrace(@"Tracer::onSpanEndSet: for span %@", span.name); - if (shouldInstrumentRendering(span)) { span.endFramerateSnapshot = [frameMetricsCollector_ currentSnapshot]; } } void Tracer::onSpanClosed(BugsnagPerformanceSpan *span) { - BSGLogTrace(@"Tracer::onSpanClosed: for span %@", span.name); - @synchronized (span) { for (BugsnagPerformanceSpanCondition *condition in span.conditionsToEndOnClose) { [condition closeWithEndTime:span.endTime]; @@ -102,12 +84,10 @@ spanStackingHandler_->onSpanClosed(span.spanId); if(span.state == SpanStateAborted) { - BSGLogTrace(@"Tracer::onSpanClosed: span %@ has been aborted, so ignoring", span.name); return; } if (!sampler_->sampled(span)) { - BSGLogTrace(@"Tracer::onSpanClosed: span %@ was not sampled (P=%f), so dropping", span.name, sampler_->getProbability()); [span abortUnconditionally]; return; } @@ -115,17 +95,14 @@ if (span != nil && span.state == SpanStateEnded) { callOnSpanEndCallbacks(span); if (span.state == SpanStateAborted) { - BSGLogTrace(@"Tracer::onSpanClosed: span %@ was rejected in the OnEnd callbacks, so dropping", span.name); return; } } if (shouldInstrumentRendering(span)) { - BSGLogTrace(@"Tracer::onSpanClosed: Processing framerate metrics for span %@", span.name); processFrameMetrics(span); } - BSGLogTrace(@"Tracer::onSpanClosed: Adding span %@ to batch", span.name); batch_->add(span); } @@ -134,7 +111,6 @@ return nil; } if (span.state != SpanStateOpen && !span.isBlocked) { - BSGLogDebug(@"Tracer::onSpanBlocked: span %@ has state %d, so ignoring", span.name, span.state); return nil; } BugsnagPerformanceSpanCondition *condition = [BugsnagPerformanceSpanCondition conditionWithSpan:span onClosedCallback:^(BugsnagPerformanceSpanCondition *c, CFAbsoluteTime endTime) { @@ -157,7 +133,6 @@ [condition addOnDeactivatedCallback:^(BugsnagPerformanceSpanCondition *c) { __strong BugsnagPerformanceSpan *strongSpan = c.span; if (strongSpan.state == SpanStateEnded && !strongSpan.isBlocked) { - BSGLogTrace(@"Tracer::onSpanBlocked: Processing unblocked span %@", span.name); @synchronized (this->blockedSpans_) { [this->blockedSpans_ removeObject:strongSpan]; this->conditionTimeoutExecutor_->cancelTimeout(c); @@ -166,7 +141,6 @@ } }]; this->conditionTimeoutExecutor_->sheduleTimeout(condition, timeout); - BSGLogTrace(@"Tracer::onSpanBlocked: Blocked span %@ with timeout %f", span.name, timeout); return condition; } @@ -189,7 +163,6 @@ return; } if (span.state != SpanStateEnded) { - BSGLogDebug(@"Tracer::callOnSpanEndCallbacks: span %@ has state %d, so ignoring", span.name, span.state); return; } @@ -204,13 +177,11 @@ shouldDiscardSpan = false; } if(shouldDiscardSpan) { - BSGLogDebug(@"Tracer::callOnSpanEndCallbacks: span %@ OnEnd callback returned false. Dropping...", span.name); [span abortUnconditionally]; return; } } CFAbsoluteTime callbacksEndTime = CFAbsoluteTimeGetCurrent(); - BSGLogDebug(@"Tracer::callOnSpanEndCallbacks: Adding span %@ to batch", span.name); [span internalSetAttribute:@"bugsnag.span.callbacks_duration" withValue:@(intervalToNanoseconds(callbacksEndTime - callbacksStartTime))]; } @@ -273,7 +244,6 @@ } void Tracer::cancelQueuedSpan(BugsnagPerformanceSpan *span) noexcept { - BSGLogTrace(@"Tracer::cancelQueuedSpan(%@)", span.name); if (span) { [span abortUnconditionally]; batch_->removeSpan(span.traceIdHi, span.traceIdLo, span.spanId); @@ -281,14 +251,6 @@ } } -void Tracer::markPrewarmSpan(BugsnagPerformanceSpan *span) noexcept { - BSGLogTrace(@"Tracer::markPrewarmSpan(%@)", span.name); - std::lock_guard guard(prewarmSpansMutex_); - if (willDiscardPrewarmSpans_) { - [prewarmSpans_ addObject:span]; - } -} - PlainSpanFactoryCallbacks * Tracer::createPlainSpanFactoryCallbacks() noexcept { __block auto blockThis = this; @@ -345,21 +307,13 @@ return blockThis->spanStackingHandler_->hasSpanWithAttribute(@"bugsnag.span.category", @"view_load"); }; - auto onSpanStarted = ^(BugsnagPerformanceSpan * _Nonnull span) { - if (blockThis->willDiscardPrewarmSpans_) { - blockThis->markPrewarmSpan(span); - } - }; - - auto onViewLoadSpanStarted = ^(BugsnagPerformanceSpan * _Nonnull span, NSString * _Nonnull className) { + auto onViewLoadSpanStarted = ^(NSString * _Nonnull className) { if (onViewLoadSpanStarted_ != nil) { onViewLoadSpanStarted_(className); } - onSpanStarted(span); }; callbacks.onViewLoadSpanStarted = onViewLoadSpanStarted; - callbacks.onViewLoadPhaseSpanStarted = onSpanStarted; return callbacks; } @@ -376,20 +330,6 @@ [span endWithAbsoluteTime:endTime]; } -void -Tracer::onPrewarmPhaseEnded(void) noexcept { - BSGLogDebug(@"Tracer::onPrewarmPhaseEnded()"); - std::lock_guard guard(prewarmSpansMutex_); - willDiscardPrewarmSpans_ = false; - for (BugsnagPerformanceSpan *span: prewarmSpans_) { - // Only cancel unfinished prewarm spans - if (span.state == SpanStateOpen) { - cancelQueuedSpan(span); - } - } - [prewarmSpans_ removeAllObjects]; -} - bool Tracer::shouldInstrumentRendering(BugsnagPerformanceSpan *span) noexcept { switch (span.metricsOptions.rendering) { diff --git a/Tests/BugsnagPerformanceTests/TracerTests.mm b/Tests/BugsnagPerformanceTests/TracerTests.mm index 755a23c1..1486dcf8 100644 --- a/Tests/BugsnagPerformanceTests/TracerTests.mm +++ b/Tests/BugsnagPerformanceTests/TracerTests.mm @@ -18,188 +18,8 @@ @interface TracerTests : XCTestCase @end -static BugsnagPerformanceConfiguration *newConfig() { - return [[BugsnagPerformanceConfiguration alloc] initWithApiKey:@"11111111111111111111111111111111"]; -} - @implementation TracerTests -- (void)testPrewarmEndBefore { - auto earlyConfig = [BSGEarlyConfiguration new]; - earlyConfig.appWasLaunchedPreWarmed = YES; - auto config = newConfig(); - - auto stackingHandler = std::make_shared(); - auto sampler = std::make_shared(); - auto frameMetricsCollector = [FrameMetricsCollector new]; - auto conditionTimeoutExecutor = std::make_shared(); - auto spanAttributesProvider = std::make_shared(); - sampler->setProbability(1.0); - auto batch = 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 tracer = std::make_shared(stackingHandler, - sampler, - batch, - frameMetricsCollector, - conditionTimeoutExecutor, - plainSpanFactory, - viewLoadSpanFactory, - networkSpanFactory, - spanStartCallbacks, - spanEndCallbacks, - ^(){}); - tracer->earlyConfigure(earlyConfig); - tracer->earlySetup(); - tracer->configure(config); - tracer->start(); - - SpanOptions spanOptions; - auto span = tracer->startViewLoadSpan(BugsnagPerformanceViewTypeUIKit, @"myclass", spanOptions); - [span end]; - tracer->onPrewarmPhaseEnded(); - auto spans = batch->drain(true); - XCTAssertEqual(spans.count, 1UL); -} - -- (void)testPrewarmEndAfter { - auto earlyConfig = [BSGEarlyConfiguration new]; - earlyConfig.appWasLaunchedPreWarmed = YES; - auto config = newConfig(); - - 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 tracer = std::make_shared(stackingHandler, - sampler, - batch, - frameMetricsCollector, - conditionTimeoutExecutor, - plainSpanFactory, - viewLoadSpanFactory, - networkSpanFactory, - spanStartCallbacks, - spanEndCallbacks, - ^(){}); - tracer->earlyConfigure(earlyConfig); - tracer->earlySetup(); - tracer->configure(config); - tracer->start(); - - SpanOptions spanOptions; - auto span = tracer->startViewLoadSpan(BugsnagPerformanceViewTypeUIKit, @"myclass", spanOptions); - tracer->onPrewarmPhaseEnded(); - [span end]; - auto spans = batch->drain(true); - XCTAssertEqual(spans.count, 0UL); -} - -- (void)testNoPrewarmEndBefore { - auto earlyConfig = [BSGEarlyConfiguration new]; - earlyConfig.appWasLaunchedPreWarmed = NO; - auto config = newConfig(); - - 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 tracer = std::make_shared(stackingHandler, - sampler, - batch, - frameMetricsCollector, - conditionTimeoutExecutor, - plainSpanFactory, - viewLoadSpanFactory, - networkSpanFactory, - spanStartCallbacks, - spanEndCallbacks, - ^(){}); - tracer->earlyConfigure(earlyConfig); - tracer->earlySetup(); - tracer->configure(config); - tracer->start(); - - SpanOptions spanOptions; - auto span = tracer->startViewLoadSpan(BugsnagPerformanceViewTypeUIKit, @"myclass", spanOptions); - [span end]; - tracer->onPrewarmPhaseEnded(); - auto spans = batch->drain(true); - XCTAssertEqual(spans.count, 1UL); -} - -- (void)testNoPrewarmEndAfter { - auto earlyConfig = [BSGEarlyConfiguration new]; - earlyConfig.appWasLaunchedPreWarmed = NO; - auto config = newConfig(); - - 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 tracer = std::make_shared(stackingHandler, - sampler, - batch, - frameMetricsCollector, - conditionTimeoutExecutor, - plainSpanFactory, - viewLoadSpanFactory, - networkSpanFactory, - spanStartCallbacks, - spanEndCallbacks, - ^(){}); - tracer->earlyConfigure(earlyConfig); - tracer->earlySetup(); - tracer->configure(config); - tracer->start(); - - SpanOptions spanOptions; - auto span = tracer->startViewLoadSpan(BugsnagPerformanceViewTypeUIKit, @"myclass", spanOptions); - tracer->onPrewarmPhaseEnded(); - [span end]; - auto spans = batch->drain(true); - XCTAssertEqual(spans.count, 1UL); -} - - (void)testNetworkSpan { auto stackingHandler = std::make_shared(); auto sampler = std::make_shared(); From 595ae86caee2868c593adbaed076c70269c7cb45 Mon Sep 17 00:00:00 2001 From: Daria Bialobrzeska <88393714+DariaKunoichi@users.noreply.github.com> Date: Thu, 23 Oct 2025 11:45:38 +0200 Subject: [PATCH 43/53] Separate test features into smaller files (#490) * Separate test features into smaller files * Change name of hardcoded feature for full builds * Move automatic and metrics related features to folders --- .buildkite/pipeline.full.yml | 16 +- .buildkite/pipeline.yml | 8 +- features/default/attributes.feature | 38 ++ .../automatic/automatic_app_start.feature | 75 +++ .../automatic/automatic_network.feature | 238 ++++++++++ .../automatic_spans_generic_view_load.feature | 0 .../automatic/automatic_swift_ui.feature | 140 ++++++ .../automatic_view_load.feature} | 444 +----------------- features/default/callbacks.feature | 17 + features/default/conditions.feature | 62 +++ features/default/early_spans.feature | 51 ++ features/default/limits.feature | 33 ++ features/default/manual_spans.feature | 265 +---------- .../metrics_cpu.feature} | 300 +----------- .../default/metrics/metrics_frame.feature | 149 ++++++ .../default/metrics/metrics_memory.feature | 168 +++++++ features/default/network.feature | 73 --- 17 files changed, 999 insertions(+), 1078 deletions(-) create mode 100644 features/default/attributes.feature create mode 100644 features/default/automatic/automatic_app_start.feature create mode 100644 features/default/automatic/automatic_network.feature rename features/default/{ => automatic}/automatic_spans_generic_view_load.feature (100%) create mode 100644 features/default/automatic/automatic_swift_ui.feature rename features/default/{automatic_spans.feature => automatic/automatic_view_load.feature} (57%) create mode 100644 features/default/callbacks.feature create mode 100644 features/default/early_spans.feature create mode 100644 features/default/limits.feature rename features/default/{metrics.feature => metrics/metrics_cpu.feature} (50%) create mode 100644 features/default/metrics/metrics_frame.feature create mode 100644 features/default/metrics/metrics_memory.feature delete mode 100644 features/default/network.feature diff --git a/.buildkite/pipeline.full.yml b/.buildkite/pipeline.full.yml index d0f151ff..0fe91701 100644 --- a/.buildkite/pipeline.full.yml +++ b/.buildkite/pipeline.full.yml @@ -208,7 +208,7 @@ steps: - "--farm=bb" - "--no-tunnel" - "--aws-public-ip" - - "features/default/automatic_spans.feature" + - "features/default/automatic" test-collector#v1.10.2: files: "reports/TEST-*.xml" format: "junit" @@ -241,7 +241,7 @@ steps: - "--farm=bb" - "--no-tunnel" - "--aws-public-ip" - - "features/default/automatic_spans.feature" + - "features/default/automatic" test-collector#v1.10.2: files: "reports/TEST-*.xml" format: "junit" @@ -274,7 +274,7 @@ steps: - "--farm=bb" - "--no-tunnel" - "--aws-public-ip" - - "features/default/automatic_spans.feature" + - "features/default/automatic" test-collector#v1.10.2: files: "reports/TEST-*.xml" format: "junit" @@ -307,7 +307,7 @@ steps: - "--farm=bb" - "--no-tunnel" - "--aws-public-ip" - - "features/default/automatic_spans.feature" + - "features/default/automatic" test-collector#v1.10.2: files: "reports/TEST-*.xml" format: "junit" @@ -471,7 +471,7 @@ steps: - "--farm=bb" - "--no-tunnel" - "--aws-public-ip" - - "--exclude=features/default/automatic_spans_generic_view_load.feature" + - "--exclude=features/default/automatic/automatic_spans_generic_view_load.feature" - "features/default" test-collector#v1.10.2: files: "reports/TEST-*.xml" @@ -504,7 +504,7 @@ steps: - "--farm=bb" - "--no-tunnel" - "--aws-public-ip" - - "--exclude=features/default/automatic_spans_generic_view_load.feature" + - "--exclude=features/default/automatic/automatic_spans_generic_view_load.feature" - "features/default" test-collector#v1.10.2: files: "reports/TEST-*.xml" @@ -537,7 +537,7 @@ steps: - "--farm=bb" - "--no-tunnel" - "--aws-public-ip" - - "--exclude=features/default/automatic_spans_generic_view_load.feature" + - "--exclude=features/default/automatic/automatic_spans_generic_view_load.feature" - "features/default" test-collector#v1.10.2: files: "reports/TEST-*.xml" @@ -570,7 +570,7 @@ steps: - "--farm=bb" - "--no-tunnel" - "--aws-public-ip" - - "--exclude=features/default/automatic_spans_generic_view_load.feature" + - "--exclude=features/default/automatic/automatic_spans_generic_view_load.feature" - "features/default" test-collector#v1.10.2: files: "reports/TEST-*.xml" diff --git a/.buildkite/pipeline.yml b/.buildkite/pipeline.yml index f1ad4b62..17b88902 100644 --- a/.buildkite/pipeline.yml +++ b/.buildkite/pipeline.yml @@ -224,7 +224,7 @@ steps: - "--device=IOS_18" - "--fail-fast" - "--farm=bs" - - "features/default/automatic_spans.feature" + - "features/default/automatic" test-collector#v1.10.2: files: "reports/TEST-*.xml" format: "junit" @@ -257,7 +257,7 @@ steps: - "--farm=bb" - "--no-tunnel" - "--aws-public-ip" - - "features/default/automatic_spans.feature" + - "features/default/automatic" test-collector#v1.10.2: files: "reports/TEST-*.xml" format: "junit" @@ -355,7 +355,7 @@ steps: - "--device=IOS_18" - "--fail-fast" - "--farm=bs" - - "--exclude=features/default/automatic_spans_generic_view_load.feature" + - "--exclude=features/default/automatic/automatic_spans_generic_view_load.feature" - "features/default" test-collector#v1.10.2: files: "reports/TEST-*.xml" @@ -388,7 +388,7 @@ steps: - "--farm=bb" - "--no-tunnel" - "--aws-public-ip" - - "--exclude=features/default/automatic_spans_generic_view_load.feature" + - "--exclude=features/default/automatic/automatic_spans_generic_view_load.feature" - "features/default" test-collector#v1.10.2: files: "reports/TEST-*.xml" diff --git a/features/default/attributes.feature b/features/default/attributes.feature new file mode 100644 index 00000000..719405b8 --- /dev/null +++ b/features/default/attributes.feature @@ -0,0 +1,38 @@ +Feature: Spans with custom attributes + + Scenario: Set attributes in a span + Given I run "SetAttributesScenario" + And I wait for 1 span + * the trace "Bugsnag-Span-Sampling" header equals "1:1" + * 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 "SetAttributesScenario" + * a span string attribute "a" equals "xyz" + * every span bool attribute "b" does not exist + * every span bool attribute "d" does not exist + * a span array attribute "e" is empty + * a span array attribute "f" is empty + * a span array attribute "x" is empty + * a span array attribute "c" contains the string value "array_0" at index 0 + * a span array attribute "c" contains the integer value 1 at index 1 + * a span array attribute "c" contains the value true at index 2 + * a span array attribute "c" contains the float value 1.5 at index 3 + + Scenario: Set attributes in a span with limits set + Given I run "SetAttributesWithLimitsScenario" + And I wait for 1 span + * the trace "Bugsnag-Span-Sampling" header equals "1:1" + * 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 "SetAttributesWithLimitsScenario" + * a span string attribute "a" equals "1234567890*** 1 CHARS TRUNCATED" + * a span array attribute "b" contains the integer value 1 at index 0 + * a span array attribute "b" contains the integer value 2 at index 1 + * a span array attribute "b" contains the integer value 3 at index 2 + * a span array attribute "b" contains no value at index 3 + + Scenario: Set attributes in a span with an attribute count limit set + Given I run "SetAttributeCountLimitScenario" + And I wait for 1 span + * the trace "Bugsnag-Span-Sampling" header equals "1:1" + * 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 "SetAttributeCountLimitScenario" + * every span string attribute "a" does not exist \ No newline at end of file diff --git a/features/default/automatic/automatic_app_start.feature b/features/default/automatic/automatic_app_start.feature new file mode 100644 index 00000000..11ee7429 --- /dev/null +++ b/features/default/automatic/automatic_app_start.feature @@ -0,0 +1,75 @@ +Feature: Automatic app start instrumentation spans + + Scenario: Auto instrument app starts without a view load + Given I run "AutoInstrumentAppStartsScenario" + And I wait for 3 seconds + And I wait for 4 spans + Then the trace "Content-Type" header equals "application/json" + * the trace "Bugsnag-Span-Sampling" header equals "1:4" + * 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 "[AppStart/iOSCold]" + * a span field "name" equals "[AppStartPhase/App launching - pre main()]" + * a span field "name" equals "[AppStartPhase/App launching - post main()]" + * a span field "name" equals "[AppStartPhase/UI init]" + * 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 1 + * every span field "startTimeUnixNano" matches the regex "^[0-9]+$" + * every span field "endTimeUnixNano" matches the regex "^[0-9]+$" + * a span string attribute "bugsnag.app_start.type" equals "cold" + * a span string attribute "bugsnag.phase" equals "App launching - pre main()" + * a span string attribute "bugsnag.phase" equals "App launching - post main()" + * a span string attribute "bugsnag.phase" equals "UI init" + * a span string attribute "bugsnag.span.category" equals "app_start" + * a span string attribute "bugsnag.span.category" equals "app_start_phase" + * a span string attribute "bugsnag.app_start.first_view_name" equals "Fixture.ViewController" + * every span bool attribute "bugsnag.span.first_class" does not exist + * the trace payload field "resourceSpans.0.resource" string attribute "service.name" matches the regex "com.bugsnag.fixtures.cocoaperformance(xcframework)?" + * the trace payload field "resourceSpans.0.resource" string attribute "telemetry.sdk.name" equals "bugsnag.performance.cocoa" + * the trace payload field "resourceSpans.0.resource" string attribute "telemetry.sdk.version" matches the regex "[0-9]+\.[0-9]+\.[0-9]+" + + Scenario: Auto instrument app starts with a view load + Given I run "AutoInstrumentAppStartsWithViewLoadScenario" + And I wait for 3 seconds + And I wait for 13 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$" + * a span field "name" equals "[AppStart/iOSCold]" + * a span field "name" equals "[AppStartPhase/App launching - pre main()]" + * a span field "name" equals "[AppStartPhase/App launching - post main()]" + * a span field "name" equals "[AppStartPhase/UI init]" + * a span field "name" equals "[ViewLoad/UIKit]/Fixture.ViewController" + * a span field "name" equals "[ViewLoadPhase/loadView]/Fixture.ViewController" + * a span field "name" equals "[ViewLoadPhase/viewDidLoad]/Fixture.ViewController" + * a span field "name" equals "[ViewLoadPhase/viewWillAppear]/Fixture.ViewController" + * a span field "name" equals "[ViewLoadPhase/View appearing]/Fixture.ViewController" + * a span field "name" equals "[ViewLoadPhase/viewDidAppear]/Fixture.ViewController" + * a span field "name" equals "[ViewLoadPhase/viewWillLayoutSubviews]/Fixture.ViewController" + * a span field "name" equals "[ViewLoadPhase/Subview layout]/Fixture.ViewController" + * a span field "name" equals "[ViewLoadPhase/viewDidLayoutSubviews]/Fixture.ViewController" + * 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 1 + * every span field "startTimeUnixNano" matches the regex "^[0-9]+$" + * every span field "endTimeUnixNano" matches the regex "^[0-9]+$" + * a span string attribute "bugsnag.app_start.type" equals "cold" + * a span string attribute "bugsnag.phase" equals "App launching - pre main()" + * a span string attribute "bugsnag.phase" equals "App launching - post main()" + * a span string attribute "bugsnag.phase" equals "UI init" + * a span string attribute "bugsnag.span.category" equals "app_start" + * a span string attribute "bugsnag.span.category" equals "app_start_phase" + * a span bool attribute "bugsnag.span.first_class" is true + * the trace payload field "resourceSpans.0.resource" string attribute "service.name" matches the regex "com.bugsnag.fixtures.cocoaperformance(xcframework)?" + * the trace payload field "resourceSpans.0.resource" string attribute "telemetry.sdk.name" equals "bugsnag.performance.cocoa" + * the trace payload field "resourceSpans.0.resource" string attribute "telemetry.sdk.version" matches the regex "[0-9]+\.[0-9]+\.[0-9]+" + * a span named "[ViewLoad/UIKit]/Fixture.ViewController" is a child of span named "[AppStartPhase/UI init]" + * a span named "[ViewLoadPhase/loadView]/Fixture.ViewController" is a child of span named "[ViewLoad/UIKit]/Fixture.ViewController" + * a span named "[ViewLoadPhase/viewDidLoad]/Fixture.ViewController" is a child of span named "[ViewLoad/UIKit]/Fixture.ViewController" + * a span named "[ViewLoadPhase/viewWillAppear]/Fixture.ViewController" is a child of span named "[ViewLoad/UIKit]/Fixture.ViewController" + * a span named "[ViewLoadPhase/View appearing]/Fixture.ViewController" is a child of span named "[ViewLoad/UIKit]/Fixture.ViewController" + * a span named "[ViewLoadPhase/viewDidAppear]/Fixture.ViewController" is a child of span named "[ViewLoad/UIKit]/Fixture.ViewController" + * a span named "[ViewLoadPhase/viewWillLayoutSubviews]/Fixture.ViewController" is a child of span named "[ViewLoad/UIKit]/Fixture.ViewController" + * a span named "[ViewLoadPhase/Subview layout]/Fixture.ViewController" is a child of span named "[ViewLoad/UIKit]/Fixture.ViewController" + * a span named "[ViewLoadPhase/viewDidLayoutSubviews]/Fixture.ViewController" is a child of span named "[ViewLoad/UIKit]/Fixture.ViewController" + * a span named "[AppStart/iOSCold]" ended at the same time as a span named "[AppStartPhase/UI init]" + * a span named "[ViewLoad/UIKit]/Fixture.ViewController" ended before a span named "[AppStartPhase/UI init]" diff --git a/features/default/automatic/automatic_network.feature b/features/default/automatic/automatic_network.feature new file mode 100644 index 00000000..76c1aa65 --- /dev/null +++ b/features/default/automatic/automatic_network.feature @@ -0,0 +1,238 @@ +Feature: Automatic network instrumentation spans + + Scenario: Automatically start a network span that has a parent + Given I run "AutoInstrumentNetworkWithParentScenario" + And I wait for 2 seconds + And I wait for 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$" + * a span field "parentSpanId" exists + * a span field "parentSpanId" is greater than 0 + * a span field "parentSpanId" does not exist + * a span field "name" equals "[HTTP/GET]" + * a span string attribute "http.flavor" exists +# * a span string attribute "http.url" matches the regex "http://.*:9[0-9]{3}/reflect\?status=200" + * a span string attribute "http.method" equals "GET" + * a span integer attribute "http.status_code" is greater than 0 + * a span integer attribute "http.response_content_length" is greater than 0 + * a span string attribute "net.host.connection.type" equals "wifi" + * every span field "spanId" matches the regex "^[A-Fa-f0-9]{16}$" + * every span field "traceId" matches the regex "^[A-Fa-f0-9]{32}$" + * a span field "kind" equals 1 + * a 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 bool attribute "bugsnag.span.first_class" is true + * a span bool attribute "bugsnag.span.first_class" does not exist + * the trace payload field "resourceSpans.0.resource" string attribute "service.name" matches the regex "com.bugsnag.fixtures.cocoaperformance(xcframework)?" + * the trace payload field "resourceSpans.0.resource" string attribute "telemetry.sdk.name" equals "bugsnag.performance.cocoa" + * the trace payload field "resourceSpans.0.resource" string attribute "telemetry.sdk.version" matches the regex "[0-9]+\.[0-9]+\.[0-9]+" + + Scenario: Automatically start a network span that has no parent + Given I run "AutoInstrumentNetworkNoParentScenario" + And I wait for 2 seconds + And I wait for 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 "parentSpanId" does not exist + * a span field "name" equals "[HTTP/GET]" + * a span string attribute "http.flavor" exists +# * a span string attribute "http.url" matches the regex "http://.*:9[0-9]{3}/reflect\?status=200" + * a span string attribute "http.method" equals "GET" + * a span integer attribute "http.status_code" is greater than 0 + * a span integer attribute "http.response_content_length" is greater than 0 + * a span string attribute "net.host.connection.type" equals "wifi" + * every span field "spanId" matches the regex "^[A-Fa-f0-9]{16}$" + * every span field "traceId" matches the regex "^[A-Fa-f0-9]{32}$" + * a span field "kind" equals 1 + * a 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 bool attribute "bugsnag.span.first_class" is true + * a span bool attribute "bugsnag.span.first_class" does not exist + * the trace payload field "resourceSpans.0.resource" string attribute "service.name" matches the regex "com.bugsnag.fixtures.cocoaperformance(xcframework)?" + * the trace payload field "resourceSpans.0.resource" string attribute "telemetry.sdk.name" equals "bugsnag.performance.cocoa" + * the trace payload field "resourceSpans.0.resource" string attribute "telemetry.sdk.version" matches the regex "[0-9]+\.[0-9]+\.[0-9]+" + + Scenario: Auto-capture multiple network spans + Given I run "AutoInstrumentNetworkMultiple" + And I wait for 10 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 "parentSpanId" does not exist + * a span field "name" equals "[HTTP/GET]" + * a span string attribute "http.flavor" exists + * a span string attribute "http.method" equals "GET" + * a span integer attribute "http.status_code" is greater than 0 + * a span integer attribute "http.response_content_length" is greater than 0 + * a span string attribute "net.host.connection.type" equals "wifi" + * 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]+$" + * the trace payload field "resourceSpans.0.resource" string attribute "service.name" matches the regex "com.bugsnag.fixtures.cocoaperformance(xcframework)?" + * the trace payload field "resourceSpans.0.resource" string attribute "telemetry.sdk.name" equals "bugsnag.performance.cocoa" + * the trace payload field "resourceSpans.0.resource" string attribute "telemetry.sdk.version" matches the regex "[0-9]+\.[0-9]+\.[0-9]+" + + Scenario: Automatically start a network span that is a file:// scheme + Given I run "AutoInstrumentFileURLRequestScenario" + Then I should receive no traces + + Scenario: Don't send an auto network span that failed to send + Given I run "AutoInstrumentNetworkBadAddressScenario" + # Only the initial command request should be captured. + Then I wait for 1 span + + Scenario: Automatically start a network span that has a null URL + Given I run "AutoInstrumentNetworkNullURLScenario" + And I wait for 1 span + 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 "parentSpanId" does not exist + * a span field "name" equals "[HTTP/GET]" + * a span string attribute "http.flavor" exists +# * a span string attribute "http.url" matches the regex "http://.*:9[0-9]{3}/reflect\?status=200" + * a span string attribute "http.method" equals "GET" + * a span integer attribute "http.status_code" is greater than 0 + * a span integer attribute "http.response_content_length" is greater than 0 + * a span string attribute "net.host.connection.type" equals "wifi" + * 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]+$" + * the trace payload field "resourceSpans.0.resource" string attribute "service.name" matches the regex "com.bugsnag.fixtures.cocoaperformance(xcframework)?" + * the trace payload field "resourceSpans.0.resource" string attribute "telemetry.sdk.name" equals "bugsnag.performance.cocoa" + * the trace payload field "resourceSpans.0.resource" string attribute "telemetry.sdk.version" matches the regex "[0-9]+\.[0-9]+\.[0-9]+" + + Scenario: Automatically start a network span triggered by AVAssetDownloadURLSession (must not crash) + Given I run "AutoInstrumentAVAssetScenario" + And I wait for 2 seconds + And I wait for 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 "parentSpanId" does not exist + * a 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}$" + * a span field "kind" equals 1 + * a 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 "bugsnag.instrumentation_message" exists + * a span string attribute "bugsnag.instrumentation_message" matches the regex "Error.*" + * a span bool attribute "bugsnag.span.first_class" is true + * a span bool attribute "bugsnag.span.first_class" does not exist + * a span string attribute "bugsnag.span.category" equals "network" + * a span string attribute "http.url" equals "unknown" + * the trace payload field "resourceSpans.0.resource" string attribute "service.name" matches the regex "com.bugsnag.fixtures.cocoaperformance(xcframework)?" + * the trace payload field "resourceSpans.0.resource" string attribute "telemetry.sdk.name" equals "bugsnag.performance.cocoa" + * the trace payload field "resourceSpans.0.resource" string attribute "telemetry.sdk.version" matches the regex "[0-9]+\.[0-9]+\.[0-9]+" + + Scenario: Capture automatic network span before configuration + Given I run "AutoInstrumentNetworkPreStartScenario" + And I wait for 2 seconds + And I wait for exactly 1 span + 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 "parentSpanId" does not exist + * a span field "name" equals "[HTTP/GET]" + * a span string attribute "http.flavor" exists +# * a span string attribute "http.url" matches the regex "http://.*:9[0-9]{3}/reflect\?status=200" + * a span string attribute "http.method" equals "GET" + * a span integer attribute "http.status_code" is greater than 0 + * a span integer attribute "http.response_content_length" is greater than 0 + * a span string attribute "net.host.connection.type" equals "wifi" + * 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]+$" + * the trace payload field "resourceSpans.0.resource" string attribute "service.name" matches the regex "com.bugsnag.fixtures.cocoaperformance(xcframework)?" + * the trace payload field "resourceSpans.0.resource" string attribute "telemetry.sdk.name" equals "bugsnag.performance.cocoa" + * the trace payload field "resourceSpans.0.resource" string attribute "telemetry.sdk.version" matches the regex "[0-9]+\.[0-9]+\.[0-9]+" + + Scenario: Invalidate calls on shared session should be ignored + Given I run "AutoInstrumentNetworkSharedSessionInvalidateScenario" + And I wait for exactly 1 span + 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 "parentSpanId" does not exist + * a span field "name" equals "[HTTP/GET]" + * a span string attribute "http.flavor" exists +# * a span string attribute "http.url" matches the regex "http://.*:9[0-9]{3}/reflect\?status=200" + * a span string attribute "http.method" equals "GET" + * a span integer attribute "http.status_code" is greater than 0 + * a span integer attribute "http.response_content_length" is greater than 0 + * a span string attribute "net.host.connection.type" equals "wifi" + * 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]+$" + * the trace payload field "resourceSpans.0.resource" string attribute "service.name" matches the regex "com.bugsnag.fixtures.cocoaperformance(xcframework)?" + * the trace payload field "resourceSpans.0.resource" string attribute "telemetry.sdk.name" equals "bugsnag.performance.cocoa" + * the trace payload field "resourceSpans.0.resource" string attribute "telemetry.sdk.version" matches the regex "[0-9]+\.[0-9]+\.[0-9]+" + + Scenario: Capture automatic network span before configuration (disabled) + Given I run "AutoInstrumentNetworkPreStartDisabledScenario" + And I should receive no traces + + Scenario: AutoInstrumentNetworkCallbackScenario + Given I run "AutoInstrumentNetworkCallbackScenario" + And I wait for exactly 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. + And I wait for 20 seconds + # There will actually be any number of requests by this point since we're not filtering at all. + And I wait for 1 span + 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$" + * a 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" + + Scenario: AutoInstrumentNetworkTracePropagationScenario: Allow All + Given I load scenario "AutoInstrumentNetworkTracePropagationScenario" + And I configure bugsnag "propagateTraceParentToUrlsMatching" to ".*" + And I invoke "setCallSitesWithCallSiteStrs:" with parameter "?test=1" + And I start bugsnag + And I run the loaded scenario + And I wait to receive a reflection + Then the reflection "traceparent" header matches the regex "^00-[A-Fa-f0-9]{32}-[A-Fa-f0-9]{16}-01" + + Scenario: AutoInstrumentNetworkTracePropagationScenario: Allow None by default + Given I load scenario "AutoInstrumentNetworkTracePropagationScenario" + And I invoke "setCallSitesWithCallSiteStrs:" with parameter "?test=1" + And I start bugsnag + And I run the loaded scenario + And I wait to receive a reflection + Then the reflection "traceparent" header is not present + + Scenario: AutoInstrumentNetworkTracePropagationScenario: Allow Some + Given I load scenario "AutoInstrumentNetworkTracePropagationScenario" + And I configure bugsnag "propagateTraceParentToUrlsMatching" to ".*test.*" + And I invoke "setCallSitesWithCallSiteStrs:" with parameter "?test=1,?temp=1" + And I start bugsnag + And I run the loaded scenario + Then I wait to receive 2 reflections + And the reflection "traceparent" header matches the regex "^00-[A-Fa-f0-9]{32}-[A-Fa-f0-9]{16}-01" + Then I discard the oldest reflection + And the reflection "traceparent" header is not present diff --git a/features/default/automatic_spans_generic_view_load.feature b/features/default/automatic/automatic_spans_generic_view_load.feature similarity index 100% rename from features/default/automatic_spans_generic_view_load.feature rename to features/default/automatic/automatic_spans_generic_view_load.feature diff --git a/features/default/automatic/automatic_swift_ui.feature b/features/default/automatic/automatic_swift_ui.feature new file mode 100644 index 00000000..e9c3683d --- /dev/null +++ b/features/default/automatic/automatic_swift_ui.feature @@ -0,0 +1,140 @@ +Feature: Automatic swift UI spans + + Scenario: AutoInstrumentSwiftUIScenario no change + Given I run "AutoInstrumentSwiftUIScenario" + And I wait for 3 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$" + * a span field "name" equals "[ViewLoad/SwiftUI]/My VStack view" + * a span field "name" equals "[ViewLoadPhase/body]/My VStack view" + * a span field "name" equals "[ViewLoadPhase/body]/My Image view" + # ios < 15 won't have the "view appearing" span + * 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 1 + * every span field "startTimeUnixNano" matches the regex "^[0-9]+$" + * every span field "endTimeUnixNano" matches the regex "^[0-9]+$" + * a span string attribute "bugsnag.span.category" equals "view_load" + * a span string attribute "bugsnag.span.category" equals "view_load_phase" + * a span string attribute "bugsnag.view.name" equals "My VStack view" + * a span string attribute "bugsnag.view.name" equals "My Image view" + * a span bool attribute "bugsnag.span.first_class" is true + * a span string attribute "bugsnag.view.type" equals "SwiftUI" + * the trace payload field "resourceSpans.0.resource" string attribute "service.name" matches the regex "com.bugsnag.fixtures.cocoaperformance(xcframework)?" + * the trace payload field "resourceSpans.0.resource" string attribute "telemetry.sdk.name" equals "bugsnag.performance.cocoa" + * the trace payload field "resourceSpans.0.resource" string attribute "telemetry.sdk.version" matches the regex "[0-9]+\.[0-9]+\.[0-9]+" + + Scenario: AutoInstrumentSwiftUIScenario with change + Given I run "AutoInstrumentSwiftUIScenario" + And I wait for 3 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$" + * a span field "name" equals "[ViewLoad/SwiftUI]/My VStack view" + * a span field "name" equals "[ViewLoadPhase/body]/My VStack view" + * a span field "name" equals "[ViewLoadPhase/body]/My Image view" + * 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 1 + * every span field "startTimeUnixNano" matches the regex "^[0-9]+$" + * every span field "endTimeUnixNano" matches the regex "^[0-9]+$" + * a span string attribute "bugsnag.span.category" equals "view_load" + * a span string attribute "bugsnag.span.category" equals "view_load_phase" + * a span string attribute "bugsnag.view.name" equals "My VStack view" + * a span string attribute "bugsnag.view.name" equals "My Image view" + * a span bool attribute "bugsnag.span.first_class" is true + * a span string attribute "bugsnag.view.type" equals "SwiftUI" + * the trace payload field "resourceSpans.0.resource" string attribute "service.name" matches the regex "com.bugsnag.fixtures.cocoaperformance(xcframework)?" + * the trace payload field "resourceSpans.0.resource" string attribute "telemetry.sdk.name" equals "bugsnag.performance.cocoa" + * the trace payload field "resourceSpans.0.resource" string attribute "telemetry.sdk.version" matches the regex "[0-9]+\.[0-9]+\.[0-9]+" + And I discard every trace + And I invoke "switchView" + Then I wait for 2 spans + * a span field "name" equals "[ViewLoad/SwiftUI]/Text" + * a span field "name" equals "[ViewLoadPhase/body]/Text" + * 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 1 + * every span field "startTimeUnixNano" matches the regex "^[0-9]+$" + * every span field "endTimeUnixNano" matches the regex "^[0-9]+$" + * a span string attribute "bugsnag.span.category" equals "view_load" + * a span string attribute "bugsnag.span.category" equals "view_load_phase" + * every span string attribute "bugsnag.view.name" equals "Text" + * a span bool attribute "bugsnag.span.first_class" is true + * a span string attribute "bugsnag.view.type" equals "SwiftUI" + + Scenario: AutoInstrumentSwiftUIDeferredScenario toggleEndSpanDefer + Given I run "AutoInstrumentSwiftUIDeferredScenario" + And I wait for 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$" + * a span field "name" equals "[ViewLoadPhase/body]/vstack1" + * a span field "name" equals "[ViewLoadPhase/body]/text1" + * 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 1 + * every span field "startTimeUnixNano" matches the regex "^[0-9]+$" + * every span field "endTimeUnixNano" matches the regex "^[0-9]+$" + * every span string attribute "bugsnag.span.category" equals "view_load_phase" + * a span string attribute "bugsnag.view.name" equals "vstack1" + * a span string attribute "bugsnag.view.name" equals "text1" + * the trace payload field "resourceSpans.0.resource" string attribute "service.name" matches the regex "com.bugsnag.fixtures.cocoaperformance(xcframework)?" + * the trace payload field "resourceSpans.0.resource" string attribute "telemetry.sdk.name" equals "bugsnag.performance.cocoa" + * the trace payload field "resourceSpans.0.resource" string attribute "telemetry.sdk.version" matches the regex "[0-9]+\.[0-9]+\.[0-9]+" + Then I discard every trace + And I invoke "toggleEndSpanDefer" + And I wait for 2 spans + * a span field "name" equals "[ViewLoadPhase/body]/vstack1" + * a span field "name" equals "[ViewLoadPhase/body]/text1" + * 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 1 + * every span field "startTimeUnixNano" matches the regex "^[0-9]+$" + * every span field "endTimeUnixNano" matches the regex "^[0-9]+$" + * every span string attribute "bugsnag.span.category" equals "view_load_phase" + * a span string attribute "bugsnag.view.name" equals "vstack1" + * a span string attribute "bugsnag.view.name" equals "text1" + + Scenario: AutoInstrumentSwiftUIDeferredScenario toggle everything + Given I run "AutoInstrumentSwiftUIDeferredScenario" + And I wait for 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$" + * a span field "name" equals "[ViewLoadPhase/body]/vstack1" + * a span field "name" equals "[ViewLoadPhase/body]/text1" + * 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 1 + * every span field "startTimeUnixNano" matches the regex "^[0-9]+$" + * every span field "endTimeUnixNano" matches the regex "^[0-9]+$" + * every span string attribute "bugsnag.span.category" equals "view_load_phase" + * a span string attribute "bugsnag.view.name" equals "vstack1" + * a span string attribute "bugsnag.view.name" equals "text1" + * the trace payload field "resourceSpans.0.resource" string attribute "service.name" matches the regex "com.bugsnag.fixtures.cocoaperformance(xcframework)?" + * the trace payload field "resourceSpans.0.resource" string attribute "telemetry.sdk.name" equals "bugsnag.performance.cocoa" + * the trace payload field "resourceSpans.0.resource" string attribute "telemetry.sdk.version" matches the regex "[0-9]+\.[0-9]+\.[0-9]+" + Then I discard every trace + And I invoke "toggleEndSpanDefer" + And I wait for 2 spans + * a span field "name" equals "[ViewLoadPhase/body]/vstack1" + * a span field "name" equals "[ViewLoadPhase/body]/text1" + * 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 1 + * every span field "startTimeUnixNano" matches the regex "^[0-9]+$" + * every span field "endTimeUnixNano" matches the regex "^[0-9]+$" + * every span string attribute "bugsnag.span.category" equals "view_load_phase" + * a span string attribute "bugsnag.view.name" equals "vstack1" + * a span string attribute "bugsnag.view.name" equals "text1" + Then I discard every trace + And I invoke "toggleHideText1" + And I wait for 2 spans + * a span field "name" equals "[ViewLoad/SwiftUI]/vstack1" + * a span field "name" equals "[ViewLoadPhase/body]/vstack1" + * 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 1 + * every span field "startTimeUnixNano" matches the regex "^[0-9]+$" + * every span field "endTimeUnixNano" matches the regex "^[0-9]+$" + * a span string attribute "bugsnag.span.category" equals "view_load" + * a span string attribute "bugsnag.span.category" equals "view_load_phase" + * every span string attribute "bugsnag.view.name" equals "vstack1" diff --git a/features/default/automatic_spans.feature b/features/default/automatic/automatic_view_load.feature similarity index 57% rename from features/default/automatic_spans.feature rename to features/default/automatic/automatic_view_load.feature index 5cc2cc2c..600c915b 100644 --- a/features/default/automatic_spans.feature +++ b/features/default/automatic/automatic_view_load.feature @@ -1,78 +1,4 @@ -Feature: Automatic instrumentation spans - - Scenario: Auto instrument app starts without a view load - Given I run "AutoInstrumentAppStartsScenario" - And I wait for 3 seconds - And I wait for 4 spans - Then the trace "Content-Type" header equals "application/json" - * the trace "Bugsnag-Span-Sampling" header equals "1:4" - * 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 "[AppStart/iOSCold]" - * a span field "name" equals "[AppStartPhase/App launching - pre main()]" - * a span field "name" equals "[AppStartPhase/App launching - post main()]" - * a span field "name" equals "[AppStartPhase/UI init]" - * 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 1 - * every span field "startTimeUnixNano" matches the regex "^[0-9]+$" - * every span field "endTimeUnixNano" matches the regex "^[0-9]+$" - * a span string attribute "bugsnag.app_start.type" equals "cold" - * a span string attribute "bugsnag.phase" equals "App launching - pre main()" - * a span string attribute "bugsnag.phase" equals "App launching - post main()" - * a span string attribute "bugsnag.phase" equals "UI init" - * a span string attribute "bugsnag.span.category" equals "app_start" - * a span string attribute "bugsnag.span.category" equals "app_start_phase" - * a span string attribute "bugsnag.app_start.first_view_name" equals "Fixture.ViewController" - * every span bool attribute "bugsnag.span.first_class" does not exist - * the trace payload field "resourceSpans.0.resource" string attribute "service.name" matches the regex "com.bugsnag.fixtures.cocoaperformance(xcframework)?" - * the trace payload field "resourceSpans.0.resource" string attribute "telemetry.sdk.name" equals "bugsnag.performance.cocoa" - * the trace payload field "resourceSpans.0.resource" string attribute "telemetry.sdk.version" matches the regex "[0-9]+\.[0-9]+\.[0-9]+" - - Scenario: Auto instrument app starts with a view load - Given I run "AutoInstrumentAppStartsWithViewLoadScenario" - And I wait for 3 seconds - And I wait for 13 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$" - * a span field "name" equals "[AppStart/iOSCold]" - * a span field "name" equals "[AppStartPhase/App launching - pre main()]" - * a span field "name" equals "[AppStartPhase/App launching - post main()]" - * a span field "name" equals "[AppStartPhase/UI init]" - * a span field "name" equals "[ViewLoad/UIKit]/Fixture.ViewController" - * a span field "name" equals "[ViewLoadPhase/loadView]/Fixture.ViewController" - * a span field "name" equals "[ViewLoadPhase/viewDidLoad]/Fixture.ViewController" - * a span field "name" equals "[ViewLoadPhase/viewWillAppear]/Fixture.ViewController" - * a span field "name" equals "[ViewLoadPhase/View appearing]/Fixture.ViewController" - * a span field "name" equals "[ViewLoadPhase/viewDidAppear]/Fixture.ViewController" - * a span field "name" equals "[ViewLoadPhase/viewWillLayoutSubviews]/Fixture.ViewController" - * a span field "name" equals "[ViewLoadPhase/Subview layout]/Fixture.ViewController" - * a span field "name" equals "[ViewLoadPhase/viewDidLayoutSubviews]/Fixture.ViewController" - * 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 1 - * every span field "startTimeUnixNano" matches the regex "^[0-9]+$" - * every span field "endTimeUnixNano" matches the regex "^[0-9]+$" - * a span string attribute "bugsnag.app_start.type" equals "cold" - * a span string attribute "bugsnag.phase" equals "App launching - pre main()" - * a span string attribute "bugsnag.phase" equals "App launching - post main()" - * a span string attribute "bugsnag.phase" equals "UI init" - * a span string attribute "bugsnag.span.category" equals "app_start" - * a span string attribute "bugsnag.span.category" equals "app_start_phase" - * a span bool attribute "bugsnag.span.first_class" is true - * the trace payload field "resourceSpans.0.resource" string attribute "service.name" matches the regex "com.bugsnag.fixtures.cocoaperformance(xcframework)?" - * the trace payload field "resourceSpans.0.resource" string attribute "telemetry.sdk.name" equals "bugsnag.performance.cocoa" - * the trace payload field "resourceSpans.0.resource" string attribute "telemetry.sdk.version" matches the regex "[0-9]+\.[0-9]+\.[0-9]+" - * a span named "[ViewLoad/UIKit]/Fixture.ViewController" is a child of span named "[AppStartPhase/UI init]" - * a span named "[ViewLoadPhase/loadView]/Fixture.ViewController" is a child of span named "[ViewLoad/UIKit]/Fixture.ViewController" - * a span named "[ViewLoadPhase/viewDidLoad]/Fixture.ViewController" is a child of span named "[ViewLoad/UIKit]/Fixture.ViewController" - * a span named "[ViewLoadPhase/viewWillAppear]/Fixture.ViewController" is a child of span named "[ViewLoad/UIKit]/Fixture.ViewController" - * a span named "[ViewLoadPhase/View appearing]/Fixture.ViewController" is a child of span named "[ViewLoad/UIKit]/Fixture.ViewController" - * a span named "[ViewLoadPhase/viewDidAppear]/Fixture.ViewController" is a child of span named "[ViewLoad/UIKit]/Fixture.ViewController" - * a span named "[ViewLoadPhase/viewWillLayoutSubviews]/Fixture.ViewController" is a child of span named "[ViewLoad/UIKit]/Fixture.ViewController" - * a span named "[ViewLoadPhase/Subview layout]/Fixture.ViewController" is a child of span named "[ViewLoad/UIKit]/Fixture.ViewController" - * a span named "[ViewLoadPhase/viewDidLayoutSubviews]/Fixture.ViewController" is a child of span named "[ViewLoad/UIKit]/Fixture.ViewController" - * a span named "[AppStart/iOSCold]" ended at the same time as a span named "[AppStartPhase/UI init]" - * a span named "[ViewLoad/UIKit]/Fixture.ViewController" ended before a span named "[AppStartPhase/UI init]" +Feature: Automatic view load instrumentation spans Scenario: AutoInstrumentViewLoadScenario Given I run "AutoInstrumentViewLoadScenario" @@ -427,345 +353,6 @@ Feature: Automatic instrumentation spans * the trace payload field "resourceSpans.0.resource" string attribute "telemetry.sdk.name" equals "bugsnag.performance.cocoa" * the trace payload field "resourceSpans.0.resource" string attribute "telemetry.sdk.version" matches the regex "[0-9]+\.[0-9]+\.[0-9]+" - Scenario: AutoInstrumentSwiftUIScenario no change - Given I run "AutoInstrumentSwiftUIScenario" - And I wait for 3 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$" - * a span field "name" equals "[ViewLoad/SwiftUI]/My VStack view" - * a span field "name" equals "[ViewLoadPhase/body]/My VStack view" - * a span field "name" equals "[ViewLoadPhase/body]/My Image view" - # ios < 15 won't have the "view appearing" span - * 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 1 - * every span field "startTimeUnixNano" matches the regex "^[0-9]+$" - * every span field "endTimeUnixNano" matches the regex "^[0-9]+$" - * a span string attribute "bugsnag.span.category" equals "view_load" - * a span string attribute "bugsnag.span.category" equals "view_load_phase" - * a span string attribute "bugsnag.view.name" equals "My VStack view" - * a span string attribute "bugsnag.view.name" equals "My Image view" - * a span bool attribute "bugsnag.span.first_class" is true - * a span string attribute "bugsnag.view.type" equals "SwiftUI" - * the trace payload field "resourceSpans.0.resource" string attribute "service.name" matches the regex "com.bugsnag.fixtures.cocoaperformance(xcframework)?" - * the trace payload field "resourceSpans.0.resource" string attribute "telemetry.sdk.name" equals "bugsnag.performance.cocoa" - * the trace payload field "resourceSpans.0.resource" string attribute "telemetry.sdk.version" matches the regex "[0-9]+\.[0-9]+\.[0-9]+" - - Scenario: AutoInstrumentSwiftUIScenario with change - Given I run "AutoInstrumentSwiftUIScenario" - And I wait for 3 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$" - * a span field "name" equals "[ViewLoad/SwiftUI]/My VStack view" - * a span field "name" equals "[ViewLoadPhase/body]/My VStack view" - * a span field "name" equals "[ViewLoadPhase/body]/My Image view" - * 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 1 - * every span field "startTimeUnixNano" matches the regex "^[0-9]+$" - * every span field "endTimeUnixNano" matches the regex "^[0-9]+$" - * a span string attribute "bugsnag.span.category" equals "view_load" - * a span string attribute "bugsnag.span.category" equals "view_load_phase" - * a span string attribute "bugsnag.view.name" equals "My VStack view" - * a span string attribute "bugsnag.view.name" equals "My Image view" - * a span bool attribute "bugsnag.span.first_class" is true - * a span string attribute "bugsnag.view.type" equals "SwiftUI" - * the trace payload field "resourceSpans.0.resource" string attribute "service.name" matches the regex "com.bugsnag.fixtures.cocoaperformance(xcframework)?" - * the trace payload field "resourceSpans.0.resource" string attribute "telemetry.sdk.name" equals "bugsnag.performance.cocoa" - * the trace payload field "resourceSpans.0.resource" string attribute "telemetry.sdk.version" matches the regex "[0-9]+\.[0-9]+\.[0-9]+" - And I discard every trace - And I invoke "switchView" - Then I wait for 2 spans - * a span field "name" equals "[ViewLoad/SwiftUI]/Text" - * a span field "name" equals "[ViewLoadPhase/body]/Text" - * 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 1 - * every span field "startTimeUnixNano" matches the regex "^[0-9]+$" - * every span field "endTimeUnixNano" matches the regex "^[0-9]+$" - * a span string attribute "bugsnag.span.category" equals "view_load" - * a span string attribute "bugsnag.span.category" equals "view_load_phase" - * every span string attribute "bugsnag.view.name" equals "Text" - * a span bool attribute "bugsnag.span.first_class" is true - * a span string attribute "bugsnag.view.type" equals "SwiftUI" - - Scenario: AutoInstrumentSwiftUIDeferredScenario toggleEndSpanDefer - Given I run "AutoInstrumentSwiftUIDeferredScenario" - And I wait for 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$" - * a span field "name" equals "[ViewLoadPhase/body]/vstack1" - * a span field "name" equals "[ViewLoadPhase/body]/text1" - * 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 1 - * every span field "startTimeUnixNano" matches the regex "^[0-9]+$" - * every span field "endTimeUnixNano" matches the regex "^[0-9]+$" - * every span string attribute "bugsnag.span.category" equals "view_load_phase" - * a span string attribute "bugsnag.view.name" equals "vstack1" - * a span string attribute "bugsnag.view.name" equals "text1" - * the trace payload field "resourceSpans.0.resource" string attribute "service.name" matches the regex "com.bugsnag.fixtures.cocoaperformance(xcframework)?" - * the trace payload field "resourceSpans.0.resource" string attribute "telemetry.sdk.name" equals "bugsnag.performance.cocoa" - * the trace payload field "resourceSpans.0.resource" string attribute "telemetry.sdk.version" matches the regex "[0-9]+\.[0-9]+\.[0-9]+" - Then I discard every trace - And I invoke "toggleEndSpanDefer" - And I wait for 2 spans - * a span field "name" equals "[ViewLoadPhase/body]/vstack1" - * a span field "name" equals "[ViewLoadPhase/body]/text1" - * 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 1 - * every span field "startTimeUnixNano" matches the regex "^[0-9]+$" - * every span field "endTimeUnixNano" matches the regex "^[0-9]+$" - * every span string attribute "bugsnag.span.category" equals "view_load_phase" - * a span string attribute "bugsnag.view.name" equals "vstack1" - * a span string attribute "bugsnag.view.name" equals "text1" - - Scenario: AutoInstrumentSwiftUIDeferredScenario toggle everything - Given I run "AutoInstrumentSwiftUIDeferredScenario" - And I wait for 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$" - * a span field "name" equals "[ViewLoadPhase/body]/vstack1" - * a span field "name" equals "[ViewLoadPhase/body]/text1" - * 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 1 - * every span field "startTimeUnixNano" matches the regex "^[0-9]+$" - * every span field "endTimeUnixNano" matches the regex "^[0-9]+$" - * every span string attribute "bugsnag.span.category" equals "view_load_phase" - * a span string attribute "bugsnag.view.name" equals "vstack1" - * a span string attribute "bugsnag.view.name" equals "text1" - * the trace payload field "resourceSpans.0.resource" string attribute "service.name" matches the regex "com.bugsnag.fixtures.cocoaperformance(xcframework)?" - * the trace payload field "resourceSpans.0.resource" string attribute "telemetry.sdk.name" equals "bugsnag.performance.cocoa" - * the trace payload field "resourceSpans.0.resource" string attribute "telemetry.sdk.version" matches the regex "[0-9]+\.[0-9]+\.[0-9]+" - Then I discard every trace - And I invoke "toggleEndSpanDefer" - And I wait for 2 spans - * a span field "name" equals "[ViewLoadPhase/body]/vstack1" - * a span field "name" equals "[ViewLoadPhase/body]/text1" - * 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 1 - * every span field "startTimeUnixNano" matches the regex "^[0-9]+$" - * every span field "endTimeUnixNano" matches the regex "^[0-9]+$" - * every span string attribute "bugsnag.span.category" equals "view_load_phase" - * a span string attribute "bugsnag.view.name" equals "vstack1" - * a span string attribute "bugsnag.view.name" equals "text1" - Then I discard every trace - And I invoke "toggleHideText1" - And I wait for 2 spans - * a span field "name" equals "[ViewLoad/SwiftUI]/vstack1" - * a span field "name" equals "[ViewLoadPhase/body]/vstack1" - * 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 1 - * every span field "startTimeUnixNano" matches the regex "^[0-9]+$" - * every span field "endTimeUnixNano" matches the regex "^[0-9]+$" - * a span string attribute "bugsnag.span.category" equals "view_load" - * a span string attribute "bugsnag.span.category" equals "view_load_phase" - * every span string attribute "bugsnag.view.name" equals "vstack1" - - Scenario: Automatically start a network span that has a parent - Given I run "AutoInstrumentNetworkWithParentScenario" - And I wait for 2 seconds - And I wait for 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$" - * a span field "parentSpanId" exists - * a span field "parentSpanId" is greater than 0 - * a span field "parentSpanId" does not exist - * a span field "name" equals "[HTTP/GET]" - * a span string attribute "http.flavor" exists -# * a span string attribute "http.url" matches the regex "http://.*:9[0-9]{3}/reflect\?status=200" - * a span string attribute "http.method" equals "GET" - * a span integer attribute "http.status_code" is greater than 0 - * a span integer attribute "http.response_content_length" is greater than 0 - * a span string attribute "net.host.connection.type" equals "wifi" - * every span field "spanId" matches the regex "^[A-Fa-f0-9]{16}$" - * every span field "traceId" matches the regex "^[A-Fa-f0-9]{32}$" - * a span field "kind" equals 1 - * a 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 bool attribute "bugsnag.span.first_class" is true - * a span bool attribute "bugsnag.span.first_class" does not exist - * the trace payload field "resourceSpans.0.resource" string attribute "service.name" matches the regex "com.bugsnag.fixtures.cocoaperformance(xcframework)?" - * the trace payload field "resourceSpans.0.resource" string attribute "telemetry.sdk.name" equals "bugsnag.performance.cocoa" - * the trace payload field "resourceSpans.0.resource" string attribute "telemetry.sdk.version" matches the regex "[0-9]+\.[0-9]+\.[0-9]+" - - Scenario: Automatically start a network span that has no parent - Given I run "AutoInstrumentNetworkNoParentScenario" - And I wait for 2 seconds - And I wait for 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 "parentSpanId" does not exist - * a span field "name" equals "[HTTP/GET]" - * a span string attribute "http.flavor" exists -# * a span string attribute "http.url" matches the regex "http://.*:9[0-9]{3}/reflect\?status=200" - * a span string attribute "http.method" equals "GET" - * a span integer attribute "http.status_code" is greater than 0 - * a span integer attribute "http.response_content_length" is greater than 0 - * a span string attribute "net.host.connection.type" equals "wifi" - * every span field "spanId" matches the regex "^[A-Fa-f0-9]{16}$" - * every span field "traceId" matches the regex "^[A-Fa-f0-9]{32}$" - * a span field "kind" equals 1 - * a 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 bool attribute "bugsnag.span.first_class" is true - * a span bool attribute "bugsnag.span.first_class" does not exist - * the trace payload field "resourceSpans.0.resource" string attribute "service.name" matches the regex "com.bugsnag.fixtures.cocoaperformance(xcframework)?" - * the trace payload field "resourceSpans.0.resource" string attribute "telemetry.sdk.name" equals "bugsnag.performance.cocoa" - * the trace payload field "resourceSpans.0.resource" string attribute "telemetry.sdk.version" matches the regex "[0-9]+\.[0-9]+\.[0-9]+" - - Scenario: Auto-capture multiple network spans - Given I run "AutoInstrumentNetworkMultiple" - And I wait for 10 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 "parentSpanId" does not exist - * a span field "name" equals "[HTTP/GET]" - * a span string attribute "http.flavor" exists - * a span string attribute "http.method" equals "GET" - * a span integer attribute "http.status_code" is greater than 0 - * a span integer attribute "http.response_content_length" is greater than 0 - * a span string attribute "net.host.connection.type" equals "wifi" - * 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]+$" - * the trace payload field "resourceSpans.0.resource" string attribute "service.name" matches the regex "com.bugsnag.fixtures.cocoaperformance(xcframework)?" - * the trace payload field "resourceSpans.0.resource" string attribute "telemetry.sdk.name" equals "bugsnag.performance.cocoa" - * the trace payload field "resourceSpans.0.resource" string attribute "telemetry.sdk.version" matches the regex "[0-9]+\.[0-9]+\.[0-9]+" - - Scenario: Automatically start a network span that is a file:// scheme - Given I run "AutoInstrumentFileURLRequestScenario" - Then I should receive no traces - - Scenario: Don't send an auto network span that failed to send - Given I run "AutoInstrumentNetworkBadAddressScenario" - # Only the initial command request should be captured. - Then I wait for 1 span - - Scenario: Automatically start a network span that has a null URL - Given I run "AutoInstrumentNetworkNullURLScenario" - And I wait for 1 span - 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 "parentSpanId" does not exist - * a span field "name" equals "[HTTP/GET]" - * a span string attribute "http.flavor" exists -# * a span string attribute "http.url" matches the regex "http://.*:9[0-9]{3}/reflect\?status=200" - * a span string attribute "http.method" equals "GET" - * a span integer attribute "http.status_code" is greater than 0 - * a span integer attribute "http.response_content_length" is greater than 0 - * a span string attribute "net.host.connection.type" equals "wifi" - * 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]+$" - * the trace payload field "resourceSpans.0.resource" string attribute "service.name" matches the regex "com.bugsnag.fixtures.cocoaperformance(xcframework)?" - * the trace payload field "resourceSpans.0.resource" string attribute "telemetry.sdk.name" equals "bugsnag.performance.cocoa" - * the trace payload field "resourceSpans.0.resource" string attribute "telemetry.sdk.version" matches the regex "[0-9]+\.[0-9]+\.[0-9]+" - - Scenario: Automatically start a network span triggered by AVAssetDownloadURLSession (must not crash) - Given I run "AutoInstrumentAVAssetScenario" - And I wait for 2 seconds - And I wait for 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 "parentSpanId" does not exist - * a 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}$" - * a span field "kind" equals 1 - * a 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 "bugsnag.instrumentation_message" exists - * a span string attribute "bugsnag.instrumentation_message" matches the regex "Error.*" - * a span bool attribute "bugsnag.span.first_class" is true - * a span bool attribute "bugsnag.span.first_class" does not exist - * a span string attribute "bugsnag.span.category" equals "network" - * a span string attribute "http.url" equals "unknown" - * the trace payload field "resourceSpans.0.resource" string attribute "service.name" matches the regex "com.bugsnag.fixtures.cocoaperformance(xcframework)?" - * the trace payload field "resourceSpans.0.resource" string attribute "telemetry.sdk.name" equals "bugsnag.performance.cocoa" - * the trace payload field "resourceSpans.0.resource" string attribute "telemetry.sdk.version" matches the regex "[0-9]+\.[0-9]+\.[0-9]+" - - Scenario: Capture automatic network span before configuration - Given I run "AutoInstrumentNetworkPreStartScenario" - And I wait for 2 seconds - And I wait for exactly 1 span - 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 "parentSpanId" does not exist - * a span field "name" equals "[HTTP/GET]" - * a span string attribute "http.flavor" exists -# * a span string attribute "http.url" matches the regex "http://.*:9[0-9]{3}/reflect\?status=200" - * a span string attribute "http.method" equals "GET" - * a span integer attribute "http.status_code" is greater than 0 - * a span integer attribute "http.response_content_length" is greater than 0 - * a span string attribute "net.host.connection.type" equals "wifi" - * 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]+$" - * the trace payload field "resourceSpans.0.resource" string attribute "service.name" matches the regex "com.bugsnag.fixtures.cocoaperformance(xcframework)?" - * the trace payload field "resourceSpans.0.resource" string attribute "telemetry.sdk.name" equals "bugsnag.performance.cocoa" - * the trace payload field "resourceSpans.0.resource" string attribute "telemetry.sdk.version" matches the regex "[0-9]+\.[0-9]+\.[0-9]+" - - Scenario: Invalidate calls on shared session should be ignored - Given I run "AutoInstrumentNetworkSharedSessionInvalidateScenario" - And I wait for exactly 1 span - 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 "parentSpanId" does not exist - * a span field "name" equals "[HTTP/GET]" - * a span string attribute "http.flavor" exists -# * a span string attribute "http.url" matches the regex "http://.*:9[0-9]{3}/reflect\?status=200" - * a span string attribute "http.method" equals "GET" - * a span integer attribute "http.status_code" is greater than 0 - * a span integer attribute "http.response_content_length" is greater than 0 - * a span string attribute "net.host.connection.type" equals "wifi" - * 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]+$" - * the trace payload field "resourceSpans.0.resource" string attribute "service.name" matches the regex "com.bugsnag.fixtures.cocoaperformance(xcframework)?" - * the trace payload field "resourceSpans.0.resource" string attribute "telemetry.sdk.name" equals "bugsnag.performance.cocoa" - * the trace payload field "resourceSpans.0.resource" string attribute "telemetry.sdk.version" matches the regex "[0-9]+\.[0-9]+\.[0-9]+" - - Scenario: Capture automatic network span before configuration (disabled) - Given I run "AutoInstrumentNetworkPreStartDisabledScenario" - And I should receive no traces - - Scenario: Make sure OnSpanEnd callbacks also get called for early spans. - Given I run "EarlySpanOnEndScenario" - And I wait for exactly 1 span - 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 "parentSpanId" does not exist - * a span field "name" equals "[HTTP/GET]" - * a span string attribute "http.flavor" exists - * a span string attribute "http.method" equals "GET" - * a span integer attribute "http.status_code" is greater than 0 - * a span integer attribute "http.response_content_length" is greater than 0 - * a span string attribute "net.host.connection.type" equals "wifi" - * 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]+$" - * the trace payload field "resourceSpans.0.resource" string attribute "service.name" matches the regex "com.bugsnag.fixtures.cocoaperformance(xcframework)?" - * the trace payload field "resourceSpans.0.resource" string attribute "telemetry.sdk.name" equals "bugsnag.performance.cocoa" - * the trace payload field "resourceSpans.0.resource" string attribute "telemetry.sdk.version" matches the regex "[0-9]+\.[0-9]+\.[0-9]+" - Scenario: ComplexViewScenario Given I run "ComplexViewScenario" And I wait for 27 spans @@ -816,32 +403,3 @@ Feature: Automatic instrumentation spans * the trace payload field "resourceSpans.0.resource" string attribute "service.name" matches the regex "com.bugsnag.fixtures.cocoaperformance(xcframework)?" * the trace payload field "resourceSpans.0.resource" string attribute "telemetry.sdk.name" equals "bugsnag.performance.cocoa" * the trace payload field "resourceSpans.0.resource" string attribute "telemetry.sdk.version" matches the regex "[0-9]+\.[0-9]+\.[0-9]+" - - Scenario: ModifyEarlySpansScenario - Given I run "ModifyEarlySpansScenario" - And I wait for exactly 5 spans - Then the trace "Content-Type" header equals "application/json" - * the trace "Bugsnag-Span-Sampling" header equals "1:5" - * 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 "[AppStart/iOSCold]" - * a span field "name" equals "[AppStartPhase/App launching - pre main()]" - * a span field "name" equals "[AppStartPhase/App launching - post main()]" - * a span field "name" equals "[AppStartPhase/UI init]" - * a 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}$" - * a span field "kind" equals 1 - * a 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 "bugsnag.app_start.type" equals "cold" - * a span string attribute "bugsnag.phase" equals "App launching - pre main()" - * a span string attribute "bugsnag.phase" equals "App launching - post main()" - * a span string attribute "bugsnag.phase" equals "UI init" - * a span string attribute "bugsnag.span.category" equals "app_start" - * a span string attribute "bugsnag.span.category" equals "app_start_phase" - * every span bool attribute "bugsnag.span.first_class" does not exist - * every span string attribute "modifiedOnEnd" equals "yes" - * the trace payload field "resourceSpans.0.resource" string attribute "service.name" matches the regex "com.bugsnag.fixtures.cocoaperformance(xcframework)?" - * the trace payload field "resourceSpans.0.resource" string attribute "telemetry.sdk.name" equals "bugsnag.performance.cocoa" - * the trace payload field "resourceSpans.0.resource" string attribute "telemetry.sdk.version" matches the regex "[0-9]+\.[0-9]+\.[0-9]+" diff --git a/features/default/callbacks.feature b/features/default/callbacks.feature new file mode 100644 index 00000000..7e298178 --- /dev/null +++ b/features/default/callbacks.feature @@ -0,0 +1,17 @@ +Feature: Setting callbacks + + Scenario: Set OnStart + Given I run "OnStartCallbackScenario" + And I wait for exactly 1 span + * the trace "Bugsnag-Span-Sampling" header equals "1:1" + * 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 "OnStartCallbackScenario" + * a span bool attribute "start_callback_1" is true + * a span bool attribute "start_callback_2" is true + + Scenario: Set OnEnd + Given I run "OnEndCallbackScenario" + And I wait for exactly 1 span + * the trace "Bugsnag-Span-Sampling" header equals "1:1" + * 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" \ No newline at end of file diff --git a/features/default/conditions.feature b/features/default/conditions.feature index d24f8bd8..12e42cfe 100644 --- a/features/default/conditions.feature +++ b/features/default/conditions.feature @@ -51,3 +51,65 @@ Feature: Conditions * every span string attribute "bugsnag.span.category" equals "custom" * a span named "ConditionsOverrideEndTimeBackwardsScenario" duration is equal or greater than 1.0 + Scenario: Span Conditions - condition closed + Given I run "SpanConditionsSimpleConditionScenario" + And I wait for 2 spans + Then the trace "Content-Type" header equals "application/json" + * the trace "Bugsnag-Span-Sampling" header equals "1:2" + * 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 "SpanConditionsSimpleConditionScenarioSpan1" + * a span field "name" equals "SpanConditionsSimpleConditionScenarioSpan2" + * 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 1 + * every span field "startTimeUnixNano" matches the regex "^[0-9]+$" + * every span field "endTimeUnixNano" matches the regex "^[0-9]+$" + * a span named "SpanConditionsSimpleConditionScenarioSpan1" ended after a span named "SpanConditionsSimpleConditionScenarioSpan2" + * a span named "SpanConditionsSimpleConditionScenarioSpan2" is a child of span named "SpanConditionsSimpleConditionScenarioSpan1" + + Scenario: Span Conditions - condition timed out + Given I run "SpanConditionsConditionTimedOutScenario" + And I wait for 2 spans + Then the trace "Content-Type" header equals "application/json" + * the trace "Bugsnag-Span-Sampling" header equals "1:2" + * 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 "SpanConditionsConditionTimedOutScenarioSpan1" + * a span field "name" equals "SpanConditionsConditionTimedOutScenarioSpan2" + * 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 1 + * every span field "startTimeUnixNano" matches the regex "^[0-9]+$" + * every span field "endTimeUnixNano" matches the regex "^[0-9]+$" + * a span named "SpanConditionsConditionTimedOutScenarioSpan1" ended before a span named "SpanConditionsConditionTimedOutScenarioSpan2" started + + Scenario: Span Conditions - multiple conditions + Given I run "SpanConditionsMultipleConditionsScenario" + And I wait for 3 spans + Then the trace "Content-Type" header equals "application/json" + * the trace "Bugsnag-Span-Sampling" header equals "1:3" + * 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 "SpanConditionsMultipleConditionsScenarioSpan1" + * a span field "name" equals "SpanConditionsMultipleConditionsScenarioSpan2" + * a span field "name" equals "SpanConditionsMultipleConditionsScenarioSpan3" + * 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 1 + * every span field "startTimeUnixNano" matches the regex "^[0-9]+$" + * every span field "endTimeUnixNano" matches the regex "^[0-9]+$" + * a span named "SpanConditionsMultipleConditionsScenarioSpan3" ended after a span named "SpanConditionsMultipleConditionsScenarioSpan2" + * a span named "SpanConditionsMultipleConditionsScenarioSpan1" ended after a span named "SpanConditionsMultipleConditionsScenarioSpan3" + + Scenario: Span Conditions - blocking blocked ended span + Given I run "SpanConditionsBlockedSpanScenario" + And I wait for 1 span + Then the trace "Content-Type" header equals "application/json" + * the trace "Bugsnag-Span-Sampling" header equals "1:2" + * 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 "SpanConditionsBlockedSpanScenarioSpan1" + * a span field "name" equals "SpanConditionsBlockedSpanScenarioSpan2" + * 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 1 + * every span field "startTimeUnixNano" matches the regex "^[0-9]+$" + * every span field "endTimeUnixNano" matches the regex "^[0-9]+$" + * a span named "SpanConditionsBlockedSpanScenarioSpan1" ended after a span named "SpanConditionsBlockedSpanScenarioSpan2" diff --git a/features/default/early_spans.feature b/features/default/early_spans.feature new file mode 100644 index 00000000..3c5395cd --- /dev/null +++ b/features/default/early_spans.feature @@ -0,0 +1,51 @@ +Feature: Early spans + + Scenario: Make sure OnSpanEnd callbacks also get called for early spans. + Given I run "EarlySpanOnEndScenario" + And I wait for exactly 1 span + 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 "parentSpanId" does not exist + * a span field "name" equals "[HTTP/GET]" + * a span string attribute "http.flavor" exists + * a span string attribute "http.method" equals "GET" + * a span integer attribute "http.status_code" is greater than 0 + * a span integer attribute "http.response_content_length" is greater than 0 + * a span string attribute "net.host.connection.type" equals "wifi" + * 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]+$" + * the trace payload field "resourceSpans.0.resource" string attribute "service.name" matches the regex "com.bugsnag.fixtures.cocoaperformance(xcframework)?" + * the trace payload field "resourceSpans.0.resource" string attribute "telemetry.sdk.name" equals "bugsnag.performance.cocoa" + * the trace payload field "resourceSpans.0.resource" string attribute "telemetry.sdk.version" matches the regex "[0-9]+\.[0-9]+\.[0-9]+" + + Scenario: ModifyEarlySpansScenario + Given I run "ModifyEarlySpansScenario" + And I wait for exactly 5 spans + Then the trace "Content-Type" header equals "application/json" + * the trace "Bugsnag-Span-Sampling" header equals "1:5" + * 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 "[AppStart/iOSCold]" + * a span field "name" equals "[AppStartPhase/App launching - pre main()]" + * a span field "name" equals "[AppStartPhase/App launching - post main()]" + * a span field "name" equals "[AppStartPhase/UI init]" + * a 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}$" + * a span field "kind" equals 1 + * a 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 "bugsnag.app_start.type" equals "cold" + * a span string attribute "bugsnag.phase" equals "App launching - pre main()" + * a span string attribute "bugsnag.phase" equals "App launching - post main()" + * a span string attribute "bugsnag.phase" equals "UI init" + * a span string attribute "bugsnag.span.category" equals "app_start" + * a span string attribute "bugsnag.span.category" equals "app_start_phase" + * every span bool attribute "bugsnag.span.first_class" does not exist + * every span string attribute "modifiedOnEnd" equals "yes" + * the trace payload field "resourceSpans.0.resource" string attribute "service.name" matches the regex "com.bugsnag.fixtures.cocoaperformance(xcframework)?" + * the trace payload field "resourceSpans.0.resource" string attribute "telemetry.sdk.name" equals "bugsnag.performance.cocoa" + * the trace payload field "resourceSpans.0.resource" string attribute "telemetry.sdk.version" matches the regex "[0-9]+\.[0-9]+\.[0-9]+" diff --git a/features/default/limits.feature b/features/default/limits.feature new file mode 100644 index 00000000..e8ecd3fc --- /dev/null +++ b/features/default/limits.feature @@ -0,0 +1,33 @@ +Feature: Checking payloads limits + + Scenario: Trace exceeds the max package size + Given I set the HTTP status code for the next requests to 402,402,402 + And I run "MaxPayloadSizeScenario" + And I wait for exactly 1 span + 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-Span-Sampling" header equals "1:1" + * 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$" + * the trace "Bugsnag-Uncompressed-Content-Length" header matches the regex "^[0-9]+$" + * every span field "name" equals "MaxPayloadSizeScenario" + * 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 1 + * every span field "startTimeUnixNano" matches the regex "^[0-9]+$" + * every span field "endTimeUnixNano" matches the regex "^[0-9]+$" + * every span bool attribute "bugsnag.app.in_foreground" is true + * every span string attribute "net.host.connection.type" equals "wifi" + * every span bool attribute "bugsnag.span.first_class" is true + * the trace payload field "resourceSpans.0.resource" string attribute "bugsnag.app.bundle_version" equals "30" + * the trace payload field "resourceSpans.0.resource" string attribute "deployment.environment" equals "staging" + * the trace payload field "resourceSpans.0.resource" string attribute "device.id" exists + * the trace payload field "resourceSpans.0.resource" string attribute "device.manufacturer" equals "Apple" + * the trace payload field "resourceSpans.0.resource" string attribute "device.model.identifier" exists + * the trace payload field "resourceSpans.0.resource" string attribute "host.arch" matches the regex "arm64|amd64" + * the trace payload field "resourceSpans.0.resource" string attribute "os.name" equals "iOS" + * the trace payload field "resourceSpans.0.resource" string attribute "os.type" equals "darwin" + * the trace payload field "resourceSpans.0.resource" string attribute "os.version" exists + * the trace payload field "resourceSpans.0.resource" string attribute "service.name" matches the regex "com.bugsnag.fixtures.cocoaperformance(xcframework)?" + * the trace payload field "resourceSpans.0.resource" string attribute "service.version" equals "10.0" + * the trace payload field "resourceSpans.0.resource" string attribute "telemetry.sdk.name" equals "bugsnag.performance.cocoa" + * the trace payload field "resourceSpans.0.resource" string attribute "telemetry.sdk.version" matches the regex "[0-9]+\.[0-9]+\.[0-9]+" \ No newline at end of file diff --git a/features/default/manual_spans.feature b/features/default/manual_spans.feature index c534759a..4b92a9f7 100644 --- a/features/default/manual_spans.feature +++ b/features/default/manual_spans.feature @@ -141,6 +141,20 @@ Feature: Manual creation of spans * every span field "endTimeUnixNano" matches the regex "^[0-9]+$" * every span bool attribute "bugsnag.span.first_class" does not exist + Scenario: ManualNetworkCallbackScenario + Given I run "ManualNetworkCallbackScenario" + And I wait for exactly 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: Manually start a network span with callback set to nil Given I run "ManualNetworkSpanCallbackSetToNilScenario" And I wait for 1 span @@ -268,257 +282,6 @@ Feature: Manual creation of spans * every span field "endTimeUnixNano" matches the regex "^[0-9]+$" * every span bool attribute "bugsnag.span.first_class" is false - Scenario: Trace exceeds the max package size - Given I set the HTTP status code for the next requests to 402,402,402 - And I run "MaxPayloadSizeScenario" - And I wait for exactly 1 span - 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-Span-Sampling" header equals "1:1" - * 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$" - * the trace "Bugsnag-Uncompressed-Content-Length" header matches the regex "^[0-9]+$" - * every span field "name" equals "MaxPayloadSizeScenario" - * 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 1 - * every span field "startTimeUnixNano" matches the regex "^[0-9]+$" - * every span field "endTimeUnixNano" matches the regex "^[0-9]+$" - * every span bool attribute "bugsnag.app.in_foreground" is true - * every span string attribute "net.host.connection.type" equals "wifi" - * every span bool attribute "bugsnag.span.first_class" is true - * the trace payload field "resourceSpans.0.resource" string attribute "bugsnag.app.bundle_version" equals "30" - * the trace payload field "resourceSpans.0.resource" string attribute "deployment.environment" equals "staging" - * the trace payload field "resourceSpans.0.resource" string attribute "device.id" exists - * the trace payload field "resourceSpans.0.resource" string attribute "device.manufacturer" equals "Apple" - * the trace payload field "resourceSpans.0.resource" string attribute "device.model.identifier" exists - * the trace payload field "resourceSpans.0.resource" string attribute "host.arch" matches the regex "arm64|amd64" - * the trace payload field "resourceSpans.0.resource" string attribute "os.name" equals "iOS" - * the trace payload field "resourceSpans.0.resource" string attribute "os.type" equals "darwin" - * the trace payload field "resourceSpans.0.resource" string attribute "os.version" exists - * the trace payload field "resourceSpans.0.resource" string attribute "service.name" matches the regex "com.bugsnag.fixtures.cocoaperformance(xcframework)?" - * the trace payload field "resourceSpans.0.resource" string attribute "service.version" equals "10.0" - * the trace payload field "resourceSpans.0.resource" string attribute "telemetry.sdk.name" equals "bugsnag.performance.cocoa" - * the trace payload field "resourceSpans.0.resource" string attribute "telemetry.sdk.version" matches the regex "[0-9]+\.[0-9]+\.[0-9]+" - - Scenario: Set attributes in a span - Given I run "SetAttributesScenario" - And I wait for 1 span - * the trace "Bugsnag-Span-Sampling" header equals "1:1" - * 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 "SetAttributesScenario" - * a span string attribute "a" equals "xyz" - * every span bool attribute "b" does not exist - * every span bool attribute "d" does not exist - * a span array attribute "e" is empty - * a span array attribute "f" is empty - * a span array attribute "x" is empty - * a span array attribute "c" contains the string value "array_0" at index 0 - * a span array attribute "c" contains the integer value 1 at index 1 - * a span array attribute "c" contains the value true at index 2 - * a span array attribute "c" contains the float value 1.5 at index 3 - - Scenario: Set attributes in a span with limits set - Given I run "SetAttributesWithLimitsScenario" - And I wait for 1 span - * the trace "Bugsnag-Span-Sampling" header equals "1:1" - * 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 "SetAttributesWithLimitsScenario" - * a span string attribute "a" equals "1234567890*** 1 CHARS TRUNCATED" - * a span array attribute "b" contains the integer value 1 at index 0 - * a span array attribute "b" contains the integer value 2 at index 1 - * a span array attribute "b" contains the integer value 3 at index 2 - * a span array attribute "b" contains no value at index 3 - - Scenario: Set attributes in a span with an attribute count limit set - Given I run "SetAttributeCountLimitScenario" - And I wait for 1 span - * the trace "Bugsnag-Span-Sampling" header equals "1:1" - * 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 "SetAttributeCountLimitScenario" - * every span string attribute "a" does not exist - - Scenario: Set OnStart - Given I run "OnStartCallbackScenario" - And I wait for exactly 1 span - * the trace "Bugsnag-Span-Sampling" header equals "1:1" - * 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 "OnStartCallbackScenario" - * a span bool attribute "start_callback_1" is true - * a span bool attribute "start_callback_2" is true - - Scenario: Set OnEnd - Given I run "OnEndCallbackScenario" - And I wait for exactly 1 span - * the trace "Bugsnag-Span-Sampling" header equals "1:1" - * 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" - - Scenario: Frame metrics - no slow frames - Given I run "FrameMetricsNoSlowFramesScenario" - And I wait for 1 span - Then the trace "Content-Type" header equals "application/json" - * the trace "Bugsnag-Span-Sampling" header equals "1:1" - * 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 "FrameMetricsNoSlowFramesScenario" - * 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 1 - * every span field "startTimeUnixNano" matches the regex "^[0-9]+$" - * every span field "endTimeUnixNano" matches the regex "^[0-9]+$" - * every span bool attribute "bugsnag.span.first_class" is true - * a span integer attribute "bugsnag.rendering.total_frames" is greater than 0 - * a span integer attribute "bugsnag.rendering.slow_frames" equals 0 - * a span integer attribute "bugsnag.rendering.frozen_frames" equals 0 - - Scenario: Frame metrics - slow frames - Given I run "FrameMetricsSlowFramesScenario" - And I wait for 1 span - Then the trace "Content-Type" header equals "application/json" - * the trace "Bugsnag-Span-Sampling" header equals "1:1" - * 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 "FrameMetricsSlowFramesScenario" - * 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 1 - * every span field "startTimeUnixNano" matches the regex "^[0-9]+$" - * every span field "endTimeUnixNano" matches the regex "^[0-9]+$" - * every span bool attribute "bugsnag.span.first_class" is true - * a span integer attribute "bugsnag.rendering.total_frames" is greater than 0 - * a span integer attribute "bugsnag.rendering.slow_frames" equals 3 - * a span integer attribute "bugsnag.rendering.frozen_frames" equals 0 - - Scenario: Frame metrics - frozen frames - Given I run "FrameMetricsFronzenFramesScenario" - And I wait for 3 spans - Then the trace "Content-Type" header equals "application/json" - * the trace "Bugsnag-Span-Sampling" header equals "1:3" - * 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 "FrameMetricsFronzenFramesScenario" - * 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 1 - * every span field "startTimeUnixNano" matches the regex "^[0-9]+$" - * every span field "endTimeUnixNano" matches the regex "^[0-9]+$" - * a span bool attribute "bugsnag.span.first_class" is true - * a span integer attribute "bugsnag.rendering.total_frames" is greater than 4 - * a span integer attribute "bugsnag.rendering.slow_frames" equals 2 - * a span integer attribute "bugsnag.rendering.frozen_frames" equals 2 - * the span named "FrameMetricsFronzenFramesScenario" is the parent of every span named "FrozenFrame" - - Scenario: Frame metrics - autoInstrumentRendering off - Given I run "FrameMetricsAutoInstrumentRenderingOffScenario" - And I wait for 1 span - Then the trace "Content-Type" header equals "application/json" - * the trace "Bugsnag-Span-Sampling" header equals "1:1" - * 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 "FrameMetricsAutoInstrumentRenderingOffScenario" - * 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 1 - * every span field "startTimeUnixNano" matches the regex "^[0-9]+$" - * every span field "endTimeUnixNano" matches the regex "^[0-9]+$" - * every span bool attribute "bugsnag.span.first_class" is true - * every span integer attribute "bugsnag.rendering.total_frames" does not exist - * every span integer attribute "bugsnag.rendering.slow_frames" does not exist - * every span integer attribute "bugsnag.rendering.frozen_frames" does not exist - - Scenario: Frame metrics - span instrumentRendering off - Given I run "FrameMetricsSpanInstrumentRenderingOffScenario" - And I wait for 1 span - Then the trace "Content-Type" header equals "application/json" - * the trace "Bugsnag-Span-Sampling" header equals "1:1" - * 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 "FrameMetricsSpanInstrumentRenderingOffScenario" - * 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 1 - * every span field "startTimeUnixNano" matches the regex "^[0-9]+$" - * every span field "endTimeUnixNano" matches the regex "^[0-9]+$" - * every span bool attribute "bugsnag.span.first_class" is true - * every span integer attribute "bugsnag.rendering.total_frames" does not exist - * every span integer attribute "bugsnag.rendering.slow_frames" does not exist - * every span integer attribute "bugsnag.rendering.frozen_frames" does not exist - - Scenario: Frame metrics - non firstClass span with instrumentRendering off - Given I run "FrameMetricsNonFirstClassSpanInstrumentRenderingOnScenario" - And I wait for 1 span - Then the trace "Content-Type" header equals "application/json" - * the trace "Bugsnag-Span-Sampling" header equals "1:1" - * 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 "FrameMetricsNonFirstClassSpanInstrumentRenderingOnScenario" - * 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 1 - * every span field "startTimeUnixNano" matches the regex "^[0-9]+$" - * every span field "endTimeUnixNano" matches the regex "^[0-9]+$" - * every span bool attribute "bugsnag.span.first_class" is false - * a span integer attribute "bugsnag.rendering.total_frames" is greater than 0 - * a span integer attribute "bugsnag.rendering.slow_frames" equals 3 - * a span integer attribute "bugsnag.rendering.frozen_frames" equals 0 - - Scenario: Span Conditions - condition closed - Given I run "SpanConditionsSimpleConditionScenario" - And I wait for 2 spans - Then the trace "Content-Type" header equals "application/json" - * the trace "Bugsnag-Span-Sampling" header equals "1:2" - * 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 "SpanConditionsSimpleConditionScenarioSpan1" - * a span field "name" equals "SpanConditionsSimpleConditionScenarioSpan2" - * 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 1 - * every span field "startTimeUnixNano" matches the regex "^[0-9]+$" - * every span field "endTimeUnixNano" matches the regex "^[0-9]+$" - * a span named "SpanConditionsSimpleConditionScenarioSpan1" ended after a span named "SpanConditionsSimpleConditionScenarioSpan2" - * a span named "SpanConditionsSimpleConditionScenarioSpan2" is a child of span named "SpanConditionsSimpleConditionScenarioSpan1" - - Scenario: Span Conditions - condition timed out - Given I run "SpanConditionsConditionTimedOutScenario" - And I wait for 2 spans - Then the trace "Content-Type" header equals "application/json" - * the trace "Bugsnag-Span-Sampling" header equals "1:2" - * 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 "SpanConditionsConditionTimedOutScenarioSpan1" - * a span field "name" equals "SpanConditionsConditionTimedOutScenarioSpan2" - * 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 1 - * every span field "startTimeUnixNano" matches the regex "^[0-9]+$" - * every span field "endTimeUnixNano" matches the regex "^[0-9]+$" - * a span named "SpanConditionsConditionTimedOutScenarioSpan1" ended before a span named "SpanConditionsConditionTimedOutScenarioSpan2" started - - Scenario: Span Conditions - multiple conditions - Given I run "SpanConditionsMultipleConditionsScenario" - And I wait for 3 spans - Then the trace "Content-Type" header equals "application/json" - * the trace "Bugsnag-Span-Sampling" header equals "1:3" - * 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 "SpanConditionsMultipleConditionsScenarioSpan1" - * a span field "name" equals "SpanConditionsMultipleConditionsScenarioSpan2" - * a span field "name" equals "SpanConditionsMultipleConditionsScenarioSpan3" - * 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 1 - * every span field "startTimeUnixNano" matches the regex "^[0-9]+$" - * every span field "endTimeUnixNano" matches the regex "^[0-9]+$" - * a span named "SpanConditionsMultipleConditionsScenarioSpan3" ended after a span named "SpanConditionsMultipleConditionsScenarioSpan2" - * a span named "SpanConditionsMultipleConditionsScenarioSpan1" ended after a span named "SpanConditionsMultipleConditionsScenarioSpan3" - - Scenario: Span Conditions - blocking blocked ended span - Given I run "SpanConditionsBlockedSpanScenario" - And I wait for 1 span - Then the trace "Content-Type" header equals "application/json" - * the trace "Bugsnag-Span-Sampling" header equals "1:2" - * 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 "SpanConditionsBlockedSpanScenarioSpan1" - * a span field "name" equals "SpanConditionsBlockedSpanScenarioSpan2" - * 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 1 - * every span field "startTimeUnixNano" matches the regex "^[0-9]+$" - * every span field "endTimeUnixNano" matches the regex "^[0-9]+$" - * a span named "SpanConditionsBlockedSpanScenarioSpan1" ended after a span named "SpanConditionsBlockedSpanScenarioSpan2" - Scenario: Manually start and end a span with remote parent context Given I run "ManualSpanWithRemoteContextParentScenario" And I wait for 1 span diff --git a/features/default/metrics.feature b/features/default/metrics/metrics_cpu.feature similarity index 50% rename from features/default/metrics.feature rename to features/default/metrics/metrics_cpu.feature index c4e6a0e7..95f15cc2 100644 --- a/features/default/metrics.feature +++ b/features/default/metrics/metrics_cpu.feature @@ -1,135 +1,4 @@ -Feature: Spans with collected metrics - - Scenario: Frame rendering metrics - normal start, no slow frames - Given I load scenario "RenderingMetricsScenario" - And I configure bugsnag "renderingMetrics" to "true" - And I set the sampling probability to "1.0" - And I configure scenario "variant_name" to "NoSlow" - And I start bugsnag - And I run the loaded scenario - And I wait for 1 span - Then the trace "Content-Type" header equals "application/json" - * the trace "Bugsnag-Span-Sampling" header equals "1:1" - * 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 "RenderingMetricsScenarioNoSlow" - * 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 1 - * every span field "startTimeUnixNano" matches the regex "^[0-9]+$" - * every span field "endTimeUnixNano" matches the regex "^[0-9]+$" - * every span bool attribute "bugsnag.span.first_class" is true - * a span integer attribute "bugsnag.rendering.total_frames" is greater than 0 - * a span integer attribute "bugsnag.rendering.slow_frames" equals 0 - * a span integer attribute "bugsnag.rendering.frozen_frames" equals 0 - - Scenario: Frame rendering metrics - early start, no slow frames - Given I load scenario "RenderingMetricsScenario" - And I configure bugsnag "renderingMetrics" to "true" - And I configure scenario "spanStartTime" to "early" - And I configure scenario "variant_name" to "EarlyStart" - And I set the sampling probability to "1.0" - And I start bugsnag - And I run the loaded scenario - And I wait for 1 span - Then the trace "Content-Type" header equals "application/json" - * the trace "Bugsnag-Span-Sampling" header equals "1:1" - * 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 "RenderingMetricsScenarioEarlyStart" - * 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 1 - * every span field "startTimeUnixNano" matches the regex "^[0-9]+$" - * every span field "endTimeUnixNano" matches the regex "^[0-9]+$" - * every span bool attribute "bugsnag.span.first_class" is true - * a span integer attribute "bugsnag.rendering.total_frames" is greater than 0 - * a span integer attribute "bugsnag.rendering.slow_frames" equals 0 - * a span integer attribute "bugsnag.rendering.frozen_frames" equals 0 - - Scenario: Frame metrics - slow frames - Given I run "FrameMetricsSlowFramesScenario" - And I wait for 1 span - Then the trace "Content-Type" header equals "application/json" - * the trace "Bugsnag-Span-Sampling" header equals "1:1" - * 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 "FrameMetricsSlowFramesScenario" - * 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 1 - * every span field "startTimeUnixNano" matches the regex "^[0-9]+$" - * every span field "endTimeUnixNano" matches the regex "^[0-9]+$" - * every span bool attribute "bugsnag.span.first_class" is true - * a span integer attribute "bugsnag.rendering.total_frames" is greater than 0 - * a span integer attribute "bugsnag.rendering.slow_frames" equals 3 - * a span integer attribute "bugsnag.rendering.frozen_frames" equals 0 - - Scenario: Frame metrics - frozen frames - Given I run "FrameMetricsFronzenFramesScenario" - And I wait for 3 spans - Then the trace "Content-Type" header equals "application/json" - * the trace "Bugsnag-Span-Sampling" header equals "1:3" - * 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 "FrameMetricsFronzenFramesScenario" - * 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 1 - * every span field "startTimeUnixNano" matches the regex "^[0-9]+$" - * every span field "endTimeUnixNano" matches the regex "^[0-9]+$" - * a span bool attribute "bugsnag.span.first_class" is true - * a span integer attribute "bugsnag.rendering.total_frames" is greater than 4 - * a span integer attribute "bugsnag.rendering.slow_frames" equals 2 - * a span integer attribute "bugsnag.rendering.frozen_frames" equals 2 - * the span named "FrameMetricsFronzenFramesScenario" is the parent of every span named "FrozenFrame" - - Scenario: Frame metrics - autoInstrumentRendering off - Given I run "FrameMetricsAutoInstrumentRenderingOffScenario" - And I wait for 1 span - Then the trace "Content-Type" header equals "application/json" - * the trace "Bugsnag-Span-Sampling" header equals "1:1" - * 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 "FrameMetricsAutoInstrumentRenderingOffScenario" - * 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 1 - * every span field "startTimeUnixNano" matches the regex "^[0-9]+$" - * every span field "endTimeUnixNano" matches the regex "^[0-9]+$" - * every span bool attribute "bugsnag.span.first_class" is true - * every span integer attribute "bugsnag.rendering.total_frames" does not exist - * every span integer attribute "bugsnag.rendering.slow_frames" does not exist - * every span integer attribute "bugsnag.rendering.frozen_frames" does not exist - - Scenario: Frame metrics - span instrumentRendering off - Given I run "FrameMetricsSpanInstrumentRenderingOffScenario" - And I wait for 1 span - Then the trace "Content-Type" header equals "application/json" - * the trace "Bugsnag-Span-Sampling" header equals "1:1" - * 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 "FrameMetricsSpanInstrumentRenderingOffScenario" - * 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 1 - * every span field "startTimeUnixNano" matches the regex "^[0-9]+$" - * every span field "endTimeUnixNano" matches the regex "^[0-9]+$" - * every span bool attribute "bugsnag.span.first_class" is true - * every span integer attribute "bugsnag.rendering.total_frames" does not exist - * every span integer attribute "bugsnag.rendering.slow_frames" does not exist - * every span integer attribute "bugsnag.rendering.frozen_frames" does not exist - - Scenario: Frame metrics - non firstClass span with instrumentRendering off - Given I run "FrameMetricsNonFirstClassSpanInstrumentRenderingOnScenario" - And I wait for 1 span - Then the trace "Content-Type" header equals "application/json" - * the trace "Bugsnag-Span-Sampling" header equals "1:1" - * 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 "FrameMetricsNonFirstClassSpanInstrumentRenderingOnScenario" - * 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 1 - * every span field "startTimeUnixNano" matches the regex "^[0-9]+$" - * every span field "endTimeUnixNano" matches the regex "^[0-9]+$" - * every span bool attribute "bugsnag.span.first_class" is false - * a span integer attribute "bugsnag.rendering.total_frames" is greater than 0 - * a span integer attribute "bugsnag.rendering.slow_frames" equals 3 - * a span integer attribute "bugsnag.rendering.frozen_frames" equals 0 +Feature: Spans with collected CPU metrics Scenario: With default settings, CPU metrics are disabled Given I load scenario "CPUMetricsScenario" @@ -417,170 +286,3 @@ Feature: Spans with collected metrics * a span array attribute "bugsnag.system.cpu_measures_overhead" contains from 3 to 4 elements * a span float attribute "bugsnag.system.cpu_mean_overhead" is greater than 0.0 * a span float attribute "bugsnag.system.cpu_mean_overhead" is less than 10.0 - - Scenario: With default settings, memory metrics are disabled - Given I load scenario "MemoryMetricsScenario" - And I configure scenario "run_delay" to "0" - And I configure scenario "span_duration" to "0" - And I configure scenario "variant_name" to "DefaultSettings" - And I start bugsnag - And I run the loaded scenario - And I wait for 1 span - Then the trace "Content-Type" header equals "application/json" - * the trace "Bugsnag-Span-Sampling" header equals "1:1" - * 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 "MemoryMetricsScenarioDefaultSettings" - * 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 1 - * every span field "startTimeUnixNano" matches the regex "^[0-9]+$" - * every span field "endTimeUnixNano" matches the regex "^[0-9]+$" - * the trace payload field "resourceSpans.0.resource" string attribute "service.name" matches the regex "com.bugsnag.fixtures.cocoaperformance(xcframework)?" - * the trace payload field "resourceSpans.0.resource" string attribute "telemetry.sdk.name" equals "bugsnag.performance.cocoa" - * the trace payload field "resourceSpans.0.resource" string attribute "telemetry.sdk.version" matches the regex "[0-9]+\.[0-9]+\.[0-9]+" - * every span bool attribute "bugsnag.span.first_class" is true - * every span attribute "bugsnag.system.memory.timestamps" does not exist - * every span attribute "bugsnag.system.memory.spaces.device.size" does not exist - * every span attribute "bugsnag.system.memory.spaces.device.used" does not exist - * every span attribute "bugsnag.system.memory.spaces.device.mean" does not exist - - Scenario: First class spans produce memory metrics - Given I load scenario "MemoryMetricsScenario" - And I configure bugsnag "memoryMetrics" to "true" - And I configure scenario "run_delay" to "0" - And I configure scenario "span_duration" to "0" - And I configure scenario "opts_first_class" to "yes" - And I configure scenario "variant_name" to "FirstClass" - And I start bugsnag - And I run the loaded scenario - And I wait for 1 span - Then the trace "Content-Type" header equals "application/json" - * the trace "Bugsnag-Span-Sampling" header equals "1:1" - * 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 "MemoryMetricsScenarioFirstClass" - * 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 1 - * every span field "startTimeUnixNano" matches the regex "^[0-9]+$" - * every span field "endTimeUnixNano" matches the regex "^[0-9]+$" - * the trace payload field "resourceSpans.0.resource" string attribute "service.name" matches the regex "com.bugsnag.fixtures.cocoaperformance(xcframework)?" - * the trace payload field "resourceSpans.0.resource" string attribute "telemetry.sdk.name" equals "bugsnag.performance.cocoa" - * the trace payload field "resourceSpans.0.resource" string attribute "telemetry.sdk.version" matches the regex "[0-9]+\.[0-9]+\.[0-9]+" - * every span bool attribute "bugsnag.span.first_class" is true - * a span array attribute "bugsnag.system.memory.timestamps" contains 2 elements - * a span integer attribute "bugsnag.system.memory.spaces.device.size" is greater than 0 - * a span array attribute "bugsnag.system.memory.spaces.device.used" contains 2 elements - * a span integer attribute "bugsnag.system.memory.spaces.device.mean" is greater than 0 - - Scenario: Non-first-class spans don't produce memory metrics - Given I load scenario "MemoryMetricsScenario" - And I configure bugsnag "memoryMetrics" to "false" - And I configure scenario "run_delay" to "0" - And I configure scenario "span_duration" to "0" - And I configure scenario "opts_first_class" to "no" - And I configure scenario "variant_name" to "NonFirstClass" - And I start bugsnag - And I run the loaded scenario - And I wait for 1 span - Then the trace "Content-Type" header equals "application/json" - * the trace "Bugsnag-Span-Sampling" header equals "1:1" - * 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 "MemoryMetricsScenarioNonFirstClass" - * 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 1 - * every span field "startTimeUnixNano" matches the regex "^[0-9]+$" - * every span field "endTimeUnixNano" matches the regex "^[0-9]+$" - * the trace payload field "resourceSpans.0.resource" string attribute "service.name" matches the regex "com.bugsnag.fixtures.cocoaperformance(xcframework)?" - * the trace payload field "resourceSpans.0.resource" string attribute "telemetry.sdk.name" equals "bugsnag.performance.cocoa" - * the trace payload field "resourceSpans.0.resource" string attribute "telemetry.sdk.version" matches the regex "[0-9]+\.[0-9]+\.[0-9]+" - * every span bool attribute "bugsnag.span.first_class" is false - * every span attribute "bugsnag.system.memory.timestamps" does not exist - * every span attribute "bugsnag.system.memory.spaces.device.size" does not exist - * every span attribute "bugsnag.system.memory.spaces.device.used" does not exist - * every span attribute "bugsnag.system.memory.spaces.device.mean" does not exist - - Scenario: When memory metrics opts are enabled, we produce memory metrics even if not first class - Given I load scenario "MemoryMetricsScenario" - And I configure bugsnag "memoryMetrics" to "true" - And I configure scenario "run_delay" to "0" - And I configure scenario "span_duration" to "0" - And I configure scenario "opts_first_class" to "no" - And I configure scenario "opts_metrics_memory" to "yes" - And I configure scenario "variant_name" to "NonFirstClassEnabled" - And I start bugsnag - And I run the loaded scenario - And I wait for 1 span - Then the trace "Content-Type" header equals "application/json" - * the trace "Bugsnag-Span-Sampling" header equals "1:1" - * 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 "MemoryMetricsScenarioNonFirstClassEnabled" - * 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 1 - * every span field "startTimeUnixNano" matches the regex "^[0-9]+$" - * every span field "endTimeUnixNano" matches the regex "^[0-9]+$" - * the trace payload field "resourceSpans.0.resource" string attribute "service.name" matches the regex "com.bugsnag.fixtures.cocoaperformance(xcframework)?" - * the trace payload field "resourceSpans.0.resource" string attribute "telemetry.sdk.name" equals "bugsnag.performance.cocoa" - * the trace payload field "resourceSpans.0.resource" string attribute "telemetry.sdk.version" matches the regex "[0-9]+\.[0-9]+\.[0-9]+" - * every span bool attribute "bugsnag.span.first_class" is false - * a span array attribute "bugsnag.system.memory.timestamps" contains 2 elements - * a span integer attribute "bugsnag.system.memory.spaces.device.size" is greater than 0 - * a span array attribute "bugsnag.system.memory.spaces.device.used" contains 2 elements - * a span integer attribute "bugsnag.system.memory.spaces.device.mean" is greater than 0 - - Scenario: Longer span duration captures more memory samples - Given I load scenario "MemoryMetricsScenario" - And I configure bugsnag "memoryMetrics" to "true" - And I configure scenario "run_delay" to "0" - And I configure scenario "span_duration" to "1.5" - And I configure scenario "opts_first_class" to "yes" - And I configure scenario "variant_name" to "LongerDuration" - And I start bugsnag - And I run the loaded scenario - And I wait for 1 span - Then the trace "Content-Type" header equals "application/json" - * the trace "Bugsnag-Span-Sampling" header equals "1:1" - * 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 "MemoryMetricsScenarioLongerDuration" - * 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 1 - * every span field "startTimeUnixNano" matches the regex "^[0-9]+$" - * every span field "endTimeUnixNano" matches the regex "^[0-9]+$" - * the trace payload field "resourceSpans.0.resource" string attribute "service.name" matches the regex "com.bugsnag.fixtures.cocoaperformance(xcframework)?" - * the trace payload field "resourceSpans.0.resource" string attribute "telemetry.sdk.name" equals "bugsnag.performance.cocoa" - * the trace payload field "resourceSpans.0.resource" string attribute "telemetry.sdk.version" matches the regex "[0-9]+\.[0-9]+\.[0-9]+" - * every span bool attribute "bugsnag.span.first_class" is true - * a span array attribute "bugsnag.system.memory.timestamps" contains from 3 to 4 elements - * a span integer attribute "bugsnag.system.memory.spaces.device.size" is greater than 0 - * a span array attribute "bugsnag.system.memory.spaces.device.used" contains from 3 to 4 elements - * a span integer attribute "bugsnag.system.memory.spaces.device.mean" is greater than 0 - - Scenario: If we generate spans later, we still expect memory samples - Given I load scenario "MemoryMetricsScenario" - And I configure bugsnag "memoryMetrics" to "true" - And I configure scenario "run_delay" to "1.1" - And I configure scenario "span_duration" to "1.5" - And I configure scenario "opts_first_class" to "yes" - And I configure scenario "variant_name" to "GenerateLater" - And I start bugsnag - And I run the loaded scenario - And I wait for 1 span - Then the trace "Content-Type" header equals "application/json" - * the trace "Bugsnag-Span-Sampling" header equals "1:1" - * 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 "MemoryMetricsScenarioGenerateLater" - * 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 1 - * every span field "startTimeUnixNano" matches the regex "^[0-9]+$" - * every span field "endTimeUnixNano" matches the regex "^[0-9]+$" - * the trace payload field "resourceSpans.0.resource" string attribute "service.name" matches the regex "com.bugsnag.fixtures.cocoaperformance(xcframework)?" - * the trace payload field "resourceSpans.0.resource" string attribute "telemetry.sdk.name" equals "bugsnag.performance.cocoa" - * the trace payload field "resourceSpans.0.resource" string attribute "telemetry.sdk.version" matches the regex "[0-9]+\.[0-9]+\.[0-9]+" - * every span bool attribute "bugsnag.span.first_class" is true - * a span array attribute "bugsnag.system.memory.timestamps" contains from 3 to 4 elements - * a span integer attribute "bugsnag.system.memory.spaces.device.size" is greater than 0 - * a span array attribute "bugsnag.system.memory.spaces.device.used" contains from 3 to 4 elements - * a span integer attribute "bugsnag.system.memory.spaces.device.mean" is greater than 0 diff --git a/features/default/metrics/metrics_frame.feature b/features/default/metrics/metrics_frame.feature new file mode 100644 index 00000000..c74cec9e --- /dev/null +++ b/features/default/metrics/metrics_frame.feature @@ -0,0 +1,149 @@ +Feature: Spans with collected frame metrics + + Scenario: Frame metrics - no slow frames + Given I run "FrameMetricsNoSlowFramesScenario" + And I wait for 1 span + Then the trace "Content-Type" header equals "application/json" + * the trace "Bugsnag-Span-Sampling" header equals "1:1" + * 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 "FrameMetricsNoSlowFramesScenario" + * 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 1 + * every span field "startTimeUnixNano" matches the regex "^[0-9]+$" + * every span field "endTimeUnixNano" matches the regex "^[0-9]+$" + * every span bool attribute "bugsnag.span.first_class" is true + * a span integer attribute "bugsnag.rendering.total_frames" is greater than 0 + * a span integer attribute "bugsnag.rendering.slow_frames" equals 0 + * a span integer attribute "bugsnag.rendering.frozen_frames" equals 0 + + Scenario: Frame rendering metrics - normal start, no slow frames + Given I load scenario "RenderingMetricsScenario" + And I configure bugsnag "renderingMetrics" to "true" + And I set the sampling probability to "1.0" + And I configure scenario "variant_name" to "NoSlow" + And I start bugsnag + And I run the loaded scenario + And I wait for 1 span + Then the trace "Content-Type" header equals "application/json" + * the trace "Bugsnag-Span-Sampling" header equals "1:1" + * 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 "RenderingMetricsScenarioNoSlow" + * 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 1 + * every span field "startTimeUnixNano" matches the regex "^[0-9]+$" + * every span field "endTimeUnixNano" matches the regex "^[0-9]+$" + * every span bool attribute "bugsnag.span.first_class" is true + * a span integer attribute "bugsnag.rendering.total_frames" is greater than 0 + * a span integer attribute "bugsnag.rendering.slow_frames" equals 0 + * a span integer attribute "bugsnag.rendering.frozen_frames" equals 0 + + Scenario: Frame rendering metrics - early start, no slow frames + Given I load scenario "RenderingMetricsScenario" + And I configure bugsnag "renderingMetrics" to "true" + And I configure scenario "spanStartTime" to "early" + And I configure scenario "variant_name" to "EarlyStart" + And I set the sampling probability to "1.0" + And I start bugsnag + And I run the loaded scenario + And I wait for 1 span + Then the trace "Content-Type" header equals "application/json" + * the trace "Bugsnag-Span-Sampling" header equals "1:1" + * 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 "RenderingMetricsScenarioEarlyStart" + * 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 1 + * every span field "startTimeUnixNano" matches the regex "^[0-9]+$" + * every span field "endTimeUnixNano" matches the regex "^[0-9]+$" + * every span bool attribute "bugsnag.span.first_class" is true + * a span integer attribute "bugsnag.rendering.total_frames" is greater than 0 + * a span integer attribute "bugsnag.rendering.slow_frames" equals 0 + * a span integer attribute "bugsnag.rendering.frozen_frames" equals 0 + + Scenario: Frame metrics - slow frames + Given I run "FrameMetricsSlowFramesScenario" + And I wait for 1 span + Then the trace "Content-Type" header equals "application/json" + * the trace "Bugsnag-Span-Sampling" header equals "1:1" + * 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 "FrameMetricsSlowFramesScenario" + * 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 1 + * every span field "startTimeUnixNano" matches the regex "^[0-9]+$" + * every span field "endTimeUnixNano" matches the regex "^[0-9]+$" + * every span bool attribute "bugsnag.span.first_class" is true + * a span integer attribute "bugsnag.rendering.total_frames" is greater than 0 + * a span integer attribute "bugsnag.rendering.slow_frames" equals 3 + * a span integer attribute "bugsnag.rendering.frozen_frames" equals 0 + + Scenario: Frame metrics - frozen frames + Given I run "FrameMetricsFronzenFramesScenario" + And I wait for 3 spans + Then the trace "Content-Type" header equals "application/json" + * the trace "Bugsnag-Span-Sampling" header equals "1:3" + * 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 "FrameMetricsFronzenFramesScenario" + * 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 1 + * every span field "startTimeUnixNano" matches the regex "^[0-9]+$" + * every span field "endTimeUnixNano" matches the regex "^[0-9]+$" + * a span bool attribute "bugsnag.span.first_class" is true + * a span integer attribute "bugsnag.rendering.total_frames" is greater than 4 + * a span integer attribute "bugsnag.rendering.slow_frames" equals 2 + * a span integer attribute "bugsnag.rendering.frozen_frames" equals 2 + * the span named "FrameMetricsFronzenFramesScenario" is the parent of every span named "FrozenFrame" + + Scenario: Frame metrics - autoInstrumentRendering off + Given I run "FrameMetricsAutoInstrumentRenderingOffScenario" + And I wait for 1 span + Then the trace "Content-Type" header equals "application/json" + * the trace "Bugsnag-Span-Sampling" header equals "1:1" + * 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 "FrameMetricsAutoInstrumentRenderingOffScenario" + * 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 1 + * every span field "startTimeUnixNano" matches the regex "^[0-9]+$" + * every span field "endTimeUnixNano" matches the regex "^[0-9]+$" + * every span bool attribute "bugsnag.span.first_class" is true + * every span integer attribute "bugsnag.rendering.total_frames" does not exist + * every span integer attribute "bugsnag.rendering.slow_frames" does not exist + * every span integer attribute "bugsnag.rendering.frozen_frames" does not exist + + Scenario: Frame metrics - span instrumentRendering off + Given I run "FrameMetricsSpanInstrumentRenderingOffScenario" + And I wait for 1 span + Then the trace "Content-Type" header equals "application/json" + * the trace "Bugsnag-Span-Sampling" header equals "1:1" + * 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 "FrameMetricsSpanInstrumentRenderingOffScenario" + * 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 1 + * every span field "startTimeUnixNano" matches the regex "^[0-9]+$" + * every span field "endTimeUnixNano" matches the regex "^[0-9]+$" + * every span bool attribute "bugsnag.span.first_class" is true + * every span integer attribute "bugsnag.rendering.total_frames" does not exist + * every span integer attribute "bugsnag.rendering.slow_frames" does not exist + * every span integer attribute "bugsnag.rendering.frozen_frames" does not exist + + Scenario: Frame metrics - non firstClass span with instrumentRendering off + Given I run "FrameMetricsNonFirstClassSpanInstrumentRenderingOnScenario" + And I wait for 1 span + Then the trace "Content-Type" header equals "application/json" + * the trace "Bugsnag-Span-Sampling" header equals "1:1" + * 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 "FrameMetricsNonFirstClassSpanInstrumentRenderingOnScenario" + * 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 1 + * every span field "startTimeUnixNano" matches the regex "^[0-9]+$" + * every span field "endTimeUnixNano" matches the regex "^[0-9]+$" + * every span bool attribute "bugsnag.span.first_class" is false + * a span integer attribute "bugsnag.rendering.total_frames" is greater than 0 + * a span integer attribute "bugsnag.rendering.slow_frames" equals 3 + * a span integer attribute "bugsnag.rendering.frozen_frames" equals 0 diff --git a/features/default/metrics/metrics_memory.feature b/features/default/metrics/metrics_memory.feature new file mode 100644 index 00000000..4162fb91 --- /dev/null +++ b/features/default/metrics/metrics_memory.feature @@ -0,0 +1,168 @@ +Feature: Spans with collected memory metrics + + Scenario: With default settings, memory metrics are disabled + Given I load scenario "MemoryMetricsScenario" + And I configure scenario "run_delay" to "0" + And I configure scenario "span_duration" to "0" + And I configure scenario "variant_name" to "DefaultSettings" + And I start bugsnag + And I run the loaded scenario + And I wait for 1 span + Then the trace "Content-Type" header equals "application/json" + * the trace "Bugsnag-Span-Sampling" header equals "1:1" + * 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 "MemoryMetricsScenarioDefaultSettings" + * 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 1 + * every span field "startTimeUnixNano" matches the regex "^[0-9]+$" + * every span field "endTimeUnixNano" matches the regex "^[0-9]+$" + * the trace payload field "resourceSpans.0.resource" string attribute "service.name" matches the regex "com.bugsnag.fixtures.cocoaperformance(xcframework)?" + * the trace payload field "resourceSpans.0.resource" string attribute "telemetry.sdk.name" equals "bugsnag.performance.cocoa" + * the trace payload field "resourceSpans.0.resource" string attribute "telemetry.sdk.version" matches the regex "[0-9]+\.[0-9]+\.[0-9]+" + * every span bool attribute "bugsnag.span.first_class" is true + * every span attribute "bugsnag.system.memory.timestamps" does not exist + * every span attribute "bugsnag.system.memory.spaces.device.size" does not exist + * every span attribute "bugsnag.system.memory.spaces.device.used" does not exist + * every span attribute "bugsnag.system.memory.spaces.device.mean" does not exist + + Scenario: First class spans produce memory metrics + Given I load scenario "MemoryMetricsScenario" + And I configure bugsnag "memoryMetrics" to "true" + And I configure scenario "run_delay" to "0" + And I configure scenario "span_duration" to "0" + And I configure scenario "opts_first_class" to "yes" + And I configure scenario "variant_name" to "FirstClass" + And I start bugsnag + And I run the loaded scenario + And I wait for 1 span + Then the trace "Content-Type" header equals "application/json" + * the trace "Bugsnag-Span-Sampling" header equals "1:1" + * 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 "MemoryMetricsScenarioFirstClass" + * 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 1 + * every span field "startTimeUnixNano" matches the regex "^[0-9]+$" + * every span field "endTimeUnixNano" matches the regex "^[0-9]+$" + * the trace payload field "resourceSpans.0.resource" string attribute "service.name" matches the regex "com.bugsnag.fixtures.cocoaperformance(xcframework)?" + * the trace payload field "resourceSpans.0.resource" string attribute "telemetry.sdk.name" equals "bugsnag.performance.cocoa" + * the trace payload field "resourceSpans.0.resource" string attribute "telemetry.sdk.version" matches the regex "[0-9]+\.[0-9]+\.[0-9]+" + * every span bool attribute "bugsnag.span.first_class" is true + * a span array attribute "bugsnag.system.memory.timestamps" contains 2 elements + * a span integer attribute "bugsnag.system.memory.spaces.device.size" is greater than 0 + * a span array attribute "bugsnag.system.memory.spaces.device.used" contains 2 elements + * a span integer attribute "bugsnag.system.memory.spaces.device.mean" is greater than 0 + + Scenario: Non-first-class spans don't produce memory metrics + Given I load scenario "MemoryMetricsScenario" + And I configure bugsnag "memoryMetrics" to "false" + And I configure scenario "run_delay" to "0" + And I configure scenario "span_duration" to "0" + And I configure scenario "opts_first_class" to "no" + And I configure scenario "variant_name" to "NonFirstClass" + And I start bugsnag + And I run the loaded scenario + And I wait for 1 span + Then the trace "Content-Type" header equals "application/json" + * the trace "Bugsnag-Span-Sampling" header equals "1:1" + * 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 "MemoryMetricsScenarioNonFirstClass" + * 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 1 + * every span field "startTimeUnixNano" matches the regex "^[0-9]+$" + * every span field "endTimeUnixNano" matches the regex "^[0-9]+$" + * the trace payload field "resourceSpans.0.resource" string attribute "service.name" matches the regex "com.bugsnag.fixtures.cocoaperformance(xcframework)?" + * the trace payload field "resourceSpans.0.resource" string attribute "telemetry.sdk.name" equals "bugsnag.performance.cocoa" + * the trace payload field "resourceSpans.0.resource" string attribute "telemetry.sdk.version" matches the regex "[0-9]+\.[0-9]+\.[0-9]+" + * every span bool attribute "bugsnag.span.first_class" is false + * every span attribute "bugsnag.system.memory.timestamps" does not exist + * every span attribute "bugsnag.system.memory.spaces.device.size" does not exist + * every span attribute "bugsnag.system.memory.spaces.device.used" does not exist + * every span attribute "bugsnag.system.memory.spaces.device.mean" does not exist + + Scenario: When memory metrics opts are enabled, we produce memory metrics even if not first class + Given I load scenario "MemoryMetricsScenario" + And I configure bugsnag "memoryMetrics" to "true" + And I configure scenario "run_delay" to "0" + And I configure scenario "span_duration" to "0" + And I configure scenario "opts_first_class" to "no" + And I configure scenario "opts_metrics_memory" to "yes" + And I configure scenario "variant_name" to "NonFirstClassEnabled" + And I start bugsnag + And I run the loaded scenario + And I wait for 1 span + Then the trace "Content-Type" header equals "application/json" + * the trace "Bugsnag-Span-Sampling" header equals "1:1" + * 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 "MemoryMetricsScenarioNonFirstClassEnabled" + * 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 1 + * every span field "startTimeUnixNano" matches the regex "^[0-9]+$" + * every span field "endTimeUnixNano" matches the regex "^[0-9]+$" + * the trace payload field "resourceSpans.0.resource" string attribute "service.name" matches the regex "com.bugsnag.fixtures.cocoaperformance(xcframework)?" + * the trace payload field "resourceSpans.0.resource" string attribute "telemetry.sdk.name" equals "bugsnag.performance.cocoa" + * the trace payload field "resourceSpans.0.resource" string attribute "telemetry.sdk.version" matches the regex "[0-9]+\.[0-9]+\.[0-9]+" + * every span bool attribute "bugsnag.span.first_class" is false + * a span array attribute "bugsnag.system.memory.timestamps" contains 2 elements + * a span integer attribute "bugsnag.system.memory.spaces.device.size" is greater than 0 + * a span array attribute "bugsnag.system.memory.spaces.device.used" contains 2 elements + * a span integer attribute "bugsnag.system.memory.spaces.device.mean" is greater than 0 + + Scenario: Longer span duration captures more memory samples + Given I load scenario "MemoryMetricsScenario" + And I configure bugsnag "memoryMetrics" to "true" + And I configure scenario "run_delay" to "0" + And I configure scenario "span_duration" to "1.5" + And I configure scenario "opts_first_class" to "yes" + And I configure scenario "variant_name" to "LongerDuration" + And I start bugsnag + And I run the loaded scenario + And I wait for 1 span + Then the trace "Content-Type" header equals "application/json" + * the trace "Bugsnag-Span-Sampling" header equals "1:1" + * 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 "MemoryMetricsScenarioLongerDuration" + * 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 1 + * every span field "startTimeUnixNano" matches the regex "^[0-9]+$" + * every span field "endTimeUnixNano" matches the regex "^[0-9]+$" + * the trace payload field "resourceSpans.0.resource" string attribute "service.name" matches the regex "com.bugsnag.fixtures.cocoaperformance(xcframework)?" + * the trace payload field "resourceSpans.0.resource" string attribute "telemetry.sdk.name" equals "bugsnag.performance.cocoa" + * the trace payload field "resourceSpans.0.resource" string attribute "telemetry.sdk.version" matches the regex "[0-9]+\.[0-9]+\.[0-9]+" + * every span bool attribute "bugsnag.span.first_class" is true + * a span array attribute "bugsnag.system.memory.timestamps" contains from 3 to 4 elements + * a span integer attribute "bugsnag.system.memory.spaces.device.size" is greater than 0 + * a span array attribute "bugsnag.system.memory.spaces.device.used" contains from 3 to 4 elements + * a span integer attribute "bugsnag.system.memory.spaces.device.mean" is greater than 0 + + Scenario: If we generate spans later, we still expect memory samples + Given I load scenario "MemoryMetricsScenario" + And I configure bugsnag "memoryMetrics" to "true" + And I configure scenario "run_delay" to "1.1" + And I configure scenario "span_duration" to "1.5" + And I configure scenario "opts_first_class" to "yes" + And I configure scenario "variant_name" to "GenerateLater" + And I start bugsnag + And I run the loaded scenario + And I wait for 1 span + Then the trace "Content-Type" header equals "application/json" + * the trace "Bugsnag-Span-Sampling" header equals "1:1" + * 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 "MemoryMetricsScenarioGenerateLater" + * 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 1 + * every span field "startTimeUnixNano" matches the regex "^[0-9]+$" + * every span field "endTimeUnixNano" matches the regex "^[0-9]+$" + * the trace payload field "resourceSpans.0.resource" string attribute "service.name" matches the regex "com.bugsnag.fixtures.cocoaperformance(xcframework)?" + * the trace payload field "resourceSpans.0.resource" string attribute "telemetry.sdk.name" equals "bugsnag.performance.cocoa" + * the trace payload field "resourceSpans.0.resource" string attribute "telemetry.sdk.version" matches the regex "[0-9]+\.[0-9]+\.[0-9]+" + * every span bool attribute "bugsnag.span.first_class" is true + * a span array attribute "bugsnag.system.memory.timestamps" contains from 3 to 4 elements + * a span integer attribute "bugsnag.system.memory.spaces.device.size" is greater than 0 + * a span array attribute "bugsnag.system.memory.spaces.device.used" contains from 3 to 4 elements + * a span integer attribute "bugsnag.system.memory.spaces.device.mean" is greater than 0 diff --git a/features/default/network.feature b/features/default/network.feature deleted file mode 100644 index 4ecff6c4..00000000 --- a/features/default/network.feature +++ /dev/null @@ -1,73 +0,0 @@ -Feature: Automatic instrumentation spans - - Scenario: AutoInstrumentNetworkCallbackScenario - Given I run "AutoInstrumentNetworkCallbackScenario" - And I wait for exactly 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. - And I wait for 20 seconds - # There will actually be any number of requests by this point since we're not filtering at all. - And I wait for 1 span - 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$" - * a 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" - - Scenario: ManualNetworkCallbackScenario - Given I run "ManualNetworkCallbackScenario" - And I wait for exactly 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: AutoInstrumentNetworkTracePropagationScenario: Allow All - Given I load scenario "AutoInstrumentNetworkTracePropagationScenario" - And I configure bugsnag "propagateTraceParentToUrlsMatching" to ".*" - And I invoke "setCallSitesWithCallSiteStrs:" with parameter "?test=1" - And I start bugsnag - And I run the loaded scenario - And I wait to receive a reflection - Then the reflection "traceparent" header matches the regex "^00-[A-Fa-f0-9]{32}-[A-Fa-f0-9]{16}-01" - - Scenario: AutoInstrumentNetworkTracePropagationScenario: Allow None by default - Given I load scenario "AutoInstrumentNetworkTracePropagationScenario" - And I invoke "setCallSitesWithCallSiteStrs:" with parameter "?test=1" - And I start bugsnag - And I run the loaded scenario - And I wait to receive a reflection - Then the reflection "traceparent" header is not present - - Scenario: AutoInstrumentNetworkTracePropagationScenario: Allow Some - Given I load scenario "AutoInstrumentNetworkTracePropagationScenario" - And I configure bugsnag "propagateTraceParentToUrlsMatching" to ".*test.*" - And I invoke "setCallSitesWithCallSiteStrs:" with parameter "?test=1,?temp=1" - And I start bugsnag - And I run the loaded scenario - Then I wait to receive 2 reflections - And the reflection "traceparent" header matches the regex "^00-[A-Fa-f0-9]{32}-[A-Fa-f0-9]{16}-01" - Then I discard the oldest reflection - And the reflection "traceparent" header is not present From 9ca71dbeecc55d07bffffe2da9f043b90c378e5c Mon Sep 17 00:00:00 2001 From: Daria Bialobrzeska <88393714+DariaKunoichi@users.noreply.github.com> Date: Thu, 23 Oct 2025 15:28:12 +0200 Subject: [PATCH 44/53] Add tests for app start type customization (#491) --- .../BugsnagPerformanceAppStartSpanControl.h | 1 + .../automatic/automatic_app_start.feature | 6 +- features/default/plugins.feature | 11 +++- .../ios/Fixture.xcodeproj/project.pbxproj | 4 ++ .../project.pbxproj | 16 ++++++ .../Scenarios/AppStartTypeLateScenario.swift | 55 +++++++++++++++++++ .../AutoInstrumentAppStartsScenario.swift | 18 +++++- ...trumentAppStartsWithViewLoadScenario.swift | 19 ++++++- 8 files changed, 120 insertions(+), 10 deletions(-) create mode 100644 features/fixtures/ios/Scenarios/AppStartTypeLateScenario.swift diff --git a/Sources/BugsnagPerformance/include/BugsnagPerformance/BugsnagPerformanceAppStartSpanControl.h b/Sources/BugsnagPerformance/include/BugsnagPerformance/BugsnagPerformanceAppStartSpanControl.h index 7aec1fc9..fd10719e 100644 --- a/Sources/BugsnagPerformance/include/BugsnagPerformance/BugsnagPerformanceAppStartSpanControl.h +++ b/Sources/BugsnagPerformance/include/BugsnagPerformance/BugsnagPerformanceAppStartSpanControl.h @@ -10,6 +10,7 @@ NS_ASSUME_NONNULL_BEGIN +OBJC_EXPORT @interface BugsnagPerformanceAppStartSpanControl: NSObject - (void)setType:(NSString *_Nullable)type; - (void)clearType; diff --git a/features/default/automatic/automatic_app_start.feature b/features/default/automatic/automatic_app_start.feature index 11ee7429..0f59b386 100644 --- a/features/default/automatic/automatic_app_start.feature +++ b/features/default/automatic/automatic_app_start.feature @@ -2,10 +2,9 @@ Feature: Automatic app start instrumentation spans Scenario: Auto instrument app starts without a view load Given I run "AutoInstrumentAppStartsScenario" - And I wait for 3 seconds + Then I relaunch the app after shutdown And I wait for 4 spans Then the trace "Content-Type" header equals "application/json" - * the trace "Bugsnag-Span-Sampling" header equals "1:4" * 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 "[AppStart/iOSCold]" * a span field "name" equals "[AppStartPhase/App launching - pre main()]" @@ -22,7 +21,6 @@ Feature: Automatic app start instrumentation spans * a span string attribute "bugsnag.phase" equals "UI init" * a span string attribute "bugsnag.span.category" equals "app_start" * a span string attribute "bugsnag.span.category" equals "app_start_phase" - * a span string attribute "bugsnag.app_start.first_view_name" equals "Fixture.ViewController" * every span bool attribute "bugsnag.span.first_class" does not exist * the trace payload field "resourceSpans.0.resource" string attribute "service.name" matches the regex "com.bugsnag.fixtures.cocoaperformance(xcframework)?" * the trace payload field "resourceSpans.0.resource" string attribute "telemetry.sdk.name" equals "bugsnag.performance.cocoa" @@ -30,7 +28,7 @@ Feature: Automatic app start instrumentation spans Scenario: Auto instrument app starts with a view load Given I run "AutoInstrumentAppStartsWithViewLoadScenario" - And I wait for 3 seconds + Then I relaunch the app after shutdown And I wait for 13 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$" diff --git a/features/default/plugins.feature b/features/default/plugins.feature index 2c270d78..8fa22c55 100644 --- a/features/default/plugins.feature +++ b/features/default/plugins.feature @@ -49,4 +49,13 @@ Feature: Plugins And I wait for 5 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$" - * a span field "name" equals "[AppStart/iOSCold]customType" \ No newline at end of file + * a span field "name" equals "[AppStart/iOSCold]customType" + + Scenario: App start type plugin wont change span name if it's too late + Given I run "AppStartTypeLateScenario" + Then I relaunch the app after shutdown + And I wait for 4 seconds + And I wait for 5 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$" + * a span field "name" equals "[AppStart/iOSCold]" diff --git a/features/fixtures/ios/Fixture.xcodeproj/project.pbxproj b/features/fixtures/ios/Fixture.xcodeproj/project.pbxproj index b39cf540..2c573ef7 100644 --- a/features/fixtures/ios/Fixture.xcodeproj/project.pbxproj +++ b/features/fixtures/ios/Fixture.xcodeproj/project.pbxproj @@ -61,6 +61,7 @@ 09F3F5302D6F17B300BAA0A3 /* RenderingMetricsScenario.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09F3F52F2D6F17B300BAA0A3 /* RenderingMetricsScenario.swift */; }; 1C377BA02E78BDFE002148B2 /* StartupConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C377B9F2E78BDF8002148B2 /* StartupConfiguration.swift */; }; 1CC791752E67838A0015FFBA /* AppStartTypeScenario.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1CC791742E6783850015FFBA /* AppStartTypeScenario.swift */; }; + 1CFB95492EA798DC00025C16 /* AppStartTypeLateScenario.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1CFB95482EA798D600025C16 /* AppStartTypeLateScenario.swift */; }; 960EECE92B2316E1009FAA11 /* AutoInstrumentGenericViewLoadScenario.swift in Sources */ = {isa = PBXBuildFile; fileRef = 960EECE82B2316E1009FAA11 /* AutoInstrumentGenericViewLoadScenario.swift */; }; 96284DCE2B626B6F00025070 /* AutoInstrumentPreLoadedViewLoadScenario.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96284DCD2B626B6F00025070 /* AutoInstrumentPreLoadedViewLoadScenario.swift */; }; 962CE7EE2E62240E00380522 /* LoadingIndicatorViewSimpleStopScenario.swift in Sources */ = {isa = PBXBuildFile; fileRef = 962CE7ED2E62240E00380522 /* LoadingIndicatorViewSimpleStopScenario.swift */; }; @@ -171,6 +172,7 @@ 09F3F52F2D6F17B300BAA0A3 /* RenderingMetricsScenario.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RenderingMetricsScenario.swift; sourceTree = ""; }; 1C377B9F2E78BDF8002148B2 /* StartupConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StartupConfiguration.swift; sourceTree = ""; }; 1CC791742E6783850015FFBA /* AppStartTypeScenario.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppStartTypeScenario.swift; sourceTree = ""; }; + 1CFB95482EA798D600025C16 /* AppStartTypeLateScenario.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppStartTypeLateScenario.swift; sourceTree = ""; }; 960EECE82B2316E1009FAA11 /* AutoInstrumentGenericViewLoadScenario.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutoInstrumentGenericViewLoadScenario.swift; sourceTree = ""; }; 96284DCD2B626B6F00025070 /* AutoInstrumentPreLoadedViewLoadScenario.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutoInstrumentPreLoadedViewLoadScenario.swift; sourceTree = ""; }; 962CE7EC2E62240E00380522 /* LoadingIndicatorViewSimpleRemoveScenario.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadingIndicatorViewSimpleRemoveScenario.swift; sourceTree = ""; }; @@ -302,6 +304,7 @@ 01FE4DC128E1AF0700D1F239 /* Scenarios */ = { isa = PBXGroup; children = ( + 1CFB95482EA798D600025C16 /* AppStartTypeLateScenario.swift */, 1CC791742E6783850015FFBA /* AppStartTypeScenario.swift */, 96D528CB2C72B14300FEA2E2 /* AppDataOverrideScenario.swift */, 01D3A7DF28F0290D0063D79E /* AutoInstrumentAppStartsScenario.swift */, @@ -568,6 +571,7 @@ 96F129352DCE0CFE00A6FB2B /* ManualSpanWithRemoteContextParentScenario.swift in Sources */, 09F025072BA08804007D9F73 /* ObjCURLSession.m in Sources */, 0988B5392CAD36C500D131B1 /* InfraCheckMinimalBugsnagScenario.swift in Sources */, + 1CFB95492EA798DC00025C16 /* AppStartTypeLateScenario.swift in Sources */, CBC90CDE29CDCFF700280884 /* FirstClassNoScenario.swift in Sources */, CBC90C4329C466BD00280884 /* ForceUBSan.m in Sources */, 9691A9DD2CA5E61500707CDF /* FrameMetricsAutoInstrumentRenderingOffScenario.swift in Sources */, diff --git a/features/fixtures/ios/FixtureXcFramework.xcodeproj/project.pbxproj b/features/fixtures/ios/FixtureXcFramework.xcodeproj/project.pbxproj index 4a2bc662..0cdaface 100644 --- a/features/fixtures/ios/FixtureXcFramework.xcodeproj/project.pbxproj +++ b/features/fixtures/ios/FixtureXcFramework.xcodeproj/project.pbxproj @@ -55,6 +55,10 @@ 09F025152BAC50EC007D9F73 /* ViewDidLoadDoesntTriggerScenario.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09F025142BAC50EC007D9F73 /* ViewDidLoadDoesntTriggerScenario.swift */; }; 09F3F52A2D6C72B300BAA0A3 /* CPUMetricsScenario.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09F3F5292D6C72B300BAA0A3 /* CPUMetricsScenario.swift */; }; 09F3F52C2D6C72BD00BAA0A3 /* MemoryMetricsScenario.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09F3F52B2D6C72BD00BAA0A3 /* MemoryMetricsScenario.swift */; }; + 1C3E2DB12EAA5F4900B32AD2 /* AppStartTypeLateScenario.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C3E2DAD2EAA5F4900B32AD2 /* AppStartTypeLateScenario.swift */; }; + 1C3E2DB22EAA5F4900B32AD2 /* AppStartTypeScenario.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C3E2DAE2EAA5F4900B32AD2 /* AppStartTypeScenario.swift */; }; + 1C3E2DB32EAA5F4900B32AD2 /* ManualSpanWithRemoteContextParentScenario.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C3E2DB02EAA5F4900B32AD2 /* ManualSpanWithRemoteContextParentScenario.swift */; }; + 1C3E2DB42EAA5F4900B32AD2 /* AutoInstrumentGenericViewLoadScenario2.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C3E2DAF2EAA5F4900B32AD2 /* AutoInstrumentGenericViewLoadScenario2.swift */; }; 1CA34CAB2E81A09F00512E2F /* StartupConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1CA34CAA2E81A09F00512E2F /* StartupConfiguration.swift */; }; 55A1B7CB2CBFF140009D68A7 /* BugsnagPerformance.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 55A1B7C92CBFF140009D68A7 /* BugsnagPerformance.xcframework */; }; 55A1B7CC2CBFF140009D68A7 /* BugsnagPerformance.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 55A1B7C92CBFF140009D68A7 /* BugsnagPerformance.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; @@ -181,6 +185,10 @@ 09F025142BAC50EC007D9F73 /* ViewDidLoadDoesntTriggerScenario.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewDidLoadDoesntTriggerScenario.swift; sourceTree = ""; }; 09F3F5292D6C72B300BAA0A3 /* CPUMetricsScenario.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CPUMetricsScenario.swift; sourceTree = ""; }; 09F3F52B2D6C72BD00BAA0A3 /* MemoryMetricsScenario.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MemoryMetricsScenario.swift; sourceTree = ""; }; + 1C3E2DAD2EAA5F4900B32AD2 /* AppStartTypeLateScenario.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppStartTypeLateScenario.swift; sourceTree = ""; }; + 1C3E2DAE2EAA5F4900B32AD2 /* AppStartTypeScenario.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppStartTypeScenario.swift; sourceTree = ""; }; + 1C3E2DAF2EAA5F4900B32AD2 /* AutoInstrumentGenericViewLoadScenario2.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutoInstrumentGenericViewLoadScenario2.swift; sourceTree = ""; }; + 1C3E2DB02EAA5F4900B32AD2 /* ManualSpanWithRemoteContextParentScenario.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ManualSpanWithRemoteContextParentScenario.swift; sourceTree = ""; }; 1CA34CAA2E81A09F00512E2F /* StartupConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StartupConfiguration.swift; sourceTree = ""; }; 55A1B7C92CBFF140009D68A7 /* BugsnagPerformance.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = BugsnagPerformance.xcframework; path = ../../../BugsnagPerformance.xcframework; sourceTree = ""; }; 55A1B7CA2CBFF140009D68A7 /* BugsnagPerformanceSwift.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = BugsnagPerformanceSwift.xcframework; path = ../../../BugsnagPerformanceSwift.xcframework; sourceTree = ""; }; @@ -317,6 +325,10 @@ 01FE4DC128E1AF0700D1F239 /* Scenarios */ = { isa = PBXGroup; children = ( + 1C3E2DAD2EAA5F4900B32AD2 /* AppStartTypeLateScenario.swift */, + 1C3E2DAE2EAA5F4900B32AD2 /* AppStartTypeScenario.swift */, + 1C3E2DAF2EAA5F4900B32AD2 /* AutoInstrumentGenericViewLoadScenario2.swift */, + 1C3E2DB02EAA5F4900B32AD2 /* ManualSpanWithRemoteContextParentScenario.swift */, 96D528CB2C72B14300FEA2E2 /* AppDataOverrideScenario.swift */, 01D3A7DF28F0290D0063D79E /* AutoInstrumentAppStartsScenario.swift */, 96A25F3E2D6BC9BF00A18116 /* AutoInstrumentAppStartsWithViewLoadScenario.swift */, @@ -553,6 +565,10 @@ 098808E02B10A6E400DC1677 /* ManualViewLoadPhaseScenario.swift in Sources */, 01FE4DC528E1AF9600D1F239 /* Scenario.swift in Sources */, 09DA59A52A6E866B00A06EEE /* ManualNetworkCallbackScenario.swift in Sources */, + 1C3E2DB12EAA5F4900B32AD2 /* AppStartTypeLateScenario.swift in Sources */, + 1C3E2DB22EAA5F4900B32AD2 /* AppStartTypeScenario.swift in Sources */, + 1C3E2DB32EAA5F4900B32AD2 /* ManualSpanWithRemoteContextParentScenario.swift in Sources */, + 1C3E2DB42EAA5F4900B32AD2 /* AutoInstrumentGenericViewLoadScenario2.swift in Sources */, 09301DC12B63A65A000A7C12 /* ComplexViewScenario.swift in Sources */, 96D528CE2C75DC7000FEA2E2 /* FixedSamplingProbabilityOneScenario.swift in Sources */, CB0496942913CA300097E526 /* BatchingWithTimeoutScenario.swift in Sources */, diff --git a/features/fixtures/ios/Scenarios/AppStartTypeLateScenario.swift b/features/fixtures/ios/Scenarios/AppStartTypeLateScenario.swift new file mode 100644 index 00000000..9e6bec14 --- /dev/null +++ b/features/fixtures/ios/Scenarios/AppStartTypeLateScenario.swift @@ -0,0 +1,55 @@ +// +// AppStartTypeLateScenario.swift +// Fixture +// +// Created by Daria Bialobrzeska on 21/10/2025. +// + +import BugsnagPerformance + +@objcMembers +class AppStartTypeLateScenario: 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: AppStartTypeLateScenario.self) + startupConfig.endpoint = fixtureConfig.tracesURL + + startupConfig.saveStartupConfig() + + DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { + exit(0) + } + } + + override func customViewController() -> UIViewController? { + return AppStartTypeLateScenario_ViewController() + } +} + +class AppStartTypeLateScenario_ViewController: UIViewController { + 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 + + view = customView + } + + override func viewDidAppear(_ animated: Bool) { + DispatchQueue.main.asyncAfter(deadline: .now() + 4) { + let query = BugsnagPerformanceAppStartSpanQuery() + let spanControl = BugsnagPerformance.getSpanControls(with: query) as! BugsnagPerformanceAppStartSpanControl? + spanControl?.setType("customType") + } + } +} diff --git a/features/fixtures/ios/Scenarios/AutoInstrumentAppStartsScenario.swift b/features/fixtures/ios/Scenarios/AutoInstrumentAppStartsScenario.swift index a143302b..0c0bb087 100644 --- a/features/fixtures/ios/Scenarios/AutoInstrumentAppStartsScenario.swift +++ b/features/fixtures/ios/Scenarios/AutoInstrumentAppStartsScenario.swift @@ -9,12 +9,26 @@ import BugsnagPerformance @objcMembers class AutoInstrumentAppStartsScenario: Scenario { - + override func setInitialBugsnagConfiguration() { super.setInitialBugsnagConfiguration() - bugsnagPerfConfig.autoInstrumentAppStarts = true } override func run() { + // Save a startup configuration + let startupConfig = StartupConfiguration(configFile: nil) + startupConfig.autoInstrumentAppStarts = true + startupConfig.scenarioName = String(describing: AutoInstrumentAppStartsScenario.self) + startupConfig.endpoint = fixtureConfig.tracesURL + + startupConfig.saveStartupConfig() + + DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { + exit(0) + } + } + + override func customViewController() -> UIViewController? { + return ViewController() } } diff --git a/features/fixtures/ios/Scenarios/AutoInstrumentAppStartsWithViewLoadScenario.swift b/features/fixtures/ios/Scenarios/AutoInstrumentAppStartsWithViewLoadScenario.swift index 79922956..d6ff40e5 100644 --- a/features/fixtures/ios/Scenarios/AutoInstrumentAppStartsWithViewLoadScenario.swift +++ b/features/fixtures/ios/Scenarios/AutoInstrumentAppStartsWithViewLoadScenario.swift @@ -12,8 +12,6 @@ class AutoInstrumentAppStartsWithViewLoadScenario: Scenario { override func setInitialBugsnagConfiguration() { super.setInitialBugsnagConfiguration() - bugsnagPerfConfig.autoInstrumentAppStarts = true - bugsnagPerfConfig.autoInstrumentViewControllers = true // This test can generate a variable number of spans depending on the OS version, // so use a timed send instead. bugsnagPerfConfig.internal.autoTriggerExportOnBatchSize = 100 @@ -21,6 +19,21 @@ class AutoInstrumentAppStartsWithViewLoadScenario: Scenario { } override func run() { - + // Save a startup configuration + let startupConfig = StartupConfiguration(configFile: nil) + startupConfig.autoInstrumentAppStarts = true + startupConfig.autoInstrumentViewControllers = true + startupConfig.scenarioName = String(describing: AutoInstrumentAppStartsWithViewLoadScenario.self) + startupConfig.endpoint = fixtureConfig.tracesURL + + startupConfig.saveStartupConfig() + + DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { + exit(0) + } + } + + override func customViewController() -> UIViewController? { + return ViewController() } } From 9be471c7e5eb5d587b2c2314b397539af4399ca4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 27 Oct 2025 02:49:51 +0100 Subject: [PATCH 45/53] Bump github/codeql-action from 3.29.1 to 4.30.9 (#489) Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3.29.1 to 4.30.9. - [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/39edc492dbe16b1465b0cafca41432d857bdb31a...16140ae1a102900babc80a33c44059580f687047) --- updated-dependencies: - dependency-name: github/codeql-action dependency-version: 4.30.9 dependency-type: direct:production update-type: version-update:semver-major ... 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 e8aaadcf..15df32eb 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@39edc492dbe16b1465b0cafca41432d857bdb31a # v3.29.1 + uses: github/codeql-action/upload-sarif@16140ae1a102900babc80a33c44059580f687047 # v4.30.9 with: sarif_file: results.sarif From 8164552ec5deb87609a068095b73b20a3a7df127 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 27 Oct 2025 02:50:58 +0100 Subject: [PATCH 46/53] Bump ossf/scorecard-action from 2.4.2 to 2.4.3 (#483) Bumps [ossf/scorecard-action](https://github.com/ossf/scorecard-action) from 2.4.2 to 2.4.3. - [Release notes](https://github.com/ossf/scorecard-action/releases) - [Changelog](https://github.com/ossf/scorecard-action/blob/main/RELEASE.md) - [Commits](https://github.com/ossf/scorecard-action/compare/05b42c624433fc40578a4040d5cf5e36ddca8cde...4eaacf0543bb3f2c246792bd56e8cdeffafb205a) --- updated-dependencies: - dependency-name: ossf/scorecard-action dependency-version: 2.4.3 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 15df32eb..b758ed17 100644 --- a/.github/workflows/scorecard.yml +++ b/.github/workflows/scorecard.yml @@ -37,7 +37,7 @@ jobs: persist-credentials: false - name: "Run analysis" - uses: ossf/scorecard-action@05b42c624433fc40578a4040d5cf5e36ddca8cde # v2.4.2 + uses: ossf/scorecard-action@4eaacf0543bb3f2c246792bd56e8cdeffafb205a # v2.4.3 with: results_file: results.sarif results_format: sarif From 4d69ebf2551d2ff6cfc4a2f0e7c8973284f9e1bf Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 27 Oct 2025 02:52:41 +0100 Subject: [PATCH 47/53] Bump actions/checkout from 4.2.2 to 5.0.0 (#465) Bumps [actions/checkout](https://github.com/actions/checkout) from 4.2.2 to 5.0.0. - [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/11bd71901bbe5b1630ceea73d27597364c9af683...08c6903cd8c0fde910a37f88322edcfb5dd907a8) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: 5.0.0 dependency-type: direct:production update-type: version-update:semver-major ... 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 db4bb63c..cff001d2 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@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 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@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: clean: false fetch-depth: 100 diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml index b758ed17..de4f9929 100644 --- a/.github/workflows/scorecard.yml +++ b/.github/workflows/scorecard.yml @@ -32,7 +32,7 @@ jobs: steps: - name: "Checkout code" - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: persist-credentials: false From 4088ba2b2d9df97ad3352d621ac0f63b06f34fdf Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 27 Oct 2025 12:26:34 +0100 Subject: [PATCH 48/53] Bump actions/upload-artifact from 4.6.2 to 5.0.0 (#495) Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 4.6.2 to 5.0.0. - [Release notes](https://github.com/actions/upload-artifact/releases) - [Commits](https://github.com/actions/upload-artifact/compare/ea165f8d65b6e75b540449e92b4886f43607fa02...330a01c490aca151604b8cf639adc76d48f6c5d4) --- updated-dependencies: - dependency-name: actions/upload-artifact dependency-version: 5.0.0 dependency-type: direct:production update-type: version-update:semver-major ... 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 de4f9929..4e29f7f8 100644 --- a/.github/workflows/scorecard.yml +++ b/.github/workflows/scorecard.yml @@ -59,7 +59,7 @@ jobs: # Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF # format to the repository Actions tab. - name: "Upload artifact" - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 with: name: SARIF file path: results.sarif From f18143dc8a805bac1c3112e23d8977f0bbae11c9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 27 Oct 2025 12:27:20 +0100 Subject: [PATCH 49/53] Bump github/codeql-action from 4.30.9 to 4.31.0 (#497) Bumps [github/codeql-action](https://github.com/github/codeql-action) from 4.30.9 to 4.31.0. - [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/16140ae1a102900babc80a33c44059580f687047...4e94bd11f71e507f7f87df81788dff88d1dacbfb) --- updated-dependencies: - dependency-name: github/codeql-action dependency-version: 4.31.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/scorecard.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml index 4e29f7f8..a02ed960 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@16140ae1a102900babc80a33c44059580f687047 # v4.30.9 + uses: github/codeql-action/upload-sarif@4e94bd11f71e507f7f87df81788dff88d1dacbfb # v4.31.0 with: sarif_file: results.sarif From 45120a1566b43921c98c38a672f228599cd8c6f0 Mon Sep 17 00:00:00 2001 From: Steve Kirkland Date: Mon, 27 Oct 2025 11:28:01 +0000 Subject: [PATCH 50/53] Provide local implementation of moved Maze Runner function (#496) Co-authored-by: Steve Kirkland --- features/steps/app_steps.rb | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/features/steps/app_steps.rb b/features/steps/app_steps.rb index 29ef0aaf..aafc2d6d 100644 --- a/features/steps/app_steps.rb +++ b/features/steps/app_steps.rb @@ -371,3 +371,11 @@ def assert_received_exactly_spans(span_count, list) childSpans2.map { |span| Maze.check.true(parentSpan['spanId'] == span['parentSpanId']) } end + +def spans_from_request_list(list) + list.remaining + .flat_map { |req| req[:body]['resourceSpans'] } + .flat_map { |r| r['scopeSpans'] } + .flat_map { |s| s['spans'] } + .select { |s| !s.nil? } +end From 207594f835c8135bcdece185ca8c992531b2d6e8 Mon Sep 17 00:00:00 2001 From: Robert Bartoszewski <126675445+robert-smartbear@users.noreply.github.com> Date: Tue, 28 Oct 2025 13:04:35 +0100 Subject: [PATCH 51/53] Calling endOnDestroy on a span causes a crash in sendCurrentBatchTask (#498) * Fixed spanOnDestroy causing a crash * E2E test * Updated changelog --------- Co-authored-by: Robert Bartoszewski --- CHANGELOG.md | 7 +++ .../Private/BugsnagPerformanceSpan+Private.h | 3 +- .../Private/SpanStackingHandler.mm | 6 +-- .../Public/BugsnagPerformanceSpan.mm | 51 +++++++++++++++++-- features/default/manual_spans.feature | 31 +++++++++++ .../ios/Fixture.xcodeproj/project.pbxproj | 18 ++++--- .../Fixture/BugsnagPerformanceSpan+Internal.h | 12 +++++ .../ios/Fixture/Fixture-Bridging-Header.h | 1 + .../project.pbxproj | 16 ++++-- .../ManualSpanEndOnDestroyScenario.swift | 28 ++++++++++ 10 files changed, 154 insertions(+), 19 deletions(-) create mode 100644 features/fixtures/ios/Fixture/BugsnagPerformanceSpan+Internal.h create mode 100644 features/fixtures/ios/Scenarios/ManualSpanEndOnDestroyScenario.swift diff --git a/CHANGELOG.md b/CHANGELOG.md index d4170ec3..feaf3cc9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,13 @@ Changelog ========= +## TBD + +### Bug fixes + +* Guard against an edge case where an auto-captured URL request with a nil URL can cause a crash. + [498](https://github.com/bugsnag/bugsnag-cocoa-performance/pull/498) + ## 1.15.0 (2025-08-14) ### Enhancements diff --git a/Sources/BugsnagPerformance/Private/BugsnagPerformanceSpan+Private.h b/Sources/BugsnagPerformance/Private/BugsnagPerformanceSpan+Private.h index bf6e1269..d9e10f4d 100644 --- a/Sources/BugsnagPerformance/Private/BugsnagPerformanceSpan+Private.h +++ b/Sources/BugsnagPerformance/Private/BugsnagPerformanceSpan+Private.h @@ -36,7 +36,7 @@ NS_ASSUME_NONNULL_BEGIN @interface BugsnagPerformanceSpan () -@property(nonatomic, copy) void (^onDumped)(BugsnagPerformanceSpan *); +@property(nonatomic, copy) void (^onDumped)(SpanId); // These mark the actual times that the span was instantiated and ended, // irrespective of any time values this span will report. @@ -46,6 +46,7 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic) CFAbsoluteTime startAbsTime; @property (nonatomic) CFAbsoluteTime endAbsTime; +@property (nonatomic) BOOL isClone; @property (nonatomic) OnSpanDestroyAction onSpanDestroyAction; @property (nonatomic,readwrite) NSString *name; @property (nonatomic,readonly) NSMutableDictionary *attributes; diff --git a/Sources/BugsnagPerformance/Private/SpanStackingHandler.mm b/Sources/BugsnagPerformance/Private/SpanStackingHandler.mm index 9850888c..e6565499 100644 --- a/Sources/BugsnagPerformance/Private/SpanStackingHandler.mm +++ b/Sources/BugsnagPerformance/Private/SpanStackingHandler.mm @@ -38,12 +38,12 @@ static inline os_activity_id_t currentActivityId() { parentState->childSpansCount++; } __block auto blockThis = this; - span.onDumped = ^void(BugsnagPerformanceSpan *dumpedSpan) { + span.onDumped = ^void(SpanId spanId) { std::lock_guard blockGuard(blockThis->mutex_); - auto state = blockThis->spanStateForSpan(dumpedSpan.spanId); + auto state = blockThis->spanStateForSpan(spanId); if (state != nullptr) { state->isDumped = true; - blockThis->removeSpan(dumpedSpan.spanId); + blockThis->removeSpan(spanId); } }; } diff --git a/Sources/BugsnagPerformance/Public/BugsnagPerformanceSpan.mm b/Sources/BugsnagPerformance/Public/BugsnagPerformanceSpan.mm index 440fc8a3..9a337ac4 100644 --- a/Sources/BugsnagPerformance/Public/BugsnagPerformanceSpan.mm +++ b/Sources/BugsnagPerformance/Public/BugsnagPerformanceSpan.mm @@ -72,18 +72,22 @@ - (instancetype)initWithName:(NSString *)name } - (void)dealloc { + if (self.isClone || self.state != SpanStateOpen) { + return; + } BSGLogTrace(@"BugsnagPerformanceSpan.dealloc %@", self.name); - if (self.state == SpanStateOpen && self.onDumped) { - self.onDumped(self); + if (self.onDumped) { + self.onDumped(self.spanId); } + BugsnagPerformanceSpan *clone = [self clone]; switch(self.onSpanDestroyAction) { case OnSpanDestroyAbort: BSGLogDebug(@"BugsnagPerformanceSpan.dealloc: for span %@. Action = Abort", self.name); - [self abortIfOpen]; + [clone abortIfOpen]; break; case OnSpanDestroyEnd: BSGLogDebug(@"BugsnagPerformanceSpan.dealloc: for span %@. Action = End", self.name); - [self end]; + [clone end]; break; default: BSGLogError(@"BugsnagPerformanceSpan.dealloc: for span %@. Unknown action type %d", self.name, self.onSpanDestroyAction); @@ -320,4 +324,43 @@ - (void)forceMutate:(void (^)())block { } } +- (instancetype)clone { + BugsnagPerformanceSpan *copy = [[BugsnagPerformanceSpan alloc] + initWithName:self.name + traceId:self.traceId + spanId:self.spanId + parentId:self.parentId + startTime:self.startAbsTime + firstClass:self.firstClass + samplingProbability:self.samplingProbability + attributeCountLimit:self.attributeCountLimit + metricsOptions:self.metricsOptions + onSpanEndSet:self.onSpanEndSet + onSpanClosed:self.onSpanClosed + onSpanBlocked:self.onSpanBlocked]; + + // Copy additional state that's not set during initialization + @synchronized (self) { + copy.actuallyStartedAt = self.actuallyStartedAt; + copy.actuallyEndedAt = self.actuallyEndedAt; + copy.endAbsTime = self.endAbsTime; + copy.onSpanDestroyAction = self.onSpanDestroyAction; + copy.kind = self.kind; + copy.isMutable = self.isMutable; + copy.hasBeenProcessed = self.hasBeenProcessed; + copy.wasStartOrEndTimeProvided = self.wasStartOrEndTimeProvided; + copy.state = self.state; + copy.startClock = self.startClock; + copy.onDumped = self.onDumped; + + // Deep copy mutable collections + [copy.attributes addEntriesFromDictionary:self.attributes]; + copy.startFramerateSnapshot = self.startFramerateSnapshot; + copy.endFramerateSnapshot = self.endFramerateSnapshot; + copy.isClone = YES; + } + + return copy; +} + @end diff --git a/features/default/manual_spans.feature b/features/default/manual_spans.feature index 20bf831b..0aad6799 100644 --- a/features/default/manual_spans.feature +++ b/features/default/manual_spans.feature @@ -50,6 +50,37 @@ Feature: Manual creation of spans * the trace payload field "resourceSpans.0.resource" string attribute "telemetry.sdk.version" matches the regex "[0-9]+\.[0-9]+\.[0-9]+" * every span string attribute "bugsnag.span.category" equals "custom" + Scenario: Manually start a span and call endOnDestroy + Given I run "ManualSpanEndOnDestroyScenario" + And I wait to receive at least 1 span + 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-Span-Sampling" header equals "1:1" + * 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 "ManualSpanEndOnDestroyScenario" + * 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 1 + * every span field "startTimeUnixNano" matches the regex "^[0-9]+$" + * every span field "endTimeUnixNano" matches the regex "^[0-9]+$" + * every span bool attribute "bugsnag.app.in_foreground" is true + * every span string attribute "net.host.connection.type" equals "wifi" + * every span bool attribute "bugsnag.span.first_class" is true + * every span string attribute "TestString" equals "test" + * the trace payload field "resourceSpans.0.resource" string attribute "bugsnag.app.bundle_version" equals "30" + * the trace payload field "resourceSpans.0.resource" string attribute "deployment.environment" equals "staging" + * the trace payload field "resourceSpans.0.resource" string attribute "device.manufacturer" equals "Apple" + * the trace payload field "resourceSpans.0.resource" string attribute "device.model.identifier" exists + * the trace payload field "resourceSpans.0.resource" string attribute "host.arch" matches the regex "arm64|amd64" + * the trace payload field "resourceSpans.0.resource" string attribute "os.name" equals "iOS" + * the trace payload field "resourceSpans.0.resource" string attribute "os.type" equals "darwin" + * the trace payload field "resourceSpans.0.resource" string attribute "os.version" exists + * the trace payload field "resourceSpans.0.resource" string attribute "service.name" matches the regex "com.bugsnag.fixtures.cocoaperformance(xcframework)?" + * the trace payload field "resourceSpans.0.resource" string attribute "service.version" equals "10.0" + * the trace payload field "resourceSpans.0.resource" string attribute "telemetry.sdk.name" equals "bugsnag.performance.cocoa" + * the trace payload field "resourceSpans.0.resource" string attribute "telemetry.sdk.version" matches the regex "[0-9]+\.[0-9]+\.[0-9]+" + * every span string attribute "bugsnag.span.category" equals "custom" + Scenario: Starting and ending a span before starting the SDK Given I run "ManualSpanBeforeStartScenario" And I wait to receive at least 1 span diff --git a/features/fixtures/ios/Fixture.xcodeproj/project.pbxproj b/features/fixtures/ios/Fixture.xcodeproj/project.pbxproj index db8e54bb..e09b9b03 100644 --- a/features/fixtures/ios/Fixture.xcodeproj/project.pbxproj +++ b/features/fixtures/ios/Fixture.xcodeproj/project.pbxproj @@ -78,6 +78,7 @@ 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 */; }; + 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 */; }; CB0496942913CA300097E526 /* BatchingWithTimeoutScenario.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB0496932913CA300097E526 /* BatchingWithTimeoutScenario.swift */; }; @@ -181,6 +182,8 @@ 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 = ""; }; + 96DADF502EAFCB1100B56CE6 /* ManualSpanEndOnDestroyScenario.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ManualSpanEndOnDestroyScenario.swift; sourceTree = ""; }; + 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 = ""; }; CB0496932913CA300097E526 /* BatchingWithTimeoutScenario.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BatchingWithTimeoutScenario.swift; sourceTree = ""; }; @@ -252,19 +255,20 @@ 01FE4DA828E1AEBD00D1F239 /* AppDelegate.swift */, 01FE4DB128E1AEBF00D1F239 /* Assets.xcassets */, CB211D0629EEB615008F748D /* BugsnagPerformanceConfiguration+Private.h */, + 96DADF572EAFCE6D00B56CE6 /* BugsnagPerformanceSpan+Internal.h */, 09637A402B060E7C00F4F776 /* CommandReaderThread.swift */, - CBAAE25429125F5F006D4AA0 /* Fixture-Bridging-Header.h */, + 098C3B4E2C53CEC0006F9886 /* ErrorGenerator.h */, + 098C3B4F2C53CEC0006F9886 /* ErrorGenerator.m */, 01FE4DC628E1D5A400D1F239 /* Fixture.swift */, + CBAAE25429125F5F006D4AA0 /* Fixture-Bridging-Header.h */, + 09637A442B0B883B00F4F776 /* FixtureConfig.swift */, 01FE4DB628E1AEBF00D1F239 /* Info.plist */, 01FE4DB328E1AEBF00D1F239 /* LaunchScreen.storyboard */, 01FE4DAE28E1AEBD00D1F239 /* Main.storyboard */, 09637A422B0617FE00F4F776 /* MazeRunnerCommand.swift */, 01FE4DAA28E1AEBD00D1F239 /* SceneDelegate.swift */, - 01FE4DAC28E1AEBD00D1F239 /* ViewController.swift */, - 09637A442B0B883B00F4F776 /* FixtureConfig.swift */, - 098C3B4E2C53CEC0006F9886 /* ErrorGenerator.h */, - 098C3B4F2C53CEC0006F9886 /* ErrorGenerator.m */, 098C3B512C53CECF006F9886 /* SwiftErrorGenerator.swift */, + 01FE4DAC28E1AEBD00D1F239 /* ViewController.swift */, ); path = Fixture; sourceTree = ""; @@ -338,9 +342,10 @@ 09D59E162BDFA23600199E1B /* ManualNetworkTracePropagationScenario.swift */, 093EE63C2C32E5B900632B30 /* ManualParentSpanScenario.swift */, 01E7918928EC7B5E00855993 /* ManualSpanBeforeStartScenario.swift */, + 96DADF502EAFCB1100B56CE6 /* ManualSpanEndOnDestroyScenario.swift */, 01FE4DC228E1AF3700D1F239 /* ManualSpanScenario.swift */, - 96F129342DCE0CFE00A6FB2B /* ManualSpanWithRemoteContextParentScenario.swift */, 1C7852A52E5FA7C700BB8E2D /* ManualSpanWithContextParentNilScenario.swift */, + 96F129342DCE0CFE00A6FB2B /* ManualSpanWithRemoteContextParentScenario.swift */, CB7FD92A299BB4E300499E13 /* ManualUIViewLoadScenario.swift */, 098808DF2B10A6E400DC1677 /* ManualViewLoadPhaseScenario.swift */, 01E3F99028F46B6700003F44 /* ManualViewLoadScenario.swift */, @@ -547,6 +552,7 @@ 09637A452B0B883B00F4F776 /* FixtureConfig.swift in Sources */, CBC90CE429D1BDE400280884 /* AutoInstrumentSubViewLoadScenario.swift in Sources */, 098C3B502C53CEC0006F9886 /* ErrorGenerator.m in Sources */, + 96DADF512EAFCB1100B56CE6 /* ManualSpanEndOnDestroyScenario.swift in Sources */, 0921F02E2A69262300C764EB /* AutoInstrumentNetworkCallbackScenario.swift in Sources */, 01E7918A28EC7B5E00855993 /* ManualSpanBeforeStartScenario.swift in Sources */, 09DC622A2C6DE242000AA8E1 /* EarlySpanOnEndScenario.swift in Sources */, diff --git a/features/fixtures/ios/Fixture/BugsnagPerformanceSpan+Internal.h b/features/fixtures/ios/Fixture/BugsnagPerformanceSpan+Internal.h new file mode 100644 index 00000000..038af3b8 --- /dev/null +++ b/features/fixtures/ios/Fixture/BugsnagPerformanceSpan+Internal.h @@ -0,0 +1,12 @@ +// +// BugsnagPerformanceSpan+Internal.h +// Fixture +// +// Created by Robert Bartoszewski on 27/10/2025. +// + +#import + +@interface BugsnagPerformanceSpan () +- (void)endOnDestroy; +@end diff --git a/features/fixtures/ios/Fixture/Fixture-Bridging-Header.h b/features/fixtures/ios/Fixture/Fixture-Bridging-Header.h index 705b4143..7b5af8f3 100644 --- a/features/fixtures/ios/Fixture/Fixture-Bridging-Header.h +++ b/features/fixtures/ios/Fixture/Fixture-Bridging-Header.h @@ -5,6 +5,7 @@ #import #import #import "BugsnagPerformanceConfiguration+Private.h" +#import "BugsnagPerformanceSpan+Internal.h" #import "Logging.h" #import "ObjCURLSession.h" #import "ErrorGenerator.h" diff --git a/features/fixtures/ios/FixtureXcFramework.xcodeproj/project.pbxproj b/features/fixtures/ios/FixtureXcFramework.xcodeproj/project.pbxproj index 16c16db9..64127479 100644 --- a/features/fixtures/ios/FixtureXcFramework.xcodeproj/project.pbxproj +++ b/features/fixtures/ios/FixtureXcFramework.xcodeproj/project.pbxproj @@ -76,6 +76,7 @@ 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 */; }; + 96DADF542EAFCB6300B56CE6 /* ManualSpanEndOnDestroyScenario.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96DADF532EAFCB6300B56CE6 /* ManualSpanEndOnDestroyScenario.swift */; }; 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 */; }; @@ -195,6 +196,8 @@ 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 = ""; }; + 96DADF532EAFCB6300B56CE6 /* ManualSpanEndOnDestroyScenario.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ManualSpanEndOnDestroyScenario.swift; sourceTree = ""; }; + 96DADF582EAFCECF00B56CE6 /* BugsnagPerformanceSpan+Internal.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "BugsnagPerformanceSpan+Internal.h"; sourceTree = ""; }; 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 = ""; }; @@ -268,19 +271,20 @@ 01FE4DA828E1AEBD00D1F239 /* AppDelegate.swift */, 01FE4DB128E1AEBF00D1F239 /* Assets.xcassets */, CB211D0629EEB615008F748D /* BugsnagPerformanceConfiguration+Private.h */, + 96DADF582EAFCECF00B56CE6 /* BugsnagPerformanceSpan+Internal.h */, 09637A402B060E7C00F4F776 /* CommandReaderThread.swift */, - CBAAE25429125F5F006D4AA0 /* Fixture-Bridging-Header.h */, + 098C3B4E2C53CEC0006F9886 /* ErrorGenerator.h */, + 098C3B4F2C53CEC0006F9886 /* ErrorGenerator.m */, 01FE4DC628E1D5A400D1F239 /* Fixture.swift */, + CBAAE25429125F5F006D4AA0 /* Fixture-Bridging-Header.h */, + 09637A442B0B883B00F4F776 /* FixtureConfig.swift */, 01FE4DB628E1AEBF00D1F239 /* Info.plist */, 01FE4DB328E1AEBF00D1F239 /* LaunchScreen.storyboard */, 01FE4DAE28E1AEBD00D1F239 /* Main.storyboard */, 09637A422B0617FE00F4F776 /* MazeRunnerCommand.swift */, 01FE4DAA28E1AEBD00D1F239 /* SceneDelegate.swift */, - 01FE4DAC28E1AEBD00D1F239 /* ViewController.swift */, - 09637A442B0B883B00F4F776 /* FixtureConfig.swift */, - 098C3B4E2C53CEC0006F9886 /* ErrorGenerator.h */, - 098C3B4F2C53CEC0006F9886 /* ErrorGenerator.m */, 098C3B512C53CECF006F9886 /* SwiftErrorGenerator.swift */, + 01FE4DAC28E1AEBD00D1F239 /* ViewController.swift */, ); path = Fixture; sourceTree = ""; @@ -356,6 +360,7 @@ 09D59E162BDFA23600199E1B /* ManualNetworkTracePropagationScenario.swift */, 093EE63C2C32E5B900632B30 /* ManualParentSpanScenario.swift */, 01E7918928EC7B5E00855993 /* ManualSpanBeforeStartScenario.swift */, + 96DADF532EAFCB6300B56CE6 /* ManualSpanEndOnDestroyScenario.swift */, 01FE4DC228E1AF3700D1F239 /* ManualSpanScenario.swift */, CB7FD92A299BB4E300499E13 /* ManualUIViewLoadScenario.swift */, 098808DF2B10A6E400DC1677 /* ManualViewLoadPhaseScenario.swift */, @@ -478,6 +483,7 @@ buildActionMask = 2147483647; files = ( CBE43A9929A8EFA3000B4205 /* ProbabilityExpiryScenario.swift in Sources */, + 96DADF542EAFCB6300B56CE6 /* ManualSpanEndOnDestroyScenario.swift in Sources */, CBE0872B29F81BBB007455F2 /* AutoInstrumentNetworkNoParentScenario.swift in Sources */, 96D528CC2C72B14300FEA2E2 /* AppDataOverrideScenario.swift in Sources */, 09E045612C98649D003882D3 /* SetAttributeCountLimitScenario.swift in Sources */, diff --git a/features/fixtures/ios/Scenarios/ManualSpanEndOnDestroyScenario.swift b/features/fixtures/ios/Scenarios/ManualSpanEndOnDestroyScenario.swift new file mode 100644 index 00000000..119af053 --- /dev/null +++ b/features/fixtures/ios/Scenarios/ManualSpanEndOnDestroyScenario.swift @@ -0,0 +1,28 @@ +// +// ManualSpanEndOnDestroyScenario.swift +// Fixture +// +// Created by Robert Bartoszewski on 27/10/2025. +// + +import BugsnagPerformance + +@objcMembers +class ManualSpanEndOnDestroyScenario: Scenario { + + override func setInitialBugsnagConfiguration() { + super.setInitialBugsnagConfiguration() + bugsnagPerfConfig.internal.autoTriggerExportOnBatchSize = 1; + } + + override func run() { + let opts = BugsnagPerformanceSpanOptions() + opts.setFirstClass(.yes) + let span = BugsnagPerformance.startSpan( + name: spanName, + options: opts + ) + span.setAttribute("TestString", withValue: "test") + span.endOnDestroy() + } +} From 4ea0fabec9bd6999ec45e3d59411b5accd793e5b Mon Sep 17 00:00:00 2001 From: Robert Bartoszewski Date: Wed, 29 Oct 2025 10:17:10 +0100 Subject: [PATCH 52/53] Fixed Benchmarks fixture --- features/fixtures/benchmarks/Fixture.xcodeproj/project.pbxproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/features/fixtures/benchmarks/Fixture.xcodeproj/project.pbxproj b/features/fixtures/benchmarks/Fixture.xcodeproj/project.pbxproj index a48699ef..f3e87c32 100644 --- a/features/fixtures/benchmarks/Fixture.xcodeproj/project.pbxproj +++ b/features/fixtures/benchmarks/Fixture.xcodeproj/project.pbxproj @@ -57,7 +57,7 @@ 966AEDE82E70E10D002F12A6 /* SpanOpenCloseSuite.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SpanOpenCloseSuite.swift; sourceTree = ""; }; 9682AA3B2E70BA210033D9D8 /* BenchmarkRunner.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BenchmarkRunner.swift; sourceTree = ""; }; 9682AA3D2E70BECC0033D9D8 /* SuiteConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SuiteConfig.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 = ""; }; + CB211D0629EEB615008F748D /* BugsnagPerformanceConfiguration+Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "BugsnagPerformanceConfiguration+Private.h"; path = "../../../../Sources/BugsnagPerformance/Private/Core/Configuration/BugsnagPerformanceConfiguration+Private.h"; sourceTree = ""; }; CBAAE25429125F5F006D4AA0 /* Fixture-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Fixture-Bridging-Header.h"; sourceTree = ""; }; /* End PBXFileReference section */ From 96efead4ee0b2d2295fa3069fab97be81336c7c3 Mon Sep 17 00:00:00 2001 From: Robert Bartoszewski Date: Wed, 29 Oct 2025 10:40:22 +0100 Subject: [PATCH 53/53] Fixed E2E tests --- features/default/attributes.feature | 15 +- .../automatic/automatic_network.feature | 5 +- .../automatic/automatic_view_load.feature | 273 ++++++------------ features/default/callbacks.feature | 4 +- features/default/conditions.feature | 36 +-- features/default/early_spans.feature | 2 +- features/default/limits.feature | 2 +- features/default/manual_spans.feature | 39 ++- features/default/metrics/metrics_cpu.feature | 18 +- .../default/metrics/metrics_frame.feature | 16 +- .../default/metrics/metrics_memory.feature | 12 +- features/default/sampling.feature | 5 +- .../SamplingProbabilityZeroScenario.swift | 1 + features/steps/app_steps.rb | 6 + 14 files changed, 166 insertions(+), 268 deletions(-) diff --git a/features/default/attributes.feature b/features/default/attributes.feature index 87d96266..cd2996af 100644 --- a/features/default/attributes.feature +++ b/features/default/attributes.feature @@ -2,10 +2,9 @@ Feature: Spans with custom attributes Scenario: Set attributes in a span Given I run "SetAttributesScenario" - And I wait to receive at least 1 span - * the trace "Bugsnag-Span-Sampling" header equals "1:1" + And I wait to receive a span named "SetAttributesScenario" + * the trace "Bugsnag-Span-Sampling" header matches the regex "^1:\d{1,2}$" * 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 "SetAttributesScenario" * a span string attribute "a" equals "xyz" * every span bool attribute "b" does not exist * every span bool attribute "d" does not exist @@ -19,10 +18,9 @@ Feature: Spans with custom attributes Scenario: Set attributes in a span with limits set Given I run "SetAttributesWithLimitsScenario" - And I wait to receive at least 1 span - * the trace "Bugsnag-Span-Sampling" header equals "1:1" + And I wait to receive a span named "SetAttributesWithLimitsScenario" + * the trace "Bugsnag-Span-Sampling" header matches the regex "^1:\d{1,2}$" * 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 "SetAttributesWithLimitsScenario" * a span string attribute "a" equals "1234567890*** 1 CHARS TRUNCATED" * a span array attribute "b" contains the integer value 1 at index 0 * a span array attribute "b" contains the integer value 2 at index 1 @@ -31,8 +29,7 @@ Feature: Spans with custom attributes Scenario: Set attributes in a span with an attribute count limit set Given I run "SetAttributeCountLimitScenario" - And I wait to receive at least 1 span - * the trace "Bugsnag-Span-Sampling" header equals "1:1" + And I wait to receive a span named "SetAttributeCountLimitScenario" + * the trace "Bugsnag-Span-Sampling" header matches the regex "^1:\d{1,2}$" * 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 "SetAttributeCountLimitScenario" * every span string attribute "a" does not exist \ No newline at end of file diff --git a/features/default/automatic/automatic_network.feature b/features/default/automatic/automatic_network.feature index 8adf9079..84b85667 100644 --- a/features/default/automatic/automatic_network.feature +++ b/features/default/automatic/automatic_network.feature @@ -86,7 +86,7 @@ Feature: Automatic network instrumentation spans Scenario: Automatically start a network span that has a null URL Given I run "AutoInstrumentNetworkNullURLScenario" - And I wait to receive at least 1 span + And I wait to receive at least 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 "parentSpanId" does not exist @@ -198,10 +198,9 @@ Feature: Automatic network instrumentation spans # Wait for a long time because there can be a LOT of maze-runner related URL requests before the scenario starts. And I wait for 20 seconds # There will actually be any number of requests by this point since we're not filtering at all. - And I wait to receive at least 1 span + And I wait to receive a span named "[HTTP/GET]" 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$" - * a 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 diff --git a/features/default/automatic/automatic_view_load.feature b/features/default/automatic/automatic_view_load.feature index 79a90016..34ce9560 100644 --- a/features/default/automatic/automatic_view_load.feature +++ b/features/default/automatic/automatic_view_load.feature @@ -2,34 +2,23 @@ Feature: Automatic view load instrumentation spans Scenario: AutoInstrumentViewLoadScenario Given I run "AutoInstrumentViewLoadScenario" - And I wait to receive at least 18 spans + And I wait to receive a span named "[ViewLoad/UIKit]/Fixture.AutoInstrumentViewLoadScenario_ViewController" + And I wait to receive a span named "[ViewLoadPhase/loadView]/Fixture.AutoInstrumentViewLoadScenario_ViewController" + And I wait to receive a span named "[ViewLoadPhase/viewDidLoad]/Fixture.AutoInstrumentViewLoadScenario_ViewController" + And I wait to receive a span named "[ViewLoadPhase/viewWillAppear]/Fixture.AutoInstrumentViewLoadScenario_ViewController" + And I wait to receive a span named "[ViewLoadPhase/View appearing]/Fixture.AutoInstrumentViewLoadScenario_ViewController" + And I wait to receive a span named "[ViewLoadPhase/viewDidAppear]/Fixture.AutoInstrumentViewLoadScenario_ViewController" + And I wait to receive a span named "[ViewLoadPhase/viewWillLayoutSubviews]/Fixture.AutoInstrumentViewLoadScenario_ViewController" + And I wait to receive a span named "[ViewLoadPhase/Subview layout]/Fixture.AutoInstrumentViewLoadScenario_ViewController" + And I wait to receive a span named "[ViewLoadPhase/viewDidLayoutSubviews]/Fixture.AutoInstrumentViewLoadScenario_ViewController" 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$" - * a span field "name" equals "[ViewLoad/UIKit]/Fixture.ViewController" - * a span field "name" equals "[ViewLoadPhase/loadView]/Fixture.ViewController" - * a span field "name" equals "[ViewLoadPhase/viewDidLoad]/Fixture.ViewController" - * a span field "name" equals "[ViewLoadPhase/viewWillAppear]/Fixture.ViewController" - * a span field "name" equals "[ViewLoadPhase/View appearing]/Fixture.ViewController" - * a span field "name" equals "[ViewLoadPhase/viewDidAppear]/Fixture.ViewController" - * a span field "name" equals "[ViewLoadPhase/viewWillLayoutSubviews]/Fixture.ViewController" - * a span field "name" equals "[ViewLoadPhase/Subview layout]/Fixture.ViewController" - * a span field "name" equals "[ViewLoadPhase/viewDidLayoutSubviews]/Fixture.ViewController" - * a span field "name" equals "[ViewLoad/UIKit]/Fixture.AutoInstrumentViewLoadScenario_ViewController" - * a span field "name" equals "[ViewLoadPhase/loadView]/Fixture.AutoInstrumentViewLoadScenario_ViewController" - * a span field "name" equals "[ViewLoadPhase/viewDidLoad]/Fixture.AutoInstrumentViewLoadScenario_ViewController" - * a span field "name" equals "[ViewLoadPhase/viewWillAppear]/Fixture.AutoInstrumentViewLoadScenario_ViewController" - * a span field "name" equals "[ViewLoadPhase/View appearing]/Fixture.AutoInstrumentViewLoadScenario_ViewController" - * a span field "name" equals "[ViewLoadPhase/viewDidAppear]/Fixture.AutoInstrumentViewLoadScenario_ViewController" - * a span field "name" equals "[ViewLoadPhase/viewWillLayoutSubviews]/Fixture.AutoInstrumentViewLoadScenario_ViewController" - * a span field "name" equals "[ViewLoadPhase/Subview layout]/Fixture.AutoInstrumentViewLoadScenario_ViewController" - * a span field "name" equals "[ViewLoadPhase/viewDidLayoutSubviews]/Fixture.AutoInstrumentViewLoadScenario_ViewController" * 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 1 * every span field "startTimeUnixNano" matches the regex "^[0-9]+$" * every span field "endTimeUnixNano" matches the regex "^[0-9]+$" * a span string attribute "bugsnag.span.category" equals "view_load" - * a span string attribute "bugsnag.view.name" equals "Fixture.ViewController" * a span string attribute "bugsnag.view.name" equals "Fixture.AutoInstrumentViewLoadScenario_ViewController" * a span bool attribute "bugsnag.span.first_class" is true * a span string attribute "bugsnag.view.type" equals "UIKit" @@ -40,35 +29,26 @@ Feature: Automatic view load instrumentation spans Scenario: AutoInstrumentSubViewLoadScenario Given I run "AutoInstrumentSubViewLoadScenario" And I wait for 2 seconds - And I wait to receive at least 27 spans + And I wait to receive a span named "[ViewLoad/UIKit]/Fixture.AutoInstrumentSubViewLoadScenario_ViewController" + And I wait to receive a span named "[ViewLoadPhase/loadView]/Fixture.AutoInstrumentSubViewLoadScenario_ViewController" + And I wait to receive a span named "[ViewLoadPhase/viewDidLoad]/Fixture.AutoInstrumentSubViewLoadScenario_ViewController" + And I wait to receive a span named "[ViewLoadPhase/viewWillAppear]/Fixture.AutoInstrumentSubViewLoadScenario_ViewController" + And I wait to receive a span named "[ViewLoadPhase/View appearing]/Fixture.AutoInstrumentSubViewLoadScenario_ViewController" + And I wait to receive a span named "[ViewLoadPhase/viewDidAppear]/Fixture.AutoInstrumentSubViewLoadScenario_ViewController" + And I wait to receive a span named "[ViewLoadPhase/viewWillLayoutSubviews]/Fixture.AutoInstrumentSubViewLoadScenario_ViewController" + And I wait to receive a span named "[ViewLoadPhase/Subview layout]/Fixture.AutoInstrumentSubViewLoadScenario_ViewController" + And I wait to receive a span named "[ViewLoadPhase/viewDidLayoutSubviews]/Fixture.AutoInstrumentSubViewLoadScenario_ViewController" + And I wait to receive a span named "[ViewLoad/UIKit]/Fixture.AutoInstrumentSubViewLoadScenario_SubViewController" + And I wait to receive a span named "[ViewLoadPhase/loadView]/Fixture.AutoInstrumentSubViewLoadScenario_SubViewController" + And I wait to receive a span named "[ViewLoadPhase/viewDidLoad]/Fixture.AutoInstrumentSubViewLoadScenario_SubViewController" + And I wait to receive a span named "[ViewLoadPhase/viewWillAppear]/Fixture.AutoInstrumentSubViewLoadScenario_SubViewController" + And I wait to receive a span named "[ViewLoadPhase/View appearing]/Fixture.AutoInstrumentSubViewLoadScenario_SubViewController" + And I wait to receive a span named "[ViewLoadPhase/viewDidAppear]/Fixture.AutoInstrumentSubViewLoadScenario_SubViewController" + And I wait to receive a span named "[ViewLoadPhase/viewWillLayoutSubviews]/Fixture.AutoInstrumentSubViewLoadScenario_SubViewController" + And I wait to receive a span named "[ViewLoadPhase/Subview layout]/Fixture.AutoInstrumentSubViewLoadScenario_SubViewController" + And I wait to receive a span named "[ViewLoadPhase/viewDidLayoutSubviews]/Fixture.AutoInstrumentSubViewLoadScenario_SubViewController" 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$" - * a span field "name" equals "[ViewLoad/UIKit]/Fixture.ViewController" - * a span field "name" equals "[ViewLoadPhase/loadView]/Fixture.ViewController" - * a span field "name" equals "[ViewLoadPhase/viewDidLoad]/Fixture.ViewController" - * a span field "name" equals "[ViewLoadPhase/viewWillAppear]/Fixture.ViewController" - * a span field "name" equals "[ViewLoadPhase/View appearing]/Fixture.ViewController" - * a span field "name" equals "[ViewLoadPhase/viewDidAppear]/Fixture.ViewController" - * a span field "name" equals "[ViewLoadPhase/viewWillLayoutSubviews]/Fixture.ViewController" - * a span field "name" equals "[ViewLoadPhase/Subview layout]/Fixture.ViewController" - * a span field "name" equals "[ViewLoadPhase/viewDidLayoutSubviews]/Fixture.ViewController" - * a span named "[ViewLoadPhase/loadView]/Fixture.ViewController" is a child of span named "[ViewLoad/UIKit]/Fixture.ViewController" - * a span named "[ViewLoadPhase/viewDidLoad]/Fixture.ViewController" is a child of span named "[ViewLoad/UIKit]/Fixture.ViewController" - * a span named "[ViewLoadPhase/viewWillAppear]/Fixture.ViewController" is a child of span named "[ViewLoad/UIKit]/Fixture.ViewController" - * a span named "[ViewLoadPhase/View appearing]/Fixture.ViewController" is a child of span named "[ViewLoad/UIKit]/Fixture.ViewController" - * a span named "[ViewLoadPhase/viewDidAppear]/Fixture.ViewController" is a child of span named "[ViewLoad/UIKit]/Fixture.ViewController" - * a span named "[ViewLoadPhase/viewWillLayoutSubviews]/Fixture.ViewController" is a child of span named "[ViewLoad/UIKit]/Fixture.ViewController" - * a span named "[ViewLoadPhase/Subview layout]/Fixture.ViewController" is a child of span named "[ViewLoad/UIKit]/Fixture.ViewController" - * a span named "[ViewLoadPhase/viewDidLayoutSubviews]/Fixture.ViewController" is a child of span named "[ViewLoad/UIKit]/Fixture.ViewController" - * a span field "name" equals "[ViewLoad/UIKit]/Fixture.AutoInstrumentSubViewLoadScenario_ViewController" - * a span field "name" equals "[ViewLoadPhase/loadView]/Fixture.AutoInstrumentSubViewLoadScenario_ViewController" - * a span field "name" equals "[ViewLoadPhase/viewDidLoad]/Fixture.AutoInstrumentSubViewLoadScenario_ViewController" - * a span field "name" equals "[ViewLoadPhase/viewWillAppear]/Fixture.AutoInstrumentSubViewLoadScenario_ViewController" - * a span field "name" equals "[ViewLoadPhase/View appearing]/Fixture.AutoInstrumentSubViewLoadScenario_ViewController" - * a span field "name" equals "[ViewLoadPhase/viewDidAppear]/Fixture.AutoInstrumentSubViewLoadScenario_ViewController" - * a span field "name" equals "[ViewLoadPhase/viewWillLayoutSubviews]/Fixture.AutoInstrumentSubViewLoadScenario_ViewController" - * a span field "name" equals "[ViewLoadPhase/Subview layout]/Fixture.AutoInstrumentSubViewLoadScenario_ViewController" - * a span field "name" equals "[ViewLoadPhase/viewDidLayoutSubviews]/Fixture.AutoInstrumentSubViewLoadScenario_ViewController" * a span named "[ViewLoadPhase/loadView]/Fixture.AutoInstrumentSubViewLoadScenario_ViewController" is a child of span named "[ViewLoad/UIKit]/Fixture.AutoInstrumentSubViewLoadScenario_ViewController" * a span named "[ViewLoadPhase/viewDidLoad]/Fixture.AutoInstrumentSubViewLoadScenario_ViewController" is a child of span named "[ViewLoad/UIKit]/Fixture.AutoInstrumentSubViewLoadScenario_ViewController" * a span named "[ViewLoadPhase/viewWillAppear]/Fixture.AutoInstrumentSubViewLoadScenario_ViewController" is a child of span named "[ViewLoad/UIKit]/Fixture.AutoInstrumentSubViewLoadScenario_ViewController" @@ -77,15 +57,6 @@ Feature: Automatic view load instrumentation spans * a span named "[ViewLoadPhase/viewWillLayoutSubviews]/Fixture.AutoInstrumentSubViewLoadScenario_ViewController" is a child of span named "[ViewLoad/UIKit]/Fixture.AutoInstrumentSubViewLoadScenario_ViewController" * a span named "[ViewLoadPhase/Subview layout]/Fixture.AutoInstrumentSubViewLoadScenario_ViewController" is a child of span named "[ViewLoad/UIKit]/Fixture.AutoInstrumentSubViewLoadScenario_ViewController" * a span named "[ViewLoadPhase/viewDidLayoutSubviews]/Fixture.AutoInstrumentSubViewLoadScenario_ViewController" is a child of span named "[ViewLoad/UIKit]/Fixture.AutoInstrumentSubViewLoadScenario_ViewController" - * a span field "name" equals "[ViewLoad/UIKit]/Fixture.AutoInstrumentSubViewLoadScenario_SubViewController" - * a span field "name" equals "[ViewLoadPhase/loadView]/Fixture.AutoInstrumentSubViewLoadScenario_SubViewController" - * a span field "name" equals "[ViewLoadPhase/viewDidLoad]/Fixture.AutoInstrumentSubViewLoadScenario_SubViewController" - * a span field "name" equals "[ViewLoadPhase/viewWillAppear]/Fixture.AutoInstrumentSubViewLoadScenario_SubViewController" - * a span field "name" equals "[ViewLoadPhase/View appearing]/Fixture.AutoInstrumentSubViewLoadScenario_SubViewController" - * a span field "name" equals "[ViewLoadPhase/viewDidAppear]/Fixture.AutoInstrumentSubViewLoadScenario_SubViewController" - * a span field "name" equals "[ViewLoadPhase/viewWillLayoutSubviews]/Fixture.AutoInstrumentSubViewLoadScenario_SubViewController" - * a span field "name" equals "[ViewLoadPhase/Subview layout]/Fixture.AutoInstrumentSubViewLoadScenario_SubViewController" - * a span field "name" equals "[ViewLoadPhase/viewDidLayoutSubviews]/Fixture.AutoInstrumentSubViewLoadScenario_SubViewController" * a span named "[ViewLoadPhase/loadView]/Fixture.AutoInstrumentSubViewLoadScenario_SubViewController" is a child of span named "[ViewLoad/UIKit]/Fixture.AutoInstrumentSubViewLoadScenario_SubViewController" * a span named "[ViewLoadPhase/viewDidLoad]/Fixture.AutoInstrumentSubViewLoadScenario_SubViewController" is a child of span named "[ViewLoad/UIKit]/Fixture.AutoInstrumentSubViewLoadScenario_SubViewController" * a span named "[ViewLoadPhase/viewWillAppear]/Fixture.AutoInstrumentSubViewLoadScenario_SubViewController" is a child of span named "[ViewLoad/UIKit]/Fixture.AutoInstrumentSubViewLoadScenario_SubViewController" @@ -100,7 +71,6 @@ Feature: Automatic view load instrumentation spans * every span field "startTimeUnixNano" matches the regex "^[0-9]+$" * every span field "endTimeUnixNano" matches the regex "^[0-9]+$" * a span string attribute "bugsnag.span.category" equals "view_load" - * a span string attribute "bugsnag.view.name" equals "Fixture.ViewController" * a span string attribute "bugsnag.view.name" equals "Fixture.AutoInstrumentSubViewLoadScenario_ViewController" * a span string attribute "bugsnag.view.name" equals "Fixture.AutoInstrumentSubViewLoadScenario_SubViewController" * a span bool attribute "bugsnag.span.first_class" is true @@ -129,18 +99,17 @@ Feature: Automatic view load instrumentation spans Scenario: AutoInstrumentTabViewLoadScenario Given I run "AutoInstrumentTabViewLoadScenario" And I wait for 2 seconds - And I wait to receive at least 18 spans + And I wait to receive a span named "[ViewLoad/UIKit]/Fixture.AutoInstrumentTabViewLoadScenario_SubViewController" + And I wait to receive a span named "[ViewLoadPhase/loadView]/Fixture.AutoInstrumentTabViewLoadScenario_SubViewController" + And I wait to receive a span named "[ViewLoadPhase/viewDidLoad]/Fixture.AutoInstrumentTabViewLoadScenario_SubViewController" + And I wait to receive a span named "[ViewLoadPhase/viewWillAppear]/Fixture.AutoInstrumentTabViewLoadScenario_SubViewController" + And I wait to receive a span named "[ViewLoadPhase/View appearing]/Fixture.AutoInstrumentTabViewLoadScenario_SubViewController" + And I wait to receive a span named "[ViewLoadPhase/viewDidAppear]/Fixture.AutoInstrumentTabViewLoadScenario_SubViewController" + And I wait to receive a span named "[ViewLoadPhase/viewWillLayoutSubviews]/Fixture.AutoInstrumentTabViewLoadScenario_SubViewController" + And I wait to receive a span named "[ViewLoadPhase/Subview layout]/Fixture.AutoInstrumentTabViewLoadScenario_SubViewController" + And I wait to receive a span named "[ViewLoadPhase/viewDidLayoutSubviews]/Fixture.AutoInstrumentTabViewLoadScenario_SubViewController" 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$" - * a span field "name" equals "[ViewLoad/UIKit]/Fixture.ViewController" - * a span field "name" equals "[ViewLoadPhase/loadView]/Fixture.ViewController" - * a span field "name" equals "[ViewLoadPhase/viewDidLoad]/Fixture.ViewController" - * a span field "name" equals "[ViewLoadPhase/viewWillAppear]/Fixture.ViewController" - * a span field "name" equals "[ViewLoadPhase/View appearing]/Fixture.ViewController" - * a span field "name" equals "[ViewLoadPhase/viewDidAppear]/Fixture.ViewController" - * a span field "name" equals "[ViewLoadPhase/viewWillLayoutSubviews]/Fixture.ViewController" - * a span field "name" equals "[ViewLoadPhase/Subview layout]/Fixture.ViewController" - * a span field "name" equals "[ViewLoadPhase/viewDidLayoutSubviews]/Fixture.ViewController" * no span field "name" equals "[ViewLoad/UIKit]/Fixture.AutoInstrumentTabViewLoadScenario_ViewController" * no span field "name" equals "[ViewLoadPhase/loadView]/Fixture.AutoInstrumentTabViewLoadScenario_ViewController" * no span field "name" equals "[ViewLoadPhase/viewDidLoad]/Fixture.AutoInstrumentTabViewLoadScenario_ViewController" @@ -150,22 +119,12 @@ Feature: Automatic view load instrumentation spans * no span field "name" equals "[ViewLoadPhase/viewWillLayoutSubviews]/Fixture.AutoInstrumentTabViewLoadScenario_ViewController" * no span field "name" equals "[ViewLoadPhase/Subview layout]/Fixture.AutoInstrumentTabViewLoadScenario_ViewController" * no span field "name" equals "[ViewLoadPhase/viewDidLayoutSubviews]/Fixture.AutoInstrumentTabViewLoadScenario_ViewController" - * a span field "name" equals "[ViewLoad/UIKit]/Fixture.AutoInstrumentTabViewLoadScenario_SubViewController" - * a span field "name" equals "[ViewLoadPhase/loadView]/Fixture.AutoInstrumentTabViewLoadScenario_SubViewController" - * a span field "name" equals "[ViewLoadPhase/viewDidLoad]/Fixture.AutoInstrumentTabViewLoadScenario_SubViewController" - * a span field "name" equals "[ViewLoadPhase/viewWillAppear]/Fixture.AutoInstrumentTabViewLoadScenario_SubViewController" - * a span field "name" equals "[ViewLoadPhase/View appearing]/Fixture.AutoInstrumentTabViewLoadScenario_SubViewController" - * a span field "name" equals "[ViewLoadPhase/viewDidAppear]/Fixture.AutoInstrumentTabViewLoadScenario_SubViewController" - * a span field "name" equals "[ViewLoadPhase/viewWillLayoutSubviews]/Fixture.AutoInstrumentTabViewLoadScenario_SubViewController" - * a span field "name" equals "[ViewLoadPhase/Subview layout]/Fixture.AutoInstrumentTabViewLoadScenario_SubViewController" - * a span field "name" equals "[ViewLoadPhase/viewDidLayoutSubviews]/Fixture.AutoInstrumentTabViewLoadScenario_SubViewController" * 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 1 * every span field "startTimeUnixNano" matches the regex "^[0-9]+$" * every span field "endTimeUnixNano" matches the regex "^[0-9]+$" * a span string attribute "bugsnag.span.category" equals "view_load" - * a span string attribute "bugsnag.view.name" equals "Fixture.ViewController" * a span string attribute "bugsnag.view.name" equals "Fixture.AutoInstrumentTabViewLoadScenario_SubViewController" * a span bool attribute "bugsnag.span.first_class" is true * a span bool attribute "bugsnag.span.first_class" is false @@ -177,18 +136,17 @@ Feature: Automatic view load instrumentation spans Scenario: AutoInstrumentNavigationViewLoadScenario Given I run "AutoInstrumentNavigationViewLoadScenario" And I wait for 2 seconds - And I wait to receive at least 18 spans + And I wait to receive a span named "[ViewLoad/UIKit]/Fixture.AutoInstrumentNavigationViewLoadScenario_SubViewController" + And I wait to receive a span named "[ViewLoadPhase/loadView]/Fixture.AutoInstrumentNavigationViewLoadScenario_SubViewController" + And I wait to receive a span named "[ViewLoadPhase/viewDidLoad]/Fixture.AutoInstrumentNavigationViewLoadScenario_SubViewController" + And I wait to receive a span named "[ViewLoadPhase/viewWillAppear]/Fixture.AutoInstrumentNavigationViewLoadScenario_SubViewController" + And I wait to receive a span named "[ViewLoadPhase/View appearing]/Fixture.AutoInstrumentNavigationViewLoadScenario_SubViewController" + And I wait to receive a span named "[ViewLoadPhase/viewDidAppear]/Fixture.AutoInstrumentNavigationViewLoadScenario_SubViewController" + And I wait to receive a span named "[ViewLoadPhase/viewWillLayoutSubviews]/Fixture.AutoInstrumentNavigationViewLoadScenario_SubViewController" + And I wait to receive a span named "[ViewLoadPhase/Subview layout]/Fixture.AutoInstrumentNavigationViewLoadScenario_SubViewController" + And I wait to receive a span named "[ViewLoadPhase/viewDidLayoutSubviews]/Fixture.AutoInstrumentNavigationViewLoadScenario_SubViewController" 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$" - * a span field "name" equals "[ViewLoad/UIKit]/Fixture.ViewController" - * a span field "name" equals "[ViewLoadPhase/loadView]/Fixture.ViewController" - * a span field "name" equals "[ViewLoadPhase/viewDidLoad]/Fixture.ViewController" - * a span field "name" equals "[ViewLoadPhase/viewWillAppear]/Fixture.ViewController" - * a span field "name" equals "[ViewLoadPhase/View appearing]/Fixture.ViewController" - * a span field "name" equals "[ViewLoadPhase/viewDidAppear]/Fixture.ViewController" - * a span field "name" equals "[ViewLoadPhase/viewWillLayoutSubviews]/Fixture.ViewController" - * a span field "name" equals "[ViewLoadPhase/Subview layout]/Fixture.ViewController" - * a span field "name" equals "[ViewLoadPhase/viewDidLayoutSubviews]/Fixture.ViewController" * no span field "name" equals "[ViewLoad/UIKit]/Fixture.AutoInstrumentNavigationViewLoadScenario_ViewController" * no span field "name" equals "[ViewLoadPhase/loadView]/Fixture.AutoInstrumentNavigationViewLoadScenario_ViewController" * no span field "name" equals "[ViewLoadPhase/viewDidLoad]/Fixture.AutoInstrumentNavigationViewLoadScenario_ViewController" @@ -198,22 +156,12 @@ Feature: Automatic view load instrumentation spans * no span field "name" equals "[ViewLoadPhase/viewWillLayoutSubviews]/Fixture.AutoInstrumentNavigationViewLoadScenario_ViewController" * no span field "name" equals "[ViewLoadPhase/Subview layout]/Fixture.AutoInstrumentNavigationViewLoadScenario_ViewController" * no span field "name" equals "[ViewLoadPhase/viewDidLayoutSubviews]/Fixture.AutoInstrumentNavigationViewLoadScenario_ViewController" - * a span field "name" equals "[ViewLoad/UIKit]/Fixture.AutoInstrumentNavigationViewLoadScenario_SubViewController" - * a span field "name" equals "[ViewLoadPhase/loadView]/Fixture.AutoInstrumentNavigationViewLoadScenario_SubViewController" - * a span field "name" equals "[ViewLoadPhase/viewDidLoad]/Fixture.AutoInstrumentNavigationViewLoadScenario_SubViewController" - * a span field "name" equals "[ViewLoadPhase/viewWillAppear]/Fixture.AutoInstrumentNavigationViewLoadScenario_SubViewController" - * a span field "name" equals "[ViewLoadPhase/View appearing]/Fixture.AutoInstrumentNavigationViewLoadScenario_SubViewController" - * a span field "name" equals "[ViewLoadPhase/viewDidAppear]/Fixture.AutoInstrumentNavigationViewLoadScenario_SubViewController" - * a span field "name" equals "[ViewLoadPhase/viewWillLayoutSubviews]/Fixture.AutoInstrumentNavigationViewLoadScenario_SubViewController" - * a span field "name" equals "[ViewLoadPhase/Subview layout]/Fixture.AutoInstrumentNavigationViewLoadScenario_SubViewController" - * a span field "name" equals "[ViewLoadPhase/viewDidLayoutSubviews]/Fixture.AutoInstrumentNavigationViewLoadScenario_SubViewController" * 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 1 * every span field "startTimeUnixNano" matches the regex "^[0-9]+$" * every span field "endTimeUnixNano" matches the regex "^[0-9]+$" * a span string attribute "bugsnag.span.category" equals "view_load" - * a span string attribute "bugsnag.view.name" equals "Fixture.ViewController" * a span string attribute "bugsnag.view.name" equals "Fixture.AutoInstrumentNavigationViewLoadScenario_SubViewController" * a span bool attribute "bugsnag.span.first_class" is true * a span bool attribute "bugsnag.span.first_class" is false @@ -224,28 +172,18 @@ Feature: Automatic view load instrumentation spans Scenario: AutoInstrumentPreLoadedViewLoadScenario Given I run "AutoInstrumentPreLoadedViewLoadScenario" - And I wait to receive at least 19 spans + And I wait to receive a span named "[ViewLoad/UIKit]/Fixture.AutoInstrumentPreLoadedViewLoadScenario_ViewController (pre-load)" + And I wait to receive a span named "[ViewLoad/UIKit]/Fixture.AutoInstrumentPreLoadedViewLoadScenario_ViewController (presenting)" + And I wait to receive a span named "[ViewLoadPhase/loadView]/Fixture.AutoInstrumentPreLoadedViewLoadScenario_ViewController" + And I wait to receive a span named "[ViewLoadPhase/viewDidLoad]/Fixture.AutoInstrumentPreLoadedViewLoadScenario_ViewController" + And I wait to receive a span named "[ViewLoadPhase/viewWillAppear]/Fixture.AutoInstrumentPreLoadedViewLoadScenario_ViewController" + And I wait to receive a span named "[ViewLoadPhase/View appearing]/Fixture.AutoInstrumentPreLoadedViewLoadScenario_ViewController" + And I wait to receive a span named "[ViewLoadPhase/viewDidAppear]/Fixture.AutoInstrumentPreLoadedViewLoadScenario_ViewController" + And I wait to receive a span named "[ViewLoadPhase/viewWillLayoutSubviews]/Fixture.AutoInstrumentPreLoadedViewLoadScenario_ViewController" + And I wait to receive a span named "[ViewLoadPhase/Subview layout]/Fixture.AutoInstrumentPreLoadedViewLoadScenario_ViewController" + And I wait to receive a span named "[ViewLoadPhase/viewDidLayoutSubviews]/Fixture.AutoInstrumentPreLoadedViewLoadScenario_ViewController" 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$" - * a span field "name" equals "[ViewLoad/UIKit]/Fixture.ViewController" - * a span field "name" equals "[ViewLoadPhase/loadView]/Fixture.ViewController" - * a span field "name" equals "[ViewLoadPhase/viewDidLoad]/Fixture.ViewController" - * a span field "name" equals "[ViewLoadPhase/viewWillAppear]/Fixture.ViewController" - * a span field "name" equals "[ViewLoadPhase/View appearing]/Fixture.ViewController" - * a span field "name" equals "[ViewLoadPhase/viewDidAppear]/Fixture.ViewController" - * a span field "name" equals "[ViewLoadPhase/viewWillLayoutSubviews]/Fixture.ViewController" - * a span field "name" equals "[ViewLoadPhase/Subview layout]/Fixture.ViewController" - * a span field "name" equals "[ViewLoadPhase/viewDidLayoutSubviews]/Fixture.ViewController" - * a span field "name" equals "[ViewLoad/UIKit]/Fixture.AutoInstrumentPreLoadedViewLoadScenario_ViewController (pre-load)" - * a span field "name" equals "[ViewLoad/UIKit]/Fixture.AutoInstrumentPreLoadedViewLoadScenario_ViewController (presenting)" - * a span field "name" equals "[ViewLoadPhase/loadView]/Fixture.AutoInstrumentPreLoadedViewLoadScenario_ViewController" - * a span field "name" equals "[ViewLoadPhase/viewDidLoad]/Fixture.AutoInstrumentPreLoadedViewLoadScenario_ViewController" - * a span field "name" equals "[ViewLoadPhase/viewWillAppear]/Fixture.AutoInstrumentPreLoadedViewLoadScenario_ViewController" - * a span field "name" equals "[ViewLoadPhase/View appearing]/Fixture.AutoInstrumentPreLoadedViewLoadScenario_ViewController" - * a span field "name" equals "[ViewLoadPhase/viewDidAppear]/Fixture.AutoInstrumentPreLoadedViewLoadScenario_ViewController" - * a span field "name" equals "[ViewLoadPhase/viewWillLayoutSubviews]/Fixture.AutoInstrumentPreLoadedViewLoadScenario_ViewController" - * a span field "name" equals "[ViewLoadPhase/Subview layout]/Fixture.AutoInstrumentPreLoadedViewLoadScenario_ViewController" - * a span field "name" equals "[ViewLoadPhase/viewDidLayoutSubviews]/Fixture.AutoInstrumentPreLoadedViewLoadScenario_ViewController" * a span named "[ViewLoad/UIKit]/Fixture.AutoInstrumentPreLoadedViewLoadScenario_ViewController (pre-load)" ended at the same time as a span named "[ViewLoadPhase/viewDidLoad]/Fixture.AutoInstrumentPreLoadedViewLoadScenario_ViewController" * a span named "[ViewLoad/UIKit]/Fixture.AutoInstrumentPreLoadedViewLoadScenario_ViewController (presenting)" ended after a span named "[ViewLoadPhase/viewDidAppear]/Fixture.AutoInstrumentPreLoadedViewLoadScenario_ViewController" * a span named "[ViewLoad/UIKit]/Fixture.AutoInstrumentPreLoadedViewLoadScenario_ViewController (pre-load)" ended before a span named "[ViewLoad/UIKit]/Fixture.AutoInstrumentPreLoadedViewLoadScenario_ViewController (presenting)" started @@ -263,7 +201,6 @@ Feature: Automatic view load instrumentation spans * every span field "startTimeUnixNano" matches the regex "^[0-9]+$" * every span field "endTimeUnixNano" matches the regex "^[0-9]+$" * a span string attribute "bugsnag.span.category" equals "view_load" - * a span string attribute "bugsnag.view.name" equals "Fixture.ViewController" * a span string attribute "bugsnag.view.name" equals "Fixture.AutoInstrumentPreLoadedViewLoadScenario_ViewController (preload)" * a span string attribute "bugsnag.view.name" equals "Fixture.AutoInstrumentPreLoadedViewLoadScenario_ViewController (presentation)" * a span bool attribute "bugsnag.span.first_class" is true @@ -277,26 +214,16 @@ Feature: Automatic view load instrumentation spans And I configure scenario "keep_view_controller_alive" to "true" And I start bugsnag And I run the loaded scenario - And I wait to receive at least 17 spans + And I wait to receive a span named "[ViewLoad/UIKit]/Fixture.ViewDidLoadDoesntTriggerScenario_ViewController" + And I wait to receive a span named "[ViewLoadPhase/loadView]/Fixture.ViewDidLoadDoesntTriggerScenario_ViewController" + And I wait to receive a span named "[ViewLoadPhase/viewDidLoad]/Fixture.ViewDidLoadDoesntTriggerScenario_ViewController" + And I wait to receive a span named "[ViewLoadPhase/viewWillAppear]/Fixture.ViewDidLoadDoesntTriggerScenario_ViewController" + And I wait to receive a span named "[ViewLoadPhase/View appearing]/Fixture.ViewDidLoadDoesntTriggerScenario_ViewController" + And I wait to receive a span named "[ViewLoadPhase/viewWillLayoutSubviews]/Fixture.ViewDidLoadDoesntTriggerScenario_ViewController" + And I wait to receive a span named "[ViewLoadPhase/Subview layout]/Fixture.ViewDidLoadDoesntTriggerScenario_ViewController" + And I wait to receive a span named "[ViewLoadPhase/viewDidLayoutSubviews]/Fixture.ViewDidLoadDoesntTriggerScenario_ViewController" 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$" - * a span field "name" equals "[ViewLoad/UIKit]/Fixture.ViewController" - * a span field "name" equals "[ViewLoadPhase/loadView]/Fixture.ViewController" - * a span field "name" equals "[ViewLoadPhase/viewDidLoad]/Fixture.ViewController" - * a span field "name" equals "[ViewLoadPhase/viewWillAppear]/Fixture.ViewController" - * a span field "name" equals "[ViewLoadPhase/View appearing]/Fixture.ViewController" - * a span field "name" equals "[ViewLoadPhase/viewDidAppear]/Fixture.ViewController" - * a span field "name" equals "[ViewLoadPhase/viewWillLayoutSubviews]/Fixture.ViewController" - * a span field "name" equals "[ViewLoadPhase/Subview layout]/Fixture.ViewController" - * a span field "name" equals "[ViewLoadPhase/viewDidLayoutSubviews]/Fixture.ViewController" - * a span field "name" equals "[ViewLoad/UIKit]/Fixture.ViewDidLoadDoesntTriggerScenario_ViewController" - * a span field "name" equals "[ViewLoadPhase/loadView]/Fixture.ViewDidLoadDoesntTriggerScenario_ViewController" - * a span field "name" equals "[ViewLoadPhase/viewDidLoad]/Fixture.ViewDidLoadDoesntTriggerScenario_ViewController" - * a span field "name" equals "[ViewLoadPhase/viewWillAppear]/Fixture.ViewDidLoadDoesntTriggerScenario_ViewController" - * a span field "name" equals "[ViewLoadPhase/View appearing]/Fixture.ViewDidLoadDoesntTriggerScenario_ViewController" - * a span field "name" equals "[ViewLoadPhase/viewWillLayoutSubviews]/Fixture.ViewDidLoadDoesntTriggerScenario_ViewController" - * a span field "name" equals "[ViewLoadPhase/Subview layout]/Fixture.ViewDidLoadDoesntTriggerScenario_ViewController" - * a span field "name" equals "[ViewLoadPhase/viewDidLayoutSubviews]/Fixture.ViewDidLoadDoesntTriggerScenario_ViewController" * no span field "name" equals "ViewDidLoadDoesntTriggerScenarioOnDeinitSpan" * every span field "spanId" matches the regex "^[A-Fa-f0-9]{16}$" * every span field "traceId" matches the regex "^[A-Fa-f0-9]{32}$" @@ -304,7 +231,6 @@ Feature: Automatic view load instrumentation spans * every span field "startTimeUnixNano" matches the regex "^[0-9]+$" * every span field "endTimeUnixNano" matches the regex "^[0-9]+$" * a span string attribute "bugsnag.span.category" equals "view_load" - * a span string attribute "bugsnag.view.name" equals "Fixture.ViewController" * a span string attribute "bugsnag.view.name" equals "Fixture.ViewDidLoadDoesntTriggerScenario_ViewController" * a span bool attribute "bugsnag.span.first_class" is true * a span string attribute "bugsnag.view.type" equals "UIKit" @@ -317,27 +243,17 @@ Feature: Automatic view load instrumentation spans And I configure scenario "keep_view_controller_alive" to "false" And I start bugsnag And I run the loaded scenario - And I wait to receive at least 18 spans + And I wait to receive a span named "[ViewLoad/UIKit]/Fixture.ViewDidLoadDoesntTriggerScenario_ViewController" + And I wait to receive a span named "[ViewLoadPhase/loadView]/Fixture.ViewDidLoadDoesntTriggerScenario_ViewController" + And I wait to receive a span named "[ViewLoadPhase/viewDidLoad]/Fixture.ViewDidLoadDoesntTriggerScenario_ViewController" + And I wait to receive a span named "[ViewLoadPhase/viewWillAppear]/Fixture.ViewDidLoadDoesntTriggerScenario_ViewController" + And I wait to receive a span named "[ViewLoadPhase/View appearing]/Fixture.ViewDidLoadDoesntTriggerScenario_ViewController" + And I wait to receive a span named "[ViewLoadPhase/viewWillLayoutSubviews]/Fixture.ViewDidLoadDoesntTriggerScenario_ViewController" + And I wait to receive a span named "[ViewLoadPhase/Subview layout]/Fixture.ViewDidLoadDoesntTriggerScenario_ViewController" + And I wait to receive a span named "[ViewLoadPhase/viewDidLayoutSubviews]/Fixture.ViewDidLoadDoesntTriggerScenario_ViewController" + And I wait to receive a span named "ViewDidLoadDoesntTriggerScenarioOnDeinitSpan" 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$" - * a span field "name" equals "[ViewLoad/UIKit]/Fixture.ViewController" - * a span field "name" equals "[ViewLoadPhase/loadView]/Fixture.ViewController" - * a span field "name" equals "[ViewLoadPhase/viewDidLoad]/Fixture.ViewController" - * a span field "name" equals "[ViewLoadPhase/viewWillAppear]/Fixture.ViewController" - * a span field "name" equals "[ViewLoadPhase/View appearing]/Fixture.ViewController" - * a span field "name" equals "[ViewLoadPhase/viewDidAppear]/Fixture.ViewController" - * a span field "name" equals "[ViewLoadPhase/viewWillLayoutSubviews]/Fixture.ViewController" - * a span field "name" equals "[ViewLoadPhase/Subview layout]/Fixture.ViewController" - * a span field "name" equals "[ViewLoadPhase/viewDidLayoutSubviews]/Fixture.ViewController" - * a span field "name" equals "[ViewLoad/UIKit]/Fixture.ViewDidLoadDoesntTriggerScenario_ViewController" - * a span field "name" equals "[ViewLoadPhase/loadView]/Fixture.ViewDidLoadDoesntTriggerScenario_ViewController" - * a span field "name" equals "[ViewLoadPhase/viewDidLoad]/Fixture.ViewDidLoadDoesntTriggerScenario_ViewController" - * a span field "name" equals "[ViewLoadPhase/viewWillAppear]/Fixture.ViewDidLoadDoesntTriggerScenario_ViewController" - * a span field "name" equals "[ViewLoadPhase/View appearing]/Fixture.ViewDidLoadDoesntTriggerScenario_ViewController" - * a span field "name" equals "[ViewLoadPhase/viewWillLayoutSubviews]/Fixture.ViewDidLoadDoesntTriggerScenario_ViewController" - * a span field "name" equals "[ViewLoadPhase/Subview layout]/Fixture.ViewDidLoadDoesntTriggerScenario_ViewController" - * a span field "name" equals "[ViewLoadPhase/viewDidLayoutSubviews]/Fixture.ViewDidLoadDoesntTriggerScenario_ViewController" - * a span field "name" equals "ViewDidLoadDoesntTriggerScenarioOnDeinitSpan" * a span named "ViewDidLoadDoesntTriggerScenarioOnDeinitSpan" duration is equal or less than 1.0 * every span field "spanId" matches the regex "^[A-Fa-f0-9]{16}$" * every span field "traceId" matches the regex "^[A-Fa-f0-9]{32}$" @@ -345,7 +261,6 @@ Feature: Automatic view load instrumentation spans * every span field "startTimeUnixNano" matches the regex "^[0-9]+$" * every span field "endTimeUnixNano" matches the regex "^[0-9]+$" * a span string attribute "bugsnag.span.category" equals "view_load" - * a span string attribute "bugsnag.view.name" equals "Fixture.ViewController" * a span string attribute "bugsnag.view.name" equals "Fixture.ViewDidLoadDoesntTriggerScenario_ViewController" * a span bool attribute "bugsnag.span.first_class" is true * a span string attribute "bugsnag.view.type" equals "UIKit" @@ -355,36 +270,26 @@ Feature: Automatic view load instrumentation spans Scenario: ComplexViewScenario Given I run "ComplexViewScenario" - And I wait to receive at least 27 spans + And I wait to receive a span named "[ViewLoadPhase/loadView]/Fixture.ComplexViewScenario_ViewController" + And I wait to receive a span named "[ViewLoadPhase/loadView]/Fixture.ComplexViewScenario_TableViewController" + And I wait to receive a span named "[ViewLoadPhase/viewDidLoad]/Fixture.ComplexViewScenario_TableViewController" + And I wait to receive a span named "[ViewLoadPhase/viewDidLoad]/Fixture.ComplexViewScenario_ViewController" + And I wait to receive a span named "[ViewLoadPhase/viewWillAppear]/Fixture.ComplexViewScenario_ViewController" + And I wait to receive a span named "[ViewLoadPhase/viewWillAppear]/Fixture.ComplexViewScenario_TableViewController" + And I wait to receive a span named "[ViewLoadPhase/viewWillLayoutSubviews]/Fixture.ComplexViewScenario_ViewController" + And I wait to receive a span named "[ViewLoadPhase/Subview layout]/Fixture.ComplexViewScenario_ViewController" + And I wait to receive a span named "[ViewLoadPhase/viewDidLayoutSubviews]/Fixture.ComplexViewScenario_ViewController" + And I wait to receive a span named "[ViewLoadPhase/viewWillLayoutSubviews]/Fixture.ComplexViewScenario_TableViewController" + And I wait to receive a span named "[ViewLoadPhase/Subview layout]/Fixture.ComplexViewScenario_TableViewController" + And I wait to receive a span named "[ViewLoadPhase/viewDidLayoutSubviews]/Fixture.ComplexViewScenario_TableViewController" + And I wait to receive a span named "[ViewLoadPhase/View appearing]/Fixture.ComplexViewScenario_ViewController" + And I wait to receive a span named "[ViewLoadPhase/viewDidAppear]/Fixture.ComplexViewScenario_ViewController" + And I wait to receive a span named "[ViewLoadPhase/View appearing]/Fixture.ComplexViewScenario_TableViewController" + And I wait to receive a span named "[ViewLoadPhase/viewDidAppear]/Fixture.ComplexViewScenario_TableViewController" + And I wait to receive a span named "[ViewLoad/UIKit]/Fixture.ComplexViewScenario_TableViewController" Then the trace "Content-Type" header equals "application/json" * the trace "Bugsnag-Span-Sampling" header matches the regex "^1:\d{1,2}$" * 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 "[ViewLoadPhase/loadView]/Fixture.ViewController" - * a span field "name" equals "[ViewLoadPhase/viewDidLoad]/Fixture.ViewController" - * a span field "name" equals "[ViewLoadPhase/viewWillAppear]/Fixture.ViewController" - * a span field "name" equals "[ViewLoadPhase/viewWillLayoutSubviews]/Fixture.ViewController" - * a span field "name" equals "[ViewLoadPhase/Subview layout]/Fixture.ViewController" - * a span field "name" equals "[ViewLoadPhase/viewDidLayoutSubviews]/Fixture.ViewController" - * a span field "name" equals "[ViewLoadPhase/View appearing]/Fixture.ViewController" - * a span field "name" equals "[ViewLoadPhase/viewDidAppear]/Fixture.ViewController" - * a span field "name" equals "[ViewLoad/UIKit]/Fixture.ViewController" - * a span field "name" equals "[ViewLoadPhase/loadView]/Fixture.ComplexViewScenario_ViewController" - * a span field "name" equals "[ViewLoadPhase/loadView]/Fixture.ComplexViewScenario_TableViewController" - * a span field "name" equals "[ViewLoadPhase/viewDidLoad]/Fixture.ComplexViewScenario_TableViewController" - * a span field "name" equals "[ViewLoadPhase/viewDidLoad]/Fixture.ComplexViewScenario_ViewController" - * a span field "name" equals "[ViewLoadPhase/viewWillAppear]/Fixture.ComplexViewScenario_ViewController" - * a span field "name" equals "[ViewLoadPhase/viewWillAppear]/Fixture.ComplexViewScenario_TableViewController" - * a span field "name" equals "[ViewLoadPhase/viewWillLayoutSubviews]/Fixture.ComplexViewScenario_ViewController" - * a span field "name" equals "[ViewLoadPhase/Subview layout]/Fixture.ComplexViewScenario_ViewController" - * a span field "name" equals "[ViewLoadPhase/viewDidLayoutSubviews]/Fixture.ComplexViewScenario_ViewController" - * a span field "name" equals "[ViewLoadPhase/viewWillLayoutSubviews]/Fixture.ComplexViewScenario_TableViewController" - * a span field "name" equals "[ViewLoadPhase/Subview layout]/Fixture.ComplexViewScenario_TableViewController" - * a span field "name" equals "[ViewLoadPhase/viewDidLayoutSubviews]/Fixture.ComplexViewScenario_TableViewController" - * a span field "name" equals "[ViewLoadPhase/View appearing]/Fixture.ComplexViewScenario_ViewController" - * a span field "name" equals "[ViewLoadPhase/viewDidAppear]/Fixture.ComplexViewScenario_ViewController" - * a span field "name" equals "[ViewLoadPhase/View appearing]/Fixture.ComplexViewScenario_TableViewController" - * a span field "name" equals "[ViewLoadPhase/viewDidAppear]/Fixture.ComplexViewScenario_TableViewController" - * a span field "name" equals "[ViewLoad/UIKit]/Fixture.ComplexViewScenario_TableViewController" * 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 1 diff --git a/features/default/callbacks.feature b/features/default/callbacks.feature index 7e298178..1be3c427 100644 --- a/features/default/callbacks.feature +++ b/features/default/callbacks.feature @@ -3,7 +3,7 @@ Feature: Setting callbacks Scenario: Set OnStart Given I run "OnStartCallbackScenario" And I wait for exactly 1 span - * the trace "Bugsnag-Span-Sampling" header equals "1:1" + * the trace "Bugsnag-Span-Sampling" header matches the regex "^1:\d{1,2}$" * 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 "OnStartCallbackScenario" * a span bool attribute "start_callback_1" is true @@ -12,6 +12,6 @@ Feature: Setting callbacks Scenario: Set OnEnd Given I run "OnEndCallbackScenario" And I wait for exactly 1 span - * the trace "Bugsnag-Span-Sampling" header equals "1:1" + * the trace "Bugsnag-Span-Sampling" header matches the regex "^1:\d{1,2}$" * 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" \ No newline at end of file diff --git a/features/default/conditions.feature b/features/default/conditions.feature index e37708c3..dbfd6f32 100644 --- a/features/default/conditions.feature +++ b/features/default/conditions.feature @@ -2,7 +2,7 @@ Feature: Conditions Scenario: Manually creating and ending conditions Given I run "ConditionsBasicScenario" - And I wait to receive at least 1 span + And I wait to receive a span named "ConditionsBasicScenario" 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-Span-Sampling" header matches the regex "^1:\d{1,2}$" @@ -19,7 +19,7 @@ Feature: Conditions Scenario: Span can be blocked again after being ended as long as it is still blocked Given I run "ConditionsBlockingBlockedEndedSpanScenario" - And I wait to receive at least 1 span + And I wait to receive a span named "ConditionsBlockingBlockedEndedSpanScenario" 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-Span-Sampling" header matches the regex "^1:\d{1,2}$" @@ -36,7 +36,7 @@ Feature: Conditions Scenario: Condition should not override endTime to an earlier time Given I run "ConditionsOverrideEndTimeBackwardsScenario" - And I wait to receive at least 1 span + And I wait to receive a span named "ConditionsOverrideEndTimeBackwardsScenario" 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-Span-Sampling" header matches the regex "^1:\d{1,2}$" @@ -53,12 +53,11 @@ Feature: Conditions Scenario: Span Conditions - condition closed Given I run "SpanConditionsSimpleConditionScenario" - And I wait to receive at least 2 spans + And I wait to receive a span named "SpanConditionsSimpleConditionScenarioSpan1" + And I wait to receive a span named "SpanConditionsSimpleConditionScenarioSpan2" Then the trace "Content-Type" header equals "application/json" - * the trace "Bugsnag-Span-Sampling" header equals "1:2" + * the trace "Bugsnag-Span-Sampling" header matches the regex "^1:\d{1,2}$" * 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 "SpanConditionsSimpleConditionScenarioSpan1" - * a span field "name" equals "SpanConditionsSimpleConditionScenarioSpan2" * 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 1 @@ -69,12 +68,11 @@ Feature: Conditions Scenario: Span Conditions - condition timed out Given I run "SpanConditionsConditionTimedOutScenario" - And I wait to receive at least 2 spans + And I wait to receive a span named "SpanConditionsConditionTimedOutScenarioSpan1" + And I wait to receive a span named "SpanConditionsConditionTimedOutScenarioSpan2" Then the trace "Content-Type" header equals "application/json" - * the trace "Bugsnag-Span-Sampling" header equals "1:2" + * the trace "Bugsnag-Span-Sampling" header matches the regex "^1:\d{1,2}$" * 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 "SpanConditionsConditionTimedOutScenarioSpan1" - * a span field "name" equals "SpanConditionsConditionTimedOutScenarioSpan2" * 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 1 @@ -84,13 +82,12 @@ Feature: Conditions Scenario: Span Conditions - multiple conditions Given I run "SpanConditionsMultipleConditionsScenario" - And I wait to receive at least 3 spans + And I wait to receive a span named "SpanConditionsMultipleConditionsScenarioSpan1" + And I wait to receive a span named "SpanConditionsMultipleConditionsScenarioSpan2" + And I wait to receive a span named "SpanConditionsMultipleConditionsScenarioSpan3" Then the trace "Content-Type" header equals "application/json" - * the trace "Bugsnag-Span-Sampling" header equals "1:3" + * the trace "Bugsnag-Span-Sampling" header matches the regex "^1:\d{1,2}$" * 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 "SpanConditionsMultipleConditionsScenarioSpan1" - * a span field "name" equals "SpanConditionsMultipleConditionsScenarioSpan2" - * a span field "name" equals "SpanConditionsMultipleConditionsScenarioSpan3" * 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 1 @@ -101,12 +98,11 @@ Feature: Conditions Scenario: Span Conditions - blocking blocked ended span Given I run "SpanConditionsBlockedSpanScenario" - And I wait to receive at least 1 span + And I wait to receive a span named "SpanConditionsBlockedSpanScenarioSpan1" + And I wait to receive a span named "SpanConditionsBlockedSpanScenarioSpan2" Then the trace "Content-Type" header equals "application/json" - * the trace "Bugsnag-Span-Sampling" header equals "1:2" + * the trace "Bugsnag-Span-Sampling" header matches the regex "^1:\d{1,2}$" * 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 "SpanConditionsBlockedSpanScenarioSpan1" - * a span field "name" equals "SpanConditionsBlockedSpanScenarioSpan2" * 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 1 diff --git a/features/default/early_spans.feature b/features/default/early_spans.feature index 3c5395cd..9ea86e36 100644 --- a/features/default/early_spans.feature +++ b/features/default/early_spans.feature @@ -25,7 +25,7 @@ Feature: Early spans Given I run "ModifyEarlySpansScenario" And I wait for exactly 5 spans Then the trace "Content-Type" header equals "application/json" - * the trace "Bugsnag-Span-Sampling" header equals "1:5" + * the trace "Bugsnag-Span-Sampling" header matches the regex "^1:\d{1,2}$" * 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 "[AppStart/iOSCold]" * a span field "name" equals "[AppStartPhase/App launching - pre main()]" diff --git a/features/default/limits.feature b/features/default/limits.feature index dbfca374..2e0789cf 100644 --- a/features/default/limits.feature +++ b/features/default/limits.feature @@ -6,7 +6,7 @@ Feature: Checking payloads limits And I wait for exactly 1 span 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-Span-Sampling" header equals "1:1" + * the trace "Bugsnag-Span-Sampling" header matches the regex "^1:\d{1,2}$" * 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$" * the trace "Bugsnag-Uncompressed-Content-Length" header matches the regex "^[0-9]+$" * every span field "name" equals "MaxPayloadSizeScenario" diff --git a/features/default/manual_spans.feature b/features/default/manual_spans.feature index c63baba6..0e03dcc2 100644 --- a/features/default/manual_spans.feature +++ b/features/default/manual_spans.feature @@ -5,15 +5,13 @@ Feature: Manual creation of spans Scenario: Retry a manual span Given I set the HTTP status code for the next requests to 200,500,200,200 And I run "RetryScenario" - And I wait to receive at least 1 span - * the trace "Bugsnag-Span-Sampling" header equals "1:1" + And I wait to receive a span named "WillRetry" + * the trace "Bugsnag-Span-Sampling" header matches the regex "^1:\d{1,2}$" * 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 "WillRetry" Then I discard the oldest trace And I invoke "step2" - And I wait to receive at least 2 spans - * a span field "name" equals "WillRetry" - * a span field "name" equals "Success" + And I wait to receive a span named "WillRetry" + And I wait to receive a span named "Success" * every span bool attribute "bugsnag.span.first_class" is true * every span string attribute "bugsnag.span.category" equals "custom" @@ -55,7 +53,7 @@ Feature: Manual creation of spans And I wait to receive at least 1 span 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-Span-Sampling" header equals "1:1" + * the trace "Bugsnag-Span-Sampling" header matches the regex "^1:\d{1,2}$" * 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 "ManualSpanEndOnDestroyScenario" * every span field "spanId" matches the regex "^[A-Fa-f0-9]{16}$" @@ -84,7 +82,7 @@ Feature: Manual creation of spans Scenario: Starting and ending a span before starting the SDK Given I run "ManualSpanBeforeStartScenario" And I wait to receive at least 1 span - * the trace "Bugsnag-Span-Sampling" header equals "1:1" + * the trace "Bugsnag-Span-Sampling" header matches the regex "^1:\d{1,2}$" * 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 "BeforeStart" * every span field "spanId" matches the regex "^[A-Fa-f0-9]{16}$" @@ -138,7 +136,7 @@ Feature: Manual creation of spans Scenario: Manually report a UIViewController load span Given I run "ManualUIViewLoadScenario" And I wait to receive at least 1 span - * the trace "Bugsnag-Span-Sampling" header equals "1:1" + * the trace "Bugsnag-Span-Sampling" header matches the regex "^1:\d{1,2}$" * 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 "[ViewLoad/UIKit]/UIViewController" * every span string attribute "bugsnag.view.name" equals "UIViewController" @@ -246,22 +244,20 @@ Feature: Manual creation of spans Scenario: Manually start and end parent and child spans Given I run "ParentSpanScenario" - And I wait to receive at least 2 spans + And I wait to receive a span named "SpanParent" + And I wait to receive a span named "SpanChild" Then the trace "Content-Type" header equals "application/json" + * a span named "SpanChild" is a child of span named "SpanParent" * 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$" * the trace payload field "resourceSpans.0.resource" string attribute "service.name" matches the regex "com.bugsnag.fixtures.cocoaperformance(xcframework)?" * the trace payload field "resourceSpans.0.resource" string attribute "telemetry.sdk.name" equals "bugsnag.performance.cocoa" * the trace payload field "resourceSpans.0.resource" string attribute "telemetry.sdk.version" matches the regex "[0-9]+\.[0-9]+\.[0-9]+" - * a span field "name" equals "SpanParent" - * a span field "name" equals "SpanChild" * 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 1 * every span field "startTimeUnixNano" matches the regex "^[0-9]+$" * every span field "endTimeUnixNano" matches the regex "^[0-9]+$" * every span bool attribute "bugsnag.span.first_class" is true - # Note: The child span ends up first in the list of spans. - * the trace payload field "resourceSpans.0.scopeSpans.0.spans.0.parentSpanId" matches the regex "^[A-Fa-f0-9]{16}$" Scenario: Manually start and end child span with a manually defined parent Given I run "ManualParentSpanScenario" @@ -331,14 +327,13 @@ Feature: Manual creation of spans Scenario: Manually start and end a span with nil parent context Given I run "ManualSpanWithContextParentNilScenario" - And I wait to receive at least 3 spans + And I wait to receive a span named "ManualSpanWithContextParentNilScenario" + And I wait to receive a span named "ManualSpanWithContextParentSet" + And I wait to receive a span named "ShouldNotBeParentSpan" Then the trace "Content-Type" header equals "application/json" + * a span named "ManualSpanWithContextParentSet" is a child of span named "ManualSpanWithContextParentNilScenario" + * a span named "ManualSpanWithContextParentNilScenario" has no parent + * a span named "ShouldNotBeParentSpan" has no parent * the trace "Bugsnag-Integrity" header matches the regex "^sha1 [A-Fa-f0-9]{40}$" - * the trace "Bugsnag-Span-Sampling" header equals "1:3" + * the trace "Bugsnag-Span-Sampling" header matches the regex "^1:\d{1,2}$" * 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$" - * the trace payload field "resourceSpans.0.scopeSpans.0.spans.0.name" equals "ManualSpanWithContextParentNilScenario" - * the trace payload field "resourceSpans.0.scopeSpans.0.spans.1.name" equals "ManualSpanWithContextParentSet" - * the trace payload field "resourceSpans.0.scopeSpans.0.spans.2.name" equals "ShouldNotBeParentSpan" - * 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 diff --git a/features/default/metrics/metrics_cpu.feature b/features/default/metrics/metrics_cpu.feature index 2ee1ed13..eeea6cf3 100644 --- a/features/default/metrics/metrics_cpu.feature +++ b/features/default/metrics/metrics_cpu.feature @@ -10,7 +10,7 @@ Feature: Spans with collected CPU metrics And I run the loaded scenario And I wait to receive at least 1 span Then the trace "Content-Type" header equals "application/json" - * the trace "Bugsnag-Span-Sampling" header equals "1:1" + * the trace "Bugsnag-Span-Sampling" header matches the regex "^1:\d{1,2}$" * 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 "CPUMetricsScenarioDefaultSettingsCPUMetricsDisabled" * every span field "spanId" matches the regex "^[A-Fa-f0-9]{16}$" @@ -41,7 +41,7 @@ Feature: Spans with collected CPU metrics And I run the loaded scenario And I wait to receive at least 1 span Then the trace "Content-Type" header equals "application/json" - * the trace "Bugsnag-Span-Sampling" header equals "1:1" + * the trace "Bugsnag-Span-Sampling" header matches the regex "^1:\d{1,2}$" * 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 "CPUMetricsScenarioNoMetrics" * every span field "spanId" matches the regex "^[A-Fa-f0-9]{16}$" @@ -72,7 +72,7 @@ Feature: Spans with collected CPU metrics And I run the loaded scenario And I wait to receive at least 1 span Then the trace "Content-Type" header equals "application/json" - * the trace "Bugsnag-Span-Sampling" header equals "1:1" + * the trace "Bugsnag-Span-Sampling" header matches the regex "^1:\d{1,2}$" * 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 "CPUMetricsScenarioFirstClass" * every span field "spanId" matches the regex "^[A-Fa-f0-9]{16}$" @@ -103,7 +103,7 @@ Feature: Spans with collected CPU metrics And I run the loaded scenario And I wait to receive at least 1 span Then the trace "Content-Type" header equals "application/json" - * the trace "Bugsnag-Span-Sampling" header equals "1:1" + * the trace "Bugsnag-Span-Sampling" header matches the regex "^1:\d{1,2}$" * 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 "CPUMetricsScenarioNonFirstClass" * every span field "spanId" matches the regex "^[A-Fa-f0-9]{16}$" @@ -135,7 +135,7 @@ Feature: Spans with collected CPU metrics And I run the loaded scenario And I wait to receive at least 1 span Then the trace "Content-Type" header equals "application/json" - * the trace "Bugsnag-Span-Sampling" header equals "1:1" + * the trace "Bugsnag-Span-Sampling" header matches the regex "^1:\d{1,2}$" * 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 "CPUMetricsScenarioNonFirstClassWithMetrics" * every span field "spanId" matches the regex "^[A-Fa-f0-9]{16}$" @@ -166,7 +166,7 @@ Feature: Spans with collected CPU metrics And I run the loaded scenario And I wait to receive at least 1 span Then the trace "Content-Type" header equals "application/json" - * the trace "Bugsnag-Span-Sampling" header equals "1:1" + * the trace "Bugsnag-Span-Sampling" header matches the regex "^1:\d{1,2}$" * 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 "CPUMetricsScenarioLongerSpanDuration" * every span field "spanId" matches the regex "^[A-Fa-f0-9]{16}$" @@ -197,7 +197,7 @@ Feature: Spans with collected CPU metrics And I run the loaded scenario And I wait to receive at least 1 span Then the trace "Content-Type" header equals "application/json" - * the trace "Bugsnag-Span-Sampling" header equals "1:1" + * the trace "Bugsnag-Span-Sampling" header matches the regex "^1:\d{1,2}$" * 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 "CPUMetricsScenarioGenerateSpanLater" * every span field "spanId" matches the regex "^[A-Fa-f0-9]{16}$" @@ -232,7 +232,7 @@ Feature: Spans with collected CPU metrics And I run the loaded scenario And I wait to receive at least 1 span Then the trace "Content-Type" header equals "application/json" - * the trace "Bugsnag-Span-Sampling" header equals "1:1" + * the trace "Bugsnag-Span-Sampling" header matches the regex "^1:\d{1,2}$" * 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 "CPUMetricsScenarioMainThreadHeavyWork" * every span field "spanId" matches the regex "^[A-Fa-f0-9]{16}$" @@ -265,7 +265,7 @@ Feature: Spans with collected CPU metrics And I run the loaded scenario And I wait to receive at least 1 span Then the trace "Content-Type" header equals "application/json" - * the trace "Bugsnag-Span-Sampling" header equals "1:1" + * the trace "Bugsnag-Span-Sampling" header matches the regex "^1:\d{1,2}$" * 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 "CPUMetricsScenarioBgThreadHeavyWork" * every span field "spanId" matches the regex "^[A-Fa-f0-9]{16}$" diff --git a/features/default/metrics/metrics_frame.feature b/features/default/metrics/metrics_frame.feature index fb1ee7fe..9c8d1528 100644 --- a/features/default/metrics/metrics_frame.feature +++ b/features/default/metrics/metrics_frame.feature @@ -4,7 +4,7 @@ Feature: Spans with collected frame metrics Given I run "FrameMetricsNoSlowFramesScenario" And I wait to receive at least 1 span Then the trace "Content-Type" header equals "application/json" - * the trace "Bugsnag-Span-Sampling" header equals "1:1" + * the trace "Bugsnag-Span-Sampling" header matches the regex "^1:\d{1,2}$" * 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 "FrameMetricsNoSlowFramesScenario" * every span field "spanId" matches the regex "^[A-Fa-f0-9]{16}$" @@ -26,7 +26,7 @@ Feature: Spans with collected frame metrics And I run the loaded scenario And I wait to receive at least 1 span Then the trace "Content-Type" header equals "application/json" - * the trace "Bugsnag-Span-Sampling" header equals "1:1" + * the trace "Bugsnag-Span-Sampling" header matches the regex "^1:\d{1,2}$" * 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 "RenderingMetricsScenarioNoSlow" * every span field "spanId" matches the regex "^[A-Fa-f0-9]{16}$" @@ -49,7 +49,7 @@ Feature: Spans with collected frame metrics And I run the loaded scenario And I wait to receive at least 1 span Then the trace "Content-Type" header equals "application/json" - * the trace "Bugsnag-Span-Sampling" header equals "1:1" + * the trace "Bugsnag-Span-Sampling" header matches the regex "^1:\d{1,2}$" * 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 "RenderingMetricsScenarioEarlyStart" * every span field "spanId" matches the regex "^[A-Fa-f0-9]{16}$" @@ -66,7 +66,7 @@ Feature: Spans with collected frame metrics Given I run "FrameMetricsSlowFramesScenario" And I wait to receive at least 1 span Then the trace "Content-Type" header equals "application/json" - * the trace "Bugsnag-Span-Sampling" header equals "1:1" + * the trace "Bugsnag-Span-Sampling" header matches the regex "^1:\d{1,2}$" * 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 "FrameMetricsSlowFramesScenario" * every span field "spanId" matches the regex "^[A-Fa-f0-9]{16}$" @@ -83,7 +83,7 @@ Feature: Spans with collected frame metrics Given I run "FrameMetricsFronzenFramesScenario" And I wait to receive at least 3 spans Then the trace "Content-Type" header equals "application/json" - * the trace "Bugsnag-Span-Sampling" header equals "1:3" + * the trace "Bugsnag-Span-Sampling" header matches the regex "^1:\d{1,2}$" * 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 "FrameMetricsFronzenFramesScenario" * every span field "spanId" matches the regex "^[A-Fa-f0-9]{16}$" @@ -101,7 +101,7 @@ Feature: Spans with collected frame metrics Given I run "FrameMetricsAutoInstrumentRenderingOffScenario" And I wait to receive at least 1 span Then the trace "Content-Type" header equals "application/json" - * the trace "Bugsnag-Span-Sampling" header equals "1:1" + * the trace "Bugsnag-Span-Sampling" header matches the regex "^1:\d{1,2}$" * 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 "FrameMetricsAutoInstrumentRenderingOffScenario" * every span field "spanId" matches the regex "^[A-Fa-f0-9]{16}$" @@ -118,7 +118,7 @@ Feature: Spans with collected frame metrics Given I run "FrameMetricsSpanInstrumentRenderingOffScenario" And I wait to receive at least 1 span Then the trace "Content-Type" header equals "application/json" - * the trace "Bugsnag-Span-Sampling" header equals "1:1" + * the trace "Bugsnag-Span-Sampling" header matches the regex "^1:\d{1,2}$" * 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 "FrameMetricsSpanInstrumentRenderingOffScenario" * every span field "spanId" matches the regex "^[A-Fa-f0-9]{16}$" @@ -135,7 +135,7 @@ Feature: Spans with collected frame metrics Given I run "FrameMetricsNonFirstClassSpanInstrumentRenderingOnScenario" And I wait to receive at least 1 span Then the trace "Content-Type" header equals "application/json" - * the trace "Bugsnag-Span-Sampling" header equals "1:1" + * the trace "Bugsnag-Span-Sampling" header matches the regex "^1:\d{1,2}$" * 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 "FrameMetricsNonFirstClassSpanInstrumentRenderingOnScenario" * every span field "spanId" matches the regex "^[A-Fa-f0-9]{16}$" diff --git a/features/default/metrics/metrics_memory.feature b/features/default/metrics/metrics_memory.feature index c7bd8c38..bc26ed6d 100644 --- a/features/default/metrics/metrics_memory.feature +++ b/features/default/metrics/metrics_memory.feature @@ -9,7 +9,7 @@ Feature: Spans with collected memory metrics And I run the loaded scenario And I wait to receive at least 1 span Then the trace "Content-Type" header equals "application/json" - * the trace "Bugsnag-Span-Sampling" header equals "1:1" + * the trace "Bugsnag-Span-Sampling" header matches the regex "^1:\d{1,2}$" * 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 "MemoryMetricsScenarioDefaultSettings" * every span field "spanId" matches the regex "^[A-Fa-f0-9]{16}$" @@ -37,7 +37,7 @@ Feature: Spans with collected memory metrics And I run the loaded scenario And I wait to receive at least 1 span Then the trace "Content-Type" header equals "application/json" - * the trace "Bugsnag-Span-Sampling" header equals "1:1" + * the trace "Bugsnag-Span-Sampling" header matches the regex "^1:\d{1,2}$" * 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 "MemoryMetricsScenarioFirstClass" * every span field "spanId" matches the regex "^[A-Fa-f0-9]{16}$" @@ -65,7 +65,7 @@ Feature: Spans with collected memory metrics And I run the loaded scenario And I wait to receive at least 1 span Then the trace "Content-Type" header equals "application/json" - * the trace "Bugsnag-Span-Sampling" header equals "1:1" + * the trace "Bugsnag-Span-Sampling" header matches the regex "^1:\d{1,2}$" * 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 "MemoryMetricsScenarioNonFirstClass" * every span field "spanId" matches the regex "^[A-Fa-f0-9]{16}$" @@ -94,7 +94,7 @@ Feature: Spans with collected memory metrics And I run the loaded scenario And I wait to receive at least 1 span Then the trace "Content-Type" header equals "application/json" - * the trace "Bugsnag-Span-Sampling" header equals "1:1" + * the trace "Bugsnag-Span-Sampling" header matches the regex "^1:\d{1,2}$" * 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 "MemoryMetricsScenarioNonFirstClassEnabled" * every span field "spanId" matches the regex "^[A-Fa-f0-9]{16}$" @@ -122,7 +122,7 @@ Feature: Spans with collected memory metrics And I run the loaded scenario And I wait to receive at least 1 span Then the trace "Content-Type" header equals "application/json" - * the trace "Bugsnag-Span-Sampling" header equals "1:1" + * the trace "Bugsnag-Span-Sampling" header matches the regex "^1:\d{1,2}$" * 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 "MemoryMetricsScenarioLongerDuration" * every span field "spanId" matches the regex "^[A-Fa-f0-9]{16}$" @@ -150,7 +150,7 @@ Feature: Spans with collected memory metrics And I run the loaded scenario And I wait to receive at least 1 span Then the trace "Content-Type" header equals "application/json" - * the trace "Bugsnag-Span-Sampling" header equals "1:1" + * the trace "Bugsnag-Span-Sampling" header matches the regex "^1:\d{1,2}$" * 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 "MemoryMetricsScenarioGenerateLater" * every span field "spanId" matches the regex "^[A-Fa-f0-9]{16}$" diff --git a/features/default/sampling.feature b/features/default/sampling.feature index 6cdf56a1..226c128c 100644 --- a/features/default/sampling.feature +++ b/features/default/sampling.feature @@ -8,9 +8,8 @@ Feature: Sampling Scenario: But if the server changes the probability, we must honor that Given I set the sampling probability to "1.0" And I run "SamplingProbabilityZeroScenario" - And I wait to receive at least 2 spans + And I wait to receive a span named "Pre-start" + And I wait to receive a span named "Post-start" * the trace "Bugsnag-Span-Sampling" header equals "1:2" * 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 "Pre-start" - * a span field "name" equals "Post-start" * a span double attribute "bugsnag.sampling.p" equals 1.0 diff --git a/features/fixtures/ios/Scenarios/SamplingProbabilityZeroScenario.swift b/features/fixtures/ios/Scenarios/SamplingProbabilityZeroScenario.swift index 7c16590e..8d18e14f 100644 --- a/features/fixtures/ios/Scenarios/SamplingProbabilityZeroScenario.swift +++ b/features/fixtures/ios/Scenarios/SamplingProbabilityZeroScenario.swift @@ -13,6 +13,7 @@ class SamplingProbabilityZeroScenario: Scenario { override func setInitialBugsnagConfiguration() { super.setInitialBugsnagConfiguration() bugsnagPerfConfig.internal.initialSamplingProbability = 0 + bugsnagPerfConfig.internal.autoTriggerExportOnBatchSize = 2 } override func postLoad() { diff --git a/features/steps/app_steps.rb b/features/steps/app_steps.rb index 90e745d0..946b17ac 100644 --- a/features/steps/app_steps.rb +++ b/features/steps/app_steps.rb @@ -298,6 +298,12 @@ def get_array_attribute_contents(attribute) Maze.check.true(first_span['traceId'] == second_span['traceId'] && first_span['parentSpanId'] == second_span['spanId']) end +Then('a span named {string} has no parent') do |name| + spans = spans_from_request_list(Maze::Server.list_for('traces')) + span = spans.find { |span| span['name'] == name } + Maze.check.nil(span['parentSpanId']) +end + Then('a span named {string} started before a span named {string}') do |name1, name2| spans = spans_from_request_list(Maze::Server.list_for('traces')) first_span = spans.find { |span| span['name'] == name1 }