360移动端性能监控实践QDAS-APM(iOS篇)
作者:CQITer小编 时间:2019-07-02 16:51
app的性能问题是影响用户体验的重要因素之一。性能问题主要包含:崩溃、网络请求错误或者超时、UI响应速度慢、主线程卡顿、CPU和内存使用高、耗电量大等等。大多问题的原因在于开发者错误地使用了线程、锁、系统函数、编程规范问题、数据结构等等。解决这个问题的关键在于尽早发现和定位问题。
360作为一家注重用户体验的公司,app的性能问题无疑是被重点关注的,我们也总结出了一套自己的app性能监控体系。在平时开发和用户反馈的问题中,我们对性能问题进行了归纳,总结出了5个分别是:资源文件如何掌控、 版本质量如何保证、线上问题如何排查、开发阶段如何防止性能衰减、性能监控是否能真实反映用户体验。同时学习了业内相对完善的性能监控平台上的功能原理。从而得出了360在iOS端移动端线上性能监控方案——QDAS-APM。
二、功能和原理
QDAS-APM已经实现以下功能监控:
页面渲染时长
主线程卡顿
网络错误
FPS
大文件存储
CPU
内存使用
Crash
启动时长
下面按照功能详细介绍实现细节和原理。另外用户在使用app时会感知性能问题,我们可以将其转化为具体的性能监控指标。
1. 页面渲染时长
什么是页面渲染时长?页面渲染时长其实是从页面初始化到用户能看到页面效果的时间长度。所要了解的指标有:
生命周期系统方法执行时长
页面类名
启动类型
执行耗时
插件名称
关键度量的指标是执行耗时,不同的方法和步骤产生的耗时在用户能接受的范围内才被认为是合理。其他指标则是起有关联性作用和定位问题。直接hook UIViewController的方法明显是不可行的,原因是它只作用在UIViewController的方法,而app中大部分都采用继承UIViewController的方式。
这里列出两个可行性方案:
采用KVO,我们知道对于任意对象进行KVO操作时,系统都会帮你动态的创建一个复制类,同时实现了setter getter函数的覆盖和函数实现。
采用runtime遍历所有类为UIViewController的子类,再进行动态替换。
这两种方式更加推荐第一种,出于对兼容性、性能、以及能够直接获取UIViewController的子类的IMP。那具体如何实现呢?总结归纳为三步骤:
需要创建一个UIViewController的类别,对UIViewController的实例进行KVO,目的是让KVO创建需要监控UIViewController的子类。
添加需要监控的方法,在KVO创建出来的子类添加需要Swizzle的方法对应的SEL及其IMP。目的是控制调用原来类的方法时机。
在UIViewController的实例销毁时,在dealloc方法里将KVO监听移除,不然会导致Crash。
举个例子:我们以监控到qh_viewDidLoad方法举例:
static void qh_viewDidLoad(UIViewController *kvo_self, SEL _sel)
{
Class kvo_cls = object_getClass(kvo_self);
Class origin_cls = class_getSuperclass(kvo_cls);
// 注意点
IMP origin_imp = method_getImplementation(class_getInstanceMethod(origin_cls, _sel));
void(*func)(UIViewController *, SEL) = (void(*)(UIViewController *, SEL))origin_imp;
CFAbsoluteTime startTime = CACurrentMediaTime();
func(kvo_self, _sel);
CFAbsoluteTime endTime = CACurrentMediaTime();
NSTimeInterval duration = (endTime - startTime)*1000;
NSLog(@"Class %@ cost %g in viewDidLoad", [kvo_self class], duration);
}
会有一种特殊情况,如果KVO生成的类中对应的类原本没有实现监控方法,那么会造成什么后果呢?KVO内部生成的NSKVONotifying_ViewController实际上时继承自ViewController,因此直接取出对应的IMP调用。
OK,上面说的是对UIViewController类方法的执行时长统计。我们还想知道用户真正页面跳转后看到第一针页面图像的时长要如何采集呢?
那是不是将UIViewController类的init+loadView+viewDidLoad+viewWillAppear+viewDidAppear方法执行时长之和就是页面渲染时长了呢?
答案是否定的,下面举了三个反面例子:

如何才能判断屏幕渲染完成?是否能间接获取屏幕渲染时长?
对于异步回调和异步渲染这两种方式,用上面提到的5个方法执行时长之和是不适用的。接下来看下如何相对准确地来统计和计算的方案。




