360移動端性能監控實踐QDAS-APM(iOS篇)

1、背景

360是一家注重用戶體驗的公司,公司的口號是用戶至上。在這麼一個注重用戶體驗的氛圍裏,app的性能問題無疑是被重點關注的,一樣也是形成用戶流失的罪魁禍首之一。性能問題主要包含:崩潰、網絡請求錯誤或者超時、UI響應速度慢、主線程卡頓、CPU和內存使用高、耗電量大等等。大多問題的緣由在於開發者錯誤地使用了線程、鎖、系統函數、編程規範問題、數據結構等等。解決這個問題的關鍵在於儘早發現和定位問題。html

目前國內各大公司都有本身的一套app性能監控體系,360也不例外。在平時開發和用戶反饋的問題中,對性能問題進行了概括總結出了5個分別是:資源文件如何掌控、 版本質量如何保證、線上問題如何排查、開發階段如何防止性能衰減、性能監控是否能真實反映用戶體驗。同時學習了業內相對完善的性能監控平臺上的功能原理。從而得出了360在iOS端移動端線上性能監控方案——QDAS-APM。objective-c

2、功能和原理

QDAS-APM已經實現如下功能監控:編程

  • 頁面渲染時長
  • 主線程卡頓
  • 網絡錯誤
  • FPS
  • 大文件存儲
  • CPU
  • 內存使用
  • Crash
  • 啓動時長

下面按照功能詳細介紹實現細節和原理。另外用戶在使用app時會感知性能問題,咱們能夠將其轉化爲具體的性能監控指標。微信

(1)頁面渲染時長

什麼是頁面渲染時長,實際上是從頁面初始化到用戶能看到頁面效果的時間長度。所要了解的指標有生命週期系統方法執行時長、頁面類名、啓動類型、執行耗時和插件名稱。關鍵度量的指標是執行耗時,不一樣的方法和步驟產生的耗時在用戶能接受的範圍內才被認爲是合理。其餘指標則是起有關聯性做用和定位問題。直接hook UIViewController的方法明顯是不可行的,緣由是它只做用在UIViewController的方法,而app中大部分都採用繼承UIViewController的方式。網絡

這裏列出兩個可行性方案:數據結構

一、採用KVO,咱們知道對於任意對象進行KVO操做時,系統都會幫你動態的建立一個複製類,同時實現了setter getter函數的覆蓋和函數實現。app

二、採用runtime遍歷全部類爲UIViewController的子類,再進行動態替換。異步

這兩種方式更加推薦第一種,出於對兼容性、性能、以及可以直接獲取UIViewController的子類的IMP。那具體如何實現呢?總結概括爲三步驟:函數

一、須要建立一個UIViewController的類別,對UIViewController的實例進行KVO,目的是讓KVO建立須要監控UIViewController的子類。oop

二、添加須要監控的方法,在KVO建立出來的子類添加須要Swizzle的方法對應的SEL及其IMP。目的是控制調用原來類的方法時機。

三、在UIViewController的實例銷燬時,在dealloc方法裏將KVO監聽移除,否則會致使Crash。

舉個例子:咱們以監控到qh_viewDidLoad方法舉例:

static void qh_viewDidLoad(UIViewController *kvo_self, SEL _sel)
{
    Class kvo_cls = object_getClass(kvo_self);
    Class origin_cls = class_getSuperclass(kvo_cls);

    // 注意點
    IMP origin_imp = method_getImplementation(class_getInstanceMethod(origin_cls, _sel));

    void(*func)(UIViewController *, SEL) =  (void(*)(UIViewController *, SEL))origin_imp;

        CFAbsoluteTime startTime = CACurrentMediaTime();
        func(kvo_self, _sel);
        CFAbsoluteTime endTime = CACurrentMediaTime();
        NSTimeInterval duration = (endTime - startTime)*1000;

    NSLog(@"Class %@ cost %g in viewDidLoad", [kvo_self class], duration);
}

