@@ -48,7 +48,15 @@ import Foundation
4848 func sentryMXBacktrace( inAppLogic: SentryInAppLogic , handled: Bool ) -> [ SentryThread ] {
4949 callStacks. map { callStack in
5050 let thread = SentryThread ( threadId: 0 )
51- let frames = callStack. toFrames ( )
51+ let samples = callStack. callStackRootFrames. flatMap { samplesFrom ( mxFrame: $0) }
52+ // Group by stacktrace in case there are multiple samples with the same trace
53+ var samplesToCount = [ [ Sample . Frame] : Int] ( )
54+ for sample in samples {
55+ let count = samplesToCount [ sample. frames] ?? 0
56+ samplesToCount [ sample. frames] = sample. count + count
57+ }
58+ // Need to reverse because the root node of a flamegraph is the first frame in a stacktrace (usually main)
59+ let frames = samplesToCount. mostSampled ( ) ? . reversed ( ) . map { $0. toSentryFrame ( ) } ?? [ ]
5260 frames. forEach { $0. inApp = NSNumber ( value: inAppLogic. is ( inApp: $0. package ) ) }
5361 thread. stacktrace = SentryStacktrace ( frames: frames, registers: [ : ] )
5462 thread. crashed = NSNumber ( value: ( callStack. threadAttributed ?? false ) && !handled)
@@ -57,14 +65,44 @@ import Foundation
5765 }
5866}
5967
60- struct SentryMXCallStack : Codable {
61- let threadAttributed : Bool ?
62- let callStackRootFrames : [ SentryMXFrame ]
68+ private func samplesFrom ( mxFrame : SentryMXFrame ) -> [ Sample ] {
69+ let selfFrame = Sample . Frame ( binaryUUID : mxFrame . binaryUUID , offsetIntoBinaryTextSegment : mxFrame . offsetIntoBinaryTextSegment , binaryName : mxFrame . binaryName , address : mxFrame . address )
70+ let subframes = mxFrame . subFrames ?? [ ]
6371
64- func toFrames( ) -> [ Frame ] {
65- // The root node of a flamegraph is the first frame in a stacktrace (usually main)
66- callStackRootFrames. mostSampled ( ) ? . toFrames ( ) . reversed ( ) ?? [ ]
72+ let childCount = subframes. map { $0. sampleCount ?? 0 } . reduce ( 0 , + )
73+ let selfCount = ( mxFrame. sampleCount ?? 0 ) - childCount
74+ var result = subframes. flatMap { samplesFrom ( mxFrame: $0) } . map { Sample ( count: $0. count, frames: [ selfFrame] + $0. frames) }
75+ if selfCount > 0 {
76+ result. append ( Sample ( count: selfCount, frames: [ selfFrame] ) )
6777 }
78+ return result
79+ }
80+
81+ // A Sample is the standard data format for a flamegraph taken from https://github.com/brendangregg/FlameGraph
82+ // It is less compact than Apple's MetricKit format, but contains the same data and is easier to work with
83+ struct Sample {
84+ let count : Int
85+ let frames : [ Frame ]
86+
87+ struct Frame : Hashable {
88+ let binaryUUID : UUID
89+ let offsetIntoBinaryTextSegment : Int
90+ let binaryName : String ?
91+ let address : UInt64
92+
93+ func toSentryFrame( ) -> Sentry . Frame {
94+ let frame = Sentry . Frame ( )
95+ frame. package = binaryName
96+ frame. instructionAddress = sentry_formatHexAddressUInt64Swift ( address)
97+ frame. imageAddress = sentry_formatHexAddressUInt64Swift ( address - UInt64( offsetIntoBinaryTextSegment) )
98+ return frame
99+ }
100+ }
101+ }
102+
103+ struct SentryMXCallStack : Decodable {
104+ let threadAttributed : Bool ?
105+ let callStackRootFrames : [ SentryMXFrame ]
68106
69107 func toDebugMeta( ) -> [ DebugMeta ] {
70108 callStackRootFrames. flatMap { frame in
@@ -73,26 +111,14 @@ struct SentryMXCallStack: Codable {
73111 }
74112}
75113
76- struct SentryMXFrame : Codable {
114+ struct SentryMXFrame : Decodable {
77115 let binaryUUID : UUID
78116 let offsetIntoBinaryTextSegment : Int
79117 let binaryName : String ?
80118 let address : UInt64
81119 let subFrames : [ SentryMXFrame ] ?
82120 let sampleCount : Int ?
83121
84- func toSentryFrame( ) -> Frame {
85- let frame = Frame ( )
86- frame. package = binaryName
87- frame. instructionAddress = sentry_formatHexAddressUInt64Swift ( address)
88- frame. imageAddress = sentry_formatHexAddressUInt64Swift ( address - UInt64( offsetIntoBinaryTextSegment) )
89- return frame
90- }
91-
92- func toFrames( ) -> [ Frame ] {
93- return [ toSentryFrame ( ) ] + ( subFrames? . mostSampled ( ) ? . toFrames ( ) ?? [ ] )
94- }
95-
96122 func toDebugMeta( ) -> [ DebugMeta ] {
97123 let result = DebugMeta ( )
98124 result. type = SentryDebugImageType
@@ -119,18 +145,17 @@ extension Sequence {
119145 }
120146}
121147
122- extension Sequence where Element == SentryMXFrame {
123- // A sentry frame is a list not a tree, find the most frequently sampled element at this level of the tree.
124- func mostSampled( ) -> SentryMXFrame ? {
148+ extension Dictionary where Value == Int {
149+ func mostSampled( ) -> Key ? {
125150 var mostSamples = - 1
126- var mostSampledFrame : SentryMXFrame ?
127- for frame in self {
128- if frame . sampleCount ?? 0 > mostSamples {
129- mostSamples = frame . sampleCount ?? 0
130- mostSampledFrame = frame
151+ var mostSampledKey : Key ?
152+ for (key , value ) in self {
153+ if value > mostSamples {
154+ mostSamples = value
155+ mostSampledKey = key
131156 }
132157 }
133- return mostSampledFrame
158+ return mostSampledKey
134159 }
135160}
136161
0 commit comments