前言html
已不多写文章,不过此次感受有必要写一下。由于:git
1. 这个方案经过debug逆向得来,颇有参考意义。github
2. iOS这方面资料很是少,作这块时,不管国内外,翻遍了google,baidu都没太多合适的资料。sql
故此,我以为把整个流程记录下来,你能够认为这是一次iOS instruments的debug之旅。xcode
问题原由网络
最近作iOS性能测试,要监控一段时间内App的CPU占用和网络流量。遗憾的是,iOS instruments提供的Activity Monitor和Network模板并不知足个人需求。在UI工具中,Activity Monitor只提供了CPU瞬时值,Network也只提供了总流量,它们均不提供采集样本值。数据结构
因为iOS闭源,放出的资料又少,故解决此问题的方案屈指可数:app
(1). 设备越狱,经过后台守护进程采集数据。工具
- 缺陷:新设备没法越狱。源码分析
(2). 在产品中嵌入性能数据采集模块。
- 缺陷:我不倾向在产品中嵌入测试模块;另外,该方案对第三方产品也失效。
(3). 从instruments结果文件(.trace)中尝试获取样本值。
- 缺陷:能不能作都不知道…
因方案1,2有硬伤,故选方案3尝试。
其实,我开始时也并不肯定trace文件包含样本数据,但instruments可经过trace文件恢复监控过程的柱状图,它给了我继续这个方向的信心。
而从trace文件获取样本值,有2个方向:
(1)分析trace结构,获取明文数据
- trace实际上是文件夹,但里面文件内容均为二进制,遂放弃......
(2)经过Undocumented API解析trace文件
- 因为instruments能够解析trace文件,那么(2)方法是必定可行的,问题是怎么找到相关的Undocumented API。
Undocumented API可行性确认
Lucky,我同事发现了这玩意:TraceUtility,它一个是获取Time Profiler样本值的工具(那做者也和我同样遇到这种蛋疼问题......),而且Readme中找到重要线索:
(1)所需的Undocumented API在/Applications/Xcode.app/Contents/Applications/Instruments.app/Contents/Frameworks:
DVTInstrumentsFoundation.framework
InstrumentsPlugIn.framework
(2)Instruments模板以PlugIns形式存在于
/Applications/Xcode.app/Contents/Applications/Instruments.app/Contents/PlugIns
TraceUtility源码分析
项目就2个文件:
InstrumentsPrivateHeader.h: Undocumented API和Undocumented数据结构声明。
main.m:样本数据提取主流程。
深刻Debug分析
然而TraceUtility仅用于获取Time Profiler样本数据,并不能知足我获取CPU和网络流量样本的要求。
我须要解决的问题是:
(1)肯定instruments处理类。
(2)从处理类中读出样本数据。
1. 肯定instruments处理类
方式很简单,在loadDocument后,debug变量trace._baseInstruments
对比图一、2,XRSamplerInstrument=>XRSamplerRun,XRActivityInstrument=>XRActivityRun?,XRNetworkingInstrument=>XRNetworkingRun?;
在另外一个github项目class-dump-o-tron, 搜到了相关信息:XRSamplerRun.h,然而在ActivityPlugin.xrplugin下却没找到XRActivityRun.h,而是XRActivityInstrumentRun.h(大Apple的命名能统一点嘛……);在XRNetworkingPlugIn.xrplugin下,找到了XRNetworkingRun.h;
2. 读取trace样本数据
(1)CPU样本结果
debug变量run._data,发现了样本数据
TraceUtility使用了Undocumented API获取数据,而我在XRActivityInstrumentRun.h没找到相关API,直接经过反射获取吧。
至此,CPU样本数据获取完成。
(2)网络流量样本结果
XRNetworkRun和XRActivityInstrumentRun对象属性不同,没有_data,但_saveActivityQueries中有段sql,初步预估这玩意用了localdb,但db类型未明。另外,估计_saveInstrumentUUID应该db文件。
而后cat 2A183EAD-5B9C-45DD-B2BA-D63DCD1165D4看下,由于文件可能会在头部加注类型信息,cat结果以下:
捕获SQLite文件一个……接下来的事情就是分析表结构了,没什么难度,不做详述了。至此,网络流量样本数据获取完成。
--------------------我是一条的分割线--------------------
补充部分-20160424
关于以上代码,个人实施是在xcode7.2上,但到了xcode7.3,出现些坑爹问题:
在调用PFTLoadPlugins时候,出现crash,以下:
意思是,在xcode7.3后,PFTLoadPlugins()调用了applicationDirectoryName,但在调用applicationDirectoryName前,必须先调用initializeApplicationDirectoryName。而PFTLoadPlugins没有帮咱们作init,因此呢,crash了!!!
解决方案:本身动手调用.....
(1)加载DVTFramework。
位置:/Applications/Xcode.app/Contents/SharedFrameworks
缘由:initializeApplicationDirectoryName在该库中...
(2)在.h文件增长
@interface DVTDeveloperPaths : NSObject
+ (void)initializeApplicationDirectoryName:(id)arg1;
@end
(3)在main.m中,PFTLoadPlugins()前增长:
id test = @"../../../../../../../Applications/Xcode.app/";
[DVTDeveloperPaths initializeApplicationDirectoryName:test];
完成以上步骤后,PFTLoadPlugins()能够正常执行!