From 372ffbef48535f7179f7120c431887b8aa98018a Mon Sep 17 00:00:00 2001 From: Philipp Hofmann Date: Thu, 13 Oct 2022 14:27:28 +0200 Subject: [PATCH] feat: Custom measurements API (#2268) Add API to add custom measurements to transactions. --- CHANGELOG.md | 6 + .../iOS-Swift/iOS-Swift/ViewController.swift | 5 + .../iOS-SwiftUI.xcodeproj/project.pbxproj | 23 +++ Sentry.xcodeproj/project.pbxproj | 24 ++- Sources/Sentry/Public/Sentry.h | 1 + Sources/Sentry/Public/SentryMeasurementUnit.h | 138 +++++++++++++++ Sources/Sentry/Public/SentryOptions.h | 2 +- Sources/Sentry/Public/SentrySpanProtocol.h | 31 +++- Sources/Sentry/SentryMeasurementUnit.m | 159 ++++++++++++++++++ Sources/Sentry/SentryMeasurementValue.m | 36 ++++ Sources/Sentry/SentryNoOpSpan.m | 8 + Sources/Sentry/SentryOptions.m | 4 + Sources/Sentry/SentrySpan.m | 11 ++ Sources/Sentry/SentryTracer.m | 29 +++- Sources/Sentry/SentryTransaction.m | 18 +- .../Sentry/include/SentryMeasurementValue.h | 19 +++ Sources/Sentry/include/SentryTracer.h | 4 +- .../include/SentryTransaction+Private.h | 12 -- .../Performance/SentryTracerTests.swift | 25 +++ .../Protocol/SentryMeasurementUnitTests.swift | 62 +++++++ .../SentryTests/SentryTests-Bridging-Header.h | 1 - .../Transaction/SentryTransactionTests.swift | 56 ++++-- .../SentryTests/Transaction/TestSentrySpan.m | 8 + 23 files changed, 628 insertions(+), 54 deletions(-) create mode 100644 Sources/Sentry/Public/SentryMeasurementUnit.h create mode 100644 Sources/Sentry/SentryMeasurementUnit.m create mode 100644 Sources/Sentry/SentryMeasurementValue.m create mode 100644 Sources/Sentry/include/SentryMeasurementValue.h delete mode 100644 Sources/Sentry/include/SentryTransaction+Private.h create mode 100644 Tests/SentryTests/Protocol/SentryMeasurementUnitTests.swift diff --git a/CHANGELOG.md b/CHANGELOG.md index e9e2ec4d81c..ecb1cfa779e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## Unreleased + +### Features + +- [Custom measurements API](https://docs.sentry.io/platforms/apple/performance/instrumentation/custom-instrumentation/) (#2268) + ## 7.27.1 ### Fixes diff --git a/Samples/iOS-Swift/iOS-Swift/ViewController.swift b/Samples/iOS-Swift/iOS-Swift/ViewController.swift index e0f465e8bda..d3e87f34d99 100644 --- a/Samples/iOS-Swift/iOS-Swift/ViewController.swift +++ b/Samples/iOS-Swift/iOS-Swift/ViewController.swift @@ -141,6 +141,11 @@ class ViewController: UIViewController { @IBAction func captureTransaction(_ sender: Any) { let transaction = SentrySDK.startTransaction(name: "Some Transaction", operation: "Some Operation") + + transaction.setMeasurement(name: "duration", value: 44, unit: MeasurementUnitDuration.nanosecond) + transaction.setMeasurement(name: "information", value: 44, unit: MeasurementUnitInformation.bit) + transaction.setMeasurement(name: "duration-custom", value: 22, unit: MeasurementUnit(unit: "custom")) + let span = transaction.startChild(operation: "user", description: "calls out") DispatchQueue.main.asyncAfter(deadline: .now() + 0.1, execute: { diff --git a/Samples/iOS-SwiftUI/iOS-SwiftUI.xcodeproj/project.pbxproj b/Samples/iOS-SwiftUI/iOS-SwiftUI.xcodeproj/project.pbxproj index 62ed0d3c93f..0c5eda6143e 100644 --- a/Samples/iOS-SwiftUI/iOS-SwiftUI.xcodeproj/project.pbxproj +++ b/Samples/iOS-SwiftUI/iOS-SwiftUI.xcodeproj/project.pbxproj @@ -40,6 +40,20 @@ remoteGlobalIDString = 7BB6224826A56C4E00D0E75E; remoteInfo = "iOS-SwiftUI"; }; + 7BF01DB528F536BC00302035 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 84D4FEA828ECD52700EDAAFE /* Sentry.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 63AA759B1EB8AEF500D153DE; + remoteInfo = Sentry; + }; + 7BF01DB728F536BC00302035 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 84D4FEA828ECD52700EDAAFE /* Sentry.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 63AA76651EB8CB2F00D153DE; + remoteInfo = SentryTests; + }; 84D4FEAD28ECD52E00EDAAFE /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 84D4FEAA28ECD52E00EDAAFE /* Sentry.xcodeproj */; @@ -186,6 +200,15 @@ name = Frameworks; sourceTree = ""; }; + 7BF01DB128F536BC00302035 /* Products */ = { + isa = PBXGroup; + children = ( + 7BF01DB628F536BC00302035 /* Sentry.framework */, + 7BF01DB828F536BC00302035 /* SentryTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; 84D4FEAB28ECD52E00EDAAFE /* Products */ = { isa = PBXGroup; children = ( diff --git a/Sentry.xcodeproj/project.pbxproj b/Sentry.xcodeproj/project.pbxproj index 5f225cfca8b..e74dfabcf1f 100644 --- a/Sentry.xcodeproj/project.pbxproj +++ b/Sentry.xcodeproj/project.pbxproj @@ -353,9 +353,9 @@ 7B6438A726A70DDB000D0F65 /* UIViewControllerSentryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B6438A626A70DDB000D0F65 /* UIViewControllerSentryTests.swift */; }; 7B6438AA26A70F24000D0F65 /* UIViewController+Sentry.h in Headers */ = {isa = PBXBuildFile; fileRef = 7B6438A826A70F24000D0F65 /* UIViewController+Sentry.h */; }; 7B6438AB26A70F24000D0F65 /* UIViewController+Sentry.m in Sources */ = {isa = PBXBuildFile; fileRef = 7B6438A926A70F24000D0F65 /* UIViewController+Sentry.m */; }; + 7B68345128F7EB3D00FB7064 /* SentryMeasurementUnitTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B68345028F7EB3D00FB7064 /* SentryMeasurementUnitTests.swift */; }; 7B68D93625FF5F1A0082D139 /* SentryAppState+Equality.m in Sources */ = {isa = PBXBuildFile; fileRef = 7B68D93525FF5F1A0082D139 /* SentryAppState+Equality.m */; }; 7B6ADFCF26A02CAE0076C206 /* SentryCrashReportTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B6ADFCE26A02CAE0076C206 /* SentryCrashReportTests.swift */; }; - 7B6C5ED2264E5D6C0010D138 /* SentryTransaction+Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 7B6C5ED0264E5D6C0010D138 /* SentryTransaction+Private.h */; }; 7B6C5ED6264E62CA0010D138 /* SentryTransactionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B6C5ED5264E62CA0010D138 /* SentryTransactionTests.swift */; }; 7B6C5EDA264E8D860010D138 /* SentryFramesTrackingIntegration.h in Headers */ = {isa = PBXBuildFile; fileRef = 7B6C5ED9264E8D860010D138 /* SentryFramesTrackingIntegration.h */; }; 7B6C5EDC264E8DA80010D138 /* SentryFramesTrackingIntegration.m in Sources */ = {isa = PBXBuildFile; fileRef = 7B6C5EDB264E8DA80010D138 /* SentryFramesTrackingIntegration.m */; }; @@ -493,6 +493,10 @@ 7BC8523724588115005A70F0 /* SentryDataCategory.h in Headers */ = {isa = PBXBuildFile; fileRef = 7BC8523624588115005A70F0 /* SentryDataCategory.h */; }; 7BC852392458830A005A70F0 /* SentryEnvelopeItemType.h in Headers */ = {isa = PBXBuildFile; fileRef = 7BC852382458830A005A70F0 /* SentryEnvelopeItemType.h */; settings = {ATTRIBUTES = (Public, ); }; }; 7BC8523B2458849E005A70F0 /* SentryDataCategoryMapperTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BC8523A2458849E005A70F0 /* SentryDataCategoryMapperTests.swift */; }; + 7BC9A20028F41016001E7C4C /* SentryMeasurementValue.h in Headers */ = {isa = PBXBuildFile; fileRef = 7BC9A1FF28F41016001E7C4C /* SentryMeasurementValue.h */; }; + 7BC9A20228F41350001E7C4C /* SentryMeasurementUnit.h in Headers */ = {isa = PBXBuildFile; fileRef = 7BC9A20128F41350001E7C4C /* SentryMeasurementUnit.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 7BC9A20428F4166D001E7C4C /* SentryMeasurementValue.m in Sources */ = {isa = PBXBuildFile; fileRef = 7BC9A20328F4166D001E7C4C /* SentryMeasurementValue.m */; }; + 7BC9A20628F41781001E7C4C /* SentryMeasurementUnit.m in Sources */ = {isa = PBXBuildFile; fileRef = 7BC9A20528F41781001E7C4C /* SentryMeasurementUnit.m */; }; 7BCFA71627D0BB50008C662C /* SentryANRTracker.m in Sources */ = {isa = PBXBuildFile; fileRef = 7BCFA71527D0BB50008C662C /* SentryANRTracker.m */; }; 7BCFBD672681C95000BC27D8 /* SentryScopeObserver.h in Headers */ = {isa = PBXBuildFile; fileRef = 7BCFBD662681C95000BC27D8 /* SentryScopeObserver.h */; }; 7BCFBD6D2681D0A900BC27D8 /* SentryCrashScopeObserver.h in Headers */ = {isa = PBXBuildFile; fileRef = 7BCFBD6C2681D0A900BC27D8 /* SentryCrashScopeObserver.h */; }; @@ -1079,10 +1083,10 @@ 7B6438A626A70DDB000D0F65 /* UIViewControllerSentryTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = UIViewControllerSentryTests.swift; path = Tests/SentryTests/Categories/UIViewControllerSentryTests.swift; sourceTree = SOURCE_ROOT; }; 7B6438A826A70F24000D0F65 /* UIViewController+Sentry.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "UIViewController+Sentry.h"; path = "include/UIViewController+Sentry.h"; sourceTree = ""; }; 7B6438A926A70F24000D0F65 /* UIViewController+Sentry.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "UIViewController+Sentry.m"; sourceTree = ""; }; + 7B68345028F7EB3D00FB7064 /* SentryMeasurementUnitTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryMeasurementUnitTests.swift; sourceTree = ""; }; 7B68D93425FF5F1A0082D139 /* SentryAppState+Equality.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "SentryAppState+Equality.h"; sourceTree = ""; }; 7B68D93525FF5F1A0082D139 /* SentryAppState+Equality.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "SentryAppState+Equality.m"; sourceTree = ""; }; 7B6ADFCE26A02CAE0076C206 /* SentryCrashReportTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryCrashReportTests.swift; sourceTree = ""; }; - 7B6C5ED0264E5D6C0010D138 /* SentryTransaction+Private.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "SentryTransaction+Private.h"; path = "include/SentryTransaction+Private.h"; sourceTree = ""; }; 7B6C5ED5264E62CA0010D138 /* SentryTransactionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryTransactionTests.swift; sourceTree = ""; }; 7B6C5ED9264E8D860010D138 /* SentryFramesTrackingIntegration.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SentryFramesTrackingIntegration.h; path = include/SentryFramesTrackingIntegration.h; sourceTree = ""; }; 7B6C5EDB264E8DA80010D138 /* SentryFramesTrackingIntegration.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SentryFramesTrackingIntegration.m; sourceTree = ""; }; @@ -1226,6 +1230,10 @@ 7BC8523624588115005A70F0 /* SentryDataCategory.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SentryDataCategory.h; path = include/SentryDataCategory.h; sourceTree = ""; }; 7BC852382458830A005A70F0 /* SentryEnvelopeItemType.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SentryEnvelopeItemType.h; path = Public/SentryEnvelopeItemType.h; sourceTree = ""; }; 7BC8523A2458849E005A70F0 /* SentryDataCategoryMapperTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryDataCategoryMapperTests.swift; sourceTree = ""; }; + 7BC9A1FF28F41016001E7C4C /* SentryMeasurementValue.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SentryMeasurementValue.h; path = include/SentryMeasurementValue.h; sourceTree = ""; }; + 7BC9A20128F41350001E7C4C /* SentryMeasurementUnit.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SentryMeasurementUnit.h; path = Public/SentryMeasurementUnit.h; sourceTree = ""; }; + 7BC9A20328F4166D001E7C4C /* SentryMeasurementValue.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SentryMeasurementValue.m; sourceTree = ""; }; + 7BC9A20528F41781001E7C4C /* SentryMeasurementUnit.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SentryMeasurementUnit.m; sourceTree = ""; }; 7BC9CD4326A99F660047518E /* SentryUIViewControllerSwizzling+Test.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "SentryUIViewControllerSwizzling+Test.h"; sourceTree = ""; }; 7BCFA71427D0BAB7008C662C /* SentryANRTracker.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SentryANRTracker.h; path = include/SentryANRTracker.h; sourceTree = ""; }; 7BCFA71527D0BB50008C662C /* SentryANRTracker.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SentryANRTracker.m; sourceTree = ""; }; @@ -1614,6 +1622,8 @@ 7BD4BD4427EB29F50071F4FF /* SentryClientReport.m */, 7BD4BD4627EB2A3D0071F4FF /* SentryDiscardedEvent.h */, 7BD4BD4827EB2A5D0071F4FF /* SentryDiscardedEvent.m */, + 7BC9A20128F41350001E7C4C /* SentryMeasurementUnit.h */, + 7BC9A20528F41781001E7C4C /* SentryMeasurementUnit.m */, ); name = Protocol; sourceTree = ""; @@ -2245,6 +2255,7 @@ 7B68D93525FF5F1A0082D139 /* SentryAppState+Equality.m */, 7BD4BD4A27EB2DC20071F4FF /* SentryDiscardedEventTests.swift */, 7BD4BD4C27EB31820071F4FF /* SentryClientReportTests.swift */, + 7B68345028F7EB3D00FB7064 /* SentryMeasurementUnitTests.swift */, ); path = Protocol; sourceTree = ""; @@ -2756,7 +2767,6 @@ 8ECC674525C23A20000E2BF6 /* SentrySpanId.m */, 8E4E7C6B25DAAAFE006AB9E2 /* SentryTransaction.h */, 8ECC674425C23A1F000E2BF6 /* SentryTransaction.m */, - 7B6C5ED0264E5D6C0010D138 /* SentryTransaction+Private.h */, 8ECC673B25C23996000E2BF6 /* SentryTransactionContext.h */, 8ECC674625C23A20000E2BF6 /* SentryTransactionContext.m */, 0A56DA5E28ABA01B00C400D5 /* SentryTransactionContext+Private.h */, @@ -2776,6 +2786,8 @@ D88817D626D7149100BF2251 /* SentryTraceContext.m */, D8603DD7284F894C000E1227 /* SentryBaggage.h */, D8603DD4284F8497000E1227 /* SentryBaggage.m */, + 7BC9A1FF28F41016001E7C4C /* SentryMeasurementValue.h */, + 7BC9A20328F4166D001E7C4C /* SentryMeasurementValue.m */, ); name = Transaction; sourceTree = ""; @@ -2898,6 +2910,7 @@ 0A2D8DA8289BC905008720F6 /* SentryViewHierarchy.h in Headers */, 8EAE980C261E9F530073B6B3 /* SentryUIViewControllerPerformanceTracker.h in Headers */, 63FE717D20DA4C1100CDBAE8 /* SentryCrashCachedData.h in Headers */, + 7BC9A20028F41016001E7C4C /* SentryMeasurementValue.h in Headers */, 7BBC826D25DFCFDE005F1ED8 /* SentryInAppLogic.h in Headers */, 03BCC38A27E1BF49003232C7 /* SentryTime.h in Headers */, 7B0A54222521C21E00A71716 /* SentryFrameRemover.h in Headers */, @@ -2990,6 +3003,7 @@ 7DC27EC523997EB7006998B5 /* SentryAutoBreadcrumbTrackingIntegration.h in Headers */, 63FE707F20DA4C1000CDBAE8 /* SentryCrashVarArgs.h in Headers */, 03F84D2627DD414C008FE43F /* SentryThreadMetadataCache.hpp in Headers */, + 7BC9A20228F41350001E7C4C /* SentryMeasurementUnit.h in Headers */, 7BB654FB253DC14A00887E87 /* SentryUserFeedback.h in Headers */, 7B8ECBFA26498907005FE2EF /* SentryAppStateManager.h in Headers */, 639FCFA01EBC804600778193 /* SentryException.h in Headers */, @@ -3043,7 +3057,6 @@ 8E5D38E3261D4B57000D363D /* SentryPerformanceTrackingIntegration.h in Headers */, 63FE70F920DA4C1000CDBAE8 /* SentryCrashMonitor.h in Headers */, 7B98D7CB25FB64EC00C5A389 /* SentryOutOfMemoryTrackingIntegration.h in Headers */, - 7B6C5ED2264E5D6C0010D138 /* SentryTransaction+Private.h in Headers */, 63FE710920DA4C1000CDBAE8 /* SentryCrashFileUtils.h in Headers */, 03F84D1F27DD414C008FE43F /* SentryAsyncSafeLogging.h in Headers */, 7BE3C76B2445C27A00A38442 /* SentryCurrentDateProvider.h in Headers */, @@ -3417,6 +3430,7 @@ 8EAE9806261E87120073B6B3 /* SentryUIViewControllerPerformanceTracker.m in Sources */, D88817D826D7149100BF2251 /* SentryTraceContext.m in Sources */, 8EBF870926140D37001A6853 /* SentryPerformanceTracker.m in Sources */, + 7BC9A20428F4166D001E7C4C /* SentryMeasurementValue.m in Sources */, D859696B27BECD8F0036A46E /* SentryCoreDataTrackingIntegration.m in Sources */, 7BD86EC7264A641D005439DB /* SentrySysctl.m in Sources */, D859697327BECDD20036A46E /* SentryCoreDataSwizzling.m in Sources */, @@ -3433,6 +3447,7 @@ 0356A571288B4612008BF593 /* SentryProfilesSampler.m in Sources */, 63FE710B20DA4C1000CDBAE8 /* SentryCrashMach.c in Sources */, 63FE707720DA4C1000CDBAE8 /* Container+SentryDeepSearch.m in Sources */, + 7BC9A20628F41781001E7C4C /* SentryMeasurementUnit.m in Sources */, 63FE71A020DA4C1100CDBAE8 /* SentryCrashInstallation.m in Sources */, 63FE713520DA4C1100CDBAE8 /* SentryCrashMemory.c in Sources */, 63FE714520DA4C1100CDBAE8 /* SentryCrashObjC.c in Sources */, @@ -3465,6 +3480,7 @@ 7B5AB65D27E48E5200F1D1BA /* TestThreadInspector.swift in Sources */, 7BF9EF742722A85B00B5BBEF /* SentryClassRegistrator.m in Sources */, 63B819141EC352A7002FDF4C /* SentryInterfacesTests.m in Sources */, + 7B68345128F7EB3D00FB7064 /* SentryMeasurementUnitTests.swift in Sources */, 7B14089A248791660035403D /* SentryCrashStackEntryMapperTests.swift in Sources */, 7B869EBC249B91D8004F4FDB /* SentryDebugMetaEquality.swift in Sources */, 7B01CE3D271993AC00B5AF31 /* SentryTransportFactoryTests.swift in Sources */, diff --git a/Sources/Sentry/Public/Sentry.h b/Sources/Sentry/Public/Sentry.h index 8fb98787446..01784d2553b 100644 --- a/Sources/Sentry/Public/Sentry.h +++ b/Sources/Sentry/Public/Sentry.h @@ -25,6 +25,7 @@ FOUNDATION_EXPORT const unsigned char SentryVersionString[]; #import "SentryHub.h" #import "SentryId.h" #import "SentryIntegrationProtocol.h" +#import "SentryMeasurementUnit.h" #import "SentryMechanism.h" #import "SentryMechanismMeta.h" #import "SentryMessage.h" diff --git a/Sources/Sentry/Public/SentryMeasurementUnit.h b/Sources/Sentry/Public/SentryMeasurementUnit.h new file mode 100644 index 00000000000..e7a5b3462af --- /dev/null +++ b/Sources/Sentry/Public/SentryMeasurementUnit.h @@ -0,0 +1,138 @@ +#import "SentryDefines.h" + +NS_ASSUME_NONNULL_BEGIN + +/** + * The unit of measurement of a metric value. + * + * Units augment metric values by giving them a magnitude and semantics. There are certain types + * of units that are subdivided in their precision, such as the ``SentryMeasurementUnitDuration`` + * for time measurements. The following unit types are available: ``SentryMeasurementUnitDuration``, + * ``SentryMeasurementUnitInformation``, and``SentryMeasurementUnitFraction``. + * + * When using the units to custom measurements, Sentry will apply formatting to display + * measurement values in the UI. + */ +NS_SWIFT_NAME(MeasurementUnit) +@interface SentryMeasurementUnit : NSObject +SENTRY_NO_INIT + +/** + * Returns an initialized SentryMeasurementUnit with a custom measurement unit. + * + * @param unit Your own custom unit without built-in conversion in Sentry. + */ +- (instancetype)initWithUnit:(NSString *)unit; + +/** + * The NSString representation of the measurement unit. + */ +@property (readonly, copy) NSString *unit; + +/** Untyped value without a unit. */ +@property (class, readonly, copy) SentryMeasurementUnit *none; + +@end + +/** + * Time duration units. + */ +NS_SWIFT_NAME(MeasurementUnitDuration) +@interface SentryMeasurementUnitDuration : SentryMeasurementUnit +SENTRY_NO_INIT + +/** Nanosecond, 10^-9 seconds. */ +@property (class, readonly, copy) SentryMeasurementUnitDuration *nanosecond; + +/** Microsecond , 10^-6 seconds. */ +@property (class, readonly, copy) SentryMeasurementUnitDuration *microsecond; + +/** Millisecond, 10^-3 seconds. */ +@property (class, readonly, copy) SentryMeasurementUnitDuration *millisecond; + +/** Full second. */ +@property (class, readonly, copy) SentryMeasurementUnitDuration *second; + +/** Minute, 60 seconds. */ +@property (class, readonly, copy) SentryMeasurementUnitDuration *minute; + +/** Hour, 3600 seconds. */ +@property (class, readonly, copy) SentryMeasurementUnitDuration *hour; + +/** Day, 86,400 seconds. */ +@property (class, readonly, copy) SentryMeasurementUnitDuration *day; + +/** Week, 604,800 seconds. */ +@property (class, readonly, copy) SentryMeasurementUnitDuration *week; + +@end + +/** + * Size of information units derived from bytes. + * + * See also [Units of information](https://en.wikipedia.org/wiki/Units_of_information) + */ +NS_SWIFT_NAME(MeasurementUnitInformation) +@interface SentryMeasurementUnitInformation : SentryMeasurementUnit +SENTRY_NO_INIT + +/** Bit, corresponding to 1/8 of a byte. */ +@property (class, readonly, copy) SentryMeasurementUnitInformation *bit; + +/** Byte. */ +@property (class, readonly, copy) SentryMeasurementUnitInformation *byte; + +/** Kilobyte, 10^3 bytes. */ +@property (class, readonly, copy) SentryMeasurementUnitInformation *kilobyte; + +/** Kibibyte, 2^10 bytes. */ +@property (class, readonly, copy) SentryMeasurementUnitInformation *kibibyte; + +/** Megabyte, 10^6 bytes. */ +@property (class, readonly, copy) SentryMeasurementUnitInformation *megabyte; + +/** Mebibyte, 2^20 bytes. */ +@property (class, readonly, copy) SentryMeasurementUnitInformation *mebibyte; + +/** Gigabyte, 10^9 bytes. */ +@property (class, readonly, copy) SentryMeasurementUnitInformation *gigabyte; + +/** Gibibyte, 2^30 bytes. */ +@property (class, readonly, copy) SentryMeasurementUnitInformation *gibibyte; + +/** Terabyte, 10^12 bytes. */ +@property (class, readonly, copy) SentryMeasurementUnitInformation *terabyte; + +/** Tebibyte, 2^40 bytes. */ +@property (class, readonly, copy) SentryMeasurementUnitInformation *tebibyte; + +/** Petabyte, 10^15 bytes. */ +@property (class, readonly, copy) SentryMeasurementUnitInformation *petabyte; + +/** Pebibyte, 2^50 bytes. */ +@property (class, readonly, copy) SentryMeasurementUnitInformation *pebibyte; + +/** Exabyte, 10^18 bytes. */ +@property (class, readonly, copy) SentryMeasurementUnitInformation *exabyte; + +/** Exbibyte, 2^60 bytes. */ +@property (class, readonly, copy) SentryMeasurementUnitInformation *exbibyte; + +@end + +/** + * Units of fraction. + */ +NS_SWIFT_NAME(MeasurementUnitFraction) +@interface SentryMeasurementUnitFraction : SentryMeasurementUnit +SENTRY_NO_INIT + +/** Floating point fraction of `1`. */ +@property (class, readonly, copy) SentryMeasurementUnitFraction *ratio; + +/** Ratio expressed as a fraction of `100`. `100%` equals a ratio of `1.0`. */ +@property (class, readonly, copy) SentryMeasurementUnitFraction *percent; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Sources/Sentry/Public/SentryOptions.h b/Sources/Sentry/Public/SentryOptions.h index fec00739085..0bdd4c97741 100644 --- a/Sources/Sentry/Public/SentryOptions.h +++ b/Sources/Sentry/Public/SentryOptions.h @@ -3,7 +3,7 @@ NS_ASSUME_NONNULL_BEGIN -@class SentryDsn, SentrySdkInfo; +@class SentryDsn, SentrySdkInfo, SentryMeasurementValue; NS_SWIFT_NAME(Options) @interface SentryOptions : NSObject diff --git a/Sources/Sentry/Public/SentrySpanProtocol.h b/Sources/Sentry/Public/SentrySpanProtocol.h index 4fbc1abf50f..ce206fe8778 100644 --- a/Sources/Sentry/Public/SentrySpanProtocol.h +++ b/Sources/Sentry/Public/SentrySpanProtocol.h @@ -4,7 +4,7 @@ NS_ASSUME_NONNULL_BEGIN -@class SentrySpanId, SentryId, SentryTraceHeader; +@class SentrySpanId, SentryId, SentryTraceHeader, SentryMeasurementUnit; NS_SWIFT_NAME(Span) @protocol SentrySpan @@ -87,6 +87,35 @@ NS_SWIFT_NAME(Span) */ - (void)removeTagForKey:(NSString *)key NS_SWIFT_NAME(removeTag(key:)); +/** + * Set a measurement without unit. When setting the measurement without the unit, no formatting + * will be applied to the measurement value in the Sentry product, and the value will be shown as + * is. + * + * @discussion Setting a measurement with the same name on the same transaction multiple times only + * keeps the last value. + * + * @param name the name of the measurement + * @param value the value of the measurement + */ +- (void)setMeasurement:(NSString *)name + value:(NSNumber *)value NS_SWIFT_NAME(setMeasurement(name:value:)); + +/** + * Set a measurement with specific unit. + * + * @discussion Setting a measurement with the same name on the same transaction multiple times only + * keeps the last value. + * + * @param name the name of the measurement + * @param value the value of the measurement + * @param unit the unit the value is measured in + */ +- (void)setMeasurement:(NSString *)name + value:(NSNumber *)value + unit:(SentryMeasurementUnit *)unit + NS_SWIFT_NAME(setMeasurement(name:value:unit:)); + /** * Finishes the span by setting the end time. */ diff --git a/Sources/Sentry/SentryMeasurementUnit.m b/Sources/Sentry/SentryMeasurementUnit.m new file mode 100644 index 00000000000..f9174857b4a --- /dev/null +++ b/Sources/Sentry/SentryMeasurementUnit.m @@ -0,0 +1,159 @@ +#import "SentryMeasurementUnit.h" + +NS_ASSUME_NONNULL_BEGIN + +@implementation SentryMeasurementUnit + +- (instancetype)initWithUnit:(NSString *)unit +{ + if (self = [super init]) { + _unit = unit; + } + return self; +} + ++ (SentryMeasurementUnit *)none +{ + return [[SentryMeasurementUnitDuration alloc] initWithUnit:@""]; +} + +- (id)copyWithZone:(nullable NSZone *)zone +{ + return [[[self class] allocWithZone:zone] initWithUnit:self.unit]; +} + +@end + +@implementation SentryMeasurementUnitDuration + ++ (SentryMeasurementUnitDuration *)nanosecond +{ + return [[SentryMeasurementUnitDuration alloc] initWithUnit:@"nanosecond"]; +} + ++ (SentryMeasurementUnitDuration *)microsecond +{ + return [[SentryMeasurementUnitDuration alloc] initWithUnit:@"microsecond"]; +} + ++ (SentryMeasurementUnitDuration *)millisecond +{ + return [[SentryMeasurementUnitDuration alloc] initWithUnit:@"millisecond"]; +} + ++ (SentryMeasurementUnitDuration *)second +{ + return [[SentryMeasurementUnitDuration alloc] initWithUnit:@"second"]; +} + ++ (SentryMeasurementUnitDuration *)minute +{ + return [[SentryMeasurementUnitDuration alloc] initWithUnit:@"minute"]; +} + ++ (SentryMeasurementUnitDuration *)hour +{ + return [[SentryMeasurementUnitDuration alloc] initWithUnit:@"hour"]; +} + ++ (SentryMeasurementUnitDuration *)day +{ + return [[SentryMeasurementUnitDuration alloc] initWithUnit:@"day"]; +} + ++ (SentryMeasurementUnitDuration *)week +{ + return [[SentryMeasurementUnitDuration alloc] initWithUnit:@"week"]; +} + +@end + +@implementation SentryMeasurementUnitInformation + ++ (SentryMeasurementUnitInformation *)bit +{ + return [[SentryMeasurementUnitInformation alloc] initWithUnit:@"bit"]; +} + ++ (SentryMeasurementUnitInformation *)byte +{ + return [[SentryMeasurementUnitInformation alloc] initWithUnit:@"byte"]; +} + ++ (SentryMeasurementUnitInformation *)kilobyte +{ + return [[SentryMeasurementUnitInformation alloc] initWithUnit:@"kilobyte"]; +} + ++ (SentryMeasurementUnitInformation *)kibibyte +{ + return [[SentryMeasurementUnitInformation alloc] initWithUnit:@"kibibyte"]; +} + ++ (SentryMeasurementUnitInformation *)megabyte +{ + return [[SentryMeasurementUnitInformation alloc] initWithUnit:@"megabyte"]; +} + ++ (SentryMeasurementUnitInformation *)mebibyte +{ + return [[SentryMeasurementUnitInformation alloc] initWithUnit:@"mebibyte"]; +} + ++ (SentryMeasurementUnitInformation *)gigabyte +{ + return [[SentryMeasurementUnitInformation alloc] initWithUnit:@"gigabyte"]; +} + ++ (SentryMeasurementUnitInformation *)gibibyte +{ + return [[SentryMeasurementUnitInformation alloc] initWithUnit:@"gibibyte"]; +} + ++ (SentryMeasurementUnitInformation *)terabyte +{ + return [[SentryMeasurementUnitInformation alloc] initWithUnit:@"terabyte"]; +} + ++ (SentryMeasurementUnitInformation *)tebibyte +{ + return [[SentryMeasurementUnitInformation alloc] initWithUnit:@"tebibyte"]; +} + ++ (SentryMeasurementUnitInformation *)petabyte +{ + return [[SentryMeasurementUnitInformation alloc] initWithUnit:@"petabyte"]; +} + ++ (SentryMeasurementUnitInformation *)pebibyte +{ + return [[SentryMeasurementUnitInformation alloc] initWithUnit:@"pebibyte"]; +} + ++ (SentryMeasurementUnitInformation *)exabyte +{ + return [[SentryMeasurementUnitInformation alloc] initWithUnit:@"exabyte"]; +} + ++ (SentryMeasurementUnitInformation *)exbibyte +{ + return [[SentryMeasurementUnitInformation alloc] initWithUnit:@"exbibyte"]; +} + +@end + +@implementation SentryMeasurementUnitFraction + ++ (SentryMeasurementUnitFraction *)ratio +{ + return [[SentryMeasurementUnitFraction alloc] initWithUnit:@"ratio"]; +} + ++ (SentryMeasurementUnitFraction *)percent +{ + return [[SentryMeasurementUnitFraction alloc] initWithUnit:@"percent"]; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/Sources/Sentry/SentryMeasurementValue.m b/Sources/Sentry/SentryMeasurementValue.m new file mode 100644 index 00000000000..6099ede4ac5 --- /dev/null +++ b/Sources/Sentry/SentryMeasurementValue.m @@ -0,0 +1,36 @@ +#import "SentryMeasurementValue.h" + +NS_ASSUME_NONNULL_BEGIN + +@implementation SentryMeasurementValue + +- (instancetype)initWithValue:(NSNumber *)value +{ + if (self = [super init]) { + _value = value; + } + return self; +} + +- (instancetype)initWithValue:(NSNumber *)value unit:(SentryMeasurementUnit *)unit +{ + if (self = [super init]) { + _value = value; + _unit = unit; + } + return self; +} + +- (NSDictionary *)serialize +{ + + if (self.unit != nil) { + return @{ @"value" : _value, @"unit" : _unit.unit }; + } else { + return @{ @"value" : _value }; + } +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/Sources/Sentry/SentryNoOpSpan.m b/Sources/Sentry/SentryNoOpSpan.m index 0ad045b7596..e62d67a54c9 100644 --- a/Sources/Sentry/SentryNoOpSpan.m +++ b/Sources/Sentry/SentryNoOpSpan.m @@ -64,6 +64,14 @@ - (void)removeTagForKey:(NSString *)key { } +- (void)setMeasurement:(NSString *)name value:(NSNumber *)value +{ +} + +- (void)setMeasurement:(NSString *)name value:(NSNumber *)value unit:(SentryMeasurementUnit *)unit +{ +} + - (NSDictionary *)tags { return @{}; diff --git a/Sources/Sentry/SentryOptions.m b/Sources/Sentry/SentryOptions.m index 150b7f440a6..8f9bc0b5036 100644 --- a/Sources/Sentry/SentryOptions.m +++ b/Sources/Sentry/SentryOptions.m @@ -20,6 +20,10 @@ @implementation SentryOptions +- (void)setMeasurement:(SentryMeasurementValue *)measurement +{ +} + + (NSArray *)defaultIntegrations { return @[ diff --git a/Sources/Sentry/SentrySpan.m b/Sources/Sentry/SentrySpan.m index 2dab3697943..d0a0b656269 100644 --- a/Sources/Sentry/SentrySpan.m +++ b/Sources/Sentry/SentrySpan.m @@ -3,6 +3,7 @@ #import "NSDictionary+SentrySanitize.h" #import "SentryCurrentDate.h" #import "SentryLog.h" +#import "SentryMeasurementValue.h" #import "SentryNoOpSpan.h" #import "SentryTraceHeader.h" #import "SentryTracer.h" @@ -89,6 +90,16 @@ - (void)removeTagForKey:(NSString *)key } } +- (void)setMeasurement:(NSString *)name value:(NSNumber *)value +{ + [self.tracer setMeasurement:name value:value]; +} + +- (void)setMeasurement:(NSString *)name value:(NSNumber *)value unit:(SentryMeasurementUnit *)unit +{ + [self.tracer setMeasurement:name value:value unit:unit]; +} + - (NSDictionary *)tags { @synchronized(_tags) { diff --git a/Sources/Sentry/SentryTracer.m b/Sources/Sentry/SentryTracer.m index 167363dc263..6e999e97e4a 100644 --- a/Sources/Sentry/SentryTracer.m +++ b/Sources/Sentry/SentryTracer.m @@ -17,11 +17,11 @@ #import "SentrySpanContext.h" #import "SentrySpanId.h" #import "SentryTraceContext.h" -#import "SentryTransaction+Private.h" #import "SentryTransaction.h" #import "SentryTransactionContext.h" #import "SentryUIViewControllerPerformanceTracker.h" #import +#import #import #import @@ -61,6 +61,7 @@ @implementation SentryTracer { SentryAppStartMeasurement *appStartMeasurement; NSMutableDictionary *_tags; NSMutableDictionary *_data; + NSMutableDictionary *_measurements; dispatch_block_t _idleTimeoutBlock; NSMutableArray> *_children; @@ -160,6 +161,7 @@ - (instancetype)initWithTransactionContext:(SentryTransactionContext *)transacti _waitForChildren = waitForChildren; _tags = [[NSMutableDictionary alloc] init]; _data = [[NSMutableDictionary alloc] init]; + _measurements = [[NSMutableDictionary alloc] init]; self.finishStatus = kSentrySpanStatusUndefined; self.idleTimeout = idleTimeout; self.dispatchQueueWrapper = dispatchQueueWrapper; @@ -395,6 +397,19 @@ - (void)removeTagForKey:(NSString *)key } } +- (void)setMeasurement:(NSString *)name value:(NSNumber *)value +{ + SentryMeasurementValue *measurement = [[SentryMeasurementValue alloc] initWithValue:value]; + _measurements[name] = measurement; +} + +- (void)setMeasurement:(NSString *)name value:(NSNumber *)value unit:(SentryMeasurementUnit *)unit +{ + SentryMeasurementValue *measurement = [[SentryMeasurementValue alloc] initWithValue:value + unit:unit]; + _measurements[name] = measurement; +} + - (SentryTraceHeader *)toTraceHeader { return [self.rootSpan toTraceHeader]; @@ -687,8 +702,6 @@ - (nullable SentryAppStartMeasurement *)getAppStartMeasurement - (void)addMeasurements:(SentryTransaction *)transaction { - NSString *valueKey = @"value"; - if (appStartMeasurement != nil && appStartMeasurement.type != SentryAppStartTypeUnknown) { NSString *type = nil; if (appStartMeasurement.type == SentryAppStartTypeCold) { @@ -698,8 +711,7 @@ - (void)addMeasurements:(SentryTransaction *)transaction } if (type != nil) { - [transaction setMeasurementValue:@{ valueKey : @(appStartMeasurement.duration * 1000) } - forKey:type]; + [self setMeasurement:type value:@(appStartMeasurement.duration * 1000)]; } } @@ -717,10 +729,9 @@ - (void)addMeasurements:(SentryTransaction *)transaction BOOL oneBiggerThanZero = totalFrames > 0 || slowFrames > 0 || frozenFrames > 0; if (allBiggerThanZero && oneBiggerThanZero) { - [transaction setMeasurementValue:@{ valueKey : @(totalFrames) } forKey:@"frames_total"]; - [transaction setMeasurementValue:@{ valueKey : @(slowFrames) } forKey:@"frames_slow"]; - [transaction setMeasurementValue:@{ valueKey : @(frozenFrames) } - forKey:@"frames_frozen"]; + [self setMeasurement:@"frames_total" value:@(totalFrames)]; + [self setMeasurement:@"frames_slow" value:@(slowFrames)]; + [self setMeasurement:@"frames_frozen" value:@(frozenFrames)]; SENTRY_LOG_DEBUG(@"Frames for transaction \"%@\" Total:%ld Slow:%ld Frozen:%ld", self.context.operation, (long)totalFrames, (long)slowFrames, (long)frozenFrames); diff --git a/Sources/Sentry/SentryTransaction.m b/Sources/Sentry/SentryTransaction.m index a90f3935042..a9efc76bf7c 100644 --- a/Sources/Sentry/SentryTransaction.m +++ b/Sources/Sentry/SentryTransaction.m @@ -1,6 +1,7 @@ #import "SentryTransaction.h" #import "NSDictionary+SentrySanitize.h" #import "SentryEnvelopeItemType.h" +#import "SentryMeasurementValue.h" #import "SentryTransactionContext.h" NS_ASSUME_NONNULL_BEGIN @@ -9,7 +10,6 @@ SentryTransaction () @property (nonatomic, strong) NSArray> *spans; -@property (nonatomic, strong) NSMutableDictionary *measurements; @end @@ -23,16 +23,10 @@ - (instancetype)initWithTrace:(SentryTracer *)trace children:(NSArray *)serialize { NSMutableDictionary *serializedData = @@ -80,8 +74,14 @@ - (void)setMeasurementValue:(id)value forKey:(NSString *)key serializedData[@"extra"] = traceData; } - if (self.measurements.count > 0) { - serializedData[@"measurements"] = [self.measurements.copy sentry_sanitize]; + if (self.trace.measurements.count > 0) { + NSMutableDictionary *measurements = [NSMutableDictionary dictionary]; + + for (NSString *measurementName in self.trace.measurements.allKeys) { + measurements[measurementName] = [self.trace.measurements[measurementName] serialize]; + } + + serializedData[@"measurements"] = measurements; } if (self.trace) { diff --git a/Sources/Sentry/include/SentryMeasurementValue.h b/Sources/Sentry/include/SentryMeasurementValue.h new file mode 100644 index 00000000000..ec8a61bd275 --- /dev/null +++ b/Sources/Sentry/include/SentryMeasurementValue.h @@ -0,0 +1,19 @@ +#import "SentryDefines.h" +#import "SentryMeasurementUnit.h" +#import "SentrySerializable.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface SentryMeasurementValue : NSObject +SENTRY_NO_INIT + +- (instancetype)initWithValue:(NSNumber *)value; + +- (instancetype)initWithValue:(NSNumber *)value unit:(SentryMeasurementUnit *)unit; + +@property (nonatomic, copy, readonly) NSNumber *value; +@property (nullable, readonly, copy) SentryMeasurementUnit *unit; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Sources/Sentry/include/SentryTracer.h b/Sources/Sentry/include/SentryTracer.h index 2881ace6d50..c6dc2b7e110 100644 --- a/Sources/Sentry/include/SentryTracer.h +++ b/Sources/Sentry/include/SentryTracer.h @@ -4,7 +4,7 @@ NS_ASSUME_NONNULL_BEGIN @class SentryHub, SentryTransactionContext, SentryTraceHeader, SentryTraceContext, - SentryDispatchQueueWrapper, SentryTracer, SentryProfilesSamplerDecision; + SentryDispatchQueueWrapper, SentryTracer, SentryProfilesSamplerDecision, SentryMeasurementValue; static NSTimeInterval const SentryTracerDefaultTimeout = 3.0; @@ -76,6 +76,8 @@ static NSTimeInterval const SentryTracerDefaultTimeout = 3.0; */ @property (nullable, nonatomic, weak) id delegate; +@property (nonatomic, readonly) NSDictionary *measurements; + /** * Init a SentryTracer with given transaction context and hub and set other fields by default * diff --git a/Sources/Sentry/include/SentryTransaction+Private.h b/Sources/Sentry/include/SentryTransaction+Private.h deleted file mode 100644 index 9d9f38ff096..00000000000 --- a/Sources/Sentry/include/SentryTransaction+Private.h +++ /dev/null @@ -1,12 +0,0 @@ -#import "SentryTransaction.h" - -NS_ASSUME_NONNULL_BEGIN - -@interface -SentryTransaction (Private) - -- (void)setMeasurementValue:(id)value forKey:(NSString *)key; - -@end - -NS_ASSUME_NONNULL_END diff --git a/Tests/SentryTests/Performance/SentryTracerTests.swift b/Tests/SentryTests/Performance/SentryTracerTests.swift index c42763268ff..24f681a9ac8 100644 --- a/Tests/SentryTests/Performance/SentryTracerTests.swift +++ b/Tests/SentryTests/Performance/SentryTracerTests.swift @@ -546,6 +546,31 @@ class SentryTracerTests: XCTestCase { assertAppStartMeasurementNotPutOnTransaction() } + func testMeasurementOnChildSpan_SetTwice_OverwritesMeasurement() { + let name = "something" + let value: NSNumber = -12.34 + let unit = MeasurementUnitFraction.percent + + let sut = fixture.getSut() + let childSpan = sut.startChild(operation: "operation") + sut.setMeasurement(name: name, value: 12.0, unit: unit) + childSpan.setMeasurement(name: name, value: value, unit: unit) + childSpan.finish() + sut.finish() + fixture.hub.group.wait() + + XCTAssertEqual(1, fixture.hub.capturedEventsWithScopes.count) + let serializedTransaction = fixture.hub.capturedEventsWithScopes.first?.event.serialize() + + let measurements = serializedTransaction?["measurements"] as? [String: [String: Any]] + XCTAssertEqual(1, measurements?.count) + + let measurement = measurements?[name] + XCTAssertNotNil(measurement) + XCTAssertEqual(value, measurement?["value"] as! NSNumber) + XCTAssertEqual(unit.unit, measurement?["unit"] as! String) + } + func testFinish_WithUnfinishedChildren() { CurrentDate.setCurrentDateProvider(DefaultCurrentDateProvider.sharedInstance()) let sut = fixture.getSut(waitForChildren: false) diff --git a/Tests/SentryTests/Protocol/SentryMeasurementUnitTests.swift b/Tests/SentryTests/Protocol/SentryMeasurementUnitTests.swift new file mode 100644 index 00000000000..874ddf07ac7 --- /dev/null +++ b/Tests/SentryTests/Protocol/SentryMeasurementUnitTests.swift @@ -0,0 +1,62 @@ +import XCTest + +final class SentryMeasurementUnitTests: XCTestCase { + + func testCustomUnit() { + let unit = "custom" + let sut = MeasurementUnit(unit: unit) + + XCTAssertEqual(unit, sut.unit) + } + + func testUnitNone() { + XCTAssertEqual("", MeasurementUnit.none.unit) + } + + func testCopy() { + let unit = "custom" + let sut = MeasurementUnit(unit: unit).copy() as! MeasurementUnit + + XCTAssertEqual(unit, sut.unit) + } + + func testCopyOfSubclass() { + let unit = "custom" + let sut = MeasurementUnitDuration(unit: unit).copy() as! MeasurementUnitDuration + + XCTAssertEqual(unit, sut.unit) + } + + func testMeasurementUnitDuration() { + XCTAssertEqual("nanosecond", MeasurementUnitDuration.nanosecond.unit) + XCTAssertEqual("microsecond", MeasurementUnitDuration.microsecond.unit) + XCTAssertEqual("millisecond", MeasurementUnitDuration.millisecond.unit) + XCTAssertEqual("second", MeasurementUnitDuration.second.unit) + XCTAssertEqual("minute", MeasurementUnitDuration.minute.unit) + XCTAssertEqual("hour", MeasurementUnitDuration.hour.unit) + XCTAssertEqual("day", MeasurementUnitDuration.day.unit) + XCTAssertEqual("week", MeasurementUnitDuration.week.unit) + } + + func testMeasurementUnitInformation() { + XCTAssertEqual("bit", MeasurementUnitInformation.bit.unit) + XCTAssertEqual("byte", MeasurementUnitInformation.byte.unit) + XCTAssertEqual("kilobyte", MeasurementUnitInformation.kilobyte.unit) + XCTAssertEqual("kibibyte", MeasurementUnitInformation.kibibyte.unit) + XCTAssertEqual("megabyte", MeasurementUnitInformation.megabyte.unit) + XCTAssertEqual("mebibyte", MeasurementUnitInformation.mebibyte.unit) + XCTAssertEqual("gigabyte", MeasurementUnitInformation.gigabyte.unit) + XCTAssertEqual("gibibyte", MeasurementUnitInformation.gibibyte.unit) + XCTAssertEqual("terabyte", MeasurementUnitInformation.terabyte.unit) + XCTAssertEqual("tebibyte", MeasurementUnitInformation.tebibyte.unit) + XCTAssertEqual("petabyte", MeasurementUnitInformation.petabyte.unit) + XCTAssertEqual("pebibyte", MeasurementUnitInformation.pebibyte.unit) + XCTAssertEqual("exabyte", MeasurementUnitInformation.exabyte.unit) + XCTAssertEqual("exbibyte", MeasurementUnitInformation.exbibyte.unit) + } + + func testMeasurementUnitFraction() { + XCTAssertEqual("ratio", MeasurementUnitFraction.ratio.unit) + XCTAssertEqual("percent", MeasurementUnitFraction.percent.unit) + } +} diff --git a/Tests/SentryTests/SentryTests-Bridging-Header.h b/Tests/SentryTests/SentryTests-Bridging-Header.h index 9a335992fc6..4ad7b2c913a 100644 --- a/Tests/SentryTests/SentryTests-Bridging-Header.h +++ b/Tests/SentryTests/SentryTests-Bridging-Header.h @@ -149,7 +149,6 @@ #import "SentryTraceContext.h" #import "SentryTracer+Test.h" #import "SentryTracer.h" -#import "SentryTransaction+Private.h" #import "SentryTransaction.h" #import "SentryTransactionContext+Private.h" #import "SentryTransport.h" diff --git a/Tests/SentryTests/Transaction/SentryTransactionTests.swift b/Tests/SentryTests/Transaction/SentryTransactionTests.swift index 61747cfa02c..f6bd6da0ae1 100644 --- a/Tests/SentryTests/Transaction/SentryTransactionTests.swift +++ b/Tests/SentryTests/Transaction/SentryTransactionTests.swift @@ -8,8 +8,8 @@ class SentryTransactionTests: XCTestCase { let testKey = "extra_key" let testValue = "extra_value" - func getTransaction() -> Transaction { - return Transaction(trace: SentryTracer(), children: []) + func getTransaction(trace: SentryTracer = SentryTracer(transactionContext: TransactionContext(operation: "operation"), hub: TestHub(client: nil, andScope: nil))) -> Transaction { + return Transaction(trace: trace, children: []) } func getContext() -> TransactionContext { @@ -51,26 +51,50 @@ class SentryTransactionTests: XCTestCase { XCTAssertNil(actual["measurements"]) } - func testSerializeMeasurements_Measurements() { - let transaction = fixture.getTransaction() - - let appStart = ["value": 15_000.0] - transaction.setMeasurementValue(appStart, forKey: "app_start_cold") + func testSerializeMeasurements_DurationMeasurement() { + let name = "some_duration" + let value: NSNumber = 15_000.0 + let unit = MeasurementUnitDuration.millisecond + + let trace = SentryTracer(transactionContext: TransactionContext(operation: "operation"), hub: TestHub(client: nil, andScope: nil)) + trace.setMeasurement(name: name, value: value, unit: unit) + let transaction = fixture.getTransaction(trace: trace) + let actual = transaction.serialize() - let actualMeasurements = actual["measurements"] as? [String: [String: Double]] - XCTAssertEqual(appStart, actualMeasurements?["app_start_cold"] ) + let actualMeasurements = actual["measurements"] as? [String: [String: Any]] + XCTAssertNotNil(actualMeasurements) + + let coldStartMeasurement = actualMeasurements?[name] + XCTAssertEqual(value, coldStartMeasurement?["value"] as! NSNumber) + XCTAssertEqual(unit.unit, coldStartMeasurement?["unit"] as! String) } - - func testSerializeMeasurements_GarbageInMeasurements_GarbageSanitized() { - let transaction = fixture.getTransaction() + + func testSerializeMeasurements_MultipleMeasurements() { + let frameName = "frames_total" + let frameValue: NSNumber = 60 + + let customName = "custom-name" + let customValue: NSNumber = 20.1 + let customUnit = MeasurementUnit(unit: "custom") + + let trace = SentryTracer(transactionContext: TransactionContext(operation: "operation"), hub: TestHub(client: nil, andScope: nil)) + trace.setMeasurement(name: frameName, value: frameValue) + trace.setMeasurement(name: customName, value: customValue, unit: customUnit) + let transaction = fixture.getTransaction(trace: trace) - let appStart = ["value": self] - transaction.setMeasurementValue(appStart, forKey: "app_start_cold") let actual = transaction.serialize() - let actualMeasurements = actual["measurements"] as? [String: [String: String]] - XCTAssertEqual(["value": self.description], actualMeasurements?["app_start_cold"] ) + let actualMeasurements = actual["measurements"] as? [String: [String: Any]] + XCTAssertNotNil(actualMeasurements) + + let frameMeasurement = actualMeasurements?[frameName] + XCTAssertEqual(frameValue, frameMeasurement?["value"] as! NSNumber) + XCTAssertNil(frameMeasurement?["unit"]) + + let customMeasurement = actualMeasurements?[customName] + XCTAssertEqual(customValue, customMeasurement?["value"] as! NSNumber) + XCTAssertEqual(customUnit.unit, customMeasurement?["unit"] as! String) } func testSerialize_Tags() { diff --git a/Tests/SentryTests/Transaction/TestSentrySpan.m b/Tests/SentryTests/Transaction/TestSentrySpan.m index aeb14e8aeb4..21ad154bd5f 100644 --- a/Tests/SentryTests/Transaction/TestSentrySpan.m +++ b/Tests/SentryTests/Transaction/TestSentrySpan.m @@ -59,4 +59,12 @@ - (void)setTagValue:(nonnull NSString *)value forKey:(nonnull NSString *)key { } +- (void)setMeasurement:(nonnull NSString *)name value:(nonnull NSNumber *)value +{ +} + +- (void)setMeasurement:(NSString *)name value:(NSNumber *)value unit:(SentryMeasurementUnit *)unit +{ +} + @end