Skip to content

Commit 52790eb

Browse files
committed
feat: add mach_backtrace.c
1 parent 97f10bb commit 52790eb

File tree

14 files changed

+560
-197
lines changed

14 files changed

+560
-197
lines changed

Example/Pods/Pods.xcodeproj/project.pbxproj

Lines changed: 196 additions & 166 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Example/Pods/Target Support Files/Pods-SKApmTools_Example/Pods-SKApmTools_Example-acknowledgements.markdown

Lines changed: 2 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Example/Pods/Target Support Files/Pods-SKApmTools_Example/Pods-SKApmTools_Example-acknowledgements.plist

Lines changed: 3 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Example/Pods/Target Support Files/SKApmTools/SKApmTools-umbrella.h

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Example/SKApmTools.xcodeproj/project.pbxproj

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,7 @@
170170
607FACCC1AFB9204008FA782 /* Sources */,
171171
607FACCD1AFB9204008FA782 /* Frameworks */,
172172
607FACCE1AFB9204008FA782 /* Resources */,
173-
ED69CB639A570EF88C330E15 /* [CP] Embed Pods Frameworks */,
173+
FF146142EA7ACFEDE1AFD924 /* [CP] Embed Pods Frameworks */,
174174
);
175175
buildRules = (
176176
);
@@ -306,7 +306,7 @@
306306
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
307307
showEnvVarsInLog = 0;
308308
};
309-
ED69CB639A570EF88C330E15 /* [CP] Embed Pods Frameworks */ = {
309+
FF146142EA7ACFEDE1AFD924 /* [CP] Embed Pods Frameworks */ = {
310310
isa = PBXShellScriptBuildPhase;
311311
buildActionMask = 2147483647;
312312
files = (

Example/SKApmTools/ViewController.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ class ViewController: UIViewController {
2929
}
3030

3131
@objc func btnClicked(_ sender: UIButton) {
32-
Thread.sleep(forTimeInterval: 1)
32+
Thread.sleep(forTimeInterval: 2)
3333
}
3434

3535
override func didReceiveMemoryWarning() {
Lines changed: 16 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -5,20 +5,13 @@
55
// Created by KUN on 2022/10/24.
66
//
77

8-
/*
9-
10-
“N次卡顿超过阈值T”的判定策略:
11-
一个时间段内卡顿的次数累计大于N时才触发采集和上报
12-
13-
第一种:卡顿阈值T=200ms、卡顿次数N=1,可以判定为单次耗时较长的一次有效卡顿;
14-
第一种:卡顿阈值T=60ms、卡顿次数N=5,可以判定为频次较多的一次有效卡顿。
15-
16-
*/
178
import Foundation
189

1910
open class SKANRMonitor: NSObject{
2011

2112
@objc public static let sharedInstance = SKANRMonitor()
13+
/// 单次耗时较长的卡顿阈值: 默认值为500ms,单位:毫秒
14+
@objc public var singleTime: Int = 500
2215

2316
fileprivate var observer: CFRunLoopObserver?
2417

@@ -27,18 +20,9 @@ open class SKANRMonitor: NSObject{
2720
fileprivate var semaphore: DispatchSemaphore?
2821

2922
fileprivate var underObserving: Bool = false
30-
3123
// 卡顿次数记录
3224
fileprivate var count: Int = 0
3325

34-
/// 单次耗时较长的卡顿阈值: 默认值为200ms,单位:毫秒
35-
@objc public var singleTime: Int = 500
36-
37-
/// 频次较多的卡顿阈值: 默认值为60ms,单位:毫秒
38-
@objc public var multiTime: Int = 60
39-
/// 频次较多的卡顿次数: 默认值为5
40-
@objc public var frequency: Int = 5
41-
4226
@objc public func start() {
4327
if nil == self.observer {
4428
underObserving = true
@@ -51,16 +35,22 @@ open class SKANRMonitor: NSObject{
5135
while(self.underObserving) {
5236
if let activity = self.activity, let semaphore = self.semaphore {
5337
SKANRMonitor.sharedInstance.logActivity(activity)
54-
let result = semaphore.wait(timeout: DispatchTime.now() + .milliseconds(self.multiTime))
38+
let result = semaphore.wait(timeout: DispatchTime.now() + .milliseconds(self.singleTime))
5539
if result == .timedOut {
5640
if activity == .beforeSources || activity == .afterWaiting {
57-
self.count += 1
58-
print("!!!!!卡顿了,第\(self.count)次=====>\(activity)")
59-
if (self.count == self.frequency) {
60-
print("开始上报卡顿,第\(self.count)次=====>\(activity)")
61-
self.count = 0
41+
let stackSize = Thread.main.stackSize
42+
print("监测到卡顿")
43+
// let callStackSymbols = Thread.callStackSymbols
44+
// for (index, symbol) in callStackSymbols.enumerated() {
45+
// print("symbol【\(index)】: \(symbol)")
46+
// }
47+
let traces = SKStackTrace.stackTrace(of: Thread.main)
48+
for (index, symbol) in traces.enumerated() {
49+
print("symbol【\(index)】: \(symbol.info)")
6250
}
6351
}
52+
} else {
53+
self.count = 0
6454
}
6555
}
6656
}
@@ -79,8 +69,9 @@ open class SKANRMonitor: NSObject{
7969
fileprivate func observerCallBack() -> CFRunLoopObserverCallBack {
8070
return {(observer, activity, pointer) in
8171
SKANRMonitor.sharedInstance.activity = activity
72+
print("即将进入RunLoop")
8273
if let semaphore = SKANRMonitor.sharedInstance.semaphore {
83-
let count = semaphore.signal()
74+
semaphore.signal()
8475
}
8576
}
8677
}

SKApmTools/Classes/ReplaceMe.swift

Whitespace-only changes.
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
//
2+
// SKStackSymbol.swift
3+
// SKApmTools
4+
//
5+
// Created by KUN on 2022/10/26.
6+
//
7+
8+
import Foundation
9+
10+
public struct SKStackSymbol {
11+
12+
public let symbol: String
13+
public let file: String
14+
public let address: UInt
15+
public let symbolAddress: UInt
16+
public let image: String
17+
public let offset: UInt
18+
public let index: Int
19+
20+
public var demangledSymbol: String {
21+
return _stdlib_demangleName(symbol)
22+
}
23+
24+
public var info: String {
25+
return image.utf8CString.withUnsafeBufferPointer { (imageBuffer: UnsafeBufferPointer<CChar>) -> String in
26+
#if arch(x86_64) || arch(arm64)
27+
return String(format: "%-4ld%-35s 0x%016llx %@ + %ld \n", index, UInt(bitPattern: imageBuffer.baseAddress), address, demangledSymbol, offset)
28+
#else
29+
return String(format: "%-4d%-35s 0x%08lx %@ + %d \n", index, UInt(bitPattern: imageBuffer.baseAddress), address, demangledSymbol, offset)
30+
#endif
31+
}
32+
}
33+
}
34+
35+
@_silgen_name("swift_demangle")
36+
func _stdlib_demangleImpl(
37+
mangledName: UnsafePointer<CChar>?,
38+
mangledNameLength: UInt,
39+
outputBuffer: UnsafeMutablePointer<CChar>?,
40+
outputBufferSize: UnsafeMutablePointer<UInt>?,
41+
flags: UInt32
42+
) -> UnsafeMutablePointer<CChar>?
43+
44+
// Swift方法名还原
45+
private func _stdlib_demangleName(_ mangledName: String) -> String {
46+
return mangledName.utf8CString.withUnsafeBufferPointer {
47+
(mangledNameUTF8CStr) in
48+
49+
let demangledNamePtr = _stdlib_demangleImpl(
50+
mangledName: mangledNameUTF8CStr.baseAddress,
51+
mangledNameLength: UInt(mangledNameUTF8CStr.count - 1),
52+
outputBuffer: nil,
53+
outputBufferSize: nil,
54+
flags: 0
55+
)
56+
57+
if let demangledNamePtr = demangledNamePtr {
58+
let demangledName = String(cString: demangledNamePtr)
59+
free(demangledNamePtr)
60+
return demangledName
61+
}
62+
return mangledName
63+
}
64+
}
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
//
2+
// SKStackTrace.swift
3+
// SKApmTools
4+
//
5+
// Created by KUN on 2022/10/26.
6+
//
7+
8+
import Foundation
9+
import MachO
10+
11+
public class SKStackTrace {
12+
13+
/// 获取指定线程的堆栈信息
14+
public static func stackTrace(of thread: Thread) -> [SKStackSymbol] {
15+
let mach_thread = _machThread(from: thread)
16+
var symbols : [SKStackSymbol] = []
17+
let stackSize : UInt32 = 128
18+
let addrs = UnsafeMutablePointer<UnsafeMutableRawPointer?>.allocate(capacity: Int(stackSize))
19+
defer { addrs.deallocate() }
20+
let frameCount = mach_stack_trace(mach_thread, stack: addrs, maxSymbols: Int32(stackSize))
21+
let buf = UnsafeBufferPointer(start: addrs, count: Int(frameCount))
22+
23+
for (index, addr) in buf.enumerated() {
24+
guard let addr = addr else { continue }
25+
let address = UInt(bitPattern: addr)
26+
let symbol = SKStackSymbol(symbol: "not symbol",
27+
file: "",
28+
address: address,
29+
symbolAddress: 0,
30+
image: "dylib",
31+
offset: 0,
32+
index: index)
33+
symbols.append(symbol)
34+
}
35+
return symbols
36+
}
37+
38+
/// Thread to mach thread
39+
private static func _machThread(from thread: Thread) -> thread_t {
40+
guard let (threads, count) = _machAllThread() else {
41+
return mach_thread_self()
42+
}
43+
44+
if thread.isMainThread {
45+
return get_main_thread_t()
46+
}
47+
48+
var name : [Int8] = []
49+
let originName = thread.name
50+
51+
for i in 0 ..< count {
52+
let index = Int(i)
53+
if let p_thread = pthread_from_mach_thread_np((threads[index])) {
54+
name.append(Int8(Character("\0").ascii ?? 0))
55+
pthread_getname_np(p_thread, &name, MemoryLayout<Int8>.size * 256)
56+
if (strcmp(&name, (thread.name!.ascii)) == 0) {
57+
thread.name = originName
58+
return threads[index]
59+
}
60+
}
61+
}
62+
63+
thread.name = originName
64+
return mach_thread_self()
65+
}
66+
67+
/// get all thread
68+
private static func _machAllThread() -> (thread_act_array_t, mach_msg_type_number_t)? {
69+
var thread_array : thread_act_array_t?
70+
var number_t : mach_msg_type_number_t = 0
71+
let mach_task = mach_task_self_
72+
73+
guard task_threads(mach_task, &(thread_array), &number_t) == KERN_SUCCESS else {
74+
return nil
75+
}
76+
77+
return (thread_array!, number_t)
78+
}
79+
80+
}
81+
82+
@_silgen_name("mach_backtrace")
83+
public func mach_stack_trace(_ thread: thread_t,
84+
stack: UnsafeMutablePointer<UnsafeMutableRawPointer?>,
85+
maxSymbols: Int32) -> Int32
86+
87+
extension Character {
88+
var isAscii: Bool {
89+
return unicodeScalars.allSatisfy { $0.isASCII }
90+
}
91+
var ascii: UInt32? {
92+
return isAscii ? unicodeScalars.first?.value : nil
93+
}
94+
}
95+
96+
extension String {
97+
var ascii : [Int8] {
98+
var unicodeValues = [Int8]()
99+
for code in unicodeScalars {
100+
unicodeValues.append(Int8(code.value))
101+
}
102+
return unicodeValues
103+
}
104+
}

0 commit comments

Comments
 (0)