會有一種特殊狀況,若是KVO生成的類中對應的類本來沒有實現監控方法,那麼會形成什麼後果呢?KVO內部生成的NSKVONotifying_ViewController實際上時繼承自ViewController,所以直接取出對應的IMP調用。

OK,上面說的是對UIViewController類方法的執行時長統計。咱們還想知道用戶真正頁面跳轉後看到第一針頁面圖像的時長要如何採集呢?那是否是將UIViewController類的init+loadView+viewDidLoad+viewWillAppear+viewDidAppear方法執行時長之和就是頁面渲染時長了呢?答案是否認的,下面舉了三個反面例子:

在這裏插入圖片描述

對於異步回調和異步渲染這兩種方式,用上面提到的5個方法執行時長只和是不適用的。接下來看下如何相對準確的來統計和計算的方案。

在這裏插入圖片描述

頁面渲染的時長和頁面的佈局時長會在將來的某個時間點上達到一致。要想獲得頁面渲染的時長能夠間接的參考頁面的佈局完成時長。在UIViewController的生命週期方法裏有一個方法叫viewDidLayoutSubviews,它是幹什麼的呢?它實際上是告訴了控制器的subviews佈局完成的時間點。通常狀況下會被調用兩次,在不一樣的操做系統版本里調用次數也不一樣。

(2)主線程卡頓分析

主線程的卡頓直接影響用戶使用體驗,其表如今頁面的操做流暢性影響。首先引入一個概念FPS(Frames Per Second):每秒顯示連續圖片的幀數。每秒幀數越多,UI操做就越流暢。通常應用保持在每秒50~60幀數,會給用戶帶來流暢的感受,反之,用戶則會感知到卡頓。那爲何會出現主線程卡頓呢?首先了解下,每一幀圖像顯示到屏幕的原理。
在這裏插入圖片描述

這是觸屏幕顯示的原理流程圖。CPU負責計算顯示內容,包括視圖的建立、佈局計算、圖片解碼、文本繪製等,cpu會把計算後的結果提交給GPU,GPU進行變換、合成、渲染後,將渲染結果提交到幀緩衝區,當下一次垂直同步信號到來時,視頻控制器從緩衝區裏獲取視圖顯示到屏幕上。明白了就屏幕顯示的原理,接下來看下爲甚麼會產生卡頓。

在這裏插入圖片描述

圖上提到 V-Sync 是什麼,以及爲何要在 iPhone 的顯示流程引入它呢?在 iPhone 中使用的是雙緩衝機制,即上圖中的 FrameBuffer 有兩個緩衝區,雙緩衝區的引入是爲了提高顯示效率,可是與此同時,他引入了一個新的問題,當視頻控制器還未讀取完成時,好比屏幕內容剛顯示一半時,GPU 將新的一幀內容提交到幀緩衝區並把兩個緩衝區進行交換後,視頻控制器就會把新的一幀數據的下半段顯示到屏幕上,形成畫面撕裂現象,V-Sync 就是爲了解決畫面撕裂問題,開啓 V-Sync 後,GPU 會在顯示器發出 V-Sync 信號後,去進行新幀的渲染和緩衝區的更新。

搞清楚了 iPhone 的屏幕顯示原理後,下面來看看在 iPhone 上爲何會出現卡頓現象,上文已經說起在圖像真正在屏幕顯示以前,CPU 和 GPU 須要完成自身的任務,而若是他們完成的時間錯過了下一次 V-Sync 的到來(一般是1000/60=16.67ms),這樣就會出現顯示屏仍是以前幀的內容,這就是界面卡頓的緣由。不難發現,不管是 CPU 仍是 GPU 引發錯過 V-Sync 信號,都會形成界面卡頓。

在這裏插入圖片描述

