iOS App卡頓監控(Freezing/Lag)

靈機文化發表於2019-02-05

iOS App卡頓監控(Freezing/Lag)

筆記(寫在前面):
關於應用的效能監控,需要從多方面進行綜合考慮,此處僅從其中一個方面,進行學習研究。

如何判斷主執行緒卡頓:

監測NSRunLoop耗時情況。

NSRunLoop的呼叫主要在kCFRunLoopBeforeSourceskCFRunLoopBeforeWaiting之間,以及kCFRunLoopAfterWaiting之後。因此,若是發現這個兩個時間內耗時過長,就可以判定此時主執行緒出現卡頓情況。

一、監控NSRunLoop狀態變化

使用CFRunLoopObserverRef,實時獲得這些狀態值的變化,如下:

/// RunLoop狀態觀察回撥
static void runLoopObserverCallBack(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info)
{
    <#MyClass#> *object = (__bridge <#MyClass#>*)info;
    // 記錄狀態值
    object->activity = activity;
}
/// 註冊RunLoop狀態觀察
- (void)registerRunLoopObserver {
    CFRunLoopObserverContext context = {0,(__bridge void*)self,NULL,NULL};
    CFRunLoopObserverRef observer = CFRunLoopObserverCreate(kCFAllocatorDefault,
                                                            kCFRunLoopAllActivities,
                                                            YES,
                                                            0,
                                                            &runLoopObserverCallBack,
                                                            &context);
    CFRunLoopAddObserver(CFRunLoopGetMain(), observer, kCFRunLoopCommonModes);
}

二、RunLoop耗時計算

另外開啟一個執行緒,實時計算兩個狀態區域之間的耗時,是否達到閾值。

dispatch_semaphore_t讓子執行緒更及時地獲知主執行緒NSRunLoop狀態變化

卡頓覆蓋範圍:多次連續小卡頓單次長時間卡頓

新增計算邏輯,如下:

/// RunLoop狀態觀察回撥
static void runLoopObserverCallBack(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info)
{
    <#MyClass#> *object = (__bridge <#MyClass#>*)info;
    // 記錄狀態值
    object->activity = activity;
    
    // 傳送訊號
    dispatch_semaphore_t semaphore = object->semaphore;
    dispatch_semaphore_signal(semaphore);
}
/// 註冊RunLoop狀態觀察,並計算是否卡頓
- (void) registerRunLoopObserver {
    CFRunLoopObserverContext context = {0,(__bridge void*)self,NULL,NULL};
    CFRunLoopObserverRef observer = CFRunLoopObserverCreate(kCFAllocatorDefault,
                                                            kCFRunLoopAllActivities,
                                                            YES,
                                                            0,
                                                            &runLoopObserverCallBack,
                                                            &context);
    CFRunLoopAddObserver(CFRunLoopGetMain(), observer, kCFRunLoopCommonModes);
    
    // 建立訊號
    semaphore = dispatch_semaphore_create(0);
    
    // 在子執行緒監控時長
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        while (YES) {
            // 假定連續5次超時50ms認為卡頓(當然也包含了單次超時250ms)
            long st = dispatch_semaphore_wait(semaphore, dispatch_time(DISPATCH_TIME_NOW, 50*NSEC_PER_MSEC));
            if (st != 0) {
                if (activity==kCFRunLoopBeforeSources || activity==kCFRunLoopAfterWaiting) {
                    if (++timeoutCount < 5) {
                        continue;
                    }
                    // 發現卡頓
                    NSLog(@"卡、卡、卡、頓、頓、了");
                }
            }
            timeoutCount = 0;
        }
    });
}

三、記錄卡頓的函式呼叫

目擊卡頓現場,記錄此時的呼叫函式資訊,作為卡頓證據。

此處,使用第三方Crash收集元件PLCrashReporter,它不僅可以收集Crash資訊,也可用於實時獲取各執行緒的呼叫堆疊,使用示例如下:

PLCrashReporterConfig *config = [[PLCrashReporterConfig alloc] initWithSignalHandlerType:PLCrashReporterSignalHandlerTypeBSD
                                                                   symbolicationStrategy:PLCrashReporterSymbolicationStrategyAll];
PLCrashReporter *crashReporter = [[PLCrashReporter alloc] initWithConfiguration:config];
NSData *data = [crashReporter generateLiveReport];
PLCrashReport *reporter = [[PLCrashReport alloc] initWithData:data error:NULL];
NSString *report = [PLCrashReportTextFormatter stringValueForCrashReport:reporter
                                                          withTextFormat:PLCrashReportTextFormatiOS];
NSLog(@"------------
%@
------------", report);

特別注意:

PLCrashReporter雖然能提供較為準確的堆疊資訊,用於定位問題,特別是使用符號化策略PLCrashReporterSymbolicationStrategyAll時,能夠對堆疊資訊進行符號化,但會消耗大量資源,需要佔用較多時間,導致卡死現象(自測時,耗時超過7s,層多次到10s以上)。

不使用符號化策略PLCrashReporterSymbolicationStrategyNone,測試時,平均耗時也接近3s。

因此,加入該資訊採集,需要特別注意,建議僅在開發除錯階段使用。

為了投入線上使用,還需要再想想如何解決該問題。

四、上報伺服器

檢測到卡頓,獲取到呼叫堆疊資訊,客戶端再根據實際情況進行一定程度的過濾處理,將有價值的資訊上報伺服器。

後續對伺服器收集到的資料進行分析,定位需要優化的功能邏輯。

相關文章