談談iOS app的線上性能監測


在移動端開發者中最重要的KPI應該是崩潰率。當崩潰率穩定下來后,工作的重心就應該轉移到性能優化上。那么問題來了,如果你的項目也沒有接入任何性能監測SDK,沒有量化的指標來衡量,那你說你優化了性能領導信么?

雖然現在市面上第三方性能檢測平台已經很成熟,但筆者還是比較建議公司自己寫自己的sdk,原因如下

1. 數據安全

2. 避開費用,有的平台是MAU三萬以下不收費,超出后費用極高。

3. 可以自定義指標沒有無用代碼,一接別人SDK包體積增大不少,其實你只用到了個別幾個功能。

 

本文主要內容是給小項目團隊自己寫性能sdk的建議,也會提到當前的第三方平台。

1.頁面的打開速度

關於頁面的加載速度的測量有兩個指標,一個是頁面渲染速度,一個是頁面加載速度。

頁面渲染速度就是計算一個viewcontroller從viewdidload的第一行到viewdidappear的最后一行所用的時間。 這種方法可以適用於大多數相對靜態的頁面,一打開就直接加載的。  

頁面加載速度相對比較麻煩,適用於有些頁面並不是一進入就加載而是先發個請求,請求回調后才繼續搭建頁面的。加載時間應該是用戶點開頁面到用戶能完全的看到內容才算加載完畢,這里就需要算進去請求消耗的時間了,對於源碼級別的sdk,我們可以通過每發出一個請求就記到棧里,回調一個消除一個,當棧里的每一個請求都已經回調后才能認定是界面加載完畢。 業界也有一些黑盒的雲測平台是錄屏解析視頻流,認定一個頁面從打開到頁面穩定后為加載時間。 個人認為肯定沒有源碼級別的准啊。 (董鉑然博客園)

關於這種接入一個sdk直接hook每個頁面生命周期方法也簡單提一下吧,就是寫一個類作為UIViewController的分類,增加幾個方法如XXXviewdidload , XXXviewdidappear等,然后使用這種swizzle的做法替換方法的實現

    Method viewDidAppear = class_getInstanceMethod([UIViewController class], @selector(viewDidAppear:));
    Method XXXViewDidAppear = class_getInstanceMethod([UIViewController class], @selector(XXXViewDidAppear:));
    method_exchangeImplementations(viewDidAppear, XXXViewDidAppear);

對於一些新增的計量屬性就使用運行時關聯對象的那一套吧。 如果覺得要hook的方法太多或是太麻煩或是怕和別的hook沖突,可以使用埋點的方式,直接埋上你需要計算的開始和結束時間的采集點,這種更加靈活只關心自己關心的頁面。

 

2.內存使用值

關於內存和cpu的獲取方法業內基本都有統一的代碼了,大致如下。

+ (unsigned long)memoryUsage
{
    struct task_basic_info info;
    mach_msg_type_number_t size = sizeof(info);
    kern_return_t kr = task_info(mach_task_self(), TASK_BASIC_INFO, (task_info_t)&info, &size);
    if (kr != KERN_SUCCESS) {
        return -1;
    }
    unsigned long memorySize = info.resident_size >> 10;
    
    return memorySize;
}

需要先引入這兩個頭文件

#include <mach/task_info.h>
#include <mach/mach.h>

獲取到了數據存入數組那接下來的事情就是上報策略的制定了。沒必要每次獲取數據都上報,可以設置每次啟動上報上一次session的全部記錄就好,啟動后隔個10秒或20秒錯開請求高峰期。上報時的數據結構也要盡可能的精簡,因為不能對用戶的流量造成太大的損失,也可以選擇先壓縮后再上傳。 

 

3.CPU占用率

同樣需要內存的那兩個頭文件

