Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions Sentry.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -1102,6 +1102,8 @@
FA90FAA82E06614E008CAAE8 /* SentryExtraPackages.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA90FAA72E06614B008CAAE8 /* SentryExtraPackages.swift */; };
FA90FAFD2E070A3B008CAAE8 /* SentryURLRequestFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA90FAFC2E070A3B008CAAE8 /* SentryURLRequestFactory.swift */; };
FA914E6D2ECFD7D800C54BDD /* SentryFeedbackAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA914E6C2ECFD7D800C54BDD /* SentryFeedbackAPI.swift */; };
FA914E9B2ED61AA800C54BDD /* SentryFormatterSwift.h in Headers */ = {isa = PBXBuildFile; fileRef = FA914E952ED61AA300C54BDD /* SentryFormatterSwift.h */; };
FA914E9E2ED61BA800C54BDD /* SentryFormatterSwift.m in Sources */ = {isa = PBXBuildFile; fileRef = FA914E9C2ED61AB900C54BDD /* SentryFormatterSwift.m */; };
FA94E6912E6B92C100576666 /* SentryClientReport.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA94E68B2E6B92BE00576666 /* SentryClientReport.swift */; };
FA94E6B22E6D265800576666 /* SentryEnvelope.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA94E6B12E6D265500576666 /* SentryEnvelope.swift */; };
FA94E7242E6F339400576666 /* SentryEnvelopeItemType.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA94E7232E6F32FA00576666 /* SentryEnvelopeItemType.swift */; };
Expand Down Expand Up @@ -2485,6 +2487,8 @@
FA90FAA72E06614B008CAAE8 /* SentryExtraPackages.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryExtraPackages.swift; sourceTree = "<group>"; };
FA90FAFC2E070A3B008CAAE8 /* SentryURLRequestFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryURLRequestFactory.swift; sourceTree = "<group>"; };
FA914E6C2ECFD7D800C54BDD /* SentryFeedbackAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryFeedbackAPI.swift; sourceTree = "<group>"; };
FA914E952ED61AA300C54BDD /* SentryFormatterSwift.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SentryFormatterSwift.h; path = include/SentryFormatterSwift.h; sourceTree = "<group>"; };
FA914E9C2ED61AB900C54BDD /* SentryFormatterSwift.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SentryFormatterSwift.m; sourceTree = "<group>"; };
FA94E68B2E6B92BE00576666 /* SentryClientReport.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryClientReport.swift; sourceTree = "<group>"; };
FA94E6B12E6D265500576666 /* SentryEnvelope.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryEnvelope.swift; sourceTree = "<group>"; };
FA94E7232E6F32FA00576666 /* SentryEnvelopeItemType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryEnvelopeItemType.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -2950,6 +2954,8 @@
639889D51EDF10BE00EA7442 /* Helper */ = {
isa = PBXGroup;
children = (
FA914E952ED61AA300C54BDD /* SentryFormatterSwift.h */,
FA914E9C2ED61AB900C54BDD /* SentryFormatterSwift.m */,
FA4FB8252ECB7D27008C9EC3 /* SentryLevel.h */,
63AA76951EB9C1C200D153DE /* SentryDefines.h */,
627E7588299F6FE40085504D /* SentryInternalDefines.h */,
Expand Down Expand Up @@ -5243,6 +5249,7 @@
FAB359982E05D7E90083D5E3 /* SentryEventSwiftHelper.h in Headers */,
7B31C291277B04A000337126 /* SentryCrashPlatformSpecificDefines.h in Headers */,
D452FC732DDB553100AFF56F /* SentryWatchdogTerminationBreadcrumbProcessor.h in Headers */,
FA914E9B2ED61AA800C54BDD /* SentryFormatterSwift.h in Headers */,
D456B4382D706BFE007068CB /* SentrySpanDataKey.h in Headers */,
7B77BE3527EC8445003C9020 /* SentryDiscardReasonMapper.h in Headers */,
7B610D602512390E00B0B5D9 /* SentrySDK+Private.h in Headers */,
Expand Down Expand Up @@ -5833,6 +5840,7 @@
D48891CC2E98F22A00212823 /* SentryInfoPlistWrapperProvider.swift in Sources */,
F458D1172E186DF20028273E /* SentryScopePersistentStore+Fingerprint.swift in Sources */,
D8CB7417294724CC00A5F964 /* SentryEnvelopeAttachmentHeader.m in Sources */,
FA914E9E2ED61BA800C54BDD /* SentryFormatterSwift.m in Sources */,
D84793262788737D00BE8E99 /* SentryByteCountFormatter.m in Sources */,
FAEFA12F2E4FAE1900C431D9 /* SentrySDKSettings.swift in Sources */,
63AA769E1EB9C57A00D153DE /* SentryError.mm in Sources */,
Expand Down
7 changes: 7 additions & 0 deletions Sources/Sentry/SentryFormatterSwift.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#import "SentryFormatter.h"

NSString *
sentry_formatHexAddressUInt64Swift(uint64_t value)
{
return sentry_formatHexAddressUInt64(value);
}
278 changes: 3 additions & 275 deletions Sources/Sentry/SentryMetricKitIntegration.m
Original file line number Diff line number Diff line change
Expand Up @@ -204,187 +204,12 @@ - (void)didReceiveHangDiagnostic:(MXHangDiagnostic *)diagnostic
- (void)captureMXEvent:(SentryMXCallStackTree *)callStackTree
params:(SentryMXExceptionParams *)params
diagnosticJSON:(NSData *)diagnosticJSON
{
// When receiving MXCrashDiagnostic the callStackPerThread was always true. In that case, the
// MXCallStacks of the MXCallStackTree were individual threads, all belonging to the process
// when the crash occurred. For MXCPUException, the callStackPerThread was always false. In that
// case, the MXCallStacks stem from CPU-hungry multiple locations in the sample app during an
// observation time of 90 seconds of one app run. It's a collection of stack traces that are
// CPU-hungry.
if (callStackTree.callStackPerThread) {
SentryEvent *event = [self createEvent:params];

event.threads = [self convertToSentryThreads:callStackTree];

SentryThread *crashedThread = event.threads[0];
crashedThread.crashed = @(!params.handled);

SentryException *exception = event.exceptions[0];
exception.stacktrace = crashedThread.stacktrace;
exception.threadId = crashedThread.threadId;

event.debugMeta = [self extractDebugMetaFromMXCallStacks:callStackTree.callStacks];

// The crash event can be way from the past. We don't want to impact the current session.
// Therefore we don't call captureFatalEvent.
[self captureEvent:event withDiagnosticJSON:diagnosticJSON];
} else {
for (SentryMXCallStack *callStack in callStackTree.callStacks) {
[self buildAndCaptureMXEventFor:callStack.callStackRootFrames
params:params
diagnosticJSON:diagnosticJSON];
}
}
}

/**
* If @c callStackPerThread is @c NO , MetricKit organizes the stacktraces in a tree structure. See
* https://developer.apple.com/videos/play/wwdc2020/10078/?time=224. The stacktrace consists of the
* last sibling leaf frame plus its ancestors.
*
* The algorithm adds all frames to a list until it finds a leaf frame being the last sibling. Then
* it reports that frame with its siblings and ancestors as a stacktrace.
*
* In the following example, the algorithm starts with frame 0, continues until frame 6, and reports
* a stacktrace. Then it pops all sibling frames, goes back up to frame 3, and continues the search.
*
* It is worth noting that for the first stacktrace [0, 1, 3, 4, 5, 6] frame 2 is not included
* because the logic only includes direct siblings and direct ancestors. Frame 3 is an ancestors of
* [4,5,6], frame 1 of frame 3, but frame 2 is not a direct ancestors of [4,5,6]. It's the sibling
* of the direct ancestor frame 3. Although this might seem a bit illogical, that is what
* observations of MetricKit data unveiled.
*
* @code
* | frame 0 |
* | frame 1 |
* | frame 2 |
* | frame 3 |
* | frame 4 |
* | frame 5 |
* | frame 6 | -> stack trace consists of [0, 1, 3, 4, 5, 6]
* | frame 7 |
* | frame 8 | -> stack trace consists of [0, 1, 2, 3, 7, 8]
* | frame 9 | -> stack trace consists of [0, 1, 9]
* | frame 10 |
* | frame 11 |
* | frame 12 |
* | frame 13 | -> stack trace consists of [10, 11, 12, 13]
* @endcode
*
* The above stacktrace turns into the following two trees.
* @code
* 0
* |
* 1
* / \ \
* 3 2 9
* | |
* 4 3
* | |
* 5 7
* | |
* 6 8
*
* 10
* |
* 11
* |
* 12
* |
* 13
* @endcode
*/
- (void)buildAndCaptureMXEventFor:(NSArray<SentryMXFrame *> *)rootFrames
params:(SentryMXExceptionParams *)params
diagnosticJSON:(NSData *)diagnosticJSON
{
for (SentryMXFrame *rootFrame in rootFrames) {
NSMutableArray<SentryMXFrame *> *stackTraceFrames = [NSMutableArray array];
NSMutableSet<NSNumber *> *processedFrameAddresses = [NSMutableSet set];
NSMutableDictionary<NSNumber *, SentryMXFrame *> *addressesToParentFrames =
[NSMutableDictionary dictionary];

SentryMXFrame *currentFrame = rootFrame;
[stackTraceFrames addObject:currentFrame];

while (stackTraceFrames.count > 0) {
currentFrame = [stackTraceFrames lastObject];
[processedFrameAddresses addObject:@(currentFrame.address)];

for (SentryMXFrame *subFrame in currentFrame.subFrames) {
addressesToParentFrames[@(subFrame.address)] = currentFrame;
}
SentryMXFrame *parentFrame = addressesToParentFrames[@(currentFrame.address)];

SentryMXFrame *firstUnprocessedSibling =
[self getFirstUnprocessedSubFrames:parentFrame.subFrames ?: @[]
processedFrameAddresses:processedFrameAddresses];

BOOL lastUnprocessedSibling = firstUnprocessedSibling == nil;
BOOL noChildren = currentFrame.subFrames.count == 0;

if (noChildren && lastUnprocessedSibling) {
[self captureEventNotPerThread:stackTraceFrames
params:params
diagnosticJSON:diagnosticJSON];

if (parentFrame == nil) {
// No parent frames
[stackTraceFrames removeLastObject];
} else {
// Pop all sibling frames
for (int i = 0; i < parentFrame.subFrames.count; i++) {
[stackTraceFrames removeLastObject];
}
}
} else {
SentryMXFrame *nonProcessedSubFrame =
[self getFirstUnprocessedSubFrames:currentFrame.subFrames ?: @[]
processedFrameAddresses:processedFrameAddresses];

// Keep adding sub frames
if (nonProcessedSubFrame != nil) {
[stackTraceFrames addObject:nonProcessedSubFrame];
} // Keep adding sibling frames
else if (firstUnprocessedSibling != nil) {
[stackTraceFrames addObject:firstUnprocessedSibling];
} // Keep popping
else {
[stackTraceFrames removeLastObject];
}
}
}
}
}

- (nullable SentryMXFrame *)getFirstUnprocessedSubFrames:(NSArray<SentryMXFrame *> *)subFrames
processedFrameAddresses:
(NSSet<NSNumber *> *)processedFrameAddresses
{
return [subFrames filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(
SentryMXFrame *frame,
NSDictionary<NSString *, id> *bindings) {
return ![processedFrameAddresses containsObject:@(frame.address)];
}]].firstObject;
}

- (void)captureEventNotPerThread:(NSArray<SentryMXFrame *> *)frames
params:(SentryMXExceptionParams *)params
diagnosticJSON:(NSData *)diagnosticJSON
{
SentryEvent *event = [self createEvent:params];
[callStackTree prepareWithEvent:event inAppLogic:self.inAppLogic handled:params.handled];

SentryThread *thread = [[SentryThread alloc] initWithThreadId:@0];
thread.crashed = @(!params.handled);
thread.stacktrace = [self convertMXFramesToSentryStacktrace:frames.objectEnumerator];

SentryException *exception = event.exceptions[0];
exception.stacktrace = thread.stacktrace;
exception.threadId = thread.threadId;

event.threads = @[ thread ];
event.debugMeta = [self extractDebugMetaFromMXFrames:frames];

// The crash event can be way from the past. We don't want to impact the current session.
// Therefore we don't call captureFatalEvent.
[self captureEvent:event withDiagnosticJSON:diagnosticJSON];
}

Expand Down Expand Up @@ -419,103 +244,6 @@ - (void)captureEvent:(SentryEvent *)event withDiagnosticJSON:(NSData *)diagnosti
}
}

