隨著應(yīng)用的發(fā)展越來(lái)越大筷转,產(chǎn)品的開(kāi)發(fā)和測(cè)試逐漸開(kāi)始關(guān)注app的在線運(yùn)行性能蔚万。最近一直在關(guān)注這個(gè)方向,說(shuō)一下自己的心得和體會(huì)徽曲。
所謂的性能監(jiān)控态兴,并不是做出一堆花哨的圖表讓人賞心悅目。其實(shí)目的只有兩個(gè)疟位,一個(gè)是能夠?qū)崟r(shí)發(fā)現(xiàn)線上的問(wèn)題瞻润,通過(guò)報(bào)警機(jī)制通知到相關(guān)負(fù)責(zé)人;另外一個(gè)就是能夠看到歷史數(shù)據(jù)甜刻,供測(cè)試和開(kāi)發(fā)進(jìn)行性能優(yōu)化绍撞。那么監(jiān)控的關(guān)鍵是確定監(jiān)控指標(biāo),在監(jiān)控指標(biāo)的指導(dǎo)下確定需要客戶端打點(diǎn)收集的日志得院。
APP性能監(jiān)控指標(biāo)
最近調(diào)研了一下這個(gè)方向傻铣,行業(yè)內(nèi)把這個(gè)方向稱為APM(application performance management)。國(guó)內(nèi)外都有不少公司在做這方面的產(chǎn)品祥绞,一些大公司為了防止應(yīng)用監(jiān)控?cái)?shù)據(jù)的外泄非洲,也會(huì)自行開(kāi)發(fā)性能監(jiān)控管理平臺(tái)。國(guó)內(nèi)做得比較好的如基調(diào)蜕径、博睿两踏、云測(cè)、聽(tīng)云等兜喻,國(guó)外有New Relic梦染、Blueware、OneAPM等朴皆。 各個(gè)平臺(tái)的技術(shù)方案大同小異(這里主要說(shuō)的是IOS平臺(tái))帕识,提供一個(gè)集成到應(yīng)用的SDK(static framework 或者 .a 文件), 然后收集日志上報(bào)到其后臺(tái)進(jìn)行數(shù)據(jù)處理和緯度分析。這些平臺(tái)的商業(yè)策略一般都是先免費(fèi)提供試用遂铡,再進(jìn)行租用收費(fèi)的方式肮疗。
APP的性能指標(biāo)主要分為兩大類(lèi):InterActions(UI交互) 和 Network (網(wǎng)絡(luò)連接)。在這兩個(gè)大分類(lèi)下扒接,會(huì)有如下細(xì)分指標(biāo):
InterActions:(按時(shí)間-版本:+device類(lèi)型伪货,+os類(lèi)型)
- APP啟動(dòng)次數(shù)们衙、APP啟動(dòng)平均時(shí)間;
- Controller display次數(shù)超歌、平均display時(shí)間砍艾;
- Controller display過(guò)程中各個(gè)method(包括UI thread蒂教、worker thread中的method)的平均執(zhí)行時(shí)間巍举;
Network方面:(按時(shí)間-版本:+域名,+地區(qū)凝垛,+運(yùn)營(yíng)商懊悯, +連接網(wǎng)絡(luò)類(lèi)型)
- 平均相應(yīng)時(shí)間
- rpm(一分鐘請(qǐng)求次數(shù))
- 總耗時(shí)
- 傳輸數(shù)據(jù)大小
- 再具體到某個(gè)API接口:
(1) 平均http響應(yīng)時(shí)間 (區(qū)分web Application(webview) 和 network);
(2) rpm梦皮;
(3) 平均數(shù)據(jù)傳輸大刑糠帧(區(qū)分send和receive);
在兩大指標(biāo)上會(huì)增加如下方面的維度: 接入網(wǎng)絡(luò)剑肯、運(yùn)營(yíng)商捧毛、設(shè)備、系統(tǒng)版本让网、地區(qū)呀忧、app版本、統(tǒng)計(jì)時(shí)間溃睹;
NewRelic SDK使用說(shuō)明
我主要關(guān)注了國(guó)內(nèi)的聽(tīng)云平臺(tái)和國(guó)外的NewRelic平臺(tái)而账。國(guó)內(nèi)聽(tīng)云的統(tǒng)計(jì)平臺(tái)注冊(cè)只能看到非常基礎(chǔ)的統(tǒng)計(jì)因篇,一點(diǎn)細(xì)分分析維度泞辐,就提示你升級(jí)套餐,當(dāng)然你可以申請(qǐng)14天免費(fèi)試用竞滓,然后就是市場(chǎng)給你打電話過(guò)來(lái)咐吼。 其SDK中的framework也只提供一個(gè)頭文件,想要對(duì)統(tǒng)計(jì)數(shù)據(jù)的定制基本上沒(méi)有商佑。于是果斷切換到國(guó)外的newRelic平臺(tái)汽烦,newRlic平臺(tái)需要vpn訪問(wèn)才能快點(diǎn),一注冊(cè)所有的功能都能夠試用莉御,當(dāng)然試用期也只有14天撇吞。
關(guān)注一下SDK的使用,SDK提供了眾多可配置的頭文件礁叔,讓你能夠更加清楚的了解sdk提供的功能牍颈;
(1)最基礎(chǔ)的使用,當(dāng)然就是通過(guò)Apptoken注冊(cè)使用:(注意一下琅关,newRelic集成在debug時(shí)會(huì)檢查當(dāng)前應(yīng)用中hook的使用煮岁,需要將newrelic的注冊(cè)使用放到所有hook之后讥蔽,一般放到appdidfinishLaunch靠后的位置)
#import <NewRelicAgent/NewRelic.h>
[NewRelicAgent startWithApplicationToken:@"token"];
如果是最基礎(chǔ)的用法,我們可以通過(guò)charles攔截其數(shù)據(jù)上報(bào)画机,可以發(fā)現(xiàn)request和response的數(shù)據(jù)都是通過(guò)ssl加密冶伞、https傳輸,看不到明文數(shù)據(jù)步氏。如果需要看到明文數(shù)據(jù)可以使用非加密接口+ (void)startWithApplicationToken:(NSString*)appToken withoutSecurity:(BOOL)disableSSL
(2) SDK集成了各種指標(biāo)收集响禽,包括Interaction、SwiftInteraction荚醒、Crash芋类、NSURLSession、HTTPResponse等界阁。如下代碼所示:
typedef NS_OPTIONS(unsigned long long, NRMAFeatureFlags){
NRFeatureFlag_InteractionTracing = 1 << 1,
NRFeatureFlag_SwiftInteractionTracing = 1 << 2, //disabled by default
NRFeatureFlag_CrashReporting = 1 << 3,
NRFeatureFlag_NSURLSessionInstrumentation = 1 << 4,
NRFeatureFlag_HttpResponseBodyCapture = 1 << 5,
NRFeatureFlag_ExperimentalNetworkingInstrumentation = 1 << 13, //disabled by default
NRFeatureFlag_AllFeatures = ~0ULL //in 32-bit land the alignment is 4bytes
};
你可以按照自己的需要指定需要收集的指標(biāo)數(shù)據(jù)妒牙,使用
+ (void) enableFeatures:(NRMAFeatureFlags)featureFlags;
+ (void) disableFeatures:(NRMAFeatureFlags)featureFlags;
(3) 關(guān)于Interaction的主要方案就是hook controller中的各個(gè)生命周期方法片酝,NewRelicSDK中主要攔截了如下方法:
UIViewController
- viewDidLoad
- viewWillAppear:
- viewDidAppear:
- viewWillDisappear:
- viewDidDisappear:
- viewWillLayoutSubviews
- viewDidLayoutSubviews
UIImage:
- imageNamed:
- imageWithContentsOfFile:
- imageWithData:
- imageWithData:scale:
- initWithContentsOfFile:
- initWithData:
- initWithData:scale:
NSJSONSerialization:
- JSONObjectWithData:options:error:
- JSONObjectWithStream:options:error:
- dataWithJSONObject:options:error:
- writeJSONObject:toStream:options:error:
NSManagedObjectContext
- executeFetchRequest:error:
- processPendingChanges
(4)在ARC模式下,你也可以使用如下宏攔截你想統(tǒng)計(jì)的應(yīng)用中關(guān)鍵方法:
- (void)myMethod
{
NR_TRACE_METHOD_START(0);
// … existing code
NR_TRACE_METHOD_STOP;
}
更多的使用請(qǐng)關(guān)注其SDK文檔說(shuō)明:
https://docs.newrelic.com/docs/mobile-monitoring/mobile-sdk-api/new-relic-mobile-sdk-api/work-ios-sdk-api
newRelic SDK的數(shù)據(jù)攔截上報(bào)
newRelic SDK已經(jīng)做得非常精致,但是如果我們不想把數(shù)據(jù)上報(bào)到第三方平臺(tái)管理蚓哩,那應(yīng)該怎么辦呢帖汞,自己去研發(fā)一套纬朝,耗時(shí)費(fèi)力婚脱,而且還費(fèi)力不討好。那何不做一個(gè)數(shù)據(jù)攔截重付,把數(shù)據(jù)轉(zhuǎn)發(fā)到我們自己的后臺(tái)平臺(tái)呢顷级?
static framework public的頭文件并不包括上傳class的頭文件,但是當(dāng)網(wǎng)絡(luò)不好或者沒(méi)有網(wǎng)的時(shí)候确垫,我們可以從控制臺(tái)打印上報(bào)數(shù)據(jù)失敗的日志看到一些端倪弓颈,我們可以找到上傳日志的方法所在的類(lèi)為“NRMAHarvesterConnection”,如果是ssl上傳删掀,日志為send方法翔冀,如果是非ssl上傳,日志中顯示為sendData方法披泪;
為了更好的知道類(lèi)中方法的分布纤子,我們可以用class-dump-z工具解析出SDK中所有的頭文件。 class-dump-z是不能直接dump framework中的頭文件款票,解析出來(lái)的頭文件中會(huì)有一個(gè)source 為null的提示控硼。 一開(kāi)始覺(jué)得可能是因?yàn)閒ramework做了加密處理,找了一個(gè)越獄機(jī)器在cydia中安裝了openssh-how-to工具艾少,通過(guò)sftp把framework和Clutch上傳到越獄機(jī)器中卡乾,開(kāi)始用Clutch進(jìn)行解密,發(fā)現(xiàn)Clutch并不能直接解析缚够,也不能解析debug安裝的調(diào)試app幔妨。
最后終于將集成了newRelic SDK的的app 通過(guò)archive打包鹦赎,導(dǎo)出一個(gè)ipa。解壓出app目錄之后误堡,再用class-dump-z工具對(duì)app目錄的二進(jìn)制文件進(jìn)行頭文件導(dǎo)出古话,這時(shí)候我們就能夠看到sdk中的所有頭文件了;
我找到“NRMAHarvesterConnection”文件锁施,如下所示陪踩,可以看到其中的sendData方法。
#import <XXUnknownSuperclass.h> // Unknown library
@class NRMAConnectInformation, NSString;
@interface NRMAHarvesterConnection : XXUnknownSuperclass {
BOOL _useSSL;
NRMAConnectInformation* _connectionInformation;
NSString* _collectorHost;
NSString* _applicationToken;
NSString* _crossProcessID;
long long _serverTimestamp;
}
@property(assign) BOOL useSSL;
@property(retain) NSString* crossProcessID;
@property(assign) long long serverTimestamp;
@property(retain) NSString* applicationToken;
@property(retain) NSString* collectorHost;
@property(retain) NRMAConnectInformation* connectionInformation;
-(void).cxx_destruct;
-(id)collectorHostURL:(id)url;
-(id)collectorHostDataURL;
-(id)collectorConnectURL;
-(id)createDataPost:(id)post;
-(id)createConnectPost:(id)post;
-(id)sendData:(id)data;
-(id)sendConnect;
-(id)send:(id)send;
-(id)createPostWithURI:(id)uri message:(id)message;
-(id)init;
@end
為了更好的了解方法的輸入和輸出參數(shù)沾谜,我使用了阿里最近開(kāi)源的ali-wax的lua調(diào)試工具進(jìn)行了sendData的方法的輸入輸出了解, 我們可以看到輸入?yún)?shù)data的class類(lèi)型膊毁,是一個(gè)“NRMAHarvestData”胀莹,我們同樣可以找到其頭文件基跑,查詢到里面的JSONObject方法,可以把data轉(zhuǎn)化成一個(gè)json對(duì)象描焰。 得到這個(gè)對(duì)象了我們就可以將數(shù)據(jù)轉(zhuǎn)發(fā)到我們自己的平臺(tái)媳否,并偽造一個(gè)http上傳成功或失敗的對(duì)象給sendData方法進(jìn)行內(nèi)存數(shù)據(jù)刪除處理即可。
waxClass{"NRMAHarvesterConnection"}
--如果需要上報(bào)荆秦,攔截這個(gè)方法即可
function sendData(self,data)
print(data:class())
jsonObject = data:JSONObject()
self:ORIGsendData(data)
end
注:這里我只提供一種方案篱竭,公司產(chǎn)品并沒(méi)有采用這種方法,因?yàn)槲覀冏约旱男阅鼙O(jiān)控雖然數(shù)據(jù)粒度不夠步绸,已經(jīng)足夠日常查詢問(wèn)題使用了掺逼。更多是分享一下取巧的思路,以及紀(jì)錄一下破解的過(guò)程瓤介。