苏宁蛙测基于Instruments检测iOS性能技术
【51CTO.com原创稿件】1.背景
在苏宁智慧零售的战略中,移动应用占据重要位置,线上应用的用户体验对于产品推广有直接影响,而作为移动两大重要阵营之一的iOS系统,应用上架前的性能指标显得尤为重要。在不嵌入代码的前提下,蛙测平台需要获得应用运行时的内存、CPU、FPS、冷启动时间、热启动时间等数据,提供给开发部门,进行上线前优化。
苏宁现有的iOS应用性能测试数据,基本都是开发人员在Mac环境下用Xcode工具,基于被测应用源码或者特定签名下通过原生的Instruments工具来获取应用冷启动时间、热启动时间、内存消耗、帧率等性能指标。
对于测试人员来说,首先没有充足的Mac电脑提供,其次,出于代码的保密性,无法为测试人员开发代码权限,同时技术门槛也较高,为了让测试人员也能进行iOS应用性能测试,基于代码外黑盒方式获取iOS应用性能数据,使用TraceUtility工具解析数据,在前端页面用图表方式为测试人员提供iOS应用性能测试服务。
2.技术选型和实现原理
2.1 Instruments工具组件介绍
Instruments是Xcode的一个工具集,为我们提供了强大的程序性能分析及测试能力。它是以一个独立的 App 的形式存在的,你可以在 Xcode -> Open Developer Tool -> Instruments 中打开它。打开后功能组件页面如下图:
一、Time Profiler使用简介:
- TimeProfiler见名知意:时间分析工具,它会按照设定的时间间隔(默认1毫秒)来跟踪每一线程的堆栈信息(stacktrace),并通过比较时间间隔之间的堆栈状态,来推算出某个方法执行了多久,给出一个近似值。
二、CPU Activity Log CPU活动日志工具简介;
- CPU Activity instrument工具给出了一个设备在做什么的记录。
三、Core Animation动画渲染使用简介:
- Core Animation系要注意的一点是必须是真机调试,用于调试离屏渲染,绘图,动画,等操作,用于检测FPS信息。
四、Activity Monitor内存使用情况简介:
1)Summary(Summary of data) :Process(进程)使用内存、CPU使用时长等摘要信息:
其中参数的含义如下:
- process id ——进程id
- process name ——进程名
- user name——用户名
- %CPU——cpu占比
- threads——线程
- real mem——真正使用的内存
- virtual mem——虚拟内存
- architecture——架构
- cpu time——CPU时间
- sudden term——突然项(N/A:不适用)
2)Samples(a list of samples) ----一系列抽样(即不同时间,CPU使用时间、内存使用情况的样本值)
参数的含义如下:
- Physical Memory Wired——操作系统占用的内存
- Physical Memory Active——除操作系统外其他进程占用的内存
- Physical Memory Inactive——最近被释放的内存
- Physical Memory Used——profiling当前进程时使用的总内存
- Physical Memory Free——当前的可用内存
- Total VM Size——虚拟内存的占用量
五、Energy Usage Level 简介
查看应用的耗电量等级。电量使用level为0-20,1/20:表示运行该App,电池生命会有20个小时;20/20:表示运行该App,电池电量仅有1小时的生命。
2.2 instruments命令行调用方法
instruments命令
usage: instruments [-t template] [-D document] [-l timeLimit] [-i #] [-w device] [[-p pid] | [application [-e variable value] [argument ...]]]
- -t是指定模板;
- -D 指定生成的.trace文件;
- -l 指定运行时间,此处单位为ms;
- -w 指定设备id(把设备通过usb连接电脑后,设备id可通过system_profiler SPUSBDataType查看);
application 即待测试的App名;
如下示例:
在fcbba9b83bf99c97e5d4d56d99a264d083d5e2cd设备上启动”Time Profiler”模板运行com.suning.XX录制30000ms的Profiler.trace文件,输出路径为/Users/monky/Work instruments -l 30000 -w fcbba9b83bf99c97e5d4d56d99a264d083d5e2cd -v -t “Time Profiler“ -D /Users/monky/Work/time Profiler.trace com.suning.XX
自定义多模板.tracetemplate文件
有时候需要同时监控好几个模板,这个时候可以导出一个自己需要的.tracetemplate文件。
- a.首先打开instruments,在主界面选择需要测试的模板,如下图,通过右上角的+添加。
如下实例:
Instruments -w fcbba9b83bf99c97e5d4d56d99a264d083d5e2cd -v -t "CustomTemplate" -D templateData.trace com.suning.XX
2.3 instruments数据解析功能实现
我们生成一个自定义的数据模板MyTemplate.tracetemplate,根据instruments工具生成的trace文件,使用TraceUtility对文件进行解析,获取存储的信息。
Trace 文档的基本结构
文档结构的逻辑能从 Instruments 的交互逻辑看出来,一份文档有一个目标设备,一个监测的进程和一组 profiling 模版,然后可以使用同一组模版进行多次 profile。而对应到 Instruments 的代码中,一份文档就是一个 XR Trace 对象,除了一些元数据,它还包含一个 XR Instrument 对象的数组,其中每一个,又包含了一个 XR Run 的数组。
所以接下去要做的事情就是,找到从不同模版的 XR Instrument 中读取 XR Run 对象中的数据的方法,然后依次遍历即可。
XR Instrument对象
数据读取和保存的代码主要在 InstrumentsAnalysisCore.framework 这个框架里,存储格式也用了 SQLite,把各种读取写入查询都抽象了出来,比如用户在 Instruments 里用鼠标选了一段时间来看这段时间的数据,这样的过滤操作就不需要在每个模版里单独实现了。同时,因为数据的结构种类也比较有限,比如调用栈树,样本列表。
XRContext
在 Instruments 里,XRContext 就体现在模版列表下面的这个导航条。它是树结构的,每次选中显示一个 context,那从根结点到这个 context 就是一条 context path。XRContextContainer(通常是 view controller)保存着 context 的引用,叫做它的 contextRepresentation,而 XRContext 也引用一个真正展示它数据的 XRContextContainer(通常是 view),叫做它的 container。
不同的模版当然会有不同的 contexts,但是有时候一个模版也会包含多个不同的 contexts,所以在用户选择不同的模版查看数据,或者从那个导航条里切换当前模版的不同的数据视图的时候,当前的 context 就会变化,新的 context 的 -[XRContext display] 方法被调用,然后这个 context 会通过 -[XRContextContainer displayContext:] 传给相对应的 XRContextContainer,然后这个 container(通常是 view controller)就可以加载数据,刷新视图了。
也就是说文档中的大部分数据只有在真正需要显示的时候才会被读取,从 Instruments 用户的角度来看很容易理解,不过对于 TraceUtility 的用户来说,就是需要知道,如果自己读取的数据都是 nil,或者空数组之类的,就可以看看是不是忘记调用这个 -[XRContext display]方法了。
每一种XRContextContainer对象存储的结构层次各有不同,以下给出三种结构解析方法。
TimeProfiler模板解析实例:
CPU Activity Log模板解析实例:
Connections 模板数据解析实例:
Search Paths
虽然说要调用 Instruments 的代码只需要链接上它的 framework 就可以了,不过这里还有几个值得一提的地方。
首先是编译器搜索我们要链接的 framework 的目录。参考 Xcode 项目配置的 FRAMEWORK_SEARCH_PATHS 变量。
/Applications/Xcode.app/Contents/SharedFrameworks /Applications/Xcode.app/Contents/Applications/Instruments.app/Contents/Frameworks
如果这个没有设置对的话,链接的时候会报错,没法生成可执行程序。
然后是编译好的程序在启动过程中搜索这些动态链接的 framework 的目录。参考 Xcode 项目配置的 LD_RUNPATH_SEARCH_PATHS 变量。
/Applications/Xcode.app/Contents/SharedFrameworks /Applications/Xcode.app/Contents/OtherFrameworks /Applications/Xcode.app/Contents/Developer/Library/Frameworks /Applications/Xcode.app/Contents/Developer/Library/PrivateFrameworks /Applications/Xcode.app/Contents/Applications/Instruments.app/Contents/Frameworks
如果这个没有设置对的话,程序还是可以编译,但是程序运行的时候会报错。另外,这里不仅包含上一个配置里的目录,还多了别的,是因为我们链接的 framework 还依赖了别的 framework,这些 framework 也需要在运行的时候由 dyld 一起加载。
最后是 Instruments 自己搜索 Packages 的目录,这些目录都在 Instruments.app 的安装目录下,而函数 NSString *PFTInstrumentsAppContents() 就是用来获取这个安装目录的。它通过调用 +[NSBundle mainBundle] 来确定当前进程是 Instruments 的主 App 还是附加的命令行工具,然后返回正确的结果。
然而在我们的进程中,+[NSBundle mainBundle] 返回的结果并不能被这个函数正确识别和处理,所以我们需要 hook 这个方法来返回 Instruments.app 所对应的 bundle,从而使得 Instruments 能够正确找到需要加载的包。
2.3解析后数据
Time Profiler解析出数据如下图:
CPU Activity Log解析出数据如下图:
Core Animation解析出数据如下图:
Activity Monitor内存使用情况解析出数据如下图:
Energy Usage Level解析出数据如下图:
Connections解析出数据如下图:
3.应用效果
苏宁蛙测平台将instruments检测iOS性能技术应用在iOS稳定性测试中,将用户提测的任务结果收集分析后制作成图表展示,方便测试人员直接高效的获得目标测试App在不同系统版本、机型上的性能表现,提高相关人员的产出效率。如下图测试报告节选截图:
4.总结
考虑到文章篇幅,本文仅介绍了苏宁蛙测平台在移动端性能检测的冰山一角,有关于移动稳定性测试其他技术点将会在后续文章中进行分享。蛙测平台会在大促期间力保每一个上线应用安全、稳定、健壮,站好第一班岗,为苏宁软件质量保驾护航。同时我们也会不断成长,提升技术面,将“测试,原来可以如此简单!”作为自己前进的目标,为苏宁智慧零售战略提供坚实保障。