本系列文章将围绕如何构建客户端性能保障体系,分别从开发周期的各个阶段来讨论构建完整的性能保障体系;
- 开发阶段
静态分析和代码风格一致性
模板代码
执行单测
- 测试阶段
自动化性能测试
- 线上运行阶段
APM 性能监控
性能测试是指收集每次版本迭代的性能差异,暴露进程启动和运行时方法执行快慢、帧率等和体验息息相关的指标。有些团队会定义性能基线,每次回归测试时由人工执行一些主要用例。
对于性能测试而言,持续的性能测试是防止性能劣化非常有效的手段,而如何保证持续性,关键就是如何实现人工的替换,而尽可能采用自动化的执行方式;
本文将从实操的角度出发,概述如何实现完备的 iOS 自动化性能测试;
Instruments
Instruments 是 Xcode 工具链中重要的性能分析工具,能够揭示大部分性能问题。在 Instruments 10 时迎来了重大重构版本的 Instruments。将界面和分析核心完全解耦,使开发者可以自定义模板,阶梯性的定义开发者需要的初级、中级以及高级应用。
Instruments 10 将命令行工具集成到 xcrun xctrace 工具中,基于 xctrace 可以很方便的将分析结果 trace 文件解析成 xml 让其他工具链读取。
# 命令行启动 TimeProfile 并导出分析结果 xcrun xctrace record --template TimeProfile --launch --output ./output.trace longbridge-ios-app.app # 将分析结果导出为 xml 格式 xcrun xctrace export --input ${trace} --toc --output ${exportXMLName}
将 xcrun 集成到我们的自动化任务中,便可以执行导出测试数据做进一步分析和展示了。
然 Instruments 生成的数据是类数据库表的二进制格式,在 Instrument 10 发布之前,要解析性能数据可以借助开源库 TraceUtility 来实现,而 TraceUtility 是通过逆向工程调用了 Instruments 相关的 Framework 来实现的,也仅仅是实现了一部分的数据导出,如果要完全解析将是一个漫长的逆向过程。
在 Instruments 10 之后,xctrace 提供了导出的功能,但是导出的数据是未经符号化的函数调用地址,且仅有函数地址,缺少符号化必要的偏移量、基地址等信息(如果哪位同学知道这些信息在哪里请告诉我不胜感激)。
因为无法符号化,所以我不得不放弃使用 Instruments TimeProfile 这样的模板来统计方法调用耗时,而是采用了运行时统计。
相比 Instruments 的基于采样,运行时统计有一定侵入性,统计代码本身也可能会影响性能数据,但相比 Instruments 采样的数据更精确一些。
除了方法耗时无法符号化之外,Instruments 的另外一些模板还是值得使用的,比如获取启动耗时、获取所有网络请求记录等。
运行时统计方法耗时
所有 OC 方法的调用都会走
objc_msgSend
方法,所以理论上通过Hook objc_msgSend
方法 就可以统计所有方法的调用及耗时,而且很容易获取到 Class 、SEL 等符号信息。具体如何实现可参考 DoraemonKit 的代码片段,或者博文 https://github.com/QiShare/Qi_ObjcMsgHook 本文将不再展开概述 hook 原理。
统计相关代码可以通过 cocoapods 指定 debug 环境引入或者直接注入的形式,最好是不要发布到线上,造成线上的性能影响。
APP 端统计到耗时方法后需要做进一步的处理,比如拿到方法名称后去拉取 Git log 定位修改人,这时候就需要电脑端和 app 进行通信,本文推荐使用 peertalk(https://github.com/rsms/peertalk) 通过 USB 传输桥接通信,并且提供了更高级别的封装,使用简单可靠。
iOS自动化测试
有了上面的获取耗时的方法,我们还需要自动执行操作,iOS 自动化测试限于平台限制,各个框架基本都是基于 WebDriveAgent 来实现的,这种方式有一定缺点,第一是比较慢,第二是无法保证测试任务的高可用性,实际测下来总有一些环节会失败,所以需要增加必要的重试逻辑。
最好的方式是直接在 APP 内注入远程调用控制逻辑,本文受限于开发周期而采用老牌的 Appium 来驱动:
对于测试脚本,我使用的是 Nodejs,把方法耗时和 appium 结合以后,写出来的测试代码大概是这样:
最终导出来的方法耗时效果:
定位到耗时方法后,还可以通过 Git Log 来获取到修改人,从而通知到相关开发,甚至自动的创建 jira 任务跟踪修复情况。
而对于其他性能数据,比如 FPS、CPU 占用等,如何获取可以参考滴滴的 DoraemonKit 的相关实现,获取到之后通过 peertalk 实时推手到电脑端做进一步的处理,之所以推送到电脑端来处理是为了尽可能减少注入的统计代码对原 APP 整体性能的影响。
另外还需要一些必要的基础设施,例如对性能数据的统计分析、展示页面等,推荐使用 Nodejs 也是为了能够更方便的构建这些基础设施,毕竟 Full-Stack JavaScript 的学习成本还是比较低的。