diff --git a/Sentry.xcodeproj/project.pbxproj b/Sentry.xcodeproj/project.pbxproj index 8bf1544bfe6..da4cb4fd896 100644 --- a/Sentry.xcodeproj/project.pbxproj +++ b/Sentry.xcodeproj/project.pbxproj @@ -924,10 +924,10 @@ D8A65B5D2C98656800974B74 /* SentryReplayView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8A65B5C2C98656000974B74 /* SentryReplayView.swift */; }; D8AB40DB2806EC1900E5E9F7 /* SentryScreenshotIntegration.h in Headers */ = {isa = PBXBuildFile; fileRef = D8AB40DA2806EC1900E5E9F7 /* SentryScreenshotIntegration.h */; }; D8ACE3C72762187200F5A213 /* SentryNSDataSwizzling.m in Sources */ = {isa = PBXBuildFile; fileRef = D8ACE3C42762187200F5A213 /* SentryNSDataSwizzling.m */; }; - D8ACE3C82762187200F5A213 /* SentryFileIOTracker.m in Sources */ = {isa = PBXBuildFile; fileRef = D8ACE3C52762187200F5A213 /* SentryFileIOTracker.m */; }; + D8ACE3C82762187200F5A213 /* SentryFileIOTrackerHelper.m in Sources */ = {isa = PBXBuildFile; fileRef = D8ACE3C52762187200F5A213 /* SentryFileIOTrackerHelper.m */; }; D8ACE3C92762187200F5A213 /* SentryFileIOTrackingIntegration.m in Sources */ = {isa = PBXBuildFile; fileRef = D8ACE3C62762187200F5A213 /* SentryFileIOTrackingIntegration.m */; }; D8ACE3CD2762187D00F5A213 /* SentryNSDataSwizzling.h in Headers */ = {isa = PBXBuildFile; fileRef = D8ACE3CA2762187D00F5A213 /* SentryNSDataSwizzling.h */; }; - D8ACE3CE2762187D00F5A213 /* SentryFileIOTracker.h in Headers */ = {isa = PBXBuildFile; fileRef = D8ACE3CB2762187D00F5A213 /* SentryFileIOTracker.h */; }; + D8ACE3CE2762187D00F5A213 /* SentryFileIOTrackerHelper.h in Headers */ = {isa = PBXBuildFile; fileRef = D8ACE3CB2762187D00F5A213 /* SentryFileIOTrackerHelper.h */; }; D8ACE3CF2762187D00F5A213 /* SentryFileIOTrackingIntegration.h in Headers */ = {isa = PBXBuildFile; fileRef = D8ACE3CC2762187D00F5A213 /* SentryFileIOTrackingIntegration.h */; }; D8AE48BF2C578D540092A2A6 /* SentrySDKLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8AE48BE2C578D540092A2A6 /* SentrySDKLog.swift */; }; D8AE48C12C57B1550092A2A6 /* SentryLevelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8AE48C02C57B1550092A2A6 /* SentryLevelTests.swift */; }; @@ -1083,6 +1083,7 @@ FA7206E12E0B37C80072FDD4 /* SentryProfileCollector.mm in Sources */ = {isa = PBXBuildFile; fileRef = FA7206E02E0B37C60072FDD4 /* SentryProfileCollector.mm */; }; FA8A36182DEAA1EB0058D883 /* SentryThread+Private.h in Headers */ = {isa = PBXBuildFile; fileRef = FA8A36172DEAA1EB0058D883 /* SentryThread+Private.h */; }; FA8AFCED2E8434A8007A0E18 /* SentryThreadInspector.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA8AFCE72E8434A0007A0E18 /* SentryThreadInspector.swift */; }; + FA8AFCEF2E843903007A0E18 /* SentryFileIOTracker.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA8AFCEE2E8438FF007A0E18 /* SentryFileIOTracker.swift */; }; FA8AFCFA2E844AB6007A0E18 /* SentryThreadsafeApplicationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA8AFCF42E844AB1007A0E18 /* SentryThreadsafeApplicationTests.swift */; }; FA8AFDAC2E84FAEE007A0E18 /* TestSentryUIApplication.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA6614FB2E4B8E1500657755 /* TestSentryUIApplication.swift */; }; FA8E58F12E0AD4270049F69D /* SentryDispatchQueueWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA8E58F02E0AD4220049F69D /* SentryDispatchQueueWrapper.swift */; }; @@ -2260,10 +2261,10 @@ D8A65B5C2C98656000974B74 /* SentryReplayView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryReplayView.swift; sourceTree = ""; }; D8AB40DA2806EC1900E5E9F7 /* SentryScreenshotIntegration.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SentryScreenshotIntegration.h; path = include/SentryScreenshotIntegration.h; sourceTree = ""; }; D8ACE3C42762187200F5A213 /* SentryNSDataSwizzling.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SentryNSDataSwizzling.m; sourceTree = ""; }; - D8ACE3C52762187200F5A213 /* SentryFileIOTracker.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SentryFileIOTracker.m; sourceTree = ""; }; + D8ACE3C52762187200F5A213 /* SentryFileIOTrackerHelper.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SentryFileIOTrackerHelper.m; sourceTree = ""; }; D8ACE3C62762187200F5A213 /* SentryFileIOTrackingIntegration.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SentryFileIOTrackingIntegration.m; sourceTree = ""; }; D8ACE3CA2762187D00F5A213 /* SentryNSDataSwizzling.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SentryNSDataSwizzling.h; path = include/SentryNSDataSwizzling.h; sourceTree = ""; }; - D8ACE3CB2762187D00F5A213 /* SentryFileIOTracker.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SentryFileIOTracker.h; path = include/SentryFileIOTracker.h; sourceTree = ""; }; + D8ACE3CB2762187D00F5A213 /* SentryFileIOTrackerHelper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SentryFileIOTrackerHelper.h; path = include/SentryFileIOTrackerHelper.h; sourceTree = ""; }; D8ACE3CC2762187D00F5A213 /* SentryFileIOTrackingIntegration.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SentryFileIOTrackingIntegration.h; path = include/SentryFileIOTrackingIntegration.h; sourceTree = ""; }; D8AE48B12C5786AA0092A2A6 /* SentryLogC.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SentryLogC.h; path = include/SentryLogC.h; sourceTree = ""; }; D8AE48BE2C578D540092A2A6 /* SentrySDKLog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentrySDKLog.swift; sourceTree = ""; }; @@ -2427,6 +2428,7 @@ FA7206E02E0B37C60072FDD4 /* SentryProfileCollector.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = SentryProfileCollector.mm; sourceTree = ""; }; FA8A36172DEAA1EB0058D883 /* SentryThread+Private.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "SentryThread+Private.h"; path = "include/SentryThread+Private.h"; sourceTree = ""; }; FA8AFCE72E8434A0007A0E18 /* SentryThreadInspector.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryThreadInspector.swift; sourceTree = ""; }; + FA8AFCEE2E8438FF007A0E18 /* SentryFileIOTracker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryFileIOTracker.swift; sourceTree = ""; }; FA8AFCF42E844AB1007A0E18 /* SentryThreadsafeApplicationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryThreadsafeApplicationTests.swift; sourceTree = ""; }; FA8E58F02E0AD4220049F69D /* SentryDispatchQueueWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryDispatchQueueWrapper.swift; sourceTree = ""; }; FA90FAA72E06614B008CAAE8 /* SentryExtraPackages.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryExtraPackages.swift; sourceTree = ""; }; @@ -4236,6 +4238,7 @@ D468C0602D36699700964230 /* IO */ = { isa = PBXGroup; children = ( + FA8AFCEE2E8438FF007A0E18 /* SentryFileIOTracker.swift */, D4EDF9832D0B2A1D0071E7B3 /* Data+SentryTracing.swift */, D473ACD62D8090FC000F1CC6 /* FileManager+SentryTracing.swift */, D468C0612D3669A200964230 /* SentryFileIOTracker+SwiftHelpers.swift */, @@ -4616,8 +4619,8 @@ D8ACE3C32762185E00F5A213 /* IO */ = { isa = PBXGroup; children = ( - D8ACE3CB2762187D00F5A213 /* SentryFileIOTracker.h */, - D8ACE3C52762187200F5A213 /* SentryFileIOTracker.m */, + D8ACE3CB2762187D00F5A213 /* SentryFileIOTrackerHelper.h */, + D8ACE3C52762187200F5A213 /* SentryFileIOTrackerHelper.m */, D8ACE3CC2762187D00F5A213 /* SentryFileIOTrackingIntegration.h */, D8ACE3C62762187200F5A213 /* SentryFileIOTrackingIntegration.m */, D8ACE3CA2762187D00F5A213 /* SentryNSDataSwizzling.h */, @@ -5030,7 +5033,7 @@ 7BC3936825B1AB3E004F03D3 /* SentryLevelMapper.h in Headers */, 8E4E7C6E25DAAAFE006AB9E2 /* SentrySpan.h in Headers */, 84DEE8762B69AD6400A7BC17 /* SentryLaunchProfiling.h in Headers */, - D8ACE3CE2762187D00F5A213 /* SentryFileIOTracker.h in Headers */, + D8ACE3CE2762187D00F5A213 /* SentryFileIOTrackerHelper.h in Headers */, 03F84D2427DD414C008FE43F /* SentryCompiler.h in Headers */, 631E6D331EBC679C00712345 /* SentryQueueableRequestManager.h in Headers */, 33EB2A922C341300004FED3D /* Sentry.h in Headers */, @@ -5615,7 +5618,7 @@ 84CFA4CA2C9DF884008DA5F4 /* SentryUserFeedbackWidget.swift in Sources */, FAF1201A2E70C0EE006E1DA3 /* SentryEnvelopeHeaderHelper.m in Sources */, F49D419E2DEA3D0600D9244E /* SentryCrashExceptionApplicationHelper.m in Sources */, - D8ACE3C82762187200F5A213 /* SentryFileIOTracker.m in Sources */, + D8ACE3C82762187200F5A213 /* SentryFileIOTrackerHelper.m in Sources */, D8B088B729C9E3FF00213258 /* SentryTracerConfiguration.m in Sources */, FA7206E12E0B37C80072FDD4 /* SentryProfileCollector.mm in Sources */, 9264E1EB2E2E385E00B077CF /* SentryLogMessage.swift in Sources */, @@ -5626,6 +5629,7 @@ 03F84D3727DD4191008FE43F /* SentrySamplingProfiler.cpp in Sources */, 8453421628BE8A9500C22EEC /* SentrySpanStatus.m in Sources */, 6292585B2DAFA5F70049388F /* SentryCrashCxaThrowSwapper.c in Sources */, + FA8AFCEF2E843903007A0E18 /* SentryFileIOTracker.swift in Sources */, 92D957732E05A44600E20E66 /* SentryAsyncLog.m in Sources */, 7B08A3472924CF9C0059603A /* SentryMetricKitIntegration.m in Sources */, 623FD9022D3FA5E000803EDA /* SentryFrameCodable.swift in Sources */, diff --git a/Sources/Sentry/SentryDependencyContainer.m b/Sources/Sentry/SentryDependencyContainer.m index beebe949a9f..5490c04b4dc 100644 --- a/Sources/Sentry/SentryDependencyContainer.m +++ b/Sources/Sentry/SentryDependencyContainer.m @@ -2,7 +2,6 @@ #import "SentryDefaultThreadInspector.h" #import "SentryExtraContextProvider.h" -#import "SentryFileIOTracker.h" #import "SentryInternalCDefines.h" #import "SentryInternalDefines.h" #import "SentryLogC.h" @@ -223,17 +222,14 @@ - (nullable SentryFileManager *)fileManager SENTRY_THREAD_SANITIZER_DOUBLE_CHECK dispatchQueueWrapper:self.dispatchQueueWrapper notificationCenterWrapper:self.notificationCenterWrapper]); } - -- (SentryThreadInspector *)threadInspector SENTRY_THREAD_SANITIZER_DOUBLE_CHECKED_LOCK +- (SentryThreadInspector *)threadInspector { return SentryDependencies.threadInspector; } -- (SentryFileIOTracker *)fileIOTracker SENTRY_THREAD_SANITIZER_DOUBLE_CHECKED_LOCK +- (SentryFileIOTracker *)fileIOTracker { - SENTRY_LAZY_INIT(_fileIOTracker, - [[SentryFileIOTracker alloc] initWithThreadInspector:[self threadInspector] - processInfoWrapper:[self processInfoWrapper]]); + return SentryDependencies.fileIOTracker; } - (SentryCrash *)crashReporter SENTRY_THREAD_SANITIZER_DOUBLE_CHECKED_LOCK diff --git a/Sources/Sentry/SentryFileIOTracker.m b/Sources/Sentry/SentryFileIOTrackerHelper.m similarity index 64% rename from Sources/Sentry/SentryFileIOTracker.m rename to Sources/Sentry/SentryFileIOTrackerHelper.m index e32b8b7e975..2ee20bdd5d6 100644 --- a/Sources/Sentry/SentryFileIOTracker.m +++ b/Sources/Sentry/SentryFileIOTrackerHelper.m @@ -1,4 +1,4 @@ -#import "SentryFileIOTracker.h" +#import "SentryFileIOTrackerHelper.h" #import "SentryByteCountFormatter.h" #import "SentryClient+Private.h" #import "SentryDependencyContainer.h" @@ -18,36 +18,22 @@ #import "SentryThread.h" #import "SentryTracer.h" -@interface SentryFileIOTracker () +@interface SentryFileIOTrackerHelper () @property (nonatomic, assign) BOOL isEnabled; @property (nonatomic, strong) NSMutableSet *processingData; -@property (nonatomic, strong) SentryThreadInspector *threadInspector; -@property (nonatomic, strong) id processInfoWrapper; +@property (nonatomic, copy) SentryStacktrace *_Nullable (^stacktraceRetrieval)(void); @end -@implementation SentryFileIOTracker +@implementation SentryFileIOTrackerHelper NSString *const SENTRY_TRACKING_COUNTER_KEY = @"SENTRY_TRACKING_COUNTER_KEY"; -+ (instancetype _Nullable)sharedInstance -{ - // It is necessary to check if the SDK is enabled because accessing the tracker will otherwise - // initialize the depency container without any configured SDK options. This is a known issue - // and needs to be fixed in general. - if (!SentrySDK.isEnabled) { - return nil; - } - return SentryDependencyContainer.sharedInstance.fileIOTracker; -} - -- (instancetype)initWithThreadInspector:(SentryThreadInspector *)threadInspector - processInfoWrapper:(id)processInfoWrapper +- (instancetype)initWithThreadInspector:(SentryStacktrace *_Nullable (^)(void))stacktraceRetrieval { if (self = [super init]) { - _processInfoWrapper = processInfoWrapper; - _threadInspector = threadInspector; + self.stacktraceRetrieval = stacktraceRetrieval; } return self; } @@ -67,118 +53,113 @@ - (void)disable } - (BOOL)measureNSData:(NSData *)data - writeToFile:(NSString *)path - atomically:(BOOL)useAuxiliaryFile - origin:(NSString *)origin - method:(BOOL (^)(NSString *, BOOL))method + writeToFile:(NSString *)path + atomically:(BOOL)useAuxiliaryFile + origin:(NSString *)origin + processDirectoryPath:(NSString *)processDirectoryPath + method:(BOOL (^)(NSString *, BOOL))method { - id span = [self startTrackingWritingNSData:data filePath:path origin:origin]; + id span = [self startTrackingWritingNSData:data + filePath:path + origin:origin + processDirectoryPath:processDirectoryPath]; BOOL result = method(path, useAuxiliaryFile); if (span != nil) { - [self finishTrackingNSData:data span:span]; + [self finishTrackingNSData:@(data.length) span:span]; } return result; } - (BOOL)measureNSData:(NSData *)data - writeToFile:(NSString *)path - options:(NSDataWritingOptions)writeOptionsMask - origin:(NSString *)origin - error:(NSError **)error - method:(BOOL (^)(NSString *, NSDataWritingOptions, NSError **))method + writeToFile:(NSString *)path + options:(NSDataWritingOptions)writeOptionsMask + origin:(NSString *)origin + processDirectoryPath:(NSString *)processDirectoryPath + error:(NSError **)error + method:(BOOL (^)(NSString *, NSDataWritingOptions, NSError **))method { - id span = [self startTrackingWritingNSData:data filePath:path origin:origin]; + id span = [self startTrackingWritingNSData:data + filePath:path + origin:origin + processDirectoryPath:processDirectoryPath]; BOOL result = method(path, writeOptionsMask, error); if (span != nil) { - [self finishTrackingNSData:data span:span]; + [self finishTrackingNSData:@(data.length) span:span]; } return result; } -- (NSData *)measureNSDataFromFile:(NSString *)path - origin:(NSString *)origin - method:(NSData * (^)(NSString *))method +- (void)measureNSDataFromFile:(NSString *)path + origin:(NSString *)origin + processDirectoryPath:(NSString *)processDirectoryPath + method:(NSNumber * (^)(void))method { id span = [self startTrackingReadingFilePath:path origin:origin - operation:SentrySpanOperationFileRead]; + operation:SentrySpanOperationFileRead + processDirectoryPath:processDirectoryPath]; - NSData *result = method(path); + NSNumber *length = method(); if (span != nil) { - [self finishTrackingNSData:result span:span]; + [self finishTrackingNSData:length span:span]; } [self endTrackingFile]; - return result; } -- (NSData *)measureNSDataFromFile:(NSString *)path - options:(NSDataReadingOptions)readOptionsMask - origin:(NSString *)origin - error:(NSError **)error - method:(NSData * (^)(NSString *, NSDataReadingOptions, NSError **))method -{ - id span = [self startTrackingReadingFilePath:path - origin:origin - operation:SentrySpanOperationFileRead]; - - NSData *result = method(path, readOptionsMask, error); - - if (span != nil) { - [self finishTrackingNSData:result span:span]; - } - - [self endTrackingFile]; - return result; -} - -- (NSData *)measureNSDataFromURL:(NSURL *)url - options:(NSDataReadingOptions)readOptionsMask - origin:(NSString *)origin - error:(NSError **)error - method:(NSData * (^)(NSURL *, NSDataReadingOptions, NSError **))method +- (void)measureNSDataFromURL:(NSURL *)url + origin:(NSString *)origin + processDirectoryPath:(NSString *)processDirectoryPath + method:(NSNumber * (^)(void))method { // We dont track reads from a url that is not a file url // because these reads are handled by NSURLSession and // SentryNetworkTracker will create spans in these cases. - if (![url.scheme isEqualToString:NSURLFileScheme]) - return method(url, readOptionsMask, error); + if (![url.scheme isEqualToString:NSURLFileScheme]) { + method(); + return; + } id span = [self startTrackingReadingFilePath:url.path origin:origin - operation:SentrySpanOperationFileRead]; + operation:SentrySpanOperationFileRead + processDirectoryPath:processDirectoryPath]; - NSData *result = method(url, readOptionsMask, error); + NSNumber *length = method(); if (span != nil) { - [self finishTrackingNSData:result span:span]; + [self finishTrackingNSData:length span:span]; } [self endTrackingFile]; - return result; + return; } - (BOOL)measureNSFileManagerCreateFileAtPath:(NSString *)path data:(NSData *)data attributes:(NSDictionary *)attributes origin:(NSString *)origin + processDirectoryPath:(NSString *)processDirectoryPath method: (BOOL (^)(NSString *_Nonnull, NSData *_Nonnull, NSDictionary *_Nonnull))method { - id span = [self startTrackingWritingNSData:data filePath:path origin:origin]; + id span = [self startTrackingWritingNSData:data + filePath:path + origin:origin + processDirectoryPath:processDirectoryPath]; BOOL result = method(path, data, attributes); if (span != nil) { - [self finishTrackingNSData:data span:span]; + [self finishTrackingNSData:@(data.length) span:span]; } return result; } @@ -186,13 +167,19 @@ - (BOOL)measureNSFileManagerCreateFileAtPath:(NSString *)path - (nullable id)spanForPath:(NSString *)path origin:(NSString *)origin operation:(NSString *)operation + processDirectoryPath:(NSString *)processDirectoryPath { - return [self spanForPath:path origin:origin operation:operation size:0]; + return [self spanForPath:path + origin:origin + operation:operation + processDirectoryPath:processDirectoryPath + size:0]; } - (nullable id)spanForPath:(NSString *)path origin:(NSString *)origin operation:(NSString *)operation + processDirectoryPath:(NSString *)processDirectoryPath size:(NSUInteger)size { @synchronized(self) { @@ -230,12 +217,13 @@ - (BOOL)measureNSFileManagerCreateFileAtPath:(NSString *)path @"Automatically started a new span with description: %@, operation: %@, origin: %@", ioSpan.description, operation, origin); - [self mainThreadExtraInfo:ioSpan]; + [self mainThreadExtraInfo:ioSpan processDirectoryPath:processDirectoryPath]; return ioSpan; } - (void)mainThreadExtraInfo:(id)span + processDirectoryPath:(NSString *)processDirectoryPath { BOOL isMainThread = [NSThread isMainThread]; @@ -245,13 +233,12 @@ - (void)mainThreadExtraInfo:(id)span return; } - SentryThreadInspector *threadInspector = self.threadInspector; - SentryStacktrace *stackTrace = [threadInspector stacktraceForCurrentThreadAsyncUnsafe]; + SentryStacktrace *stackTrace = self.stacktraceRetrieval(); NSArray *frames = [stackTrace.frames filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(SentryFrame *frame, NSDictionary *bindings) { - return [frame.package hasPrefix:self.processInfoWrapper.processDirectoryPath]; + return [frame.package hasPrefix:processDirectoryPath]; }]]; if (frames.count <= 1) { @@ -268,16 +255,19 @@ - (void)mainThreadExtraInfo:(id)span - (nullable id)startTrackingWritingNSData:(NSData *)data filePath:(NSString *)path origin:(NSString *)origin + processDirectoryPath:(NSString *)processDirectoryPath { return [self spanForPath:path origin:origin operation:SentrySpanOperationFileWrite + processDirectoryPath:processDirectoryPath size:data.length]; } - (nullable id)startTrackingReadingFilePath:(NSString *)path origin:(NSString *)origin operation:(NSString *)operation + processDirectoryPath:(NSString *)processDirectoryPath { // Some iOS versions nest constructors calls. This counter help us avoid create more than one // span for the same operation. @@ -289,7 +279,11 @@ - (void)mainThreadExtraInfo:(id)span if (count) return nil; - return [self spanForPath:path origin:origin operation:operation size:0]; + return [self spanForPath:path + origin:origin + operation:operation + processDirectoryPath:processDirectoryPath + size:0]; } - (void)endTrackingFile @@ -308,10 +302,9 @@ - (void)endTrackingFile } } -- (void)finishTrackingNSData:(NSData *)data span:(id)span +- (void)finishTrackingNSData:(NSNumber *)length span:(id)span { - [span setDataValue:[NSNumber numberWithUnsignedInteger:data.length] - forKey:SentrySpanDataKeyFileSize]; + [span setDataValue:length forKey:SentrySpanDataKeyFileSize]; [span finish]; SENTRY_LOG_DEBUG(@"Automatically finished span %@", span.description); diff --git a/Sources/Sentry/SentryFileIOTrackingIntegration.m b/Sources/Sentry/SentryFileIOTrackingIntegration.m index 95b3df4671e..5cf9b964d19 100644 --- a/Sources/Sentry/SentryFileIOTrackingIntegration.m +++ b/Sources/Sentry/SentryFileIOTrackingIntegration.m @@ -1,8 +1,8 @@ #import "SentryFileIOTrackingIntegration.h" #import "SentryDependencyContainer.h" -#import "SentryFileIOTracker.h" #import "SentryNSDataSwizzling.h" #import "SentryNSFileManagerSwizzling.h" +#import "SentrySwift.h" @interface SentryFileIOTrackingIntegration () diff --git a/Sources/Sentry/include/HybridPublic/SentryDependencyContainer.h b/Sources/Sentry/include/HybridPublic/SentryDependencyContainer.h index 27fa013ca17..26cb80290ba 100644 --- a/Sources/Sentry/include/HybridPublic/SentryDependencyContainer.h +++ b/Sources/Sentry/include/HybridPublic/SentryDependencyContainer.h @@ -100,7 +100,7 @@ SENTRY_NO_INIT @property (nonatomic, strong, nullable) SentryFileManager *fileManager; @property (nonatomic, strong) id appStateManager; @property (nonatomic, strong, readonly) SentryThreadInspector *threadInspector; -@property (nonatomic, strong) SentryFileIOTracker *fileIOTracker; +@property (nonatomic, strong, readonly) SentryFileIOTracker *fileIOTracker; @property (nonatomic, strong) SentryCrash *crashReporter; @property (nonatomic, strong) SentryScopePersistentStore *scopePersistentStore; @property (nonatomic, strong) SentryDebugImageProvider *debugImageProvider; diff --git a/Sources/Sentry/include/SentryFileIOTracker.h b/Sources/Sentry/include/SentryFileIOTracker.h deleted file mode 100644 index d99f9c2ca8b..00000000000 --- a/Sources/Sentry/include/SentryFileIOTracker.h +++ /dev/null @@ -1,97 +0,0 @@ -#import "SentryDefines.h" - -NS_ASSUME_NONNULL_BEGIN - -@protocol SentryProcessInfoSource; -@class SentryThreadInspector; - -@interface SentryFileIOTracker : NSObject -SENTRY_NO_INIT - -/** - * Convenience accessor to the shared instance of the tracker in the dependency container. - * - * @note Can be used from Swift without import the entire dependency container. - * @note If the SentrySDK is not enabled this will return nil. - * - * @return The shared instance of the tracker. - */ -+ (instancetype _Nullable)sharedInstance; - -- (instancetype)initWithThreadInspector:(SentryThreadInspector *)threadInspector - processInfoWrapper:(id)processInfoWrapper; - -- (void)enable; - -- (void)disable; - -/** - * Measure NSData 'writeToFile:atomicall:' method. - */ -- (BOOL)measureNSData:(NSData *)data - writeToFile:(NSString *)path - atomically:(BOOL)useAuxiliaryFile - origin:(NSString *)origin - method:(BOOL (^)(NSString *, BOOL))method; - -/** - * Measure NSData 'writeToFile:options:error:' method. - */ -- (BOOL)measureNSData:(NSData *)data - writeToFile:(NSString *)path - options:(NSDataWritingOptions)writeOptionsMask - origin:(NSString *)origin - error:(NSError **)error - method:(BOOL (^)(NSString *, NSDataWritingOptions, NSError **))method; - -/** - * Measure NSData 'initWithContentsOfFile:' method. - */ -- (nullable NSData *)measureNSDataFromFile:(NSString *)path - origin:(NSString *)origin - method:(NSData *_Nullable (^)(NSString *))method; - -/** - * Measure NSData 'initWithContentsOfFile:options:error:' method. - */ -- (nullable NSData *)measureNSDataFromFile:(NSString *)path - options:(NSDataReadingOptions)readOptionsMask - origin:(NSString *)origin - error:(NSError **)error - method:(NSData *_Nullable (^)( - NSString *, NSDataReadingOptions, NSError **))method; - -/** - * Measure NSData 'initWithContentsOfURL:options:error:' method. - */ -- (nullable NSData *)measureNSDataFromURL:(NSURL *)url - options:(NSDataReadingOptions)readOptionsMask - origin:(NSString *)origin - error:(NSError **)error - method:(NSData *_Nullable (^)( - NSURL *, NSDataReadingOptions, NSError **))method; - -/** - * Measure NSFileManager 'createFileAtPath:contents:attributes::' method. - */ -- (BOOL)measureNSFileManagerCreateFileAtPath:(NSString *)path - data:(NSData *)data - attributes:(NSDictionary *)attributes - origin:(NSString *)origin - method:(BOOL (^)(NSString *, NSData *, - NSDictionary *))method; - -// MARK: - Internal Methods available for Swift Extension - -- (nullable id)spanForPath:(NSString *)path - origin:(NSString *)origin - operation:(NSString *)operation; - -- (nullable id)spanForPath:(NSString *)path - origin:(NSString *)origin - operation:(NSString *)operation - size:(NSUInteger)size; - -@end - -NS_ASSUME_NONNULL_END diff --git a/Sources/Sentry/include/SentryFileIOTrackerHelper.h b/Sources/Sentry/include/SentryFileIOTrackerHelper.h new file mode 100644 index 00000000000..53a5662d3c2 --- /dev/null +++ b/Sources/Sentry/include/SentryFileIOTrackerHelper.h @@ -0,0 +1,79 @@ +#import "SentryDefines.h" + +NS_ASSUME_NONNULL_BEGIN + +@class SentryStacktrace; + +@interface SentryFileIOTrackerHelper : NSObject +SENTRY_NO_INIT + +- (instancetype)initWithThreadInspector:(SentryStacktrace *_Nullable (^)(void))stacktraceRetrieval; + +- (void)enable; + +- (void)disable; + +/** + * Measure NSData 'writeToFile:atomicall:' method. + */ +- (BOOL)measureNSData:(NSData *)data + writeToFile:(NSString *)path + atomically:(BOOL)useAuxiliaryFile + origin:(NSString *)origin + processDirectoryPath:(NSString *)processDirectoryPath + method:(BOOL (^)(NSString *, BOOL))method; + +/** + * Measure NSData 'writeToFile:options:error:' method. + */ +- (BOOL)measureNSData:(NSData *)data + writeToFile:(NSString *)path + options:(NSDataWritingOptions)writeOptionsMask + origin:(NSString *)origin + processDirectoryPath:(NSString *)processDirectoryPath + error:(NSError **)error + method:(BOOL (^)(NSString *, NSDataWritingOptions, NSError **))method; + +/** + * Measure NSData 'initWithContentsOfFile:options:' method. + */ +- (void)measureNSDataFromFile:(NSString *)path + origin:(NSString *)origin + processDirectoryPath:(NSString *)processDirectoryPath + method:(NSNumber *_Nullable (^)(void))method; + +/** + * Measure NSData 'initWithContentsOfURL:options:error:' method. + */ +- (void)measureNSDataFromURL:(NSURL *)url + origin:(NSString *)origin + processDirectoryPath:(NSString *)processDirectoryPath + method:(NSNumber * (^)(void))method; + +/** + * Measure NSFileManager 'createFileAtPath:contents:attributes::' method. + */ +- (BOOL)measureNSFileManagerCreateFileAtPath:(NSString *)path + data:(NSData *)data + attributes:(NSDictionary *)attributes + origin:(NSString *)origin + processDirectoryPath:(NSString *)processDirectoryPath + method:(BOOL (^)(NSString *, NSData *, + NSDictionary *))method; + +// MARK: - Internal Methods available for Swift Extension + +- (nullable id)spanForPath:(NSString *)path + origin:(NSString *)origin + operation:(NSString *)operation + processDirectoryPath:(NSString *)processDirectoryPath; + +- (nullable id)spanForPath:(NSString *)path + origin:(NSString *)origin + operation:(NSString *)operation + processDirectoryPath:(NSString *)processDirectoryPath + size:(NSUInteger)size; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Sources/Sentry/include/SentryNSDataSwizzling.h b/Sources/Sentry/include/SentryNSDataSwizzling.h index c2572a50b69..f546443b415 100644 --- a/Sources/Sentry/include/SentryNSDataSwizzling.h +++ b/Sources/Sentry/include/SentryNSDataSwizzling.h @@ -1,9 +1,9 @@ #import "SentryDefines.h" -#import "SentryFileIOTracker.h" NS_ASSUME_NONNULL_BEGIN @class SentryOptions; +@class SentryFileIOTracker; @interface SentryNSDataSwizzling : NSObject SENTRY_NO_INIT diff --git a/Sources/Sentry/include/SentryNSFileManagerSwizzling.h b/Sources/Sentry/include/SentryNSFileManagerSwizzling.h index 4ed7b842542..020e9f99461 100644 --- a/Sources/Sentry/include/SentryNSFileManagerSwizzling.h +++ b/Sources/Sentry/include/SentryNSFileManagerSwizzling.h @@ -1,9 +1,9 @@ #import "SentryDefines.h" -#import "SentryFileIOTracker.h" NS_ASSUME_NONNULL_BEGIN @class SentryOptions; +@class SentryFileIOTracker; @interface SentryNSFileManagerSwizzling : NSObject SENTRY_NO_INIT diff --git a/Sources/Sentry/include/SentryPrivate.h b/Sources/Sentry/include/SentryPrivate.h index 159e99ff5c6..58fb89e7aa7 100644 --- a/Sources/Sentry/include/SentryPrivate.h +++ b/Sources/Sentry/include/SentryPrivate.h @@ -32,7 +32,7 @@ #import "SentryDefaultThreadInspector.h" #import "SentryDependencyContainerSwiftHelper.h" #import "SentryEvent+Serialize.h" -#import "SentryFileIOTracker.h" +#import "SentryFileIOTrackerHelper.h" #import "SentryFileManagerHelper.h" #import "SentryLevelHelper.h" #import "SentryMeta.h" diff --git a/Sources/Swift/Helper/Dependencies.swift b/Sources/Swift/Helper/Dependencies.swift index 3518169d6cd..f49b40bc170 100644 --- a/Sources/Swift/Helper/Dependencies.swift +++ b/Sources/Swift/Helper/Dependencies.swift @@ -8,7 +8,7 @@ #if !os(watchOS) && !os(macOS) && !SENTRY_NO_UIKIT @objc public static let uiDeviceWrapper = SentryDefaultUIDeviceWrapper(queueWrapper: Dependencies.dispatchQueueWrapper) #endif // !os(watchOS) && !os(macOS) && !SENTRY_NO_UIKIT - - @objc public static var threadInspector: SentryThreadInspector = SentryThreadInspector() + @objc public static var threadInspector = SentryThreadInspector() + @objc public static var fileIOTracker = SentryFileIOTracker(threadInspector: threadInspector, processInfoWrapper: processInfoWrapper) } diff --git a/Sources/Swift/Integrations/Performance/IO/SentryFileIOTracker.swift b/Sources/Swift/Integrations/Performance/IO/SentryFileIOTracker.swift new file mode 100644 index 00000000000..dee7bd10703 --- /dev/null +++ b/Sources/Swift/Integrations/Performance/IO/SentryFileIOTracker.swift @@ -0,0 +1,88 @@ +@_implementationOnly import _SentryPrivate + +@_spi(Private) @objc public protocol SpanProtocol { + @objc(setDataValue:forKey:) func setData(value: Any, key: String) + func finish() + @objc(finishWithStatus:) func finish(status: SentrySpanStatus) +} + +@_spi(Private) @objc public class SentryFileIOTracker: NSObject { + + private let helper: SentryFileIOTrackerHelper + private let processInfoWrapper: SentryProcessInfoSource + + static func sharedInstance() -> SentryFileIOTracker? { + // It is necessary to check if the SDK is enabled because accessing the tracker will otherwise + // initialize the depency container without any configured SDK options. This is a known issue + // and needs to be fixed in general. + guard SentrySDK.isEnabled else { + return nil + } + return Dependencies.fileIOTracker + } + + @objc public init(threadInspector: SentryThreadInspector, processInfoWrapper: SentryProcessInfoSource) { + self.processInfoWrapper = processInfoWrapper + helper = SentryFileIOTrackerHelper { + threadInspector.stacktraceForCurrentThreadAsyncUnsafe() + } + } + + @objc public func enable() { + helper.enable() + } + + @objc public func disable() { + helper.disable() + } + + @discardableResult @objc(measureNSData:writeToFile:atomically:origin:method:) public func measure(_ data: Data, writeToFile path: String, atomically: Bool, origin: String, method: @escaping (String, Bool) -> Bool) -> Bool { + helper.measure(data, writeToFile: path, atomically: atomically, origin: origin, processDirectoryPath: processInfoWrapper.processDirectoryPath, method: method) + } + + @objc(measureNSData:writeToFile:options:origin:error:method:) public func measure(_ data: Data, writeToFile path: String, options writeOptionsMask: NSData.WritingOptions, origin: String, method: @escaping (String, NSData.WritingOptions, NSErrorPointer) -> Bool) throws { + try helper.measure(data, writeToFile: path, options: writeOptionsMask, origin: origin, processDirectoryPath: processInfoWrapper.processDirectoryPath, method: method) + } + + // This must use NSData not Data because it is used to swizzle ObjC + @objc(measureNSDataFromFile:origin:method:) public func measureNSData(fromFile path: String, origin: String, method: @escaping (String) -> NSData?) -> NSData? { + var result: NSData? + helper.measureNSData(fromFile: path, origin: origin, processDirectoryPath: processInfoWrapper.processDirectoryPath) { + result = method(path) + return NSNumber(value: result?.length ?? 0) + } + return result + } + + // This must use NSData not Data because it is used to swizzle ObjC + @objc(measureNSDataFromFile:options:origin:error:method:) public func measureNSData(fromFile path: String, options readOptionsMask: NSData.ReadingOptions, origin: String, error: NSErrorPointer, method: @escaping (String, Data.ReadingOptions, NSErrorPointer) -> NSData?) -> NSData? { + var result: NSData? + helper.measureNSData(fromFile: path, origin: origin, processDirectoryPath: processInfoWrapper.processDirectoryPath) { + result = method(path, readOptionsMask, error) + return NSNumber(value: result?.length ?? 0) + } + return result + } + + // This must use NSData not Data because it is used to swizzle ObjC + @objc(measureNSDataFromURL:options:origin:error:method:) public func measureNSData(from url: URL, options readOptionsMask: NSData.ReadingOptions, origin: String, error: NSErrorPointer, method: @escaping (URL, NSData.ReadingOptions, NSErrorPointer) -> NSData?) -> NSData? { + var result: NSData? + helper.measureNSData(from: url, origin: origin, processDirectoryPath: processInfoWrapper.processDirectoryPath) { + result = method(url, readOptionsMask, error) + return NSNumber(value: result?.length ?? 0) + } + return result + } + + @discardableResult @objc public func measureNSFileManagerCreateFile(atPath path: String, data: Data, attributes: [FileAttributeKey: Any], origin: String, method: @escaping (String, Data, [FileAttributeKey: Any]) -> Bool) -> Bool { + helper.measureNSFileManagerCreateFile(atPath: path, data: data, attributes: attributes, origin: origin, processDirectoryPath: processInfoWrapper.processDirectoryPath, method: method) + } + + func span(forPath path: String, origin: String, operation: String) -> (any Span)? { + helper.span(forPath: path, origin: origin, operation: operation, processDirectoryPath: processInfoWrapper.processDirectoryPath) + } + + func span(forPath path: String, origin: String, operation: String, size: UInt) -> (any Span)? { + helper.span(forPath: path, origin: origin, operation: operation, processDirectoryPath: processInfoWrapper.processDirectoryPath, size: size) + } +} diff --git a/Tests/SentryTests/Integrations/Performance/IO/SentryFileIOTrackerTests.swift b/Tests/SentryTests/Integrations/Performance/IO/SentryFileIOTrackerTests.swift index ca634038e6f..a104bb7a9ee 100644 --- a/Tests/SentryTests/Integrations/Performance/IO/SentryFileIOTrackerTests.swift +++ b/Tests/SentryTests/Integrations/Performance/IO/SentryFileIOTrackerTests.swift @@ -240,7 +240,7 @@ class SentryFileIOTrackerTests: XCTestCase { let data = sut.measureNSData(fromFile: fixture.filePath, origin: "custom.origin") { path in span = self.firstSpan(transaction) usedPath = path - return self.fixture.data + return self.fixture.data as NSData } XCTAssertEqual(usedPath, fixture.filePath) @@ -255,14 +255,16 @@ class SentryFileIOTrackerTests: XCTestCase { var span: Span? var usedPath: String? var usedOptions: NSData.ReadingOptions? + var error: NSError? - let data = try? sut.measureNSData(fromFile: self.fixture.filePath, options: .uncached, origin: "custom.origin") { path, options, _ -> Data in + let data = sut.measureNSData(fromFile: self.fixture.filePath, options: .uncached, origin: "custom.origin", error: &error) { path, options, _ in span = self.firstSpan(transaction) usedOptions = options usedPath = path - return self.fixture.data + return self.fixture.data as NSData } + XCTAssertNil(error) XCTAssertEqual(usedPath, fixture.filePath) XCTAssertEqual(data?.count, fixture.data.count) XCTAssertEqual(usedOptions, .uncached) @@ -277,14 +279,16 @@ class SentryFileIOTrackerTests: XCTestCase { var usedUrl: URL? let url = URL(fileURLWithPath: fixture.filePath) var usedOptions: NSData.ReadingOptions? + var error: NSError? - let data = try? sut.measureNSData(from: url, options: .uncached, origin: "custom.origin") { url, options, _ in + let data = sut.measureNSData(from: url, options: .uncached, origin: "custom.origin", error: &error) { url, options, _ in span = self.firstSpan(transaction) usedOptions = options usedUrl = url - return self.fixture.data + return self.fixture.data as NSData } + XCTAssertNil(error) XCTAssertEqual(usedUrl, url) XCTAssertEqual(data?.count, fixture.data.count) XCTAssertEqual(usedOptions, .uncached) diff --git a/Tests/SentryTests/Integrations/Performance/IO/SentryFileIOTrackingIntegrationObjCTests.m b/Tests/SentryTests/Integrations/Performance/IO/SentryFileIOTrackingIntegrationObjCTests.m index ae60c88a652..25167024433 100644 --- a/Tests/SentryTests/Integrations/Performance/IO/SentryFileIOTrackingIntegrationObjCTests.m +++ b/Tests/SentryTests/Integrations/Performance/IO/SentryFileIOTrackingIntegrationObjCTests.m @@ -1,5 +1,5 @@ #import "SentryByteCountFormatter.h" -#import "SentryFileIOTracker.h" +#import "SentryFileIOTrackerHelper.h" #import "SentryOptions.h" #import "SentrySpan.h" #import "SentrySpanOperation.h" diff --git a/Tests/SentryTests/Integrations/Performance/IO/SentryNSFileManagerSwizzlingTests.m b/Tests/SentryTests/Integrations/Performance/IO/SentryNSFileManagerSwizzlingTests.m index 72a17ee86c1..33b9e51bd74 100644 --- a/Tests/SentryTests/Integrations/Performance/IO/SentryNSFileManagerSwizzlingTests.m +++ b/Tests/SentryTests/Integrations/Performance/IO/SentryNSFileManagerSwizzlingTests.m @@ -1,6 +1,6 @@ #import "SentryByteCountFormatter.h" #import "SentryDefaultThreadInspector.h" -#import "SentryFileIOTracker.h" +#import "SentryFileIOTrackerHelper.h" #import "SentryNSFileManagerSwizzling.h" #import "SentryOptions.h" #import "SentrySpan.h" diff --git a/Tests/SentryTests/SentryTests-Bridging-Header.h b/Tests/SentryTests/SentryTests-Bridging-Header.h index 2669d562e3a..4d8d622b8b4 100644 --- a/Tests/SentryTests/SentryTests-Bridging-Header.h +++ b/Tests/SentryTests/SentryTests-Bridging-Header.h @@ -111,7 +111,7 @@ #import "SentryEnvelopeRateLimit.h" #import "SentryEvent+Private.h" #import "SentryExtraContextProvider.h" -#import "SentryFileIOTracker.h" +#import "SentryFileIOTrackerHelper.h" #import "SentryFileIOTrackingIntegration.h" #import "SentryFileManager+Test.h" #import "SentryFileManagerHelper.h"