主線程卡頓監控的實現思路:開闢一個子線程,而後實時計算 kCFRunLoopBeforeSources 和 kCFRunLoopAfterWaiting 兩個狀態區域之間的耗時是否超過某個閥值,來判定主線程的卡頓狀況,而後經過採集當前主線程的堆棧信息和相關指標。從而上報到服務端進行處理,最終生成報表。FPS 的刷新頻率很是快,容易發生抖動,直接經過比較 FPS 來偵測卡頓是比較困難的。主線程卡頓監控也會發生抖動,因此提出綜合方案,結合主線程監控、FPS 監控,以及 CPU 使用率等指標,做爲判斷卡頓的標準。

(3)網絡監控

網絡監控通常經過 NSURLProtocol 和代碼注入(Hook)這兩種方式來實現,因爲 NSURLProtocol 做爲上層接口,使用起來更爲方便,NSURLProtocol 屬於 URL Loading System 體系中,應用層的協議支持有限,只支持 FTP,HTTP,HTTPS 等幾個應用層協議,對於使用其餘協議的流量則一籌莫展,因此存在必定的侷限性。監控底層網絡庫 CFNetwork 則沒有這個限制。若是本地有https的證書驗證也不適用於NSURLProtocol這種方式。容易引發業務數據丟失問題。

NSURLProtocol

在這裏插入圖片描述
上圖是基於NSURLProtocol協議來實現的,經過繼承自NSURLProtocol,並註冊。經過代理和自身方法來獲得網絡請求相關的指標。

HOOK方式—NSProxy

NSProxy is an abstract superclass defining an API for objects 
that act as stand-ins for other objects or for objects that don’t exist yet. 
Typically, a message to a proxy is forwarded to the real object or causes the proxy to load (or 
transform itself into) the real object. Subclasses of NSProxy can be used to implement transparent 
distributed messaging (for example, NSDistantObject) or for lazy instantiation of objects that are expensive to create.

上面的這段英文是 Apple 官方文檔給 NSProxy 的定義,NSProxy 和 NSObject 同樣都是根類,它是一個抽象類,能夠經過繼承它,並重寫 -forwardInvocation: 和 -methodSignatureForSelector: 方法以實現消息轉發到另外一個實例。綜上,NSProxy 的目的就是負責將消息轉發到真正的 target 的代理類。

那爲何咱們不用Method swizzling 替換方法須要指定類名?是由於 NSURLConnectionDelegate 和 NSURLSessionDelegate 是由業務方指定,一般來講是不肯定,因此這種場景不適合使用 Method swizzling。使用 NSProxy 能夠解決,具體實現:proxy delegate 替換 NSURLConnection 和 NSURLSession 原來的 delegate,當 proxy delegate 收到回調時,若是是要 hook 的方法,則調用 proxy 的實現,proxy 的實現最後會調用原來的 delegate;反之,則經過消息轉發機制將消息轉發給原來的 delegate。下圖示意了整個操做流程。

在這裏插入圖片描述
經過對NSURLConnection、NSURLSession和CFNetwork這三個類中關鍵方法的hook來獲取上報指標。具體hook哪些方法,請看下圖。

在這裏插入圖片描述

將hook方法中獲得的相關指標整理成須要的格式上報到服務端,服務端經過數據處理和拆分指標,彙總加計算生成最終的報表。

3、QDAS-APM在集成和使用上的便捷

因爲sdk功能基本上採用的都是主動採集功能,無需二次開發,也無需額外引入系統庫。因此在集成和使用上很是便捷。在sdk的集成上,只須要三步驟:

一、引入sdk庫

二、引入sdk頭文件

三、在app的didFinishLauchingWithOptions裏初始化sdk,並傳入appkey便可。

在這裏插入圖片描述

參考:

美團Herz

iOS性能監控方案

(360技術原創內容,轉載請務必保留文末二維碼,謝謝~)

在這裏插入圖片描述

關於360技術 360技術是360技術團隊打造的技術分享公衆號,天天推送技術乾貨內容 更多技術信息歡迎關注「360技術」微信公衆號
相關文章
相關標籤/搜索