- SDK分为
Monitor性能监测数据获取和Exception异常错误获取两部分。具体实施测试机会先在线下检控阶段根据理论值经验+开发工具的检测进行一波针对的优化。设定好Exception理论错误阈值。Monitor性能监测获取会将用户页面性能数据进行收集保存为本地文件,打开app的时候进行监测文件上报记录,解析文件,按机型和系统统计帧率/内存/CPU阈值报警数据。Exception异常错误获取包括:SignalException崩溃异常,UncaughtException崩溃异常,FpsException帧率卡顿异常,cpuException使用率超限,viewDurationException页面视图展示出的时间过长,networkException网络接口数据异常。- 将
Monitor性能监测采集分析指标作为专项优化的性能基准点。后期也可以利用采集到的大量指标数据利用机器学习知识,通过iOS的CordeML框架加载机器学习训练的模型输出Exception需要的合理阈值。
1.卡顿--SDK线上性能监控
1.1 页面帧率监控
- iOS系统中顺滑屏幕刷新率为60Hz(60次每秒)。多次掉帧或大幅度偏离16.7ms的刷新值,即可看作是发生了卡顿。基于
CADisplayLink实现FPS监控指示器。 - 每次页面刷新时会发出一个屏幕刷新信号,
CADisplayLink允许我们注册一个与刷新信号同步的回调处理。 CADisplayLink以屏幕刷新的频率调用指定selector,也就是说每次屏幕刷新的时候就调用selector,在selector方法里面统计每秒这个方法执行的次数,通过次数/时间就可以得出当前屏幕的刷新率了。- 如果出现频繁跳帧或帧率持续过低说明流畅度存在问题。
1.2页面卡死监控监测
- 结合帧率变化监测数据和 **4.1 **CPU使用情况
2.耗时--SDK线上性能监控
2.1 APP启动时间监测
- 冷启动 :App 总冷启动时间 = t1( main()之前的加载时间 ) + t2( main()之后的加载时间 )。
t1 = 系统的 dylib (动态链接库)和 App 可执行文件的加载时间;
t2 = main()函数执行之后到AppDelegate类中的applicationDidFinishLaunching方法执行结束前这段时间。
t1这部分时间开发阶段会通过xcode的Run → Environment Variables 进行采集。
t2这部分时间会代码注入进行计算开发和线上持续监测上报。
- 热启动:会对app的生命周期函数进行监测。
applicationWillEnterForeground(将进入前台)~applicationDidBecomeActive(已经变成前台)之间的计算时间进行持续监测上报。
2.2 页面启动时间监测
方案一:
hook打开页面方法操作,注入:异步线程开定时器,每100ms截图一次,对视图进行分析,判断是否白屏或预加载屏,直到非白屏或到达时间阈值,记录区间加载时间。
方案二
通过观察者的模式系统会生成一个NSKVO_Controller类,然后hook当前页面开启方法viewDidLoad,记录当前页面创建控件所需要时间。
2.3 页面展示出网络数据加载时间监测
方案一:
hook网络数据页面startLoading方法,注入:异步线程开定时器,每100ms截图一次,对视图进行分析,判断是否有loading图和页面是否白屏或预加载屏,直到页面上视图分析存在文字结束或到达时间阈值,记录区间加载时间。
方案二:
- 页面上如果出现
tableView、collectionView控件,可以使用hook代理方法cell将要出现的方法(tableView:willDisplayCell:forRowAtIndexPath:),在这里做时间统计。 - 如果界面不包含
tableView、collectionView控件,可以在基类中定义一个创建视图的基类方法(createUI),通过hook这个基类方法来实现时间统计。(针对项目改动比较大)
3.发热耗电--SDK线上性能监控
3.1 CPU使用率监测
- 在进程运行时,每个线程对 CPU 的使用率不同。各个线程对 CPU 使用率的总和,即可视为当前 App对CPU的占用率。
- 基于Mach内核底层类,获取app启动运行时长的底层原理其实就是对Mach内核底层类进行操作的。Mach中暴露可知每个线程中都存在
thread_basic_info结构体,里面存储的就有CPU使用率。通过对底层代码进行操作获取到Mach task,进而可以获取到线程信息thread_basic_info结构体。 - 这样实时统计总CPU使用率,高于百分之六十?总量异常时,提取出高CPU使用的线程数据和页面。
3.2 网络流量监测
NSURLProtocol: 采用继承抽象类NSURLProtocol对发起的网络请求进行拦截,initWithRequest,didReceiveResponse,didReceiveData方法记录关键属性,从而作为依据获取下行流量数据。NSURLProtocol方式要编写一定的代码量来实现,而且NSURLProtocol并不能拦截所有形式的请求,要对NSURLSession发起的shareSession形式单独处理。NetworkExtension:在以后的业务发展和机型占比上,如果我们的app可以允许放弃iOS9以下的用户,那么可以在官方NetworkExtension库里接管所有网络请求来获取数据。
4.内存--线下性能检控
4.1 内存泄漏检控
- 内存泄漏的产生属于固定代码编写方式层面。
4.2 爆内存监控
- 开发过程中会借助开发工具观察记录页面产生的内存变化
- 代码方案尝试Hook内存分配方法,按页面记录并抓取内存分配的堆栈和内存大小。
- 分析产生的页面的对象, 然后对可能存在的地方和循环积累大量对象的地方,手动实现
autoReleasePool自动释放池。
4.3 IO量检测
- 我们的app对于本地存储文件的读写应用场景不多,所以这里产生的性能问题应该很小。
- 使用
Instruments工具System Usage、File Activity和Network。统计出运行状态下应用的文件和网络IO操作数据。
5.项目瘦身--线下性能检控
- 使用fui(Find Unused Imports)定期扫描工程中不用的类
- APPCode定期检查工程中无用的代码
- 压缩文件资源管理
6.存储获取数据
6.1 Monitor监测统计存储
- 把监测到的数据存到json文件,文件以机型+系统版本+app名称+app版本命名。每一个 app的生命周期算作一个统计数据文件,保存到沙盒。gzip的格式压缩上传,每次启动APP,后台线程上传到后台服务,每次最大10个文件。上传失败就保存到本地等待下次启动上传。
- 文件内容包含对每个页面帧率
Fps,Cpu使用率, 页面展示数据的时间进行监测的数据。以页面名称为key,value为数组,内部包含:keepTime页面停留持续时间,openTimes页面打开次数。- 这个页面的
fps_max最大值,fps_min最小值,fps_avgs质量值(字符串拼接,前30s,fps的每秒变化值),fps_tota生命周期内页面fps的和。 cpu_max使用率最大值,cpu_min使用率最小值,cpu_avgs使用率质量值(字符串拼接,前30s,cpu的每秒变化值),cpu_total生命周期内页面cpu的和。viewDuration_max页面展示启动时间的最大值,viewDuration_min页面展示启动时间的最小值,viewDuration_avgs页面启动时间的质量值(字符串拼接,每一次打开页面的时间)
- 文件内容包含对APP信息的监测,内部包含
dev_id设备ID,dev_n设备机型,system_v设备系统,app_napp名称,app_vapp版本,network_type网络类型,time启动时间戳,app_launchDuration冷启动时间,热启动时间(字符串拼接)。
3.5.2_1585536369.json 统计文件内容如下:
{
"APP" : {
"dev_id" : "03715FB2-0C31-4C65-9AE7-92A4A5E8DCEF",
"system_v" : "12.3.1",
"app_launchDuration" : "4.804108",
"dev_n" : "iPhone9,2",
"time" : "1585536369",
"app_n" : "XXX",
"app_hotLoadDuration" : "0.297370,0.297456",
"app_v" : "3.5.2",
"network_type" : "WIFI"
},
"page" : {
"ECProductDetailController" : {
"cpu_min" : "3",
"viewDuration_max" : "0.733516",
"keepTime" : "30",
"fps_min" : "40",
"viewDuration_total" : "1.297453",
"cpu_total" : "600",
"cpu_avgs" : "86,46,33,25,10,9,11,11,7,6,37,10,41,22,12,15,13,11,8,7,6,23,6,8,3,17,66,30,9,12",
"fps_avgs" : "43,58,60,60,60,60,59,60,60,60,50,60,50,60,60,60,60,60,60,40,60,58,60,55,45,59,60,60,60,58",
"viewDuration_min" : "0.251687",
"cpu_max" : "86",
"fps_max" : "60",
"viewDuration_avgs" : "0.733516,0.251687,0.312250",
"fps_total" : "1715",
"openTimes" : "3"
},
"ECHomePageController" : {
"cpu_min" : "2",
"viewDuration_max" : "2.586971",
"keepTime" : "14",
"fps_min" : "46",
"viewDuration_total" : "2.586971",
"cpu_total" : "321",
"cpu_avgs" : "9,2,50,34,11,28,34,44,15,28,16,13,16,21",
"fps_avgs" : "59,60,48,58,60,60,46,55,60,58,59,57,60,59",
"viewDuration_min" : "2.586971",
"cpu_max" : "50",
"fps_max" : "60",
"viewDuration_avgs" : "2.586971",
"fps_total" : "799",
"openTimes" : "1"
},
"FLBFlutterViewContainer" : {
"cpu_min" : "5",
"cpu_avgs" : "56,69,43,5,5,10",
"fps_avgs" : "41,35,60,60,60,60",
"keepTime" : "6",
"fps_min" : "35",
"cpu_max" : "69",
"fps_total" : "316",
"cpu_total" : "188",
"fps_max" : "60"
}
}
}
6.2 Exception异常错误存储
以txt文件的形式存储下来,内部包含dev_id设备ID,dev_n设备机型,system_v设备系统, app_n名称,app_v版本,network_type网络类型,time启动时间戳,name页面名称,reason产生错误的具体值(如fps,cpu)。Exception错误类型ErrorType,堆栈信息等。可以接入钉钉Webhook报警和根据业务上传自己的后台建立系统。
SignalException崩溃异常UncaughtException崩溃异常FpsException帧率卡顿异常(帧率持续3秒左右均小于设置阈值40以下,保存错误记录,发送错误报警)cpuException使用率超限( 帧率持续3秒左右均大于设置阈值60,保存错误记录,发送错误报警)viewDurationException页面视图展示出的时间过长networkException网络接口数据异常(http响应code非200或失败)
例: 3.5.2_1585537822.json 异常错误报告内容如下:
<b>发送异常错误报告</b>
<b>app_n:</b>XXX
<b>app_v:</b>3.5.2
<b>system_v:</b>12.3.1
<b>dev_n:</b>iPhone9,2
<b>dev_id:</b>03715FB2-0C31-4C65-9AE7-92A4A5E8DCEF
<b>network_type:</b>WIFI
<b>time:</b>1585537822
<b>ErrorType:</b>SignalException
<b>callStackSymbols:</b>
0 enjoyChanging 0x000000010162af08 SignalExceptionHandler + 140
1 enjoyChanging 0x000000010170ff28 BLYBSDSignalHandlerCallback + 256
2 libsystem_platform.dylib 0x000000018eba19ec <redacted> + 56
3 libsystem_pthread.dylib 0x000000018eba7094 <redacted> + 380
4 libsystem_c.dylib 0x000000018ea87ea8 abort + 140
5 libc++abi.dylib 0x000000018e154788 __cxa_bad_cast + 0
6 libc++abi.dylib 0x000000018e154934 <redacted> + 0
7 libobjc.A.dylib 0x000000018e16be00 <redacted> + 124
8 enjoyChanging 0x0000000101733358 _ZL24BLYCPPExceptionTerminatev + 1932
9 libc++abi.dylib 0x000000018e160838 <redacted> + 16
10 libc++abi.dylib 0x000000018e160434 __cxa_rethrow + 144
11 libobjc.A.dylib 0x000000018e16bbc8 objc_exception_rethrow + 44
12 CoreFoundation 0x000000018ef1d11c CFRunLoopRunSpecific + 544
13 GraphicsServices 0x000000019111d79c GSEventRunModal + 104
14 UIKitCore 0x00000001bb8cb978 UIApplicationMain + 212
15 enjoyChanging 0x0000000100e4a7e0 main + 160
16 libdyld.dylib 0x000000018e9e28e0 <redacted> + 4