性能调优------卡顿监控

引言

本文将循着微信iOS卡顿监控系统的思路:起一个子线程,监控主线程的活动情况,如果发现有卡顿,就将堆栈 dump 下来。做一个简单的deom,体验一把。

Coding

废话少说,直接撸代码。首先创建一个NSThread的子类BWKPingThread。

.h文件

一个简单的初始化方法,入参为卡顿监测的时间阈值,超过该阈值即上报卡顿。

1
- (instancetype)initWithThreshold:(NSInteger)threshold;

.m文件

类拓展

在类拓展中声明三个属性:

  • threshold:存储卡端监测时间阈值
  • pingSemaphore:用于卡端监控的信号量
  • runloopObserver:用于监控主线程runloop的observer
1
2
3
4
5
6
7
@interface BWKPingThread()

@property (nonatomic, assign) NSInteger threshold;
@property (nonatomic, strong) dispatch_semaphore_t pingSemaphore;
@property (nonatomic, assign) CFRunLoopObserverRef runloopObserver;

@end

init方法 & addRunloopObserver方法

初始化工作,添加observer观测主线程runloop,在没一个runloop回调中调用dispatch_semaphore_signal()自增信号量。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
- (instancetype)initWithThreshold:(NSInteger)threshold{
self = [super init];
if (self) {
_threshold = threshold;
_pingSemaphore = dispatch_semaphore_create(0);
[self addRunloopObserver];
}
return self;
}

- (void)addRunloopObserver
{
__weak typeof(self) weakSelf = self;
CFRunLoopActivity observedActivities = kCFRunLoopBeforeSources | kCFRunLoopBeforeWaiting | kCFRunLoopAfterWaiting;
_runloopObserver = CFRunLoopObserverCreateWithHandler(kCFAllocatorDefault, observedActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
__strong __typeof(self) strongSelf = weakSelf;
if (strongSelf.pingSemaphore != NULL) {
dispatch_semaphore_signal(strongSelf.pingSemaphore);
}
});
CFRunLoopAddObserver(CFRunLoopGetMain(), _runloopObserver, kCFRunLoopCommonModes);
CFRelease(_runloopObserver);
}

main方法

重写NSThread的main方法,调用dispatch_semaphore_wait()等待信号量的释放,如果等待时间超过阈值,则监测到主线程阻塞、上报卡顿。

1
2
3
4
5
6
7
8
9
10
- (void)main
{
while (!self.cancelled) {
long status = dispatch_semaphore_wait(self.pingSemaphore, dispatch_time(DISPATCH_TIME_NOW, self.threshold * NSEC_PER_MSEC));
if (status != 0) {
NSLog(@"The main thread is blocked.");
dispatch_semaphore_wait(self.pingSemaphore, DISPATCH_TIME_FOREVER);
}
}
}

cancel方法

重写cancel方法,移除observer

1
2
3
4
5
6
7
8
9
- (void)cancel
{
[super cancel];
dispatch_semaphore_signal(self.pingSemaphore);
if (self.runloopObserver) {
CFRunLoopObserverInvalidate(self.runloopObserver);
self.runloopObserver = NULL;
}
}

测试代码

我们在主线程中创建pingThread,并通过模拟耗时操作阻塞主线程以测试卡顿监测。

1
2
3
4
5
6
7
8
9
BWKPingThread * pingThread = [[BWKPingThread alloc] initWithThreshold:1];

[pingThread start];

dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
//耗时操作
for (int i = 0; i < 100000000; i ++) {
}
});

通过控制台log可以看到成功监测到卡顿

1
2018-04-21 21:20:20.309915+0800 Ping_Demo[98239:5860372] The main thread is blocked.

卡顿堆栈?

这里推荐一个三方库plcrashreporter。通过plcrashreporter可以拿到所有线程堆栈,国内很多Crash上报平台都是通过plcrashreporter实现的。