前言
已很少寫文章,不過這次感覺有必要寫一下。因為:
1. 這個方案通過debug逆向得來,很有參考意義。
2. iOS這方面資料非常少,做這塊時,無論國內外,翻遍了google,baidu都沒太多合適的資料。
故此,我覺得把整個流程記錄下來,你可以認為這是一次iOS instruments的debug之旅。
問題起因
最近做iOS性能測試,要監控一段時間內App的CPU占用和網絡流量。遺憾的是,iOS instruments提供的Activity Monitor和Network模板並不滿足我的需求。在UI工具中,Activity Monitor只提供了CPU瞬時值,Network也只提供了總流量,它們均不提供采集樣本值。
由於iOS閉源,放出的資料又少,故解決此問題的方案屈指可數:
(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
對比圖1、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()可以正常執行!



BP$K`K_XIIH6J77 E7I](3$)BP$K`K_XIIH6J77](/image/aHR0cHM6Ly9pbWFnZXMyMDE1LmNuYmxvZ3MuY29tL2Jsb2cvNDYwNTcvMjAxNjAzLzQ2MDU3LTIwMTYwMzI4MTY0MjU3NTk4LTU2MDg4NjQ5Mi5qcGc=.png)



![Y3GL1%)C3@72RR]E]00`Z52 Y3GL1%)C3@72RR]E]00`Z52](/image/aHR0cHM6Ly9pbWFnZXMyMDE1LmNuYmxvZ3MuY29tL2Jsb2cvNDYwNTcvMjAxNjAzLzQ2MDU3LTIwMTYwMzI4MTY0MzAzMDE5LTE1ODU1ODUyNzAuanBn.png)
