@@ -8,22 +8,7 @@ extension SentryMXCallStackTree {
88 frame. toDebugMeta ( )
99 } . unique { $0. debugID }
1010 }
11-
12- func prepare( event: Event , inAppLogic: SentryInAppLogic ? , handled: Bool ) {
13- let debugMeta = toDebugMeta ( )
14- let threads = sentryMXBacktrace ( inAppLogic: inAppLogic, handled: handled)
15- // First look for the crashing thread, but for events that were not a crash (like a hang) take the first thread
16- // since those events only report one thread
17- let exceptionThread = threads. first { $0. crashed? . boolValue == true } ?? threads. first
18- event. debugMeta = debugMeta
19- event. threads = threads
20-
21- if let exceptionThread, let exception = event. exceptions ? [ 0 ] {
22- exception. stacktrace = exceptionThread. stacktrace
23- exception. threadId = exceptionThread. threadId
24- }
25- }
26-
11+
2712 // A MetricKit CallStackTree is a flamegraph, but many Sentry APIs only support
2813 // a thread backtrace. A flamegraph is just a collection of many thread backtraces
2914 // generated by taking multiple samples. For example a hang from metric kit will
@@ -35,8 +20,6 @@ extension SentryMXCallStackTree {
3520 // it represents data that is sampled across many threads and aggregated, we do not
3621 // know which samples came from which thread. Instead we create just one fake thread
3722 // that just contains the most common callstack.
38- //
39- // We hope to add flamegraph support at some point and that is tracked here: https://github.com/getsentry/sentry-cocoa/issues/7062
4023 func sentryMXBacktrace( inAppLogic: SentryInAppLogic ? , handled: Bool ) -> [ SentryThread ] {
4124 callStacks. enumerated ( ) . map { index, callStack in
4225 let thread = SentryThread ( threadId: NSNumber ( value: index) )
@@ -66,6 +49,44 @@ extension SentryMXCallStackTree {
6649 return thread
6750 }
6851 }
52+
53+ /// Flattens the call stack tree into a single thread with all frames.
54+ /// Each frame includes metadata to allow reconstructing the original tree:
55+ /// - `parent_frame_index`: The index of the parent frame in the flat list (-1 for root frames)
56+ /// - `sample_count`: The number of samples at this frame
57+ ///
58+ /// This preserves all sample data from the flamegraph rather than just the most common stack.
59+ func flattenedBacktrace( inAppLogic: SentryInAppLogic ? , handled: Bool ) -> [ SentryThread ] {
60+ callStacks. enumerated ( ) . map { index, callStack in
61+ let thread = SentryThread ( threadId: NSNumber ( value: index) )
62+ var frames : [ Frame ] = [ ]
63+
64+ // Traverse the tree and flatten all frames with parent references
65+ for rootFrame in callStack. callStackRootFrames {
66+ flattenFrame ( rootFrame, parentIndex: - 1 , frames: & frames, inAppLogic: inAppLogic)
67+ }
68+
69+ thread. stacktrace = SentryStacktrace ( frames: frames, registers: [ : ] )
70+ thread. crashed = NSNumber ( value: ( callStack. threadAttributed ?? false ) && !handled)
71+ return thread
72+ }
73+ }
74+
75+ private func flattenFrame( _ mxFrame: SentryMXFrame , parentIndex: Int , frames: inout [ Frame ] , inAppLogic: SentryInAppLogic ? ) {
76+ let currentIndex = frames. count
77+ let frame = mxFrame. toSentryFrameWithTreeData ( parentFrameIndex: parentIndex)
78+ if let package = frame. package {
79+ frame. inApp = NSNumber ( value: inAppLogic? . is ( inApp: package ) ?? false )
80+ } else {
81+ frame. inApp = true
82+ }
83+ frames. append ( frame)
84+
85+ // Recursively process child frames
86+ for subFrame in mxFrame. subFrames ?? [ ] {
87+ flattenFrame ( subFrame, parentIndex: currentIndex, frames: & frames, inAppLogic: inAppLogic)
88+ }
89+ }
6990}
7091
7192extension SentryMXCallStack {
@@ -87,7 +108,7 @@ extension SentryMXFrame {
87108 }
88109 return [ result] + ( subFrames? . flatMap { $0. toDebugMeta ( ) } ?? [ ] )
89110 }
90-
111+
91112 func toSamples( ) -> [ MXSample ] {
92113 let selfFrame = MXSample . MXFrame ( binaryUUID: binaryUUID, offsetIntoBinaryTextSegment: offsetIntoBinaryTextSegment, binaryName: binaryName, address: address)
93114 let subframes = subFrames ?? [ ]
@@ -100,6 +121,21 @@ extension SentryMXFrame {
100121 }
101122 return result
102123 }
124+
125+ /// Converts this frame to a SentryFrame.
126+ func toSentryFrameWithTreeData( parentFrameIndex: Int ) -> Frame {
127+ let frame = Frame ( )
128+ frame. package = binaryName
129+ frame. instructionAddress = sentry_formatHexAddressUInt64Swift ( address)
130+ if offsetIntoBinaryTextSegment >= 0 && offsetIntoBinaryTextSegment < address {
131+ frame. imageAddress = sentry_formatHexAddressUInt64Swift ( address - UInt64( offsetIntoBinaryTextSegment) )
132+ }
133+
134+ frame. parentIndex = parentFrameIndex as NSNumber
135+ frame. sampleCount = sampleCount as NSNumber ?
136+
137+ return frame
138+ }
103139}
104140
105141private extension MXSample . MXFrame {
0 commit comments