Skip to content

Commit 97f10bb

Browse files
committed
feat: initial SKANRMonitor
1 parent ce60f93 commit 97f10bb

File tree

4 files changed

+156
-11
lines changed

4 files changed

+156
-11
lines changed

ANR/SKANRMonitor.swift

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
//
2+
// SKANRMonitor.swift
3+
// SKApmTools
4+
//
5+
// Created by KUN on 2022/10/24.
6+
//
7+
8+
/*
9+
10+
“N次卡顿超过阈值T”的判定策略:
11+
一个时间段内卡顿的次数累计大于N时才触发采集和上报
12+
13+
第一种:卡顿阈值T=200ms、卡顿次数N=1,可以判定为单次耗时较长的一次有效卡顿;
14+
第一种:卡顿阈值T=60ms、卡顿次数N=5,可以判定为频次较多的一次有效卡顿。
15+
16+
*/
17+
import Foundation
18+
19+
open class SKANRMonitor: NSObject{
20+
21+
@objc public static let sharedInstance = SKANRMonitor()
22+
23+
fileprivate var observer: CFRunLoopObserver?
24+
25+
fileprivate var activity: CFRunLoopActivity?
26+
27+
fileprivate var semaphore: DispatchSemaphore?
28+
29+
fileprivate var underObserving: Bool = false
30+
31+
// 卡顿次数记录
32+
fileprivate var count: Int = 0
33+
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+
42+
@objc public func start() {
43+
if nil == self.observer {
44+
underObserving = true
45+
semaphore = DispatchSemaphore(value: 1)
46+
var context = CFRunLoopObserverContext(version: 0, info: nil, retain: nil, release: nil, copyDescription: nil)
47+
self.observer = CFRunLoopObserverCreate(kCFAllocatorDefault, CFRunLoopActivity.allActivities.rawValue, true, 0, observerCallBack(), &context)
48+
CFRunLoopAddObserver(CFRunLoopGetMain(), observer, CFRunLoopMode.commonModes)
49+
50+
Thread.detachNewThread {
51+
while(self.underObserving) {
52+
if let activity = self.activity, let semaphore = self.semaphore {
53+
SKANRMonitor.sharedInstance.logActivity(activity)
54+
let result = semaphore.wait(timeout: DispatchTime.now() + .milliseconds(self.multiTime))
55+
if result == .timedOut {
56+
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
62+
}
63+
}
64+
}
65+
}
66+
}
67+
}
68+
}
69+
}
70+
71+
@objc public func stop() {
72+
if nil != self.observer {
73+
underObserving = false
74+
CFRunLoopRemoveObserver(CFRunLoopGetMain(), self.observer, CFRunLoopMode.commonModes)
75+
self.observer = nil
76+
}
77+
}
78+
79+
fileprivate func observerCallBack() -> CFRunLoopObserverCallBack {
80+
return {(observer, activity, pointer) in
81+
SKANRMonitor.sharedInstance.activity = activity
82+
if let semaphore = SKANRMonitor.sharedInstance.semaphore {
83+
let count = semaphore.signal()
84+
}
85+
}
86+
}
87+
88+
fileprivate func logActivity(_ activity: CFRunLoopActivity) {
89+
switch activity {
90+
case .entry:
91+
print("即将进入RunLoop")
92+
break
93+
case .beforeTimers:
94+
print("即将处理Timer")
95+
break
96+
case .beforeSources:
97+
print("即将处理Sources")
98+
break
99+
case .beforeWaiting:
100+
print("即将进入休眠")
101+
break
102+
case .afterWaiting:
103+
print("从休眠中唤醒")
104+
break
105+
case .exit:
106+
print("退出RunLoop")
107+
break
108+
default:
109+
break
110+
}
111+
}
112+
}
113+

Example/Pods/Pods.xcodeproj/project.pbxproj

Lines changed: 17 additions & 6 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: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
/* End PBXContainerItemProxy section */
2929