+ (CGFloat)cpuUsage
{
    thread_array_t         thread_list;
    mach_msg_type_number_t thread_count;
    thread_info_data_t     thinfo;
    mach_msg_type_number_t thread_info_count;
    thread_basic_info_t basic_info_th;
    
    // get threads in the task
    kern_return_t kr = task_threads(mach_task_self(), &thread_list, &thread_count);
    if (kr != KERN_SUCCESS) {
        return -1;
    }
    
    CGFloat tot_cpu = 0;
    
    for (int j = 0; j < thread_count; j++)
        
    {
        thread_info_count = THREAD_INFO_MAX;
        kr = thread_info(thread_list[j], THREAD_BASIC_INFO,(thread_info_t)thinfo, &thread_info_count);
        if (kr != KERN_SUCCESS) {
            return -1;
        }
        
        basic_info_th = (thread_basic_info_t)thinfo;
        
        if (!(basic_info_th->flags & TH_FLAGS_IDLE)) {
            tot_cpu = tot_cpu + basic_info_th->cpu_usage / (CGFloat)TH_USAGE_SCALE * 100.0;
        }
        
    } // for each thread
    //free mem
    kr = vm_deallocate(mach_task_self(), (vm_offset_t)thread_list, thread_count * sizeof(thread_t));
    assert(kr == KERN_SUCCESS);
    return tot_cpu;
}

關於采集數據的頻率完全是自己制定的,而且大部分app都是在剛啟動不久內cpu占用較大 之后就漸漸區域穩定,所以建議在剛開始采集間隔短一點比如1s,之后采集間隔逐漸加大最后穩定到5分鍾獲取一次。

 

4.頁面的幀率

_link = [CADisplayLink displayLinkWithTarget:self selector:@selector(tick:)];
[_link addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];

- (void)tick:(CADisplayLink *)link {
    if (_lastTime == 0) {
        _lastTime = link.timestamp;
        return;
    }
    _count++;
    NSTimeInterval delta = link.timestamp - _lastTime;
    if (delta < 1) return;
    _lastTime = link.timestamp;
    float fps = _count / delta;
    _count = 0;
}  

這個測量頁面幀率的做法就是通過這個CADisplayLink刷幀方法的調用次數計算的,一般這個方法最快能每秒調用60次,如果是CPU或是GPU某個步驟耗時導致渲染錯過了一次垂直信號,那這個方法就不會被調用了,之后統計的幀數也就隨之降低了。 上面代碼中的fps就是求出的這一時刻的幀率可以塞入數組再稍作處理上傳。這里需要注意的是 性能監測平台自己對性能的影響,這個統計幀率的方法可能相對來說要耗性能一些,所以需要控制在某些時刻采集一定的樣本就及時暫停。  這個數據建議hook每個頁面加載時的方法如viewwillappear,或是tableview滑動時的代理方法,因為卡頓大多發生在這兩個場景。 對幀率有興趣的可以做一個懸浮窗測試幀率,我之前寫過一個,但是還沒有抽離成組件有興趣的可以交流下。

 

5.url響應時間監控

這里普通的做法就是繼承NSURLProtocol 這個類寫一個子類,然后在子類中實現NSURLConnectionDelegate 的那五個代理方法。 

- (NSURLRequest *)connection:(NSURLConnection *)connection willSendRequest:(NSURLRequest *)request redirectResponse:(NSURLResponse *)response
//  這個方法里可以做計時的開始

- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
//  這里可以得到返回包的總大小

- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
//  這里將每次的data累加起來,可以做加載進度圓環之類的

- (void)connectionDidFinishLoading:(NSURLConnection *)connection
//  這里作為結束的時間

- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
//  錯誤的收集 

(經過調研connection在升級ipv6后還是可以使用的)你可以得到請求的url,建議只記錄host和path,因為吧拼上的參數也上傳第一不好快速分類,第二服務端還要做截取處理增加壓力,第三浪費流量。 也可以得到response包的length。 響應時間可以根據結束方法中的時間減去開始方法中的時間得到,然后以key-value的方式上傳。個人覺得請求沒必要與viewcontroller關聯,要考慮到管理頁面棧還要考慮present和dismiss,收效甚微。並且想知道哪個url屬於哪個頁面還有很多可執行的方案。

