在這樣一個(gè)注重用戶體驗(yàn)的時(shí)代碘耳,APM
技術(shù)快速發(fā)展,國內(nèi)更是百花齊放孕豹,最近對各個(gè)公司的 APM
產(chǎn)品有一個(gè)調(diào)研涩盾,并在此基礎(chǔ)上進(jìn)行了自己的實(shí)踐。這里就從 iOS 的角度出發(fā)励背,談?wù)勛约簩σ苿佣?APM 的技術(shù)上的理解春霍,并提供相對應(yīng)的實(shí)例。
何為 APM
APM
的全稱是Application performance management
椅野,即應(yīng)用性能管理终畅,通過對應(yīng)用的可靠性、穩(wěn)定性等方面的監(jiān)控竟闪,進(jìn)而達(dá)到可以快速修復(fù)問題、提高用戶體驗(yàn)的目的杖狼。
國內(nèi)各大公司都有自己的一套監(jiān)控體系炼蛤,這個(gè)系統(tǒng)可能是自己研發(fā),也可能是第三方提供蝶涩,當(dāng)然對于這個(gè)數(shù)據(jù)為王的時(shí)代理朋,很多有實(shí)力的公司傾向于自主研發(fā)絮识,掌握核心數(shù)據(jù)。比較有代表性的 APM 產(chǎn)品有:聽云嗽上、阿里百川次舌、騰訊 bugly、NewRelic兽愤、OneAPM彼念、網(wǎng)易云捕等
說到監(jiān)控,那么指標(biāo)是我們所關(guān)注的呢浅萧?如下所示
- 網(wǎng)絡(luò)請求:成功率逐沙、狀態(tài)碼、流量洼畅、網(wǎng)絡(luò)響應(yīng)時(shí)間吩案、HTTP與HTTPS的 DNS 解析、TCP握手帝簇、SSL握手(HTTP除外)徘郭、首包時(shí)間等時(shí)間
- 界面卡頓、卡頓堆棧
- 崩潰率丧肴、崩潰堆棧
- Abort 率:也就是由于內(nèi)存過高的等原因残揉,被系統(tǒng)殺死的情況
- 交互監(jiān)控:頁面加載時(shí)間、頁面的交互痕跡
- 維度信息:地域闪湾、運(yùn)營商冲甘、網(wǎng)絡(luò)接入方式、操作系統(tǒng)途样、應(yīng)用版本等
- 其他:內(nèi)存江醇、幀率、CPU使用率何暇、啟動時(shí)間陶夜、電量等
聊聊原理
卡頓檢測
當(dāng)應(yīng)用發(fā)生卡頓的時(shí)候,一般會伴隨著掉幀裆站,所以幀率是最容易想到的指標(biāo)來判斷卡頓条辟。對于線下的測試環(huán)境,我們可以使用幀率來對開發(fā)做一些提示宏胯,告訴他們可能發(fā)生了卡頓羽嫡。但是幀率不穩(wěn)定性較高,所以一般會采取另一種方式來做卡頓檢測肩袍。那就是Runloop杭棵,對于細(xì)節(jié)可以查看 Runloop
源碼,會發(fā)現(xiàn)對于事件的處理主要就是在kCFRunLoopBeforeSources
和kCFRunLoopBeforeWaiting
狀態(tài)之間,還有kCFRunLoopAfterWaiting
之后氛赐。那我們就可以對兩個(gè)狀態(tài)進(jìn)行監(jiān)控魂爪,如果消耗時(shí)間太久先舷,就代表著卡頓的發(fā)生。
上圖摘自阿里百川滓侍,如圖所示蒋川,我們會對卡頓次數(shù)做一個(gè)判斷,如果次數(shù)為1撩笆,但時(shí)間超時(shí)捺球,則為單次耗時(shí)較長的卡頓,如果次數(shù)到達(dá)閥值浇衬,則證明是連續(xù)短時(shí)間卡頓懒构。
當(dāng)卡頓發(fā)生之后,我們?yōu)榱硕ㄎ辉爬蓿瑫占?dāng)時(shí)的一個(gè)堆棧情況胆剧,在此你可以使用 PLCrashReporter
來做,也可以自己研發(fā)一個(gè)堆棧收集庫(可參考這里來做)
對于實(shí)例醉冤,網(wǎng)上已經(jīng)有很多開源的項(xiàng)目秩霍,你可以參考這個(gè)
崩潰檢測
對于崩潰的情況,一般是由 Mach
異骋涎簦或 Objective-C
異常(NSException)引起的铃绒。我們可以針對這兩種情況抓取對應(yīng)的 Crash
事件。
Mach 異常捕獲
如果想要做mach
異常捕獲螺捐,需要注冊一個(gè)異常端口颠悬,這個(gè)異常端口會對當(dāng)前任務(wù)的所有線程有效,如果想要針對單個(gè)線程定血,可以通過 thread_set_exception_ports
注冊自己的異常端口赔癌,發(fā)生異常時(shí),首先會將異常拋給線程的異常端口澜沟,然后嘗試拋給任務(wù)的異常端口灾票,當(dāng)我們捕獲異常時(shí),就可以做一些自己的工作茫虽,比如刊苍,當(dāng)前堆棧收集等。
對于如何注冊一個(gè)異常端口濒析,這里有示意圖和 PLCrashReporter 可以參考
Unix 信號捕獲
對于Mach 異常正什,操作系統(tǒng)會將其轉(zhuǎn)換為對應(yīng)的 Unix信號,所以如果你對Mach
不熟悉的話号杏,也可以通過注冊signalHandler
的方式來做信號異常埠忘。對于實(shí)例,你可以參考這里
signal(SIGHUP, signalHandler);
signal(SIGINT, signalHandler);
signal(SIGQUIT, signalHandler);
signal(SIGABRT, signalHandler);
signal(SIGILL, signalHandler);
signal(SIGSEGV, signalHandler);
signal(SIGFPE, signalHandler);
signal(SIGBUS, signalHandler);
signal(SIGPIPE, signalHandler);
NSException 捕獲
對于NSException
異常馒索,也比較容易處理莹妒,通過注冊NSUncaughtExceptionHandler
捕獲異常信息即可,將拿到的NSException
細(xì)節(jié)寫入Crash
日志绰上,上傳到后臺做數(shù)據(jù)分析
// register the uncaught exception handler
NSSetUncaughtExceptionHandler(&handler);
Abort 率檢測
目前對于內(nèi)存過高被殺死的情況是沒有辦法直接統(tǒng)計(jì)的旨怠,一般通過排除法來做百分比的統(tǒng)計(jì),原理如下
- 程序啟動蜈块,設(shè)置標(biāo)志位
- 程序正常退出鉴腻,清楚標(biāo)志
- 程序
Crash
,清楚標(biāo)志 - 程序電量過低導(dǎo)致關(guān)機(jī)百揭,這個(gè)也沒辦法直接監(jiān)控爽哎,可以加入電量檢測來輔助判斷
- 第二次啟動,標(biāo)志位如果存在器一,則代表
Abort
一次课锌,上傳后臺做統(tǒng)計(jì)
交互監(jiān)控
對于頁面的加載時(shí)間,這個(gè)比較容易實(shí)現(xiàn)祈秕,直接通過Runtime hook
對應(yīng)的生命周期方法即可渺贤,比如 viewDidLoad
、viewWillAppear
等
對于用戶的交互痕跡请毛,比如點(diǎn)擊了那個(gè)按鈕志鞍、跳轉(zhuǎn)到了那個(gè)頁面,這些信息偏于用戶行為的收集方仿,我們也獨(dú)立研發(fā)了一個(gè)無埋點(diǎn)的SDK固棚,專門來做用戶行為數(shù)據(jù)的收集與分析,核心也是基于 hook AOP
的思想仙蚜。細(xì)節(jié)可以參考我同事的作品
網(wǎng)絡(luò)監(jiān)控
對于成功率此洲、狀態(tài)碼、流量鳍征,以及網(wǎng)絡(luò)的響應(yīng)時(shí)間之類的黍翎,我們可以主要可以通過兩種方式來做
- 針對
URLConnection
、CFNetwork
艳丛、NSURLSession
三種網(wǎng)絡(luò)做Hook
匣掸,hook
的具體技術(shù)可以是method swizzle
也可以是Proxy
、Fishhook
之類的 - 也可以使用
NSURLProtocol
對網(wǎng)絡(luò)請求的攔截氮双,進(jìn)而得到流量碰酝、響應(yīng)時(shí)間等信息,但是NSURLProtocol
有自己的局限戴差,比如NSURLProtocol
只能攔截NSURLSession
送爸,NSURLConnection
以及UIWebView
,但是對于CFNetwork
則無能為力
對于第一種方式可以Hook
哪些方法的,可以參考這個(gè)圖
對于 HTTP與HTTPS 的 DNS 解析袭厂、TCP握手墨吓、SSL握手(HTTP除外)、首包時(shí)間等時(shí)間的統(tǒng)計(jì)纹磺,稍有難度
但是帖烘,因?yàn)槲覀兯褂玫?code>URLConnection、CFNetwork
橄杨、NSURLSession
底層都是 BSDSocket
秘症,所以可以嘗試在socket
上動手腳來實(shí)現(xiàn)效果,類似于通過ViewController
的生命周期方法來統(tǒng)計(jì)頁面加載時(shí)間的做法式矫,我們Hook socket
相關(guān)的方法來做乡摹,比如通過hook
socket
連接時(shí)的 connect
方法,拿到tcp
握手的起始時(shí)間采转,通過hook
SSLHandshake
方法聪廉,在SSLHandshake
執(zhí)行的時(shí)候拿到 SSL
握手的起始時(shí)間等。目前聽云已經(jīng)提供了 HTTP
的分段時(shí)間查詢功能氏义,大家去體驗(yàn)下
int connect(int, const struct sockaddr *, socklen_t) __DARWIN_ALIAS_C(connect);
OSStatus SSLHandshake(SSLContextRef ctx)
但是對于 iOS 9 Apple 加入 ATS 新特性锄列,并要求開發(fā)者使用 HTTPS,我在 iOS9惯悠、10上對 HTTPS 網(wǎng)絡(luò)請求Hook socket
方法時(shí)候邻邮,有一些方法hook
失效,猜想應(yīng)該是Apple 進(jìn)行了加固克婶、加密筒严,導(dǎo)致一些系統(tǒng)方法沒辦法hook
,所以在 iOS9情萤、10 上無法通過socket
來取得HTTPS
網(wǎng)絡(luò)的分段時(shí)間(糾正:fishhook 無法 hook socket 的原因:https://github.com/facebook/fishhook/issues/40)
不過apple
在 iOS 10 推出一個(gè)API
鸭蛙,可以在 iOS10 版本以上進(jìn)行網(wǎng)絡(luò)信息的收集
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didFinishCollectingMetrics:(NSURLSessionTaskMetrics *)metrics
打印結(jié)果如下
(Fetch Start) 2017-02-24 09:03:06 +0000
(Domain Lookup Start) 2017-02-24 09:03:06 +0000
(Domain Lookup End) 2017-02-24 09:03:06 +0000
(Connect Start) 2017-02-24 09:03:14 +0000
(Secure Connection Start) 2017-02-24 09:03:14 +0000
(Secure Connection End) 2017-02-24 09:03:16 +0000
(Connect End) 2017-02-24 09:03:16 +0000
(Request Start) 2017-02-24 09:03:16 +0000
(Request End) 2017-02-24 09:03:16 +0000
(Response Start) 2017-02-24 09:03:16 +0000
(Response End) 2017-02-24 09:03:16 +0000
當(dāng)然,對于網(wǎng)絡(luò)各層次的時(shí)間獲取筋岛,如果你有好的方案娶视,希望您可以留言告知。同時(shí)對于一些維度信息和內(nèi)存等基礎(chǔ)指標(biāo)睁宰,很容易獲取肪获,這里就不細(xì)談了
大禮包
在調(diào)研和學(xué)習(xí)APM技術(shù)的過程中,發(fā)現(xiàn)了很多優(yōu)秀的博客柒傻,所以在此推薦給大家孝赫,有需要的可以自取