Skip to content

Commit 8f7bb4b

Browse files
committed
feat: pending data support disk cache.
1 parent c5dffe8 commit 8f7bb4b

File tree

4 files changed

+142
-30
lines changed

4 files changed

+142
-30
lines changed

Example/SKApmTools/ViewController.swift

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,21 @@ class ViewController: UIViewController {
2525
btn.addTarget(self, action: #selector(btnClicked(_:)) , for: .touchUpInside)
2626
view.addSubview(btn)
2727
}
28-
SKANRMonitor.sharedInstance.start()
28+
29+
SKANRMonitor.start()
30+
let datas = SKANRMonitor.getPendingEntities()
31+
print("待处理的卡顿数据数目: \(datas.count)")
32+
SKANRMonitor.monitorCallback { curEntity, allEntities in
33+
print("监测到卡顿: \(curEntity.validFunction)")
34+
print(curEntity.threadId)
35+
print(curEntity.occurenceTime)
36+
print(curEntity.validAddress)
37+
print(curEntity.traceContent)
38+
}
2939
}
3040

3141
@objc func btnClicked(_ sender: UIButton) {
32-
Thread.sleep(forTimeInterval: 2)
42+
Thread.sleep(forTimeInterval: 1)
3343
}
3444

3545
override func didReceiveMemoryWarning() {

SKApmTools/Classes/ANR/SKANRMonitor.swift

Lines changed: 125 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -10,65 +10,147 @@ import Foundation
1010
open class SKANRMonitor: NSObject{
1111

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

1616
fileprivate var observer: CFRunLoopObserver?
1717

1818
fileprivate var activity: CFRunLoopActivity?
1919

20+
fileprivate var lock = os_unfair_lock()
21+
2022
fileprivate var semaphore: DispatchSemaphore?
2123

2224
fileprivate var underObserving: Bool = false
2325
/// 卡顿次数记录
2426
fileprivate var count: Int = 0
2527

26-
fileprivate var pendingEntitys: [SKBacktraceEntity] = []
28+
fileprivate var pendingEntities: [SKBacktraceEntity] = []
2729

2830
fileprivate var pendingEntityDict: [String: SKBacktraceEntity] = [:]
2931

30-
@objc public func start() {
32+
public typealias MonitorCallback = (_ curEntity: SKBacktraceEntity, _ allEntities: [SKBacktraceEntity]) -> Void
33+
34+
fileprivate var callback: MonitorCallback?
35+
36+
fileprivate var filePath: String? {
37+
get {
38+
let path = NSSearchPathForDirectoriesInDomains(.cachesDirectory, .userDomainMask, true).first as NSString?
39+
if let filePath = path?.appendingPathComponent("wd_apm_anr.archive") {
40+
return filePath
41+
}
42+
return nil
43+
}
44+
}
45+
46+
public override init() {
47+
super.init()
48+
readDataFromDisk()
49+
}
50+
51+
/// 开启监测
52+
@objc public class func start() {
53+
SKANRMonitor.sharedInstance.start()
54+
}
55+
/// 停止监测
56+
@objc public class func stop() {
57+
SKANRMonitor.sharedInstance.stop()
58+
}
59+
60+
/// 监测到一个卡顿回调
61+
public class func monitorCallback(_ callback: @escaping MonitorCallback) {
62+
SKANRMonitor.sharedInstance.callback = callback
63+
}
64+
65+
/// 获取卡顿数据
66+
public class func getPendingEntities() -> [SKBacktraceEntity] {
67+
return SKANRMonitor.sharedInstance.pendingEntities
68+
}
69+
70+
/// 清理卡顿数据
71+
public class func clearPendingEntities() {
72+
SKANRMonitor.sharedInstance._clearEntities()
73+
}
74+
75+
public func _clearEntities() {
76+
os_unfair_lock_lock(&lock)
77+
pendingEntities.removeAll()
78+
pendingEntityDict.removeAll()
79+
if let filePath = filePath {
80+
if FileManager.default.fileExists(atPath: filePath) {
81+
do {
82+
try FileManager.default.removeItem(atPath: filePath)
83+
} catch {
84+
print("SKANRMonitor remove anr data from disk error = \(error.localizedDescription)")
85+
}
86+
}
87+
}
88+
os_unfair_lock_unlock(&lock)
89+
}
90+
91+
private func start() {
3192
if nil == self.observer {
3293
underObserving = true
3394
semaphore = DispatchSemaphore(value: 1)
3495
var context = CFRunLoopObserverContext(version: 0, info: nil, retain: nil, release: nil, copyDescription: nil)
3596
self.observer = CFRunLoopObserverCreate(kCFAllocatorDefault, CFRunLoopActivity.allActivities.rawValue, true, 0, observerCallBack(), &context)
3697
CFRunLoopAddObserver(CFRunLoopGetMain(), observer, CFRunLoopMode.commonModes)
3798

38-
Thread.detachNewThread {
39-
while(self.underObserving) {
40-
if let activity = self.activity, let semaphore = self.semaphore {
41-
SKANRMonitor.sharedInstance.logActivity(activity)
42-
let result = semaphore.wait(timeout: DispatchTime.now() + .milliseconds(self.singleTime))
99+
Thread.detachNewThread { [self] in
100+
while(underObserving) {
101+
if let activity = activity, let semaphore = semaphore {
102+
let result = semaphore.wait(timeout: DispatchTime.now() + .milliseconds(singleTime))
43103
if result == .timedOut {
44-
if self.observer == nil {
104+
if observer == nil {
45105
return
46106
}
47107
if activity == .beforeSources || activity == .afterWaiting {
48-
print("监测到卡顿")
49108
let entity = SKBackTrace.backTraceInfoEntity(of: Thread.main)
50-
let key = "\(entity.validAddress)_\(entity.validFunction)"
51-
if !self.pendingEntityDict.keys.contains(key) {
52-
self.pendingEntityDict.updateValue(entity, forKey: key)
53-
self.pendingEntitys.append(entity)
54-
print(entity.threadId)
55-
print(entity.validAddress)
56-
print(entity.validFunction)
57-
print(entity.traceContent)
58-
} else {
59-
print("相同的卡顿只记录一次")
60-
}
109+
handleEntity(entity)
61110
}
62111
} else {
63-
self.count = 0
112+
count = 0
64113
}
65114
}
66115
}
67116
}
68117
}
69118
}
70119

71-
@objc public func stop() {
120+
private func handleEntity(_ entity: SKBacktraceEntity) {
121+
let key = "\(entity.validAddress)_\(entity.validFunction)"
122+
if !self.pendingEntityDict.keys.contains(key) {
123+
os_unfair_lock_lock(&lock)
124+
// limit cache count 50
125+
if (pendingEntities.count >= 50) {
126+
let removeEntity = pendingEntities.removeLast()
127+
let removeKey = "\(removeEntity.validAddress)_\(removeEntity.validFunction)"
128+
self.pendingEntityDict.removeValue(forKey: removeKey)
129+
}
130+
self.pendingEntityDict.updateValue(entity, forKey: key)
131+
self.pendingEntities.insert(entity, at: 0)
132+
os_unfair_lock_unlock(&lock)
133+
134+
if let filePath = filePath {
135+
do {
136+
print("SKANRMonitor write data to filePath = \(filePath)")
137+
let data = try PropertyListEncoder().encode(self.pendingEntities)
138+
try data.write(to: URL(fileURLWithPath: filePath))
139+
}
140+
catch {
141+
print("SKANRMonitor write data to filePath error = \(error.localizedDescription)")
142+
}
143+
}
144+
145+
if let callback = self.callback {
146+
callback(entity, self.pendingEntities)
147+
}
148+
} else {
149+
print("SKANRMonitor 相同的卡顿只记录一次")
150+
}
151+
}
152+
153+
private func stop() {
72154
if nil != self.observer {
73155
underObserving = false
74156
CFRunLoopRemoveObserver(CFRunLoopGetMain(), self.observer, CFRunLoopMode.commonModes)
@@ -79,13 +161,31 @@ open class SKANRMonitor: NSObject{
79161
fileprivate func observerCallBack() -> CFRunLoopObserverCallBack {
80162
return {(observer, activity, pointer) in
81163
SKANRMonitor.sharedInstance.activity = activity
82-
print("即将进入RunLoop")
83164
if let semaphore = SKANRMonitor.sharedInstance.semaphore {
84165
semaphore.signal()
85166
}
86167
}
87168
}
88169

170+
private func readDataFromDisk() {
171+
if let filePath = filePath, FileManager.default.fileExists(atPath: filePath) {
172+
do {
173+
let data = try Data(contentsOf: URL(fileURLWithPath: filePath))
174+
let entities = try PropertyListDecoder().decode([SKBacktraceEntity].self, from: data)
175+
os_unfair_lock_lock(&lock)
176+
pendingEntities.append(contentsOf: entities)
177+
entities.forEach { e in
178+
let key = "\(e.validAddress)_\(e.validFunction)"
179+
pendingEntityDict.updateValue(e, forKey: key)
180+
}
181+
os_unfair_lock_unlock(&lock)
182+
}
183+
catch {
184+
print("SKANRMonitor read data from disk error = \(error.localizedDescription)")
185+
}
186+
}
187+
}
188+
89189
fileprivate func logActivity(_ activity: CFRunLoopActivity) {
90190
switch activity {
91191
case .entry:

SKApmTools/Classes/BackTrace/SKBackTrace.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,8 @@ public class SKBackTrace {
3737
validAddress = symbols.first?.formatAddress ?? ""
3838
validFunction = symbols.first?.demangledSymbol ?? ""
3939
}
40-
let entity = SKBacktraceEntity(threadId: UInt(mach_thread), validAddress: validAddress, validFunction: validFunction, traceContent: traceContent, traceSymbols: symbols)
40+
let time = Date.timeIntervalSinceReferenceDate
41+
let entity = SKBacktraceEntity(threadId: UInt(mach_thread), validAddress: validAddress, validFunction: validFunction, traceContent: traceContent, traceSymbols: symbols, occurenceTime: time)
4142
return entity
4243
}
4344

SKApmTools/Classes/BackTrace/SKStackSymbol.swift

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
import Foundation
99

10-
public struct SKStackSymbol {
10+
public struct SKStackSymbol: Codable {
1111
public let symbol: String
1212
public let file: String
1313
public let address: UInt
@@ -47,12 +47,13 @@ public struct SKStackSymbol {
4747
}
4848

4949

50-
public struct SKBacktraceEntity {
50+
public struct SKBacktraceEntity: Codable {
5151
public let threadId: UInt // 259
5252
public let validAddress: String // address
5353
public let validFunction: String // function
5454
public let traceContent: String
5555
public let traceSymbols: [SKStackSymbol]
56+
public let occurenceTime: TimeInterval
5657
}
5758

5859
public struct SKBacktraceEntry: Codable {

0 commit comments

Comments
 (0)