關於webView的url響應時間監控,比普通的native內url要復雜一些。 雖然上面說的統計方法也可以統計到webview的url響應時間,但是只能統計到完整url時間。webView的打開時可能會伴隨一些302跳轉之類的url,最后才到目標url。 如果一個url經歷了三次跳轉,響應時間很長,用上面說的統計方法只能統計總時間並不能定位到具體是哪一個子url異常。 這時就需要webView內特殊的時間統計了。

 

有的一個url會調多個不同團隊維護的服務導致了重定向(圖中ABC都是完整url的子url),分別在webView的兩個代理方法中埋點計算差值,從A走了didstart到B走了didstart這個時間的差值為①就是A所消耗的時間,①+②+③是完整的時間。 通過這種分離統計的方法,可以把慢url明確定位到每一個服務,不對拖累好人。

  

6.請求錯誤碼統計

如果沒有做錯誤碼統計的話,你服務器有問題或者某些地區被運營商劫持之類的,你app的界面可能顯示的就是一直轉菊花,這時是無法精確定位問題的。 所以將自己的請求都進行編號並收集錯誤碼是非常有必要的。 我們所關注的錯誤碼有三種:

1. 正常的HTTP Response code 比如200 ,404,500   

2. AFN(現在ASI用不了了,ipv6強制升級后 應該大部分人都是用AFN3.0了)指定的code 如 -1101, 1102等

3. 你自己請求制定的errorcode。 如 errorcode : 7, message : "您還沒有登錄" 或 errorcode:10,message:"簽名驗證失敗" 

還有很重要一點,就是將自己的所有接口做成一整套映射的編號, 舉個例子67對應的是persondetail接口。出現了請求錯誤,上傳的請求錯誤碼應該包含兩個部分:接口編號+錯誤編號。 錯誤編號建議優先使用自己制定的errcode,其次是AFNcode,最后才是HTTPresponsecode。

“67-10” “35-404” 如果以后上報了類似的錯誤碼,當然比只看到轉菊花要好定位的多。

 

最后提當下的第三方性能監控平台,有聽雲,OneApm,博睿。 這里就不一一對比每一個平台的優缺點了,因為大體上都是差不多的。

就拿oneapm來說吧,首先ui做的比其他今個平台都要好,讓人感覺更專業一點。  接入的方式也是對代碼侵入性較小,接一個SDK大小6M左右,加幾個framework,然后main函數加一行代碼,上傳的token是注冊后生成的。

我在自己github的一個庫里接了這個sdk,后來發現用的人還不少。里面有demo和一些性能監測平台的截圖有興趣可以去看看

https://github.com/dsxNiubility/SXNews  

核心功能(這里說一下,我只用了這些平台的移動端功能)也就是包括:UI交互,url響應時間,用戶的版本地域系統分布,及crash。 其中崩潰率如果需要統計的話需要手動上傳dsym文件。  並且用着用着就遇到了我前面說的問題,有一些我們不需要的功能,並且也有一些我們需要他卻沒有的功能,比如幀率和webView中的子url響應時間。 優點內就是FE展示時指標內的圖表很清晰,並且可以自定義定制儀表盤。這是一個小公司自己寫sdk前期難以達到的。  還有一點就是可配置,在設置頁面可以自行選擇自己所關注的指標。 然后程序每次啟動都會先有一個GET請求獲取配置,哪些打開哪些關閉,然后測量和上傳我們需要的(這里有一點可疑的是,他是真沒測量還是全都測量上傳了,只是在控制台里不給你展示罷了),我們自寫的sdk做個可配置也是必要的。

本文是作者一段時間內的經驗,希望能對你有所幫助,觀點不同歡迎討論。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM