在這樣一個注重用戶體驗的時代,APM
技術快速發展,國內更是百花齊放,最近對各個公司的 APM
產品有一個調研,並在此基礎上進行了本身的實踐。這裏就從 iOS 的角度出發,談談本身對移動端 APM 的技術上的理解,並提供相對應的實例。ios
APM
的全稱是Application performance management
,即應用性能管理,經過對應用的可靠性、穩定性等方面的監控,進而達到能夠快速修復問題、提升用戶體驗的目的。git
國內各大公司都有本身的一套監控體系,這個系統多是本身研發,也多是第三方提供,固然對於這個數據爲王的時代,不少有實力的公司傾向於自主研發,掌握核心數據。比較有表明性的 APM 產品有:聽雲、阿里百川、騰訊 bugly、NewRelic、OneAPM、網易雲捕等github
說到監控,那麼指標是咱們所關注的呢?以下所示微信
當應用發生卡頓的時候,通常會伴隨着掉幀,因此幀率是最容易想到的指標來判斷卡頓。對於線下的測試環境,咱們可使用幀率來對開發作一些提示,告訴他們可能發生了卡頓。可是幀率不穩定性較高,因此通常會採起另外一種方式來作卡頓檢測。那就是Runloop,對於細節能夠查看 Runloop
源碼,會發現對於事件的處理主要就是在kCFRunLoopBeforeSources
和kCFRunLoopBeforeWaiting
狀態之間,還有kCFRunLoopAfterWaiting
以後。那咱們就能夠對兩個狀態進行監控,若是消耗時間過久,就表明着卡頓的發生。網絡
上圖摘自阿里百川,如圖所示,咱們會對卡頓次數作一個判斷,若是次數爲1,但時間超時,則爲單次耗時較長的卡頓,若是次數到達閥值,則證實是連續短期卡頓。session
當卡頓發生以後,咱們爲了定位,會收集當時的一個堆棧狀況,在此你可使用 PLCrashReporter
來作,也能夠本身研發一個堆棧收集庫(可參考這裏來作)app
對於實例,網上已經有不少開源的項目,你能夠參考這個運維
對於崩潰的狀況,通常是由 Mach
異常或 Objective-C
異常(NSException)引發的。咱們能夠針對這兩種狀況抓取對應的 Crash
事件。socket
若是想要作mach
異常捕獲,須要註冊一個異常端口,這個異常端口會對當前任務的全部線程有效,若是想要針對單個線程,能夠經過 thread_set_exception_ports
註冊本身的異常端口,發生異常時,首先會將異常拋給線程的異常端口,而後嘗試拋給任務的異常端口,當咱們捕獲異常時,就能夠作一些本身的工做,好比,當前堆棧收集等。tcp
對於如何註冊一個異常端口,這裏有示意圖和 PLCrashReporter 能夠參考
對於Mach 異常,操做系統會將其轉換爲對應的 Unix信號,因此若是你對Mach
不熟悉的話,也能夠經過註冊signalHandler
的方式來作信號異常。對於實例,你能夠參考這裏
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
異常,也比較容易處理,經過註冊NSUncaughtExceptionHandler
捕獲異常信息便可,將拿到的NSException
細節寫入Crash
日誌,上傳到後臺作數據分析
// register the uncaught exception handler
NSSetUncaughtExceptionHandler(&handler);
複製代碼
目前對於內存太高被殺死的狀況是沒有辦法直接統計的,通常經過排除法來作百分比的統計,原理以下
Crash
,清楚標誌Abort
一次,上傳後臺作統計對於頁面的加載時間,這個比較容易實現,直接經過Runtime hook
對應的生命週期方法便可,好比 viewDidLoad
、viewWillAppear
等
對於用戶的交互痕跡,好比點擊了那個按鈕、跳轉到了那個頁面,這些信息偏於用戶行爲的收集,咱們也獨立研發了一個無埋點的SDK,專門來作用戶行爲數據的收集與分析,核心也是基於 hook AOP
的思想。細節能夠參考我同事的做品
對於成功率、狀態碼、流量,以及網絡的響應時間之類的,咱們能夠主要能夠經過兩種方式來作
URLConnection
、CFNetwork
、NSURLSession
三種網絡作Hook
,hook
的具體技術能夠是method swizzle
也能夠是Proxy
、Fishhook
之類的NSURLProtocol
對網絡請求的攔截,進而獲得流量、響應時間等信息,可是NSURLProtocol
有本身的侷限,好比NSURLProtocol
只能攔截NSURLSession
,NSURLConnection
以及UIWebView
,可是對於CFNetwork
則無能爲力對於第一種方式能夠Hook
哪些方法的,能夠參考這個圖
對於 HTTP與HTTPS 的 DNS 解析、TCP握手、SSL握手(HTTP除外)、首包時間等時間的統計,稍有難度
可是,由於咱們所使用的URLConnection
、CFNetwork
、NSURLSession
底層都是 BSDSocket
,因此能夠嘗試在socket
上動手腳來實現效果,相似於經過ViewController
的生命週期方法來統計頁面加載時間的作法,咱們Hook socket
相關的方法來作,好比經過hook
socket
鏈接時的 connect
方法,拿到tcp
握手的起始時間,經過hook
SSLHandshake
方法,在SSLHandshake
執行的時候拿到 SSL
握手的起始時間等。目前聽雲已經提供了 HTTP
的分段時間查詢功能,你們去體驗下
int connect(int, const struct sockaddr *, socklen_t) __DARWIN_ALIAS_C(connect);
OSStatus SSLHandshake(SSLContextRef ctx)
複製代碼
可是對於 iOS 9 Apple 加入 ATS 新特性,並要求開發者使用 HTTPS,我在 iOS九、10上對 HTTPS 網絡請求Hook socket
方法時候,有一些方法hook
失效,猜測應該是Apple 進行了加固、加密,致使一些系統方法沒辦法hook
,因此在 iOS九、10 上沒法經過socket
來取得HTTPS
網絡的分段時間(糾正:fishhook 沒法 hook socket 的緣由:https://github.com/facebook/fishhook/issues/40)
不過apple
在 iOS 10 推出一個API
,能夠在 iOS10 版本以上進行網絡信息的收集
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didFinishCollectingMetrics:(NSURLSessionTaskMetrics *)metrics
複製代碼
打印結果以下
(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
複製代碼
固然,對於網絡各層次的時間獲取,若是你有好的方案,但願您能夠留言告知。同時對於一些維度信息和內存等基礎指標,很容易獲取,這裏就不細談了
在調研和學習APM技術的過程當中,發現了不少優秀的博客,因此在此推薦給你們,有須要的能夠自取