3030
/* Begin PBXFileReference section */
31-
38A66F51DCEDC7A1402A1B3C /* SKApmTools.podspec */ = {isa = PBXFileReference; includeInIndex = 1; name = SKApmTools.podspec; path = ../SKApmTools.podspec; sourceTree = "<group>"; };
31+
38A66F51DCEDC7A1402A1B3C /* SKApmTools.podspec */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; name = SKApmTools.podspec; path = ../SKApmTools.podspec; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.ruby; };
3232
3BEF9F7CC1F42865D6943A18 /* Pods_SKApmTools_Tests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_SKApmTools_Tests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
3333
5041EE459CFA300E4A81FA37 /* Pods_SKApmTools_Example.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_SKApmTools_Example.framework; sourceTree = BUILT_PRODUCTS_DIR; };
3434
607FACD01AFB9204008FA782 /* SKApmTools_Example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SKApmTools_Example.app; sourceTree = BUILT_PRODUCTS_DIR; };
@@ -41,10 +41,10 @@
4141
607FACE51AFB9204008FA782 /* SKApmTools_Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SKApmTools_Tests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
4242
607FACEA1AFB9204008FA782 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
4343
607FACEB1AFB9204008FA782 /* Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Tests.swift; sourceTree = "<group>"; };
44-
BF4772EE6E8272398D769068 /* README.md */ = {isa = PBXFileReference; includeInIndex = 1; name = README.md; path = ../README.md; sourceTree = "<group>"; };
44+
BF4772EE6E8272398D769068 /* README.md */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = net.daringfireball.markdown; name = README.md; path = ../README.md; sourceTree = "<group>"; };
4545
C8F8091C3D339A6776660E47 /* Pods-SKApmTools_Example.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SKApmTools_Example.release.xcconfig"; path = "Target Support Files/Pods-SKApmTools_Example/Pods-SKApmTools_Example.release.xcconfig"; sourceTree = "<group>"; };
4646
EB2FD992DB3FE4743FB0FB73 /* Pods-SKApmTools_Tests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SKApmTools_Tests.release.xcconfig"; path = "Target Support Files/Pods-SKApmTools_Tests/Pods-SKApmTools_Tests.release.xcconfig"; sourceTree = "<group>"; };
47-
EE0C33DCEE5D33E686F53FAF /* LICENSE */ = {isa = PBXFileReference; includeInIndex = 1; name = LICENSE; path = ../LICENSE; sourceTree = "<group>"; };
47+
EE0C33DCEE5D33E686F53FAF /* LICENSE */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; name = LICENSE; path = ../LICENSE; sourceTree = "<group>"; };
4848
F4484C6B15C8F7EFC2372833 /* Pods-SKApmTools_Example.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SKApmTools_Example.debug.xcconfig"; path = "Target Support Files/Pods-SKApmTools_Example/Pods-SKApmTools_Example.debug.xcconfig"; sourceTree = "<group>"; };
4949
FCA58B36FC9AC7A26740ACFA /* Pods-SKApmTools_Tests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SKApmTools_Tests.debug.xcconfig"; path = "Target Support Files/Pods-SKApmTools_Tests/Pods-SKApmTools_Tests.debug.xcconfig"; sourceTree = "<group>"; };
5050
/* End PBXFileReference section */
@@ -156,7 +156,6 @@
156156
FCA58B36FC9AC7A26740ACFA /* Pods-SKApmTools_Tests.debug.xcconfig */,
157157
EB2FD992DB3FE4743FB0FB73 /* Pods-SKApmTools_Tests.release.xcconfig */,
158158
);
159-
name = Pods;
160159
path = Pods;
161160
sourceTree = "<group>";
162161
};
@@ -227,6 +226,7 @@
227226
developmentRegion = English;
228227
hasScannedForEncodings = 0;
229228
knownRegions = (
229+
English,
230230
en,
231231
Base,
232232
);
@@ -479,6 +479,7 @@
479479
buildSettings = {
480480
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
481481
INFOPLIST_FILE = SKApmTools/Info.plist;
482+
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
482483
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
483484
MODULE_NAME = ExampleApp;
484485
PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.demo.$(PRODUCT_NAME:rfc1034identifier)";
@@ -494,6 +495,7 @@
494495
buildSettings = {
495496
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
496497
INFOPLIST_FILE = SKApmTools/Info.plist;
498+
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
497499
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
498500
MODULE_NAME = ExampleApp;
499501
PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.demo.$(PRODUCT_NAME:rfc1034identifier)";
@@ -516,6 +518,7 @@
516518
"$(inherited)",
517519
);
518520
INFOPLIST_FILE = Tests/Info.plist;
521+
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
519522
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
520523
PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.$(PRODUCT_NAME:rfc1034identifier)";
521524
PRODUCT_NAME = "$(TARGET_NAME)";
@@ -534,6 +537,7 @@
534537
"$(inherited)",
535538
);
536539
INFOPLIST_FILE = Tests/Info.plist;
540+
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
537541
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
538542
PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.$(PRODUCT_NAME:rfc1034identifier)";
539543
PRODUCT_NAME = "$(TARGET_NAME)";

Example/SKApmTools/ViewController.swift

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,29 @@
77
//
88

99
import UIKit
10+
import SKApmTools
1011

1112
class ViewController: UIViewController {
1213

1314
override func viewDidLoad() {
1415
super.viewDidLoad()
15-
// Do any additional setup after loading the view, typically from a nib.
16+
17+
let titles = ["模拟卡顿","网络监控","日志查询","模拟Crash"]
18+
for i in 0 ..< titles.count {
19+
let width = 120
20+
let height = 45
21+
let frame = CGRect(x: Int(view.frame.width)/2 - width/2, y: 90 + i * 70, width: width, height: height)
22+
let btn = UIButton(frame: frame)
23+
btn.backgroundColor = .red
24+
btn.setTitle(titles[i], for: .normal)
25+
btn.addTarget(self, action: #selector(btnClicked(_:)) , for: .touchUpInside)
26+
view.addSubview(btn)
27+
}
28+
SKANRMonitor.sharedInstance.start()
29+
}
30+
31+
@objc func btnClicked(_ sender: UIButton) {
32+
Thread.sleep(forTimeInterval: 1)
1633
}
1734

1835
override func didReceiveMemoryWarning() {

0 commit comments

Comments
 (0)