- (NSArray<SentryThread *> *)convertToSentryThreads:(SentryMXCallStackTree *)callStackTree
{
NSUInteger i = 0;
NSMutableArray<SentryThread *> *threads = [NSMutableArray array];
for (SentryMXCallStack *callStack in callStackTree.callStacks) {
NSEnumerator<SentryMXFrame *> *frameEnumerator
= callStack.flattenedRootFrames.objectEnumerator;
// The MXFrames are in reversed order when callStackPerThread is true. The Apple docs don't
// state that. This is an assumption based on observing MetricKit data.
if (callStackTree.callStackPerThread) {
frameEnumerator = [callStack.flattenedRootFrames reverseObjectEnumerator];
}

SentryStacktrace *stacktrace = [self convertMXFramesToSentryStacktrace:frameEnumerator];

SentryThread *thread = [[SentryThread alloc] initWithThreadId:@(i)];
thread.stacktrace = stacktrace;

[threads addObject:thread];

i++;
}

return threads;
}

- (SentryStacktrace *)convertMXFramesToSentryStacktrace:(NSEnumerator<SentryMXFrame *> *)mxFrames
{
NSMutableArray<SentryFrame *> *frames = [NSMutableArray array];

for (SentryMXFrame *mxFrame in mxFrames) {
SentryFrame *frame = [[SentryFrame alloc] init];
frame.package = mxFrame.binaryName;
frame.inApp = @([self.inAppLogic isInApp:mxFrame.binaryName]);
frame.instructionAddress = sentry_formatHexAddressUInt64(mxFrame.address);
uint64_t imageAddress = mxFrame.address - mxFrame.offsetIntoBinaryTextSegment;
frame.imageAddress = sentry_formatHexAddressUInt64(imageAddress);

[frames addObject:frame];
}

SentryStacktrace *stacktrace = [[SentryStacktrace alloc] initWithFrames:frames registers:@{}];

return stacktrace;
}

/**
* We must extract the debug images from the MetricKit stacktraces as the image addresses change
* when you restart the app.
*/
- (NSArray<SentryDebugMeta *> *)extractDebugMetaFromMXCallStacks:
(NSArray<SentryMXCallStack *> *)callStacks
{
NSMutableDictionary<NSString *, SentryDebugMeta *> *debugMetas =
[NSMutableDictionary dictionary];
for (SentryMXCallStack *callStack in callStacks) {

NSArray<SentryDebugMeta *> *callStackDebugMetas =
[self extractDebugMetaFromMXFrames:callStack.flattenedRootFrames];

for (SentryDebugMeta *debugMeta in callStackDebugMetas) {
if (debugMeta.debugID != nil) {
debugMetas[SENTRY_UNWRAP_NULLABLE_VALUE(id<NSCopying>, debugMeta.debugID)]
= debugMeta;
}
}
}

return [debugMetas allValues];
}

- (NSArray<SentryDebugMeta *> *)extractDebugMetaFromMXFrames:(NSArray<SentryMXFrame *> *)mxFrames
{
NSMutableDictionary<NSString *, SentryDebugMeta *> *debugMetas =
[NSMutableDictionary dictionary];

for (SentryMXFrame *mxFrame in mxFrames) {

NSString *binaryUUID = [mxFrame.binaryUUID UUIDString];
if (debugMetas[binaryUUID]) {
continue;
}

SentryDebugMeta *debugMeta = [[SentryDebugMeta alloc] init];
debugMeta.type = SentryDebugImageType;
debugMeta.debugID = binaryUUID;
debugMeta.codeFile = mxFrame.binaryName;

uint64_t imageAddress = mxFrame.address - mxFrame.offsetIntoBinaryTextSegment;
debugMeta.imageAddress = sentry_formatHexAddressUInt64(imageAddress);

debugMetas[binaryUUID] = debugMeta;
}

return [debugMetas allValues];
}

@end

NS_ASSUME_NONNULL_END
Expand Down
1 change: 1 addition & 0 deletions Sources/Sentry/include/SentryFormatterSwift.h
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
extern NSString *sentry_formatHexAddressUInt64Swift(uint64_t value);
2 changes: 2 additions & 0 deletions Sources/Sentry/include/SentryPrivate.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,9 @@
#import "SentryDsn+Private.h"
#import "SentryEnvelopeAttachmentHeader.h"
#import "SentryEventSwiftHelper.h"
#import "SentryFormatterSwift.h"
#import "SentryHub+Private.h"
#import "SentryInternalDefines.h"
#import "SentryNSDataUtils.h"
#import "SentrySDK+Private.h"
#import "SentryTime.h"
Expand Down
Loading
Loading