Skip to content

Commit 169cf94

Browse files
committed
Simplify
1 parent 962da66 commit 169cf94

File tree

1 file changed

+54
-29
lines changed

1 file changed

+54
-29
lines changed

Sources/Swift/Core/MetricKit/SentryMXCallStackTree.swift

Lines changed: 54 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)