APM監(jiān)控卡頓并上報(bào)有兩種方案:
- 監(jiān)聽Runloop狀態(tài)回調(diào), 子線程ping主線程
if (currentMode->_observerMask & kCFRunLoopEntry )
// 通知 Observers: RunLoop 即將進(jìn)入 loop
__CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);
// 進(jìn)入loop
result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);
- 開啟do while 循環(huán)保活線程,通知Observers, Runloop觸發(fā)timer回調(diào),然后執(zhí)行被加入的Block
if (rlm->_observerMask & kCFRunLoopBeforeTimers)
// 通知 Observers: RunLoop 即將觸發(fā) Timer 回調(diào)
__CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);
if (rlm->_observerMask & kCFRunLoopBeforeSources)
// 通知 Observers: RunLoop 即將觸發(fā) Source 回調(diào)
__CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources);
// 執(zhí)行被加入的block
__CFRunLoopDoBlocks(rl, rlm);
- runloop觸發(fā)source0的回調(diào),如果source1是ready狀態(tài),會(huì)跳轉(zhuǎn)到handle_msg去處理消息, 如果有 Source1 (基于port) 處于 ready 狀態(tài),直接處理這個(gè) Source1 然后跳轉(zhuǎn)去處理消息
if (MACH_PORT_NULL != dispatchPort && !didDispatchPortLastTime) {
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
msg = (mach_msg_header_t *)msg_buffer;
if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0, &voucherState, NULL)) {
goto handle_msg;
}
#elif DEPLOYMENT_TARGET_WINDOWS
if (__CFRunLoopWaitForMultipleObjects(NULL, &dispatchPort, 0, 0, &livePort, NULL)) {
goto handle_msg;
}
#endif
}
- 回調(diào)觸發(fā)后,通知Observer進(jìn)入休眠狀態(tài)
Boolean poll = sourceHandledThisLoop || (0ULL == timeout_context->termTSR);
// 通知 Observers: RunLoop 的線程即將進(jìn)入休眠(sleep)
if (!poll && (rlm->_observerMask & kCFRunLoopBeforeWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);
__CFRunLoopSetSleeping(rl);
- 休眠后,等待mach_port消息,以便再次喚醒
do {
if (kCFUseCollectableAllocator) {
// objc_clear_stack(0);
// <rdar://problem/16393959>
memset(msg_buffer, 0, sizeof(msg_buffer));
}
msg = (mach_msg_header_t *)msg_buffer;
__CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy);
if (modeQueuePort != MACH_PORT_NULL && livePort == modeQueuePort) {
// Drain the internal queue. If one of the callout blocks sets the timerFired flag, break out and service the timer.
while (_dispatch_runloop_root_queue_perform_4CF(rlm->_queue));
if (rlm->_timerFired) {
// Leave livePort as the queue port, and service timers below
rlm->_timerFired = false;
break;
} else {
if (msg && msg != (mach_msg_header_t *)msg_buffer) free(msg);
}
} else {
// Go ahead and leave the inner loop.
break;
}
} while (1);
- 喚醒時(shí)通知Observer, Runloop的線程剛剛被喚醒了
// 通知 Observers: RunLoop 的線程剛剛被喚醒了
if (!poll && (rlm->_observerMask & kCFRunLoopAfterWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting);
// 處理消息
handle_msg:;
__CFRunLoopSetIgnoreWakeUps(rl);
- Runloop喚醒后,處理喚醒時(shí)收到的消息
///如果是Timer處理回調(diào)
///如果是dispatch,處理block
///如果是source1, 處理事件
#if USE_MK_TIMER_TOO
// 如果一個(gè) Timer 到時(shí)間了,觸發(fā)這個(gè)Timer的回調(diào)
else if (rlm->_timerPort != MACH_PORT_NULL && livePort == rlm->_timerPort) {
CFRUNLOOP_WAKEUP_FOR_TIMER();
// On Windows, we have observed an issue where the timer port is set before the time which we requested it to be set. For example, we set the fire time to be TSR 167646765860, but it is actually observed firing at TSR 167646764145, which is 1715 ticks early. The result is that, when __CFRunLoopDoTimers checks to see if any of the run loop timers should be firing, it appears to be 'too early' for the next timer, and no timers are handled.
// In this case, the timer port has been automatically reset (since it was returned from MsgWaitForMultipleObjectsEx), and if we do not re-arm it, then no timers will ever be serviced again unless something adjusts the timer list (e.g. adding or removing timers). The fix for the issue is to reset the timer here if CFRunLoopDoTimers did not handle a timer itself. 9308754
if (!__CFRunLoopDoTimers(rl, rlm, mach_absolute_time())) {
// Re-arm the next timer
__CFArmNextTimerInMode(rlm, rl);
}
}
#endif
// 如果有dispatch到main_queue的block,執(zhí)行block
else if (livePort == dispatchPort) {
CFRUNLOOP_WAKEUP_FOR_DISPATCH();
__CFRunLoopModeUnlock(rlm);
__CFRunLoopUnlock(rl);
_CFSetTSD(__CFTSDKeyIsInGCDMainQ, (void *)6, NULL);
#if DEPLOYMENT_TARGET_WINDOWS
void *msg = 0;
#endif
__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
_CFSetTSD(__CFTSDKeyIsInGCDMainQ, (void *)0, NULL);
__CFRunLoopLock(rl);
__CFRunLoopModeLock(rlm);
sourceHandledThisLoop = true;
didDispatchPortLastTime = true;
}
// 如果一個(gè) Source1 (基于port) 發(fā)出事件了,處理這個(gè)事件
else {
CFRUNLOOP_WAKEUP_FOR_SOURCE();
// If we received a voucher from this mach_msg, then put a copy of the new voucher into TSD. CFMachPortBoost will look in the TSD for the voucher. By using the value in the TSD we tie the CFMachPortBoost to this received mach_msg explicitly without a chance for anything in between the two pieces of code to set the voucher again.
voucher_t previousVoucher = _CFSetTSD(__CFTSDKeyMachMessageHasVoucher, (void *)voucherCopy, os_release);
CFRunLoopSourceRef rls = __CFRunLoopModeFindSourceForMachPort(rl, rlm, livePort);
if (rls) {
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
mach_msg_header_t *reply = NULL;
sourceHandledThisLoop = __CFRunLoopDoSource1(rl, rlm, rls, msg, msg->msgh_size, &reply) || sourceHandledThisLoop;
if (NULL != reply) {
(void)mach_msg(reply, MACH_SEND_MSG, reply->msgh_size, 0, MACH_PORT_NULL, 0, MACH_PORT_NULL);
CFAllocatorDeallocate(kCFAllocatorSystemDefault, reply);
}
#elif DEPLOYMENT_TARGET_WINDOWS
sourceHandledThisLoop = __CFRunLoopDoSource1(rl, rlm, rls) || sourceHandledThisLoop;
#endif
- 根據(jù)當(dāng)前runloop狀態(tài)判斷是否需要進(jìn)入下一個(gè)loop, 當(dāng)被外部強(qiáng)制停止或者loop超時(shí),就不繼續(xù)下一個(gè)loop, 否則進(jìn)入下一個(gè)loop
/**
if (sourceHandledThisLoop && stopAfterHandle) {
// 進(jìn)入loop時(shí)參數(shù)說處理完事件就返回
retVal = kCFRunLoopRunHandledSource;
} else if (timeout_context->termTSR < mach_absolute_time()) {
// 超出傳入?yún)?shù)標(biāo)記的超時(shí)時(shí)間了
retVal = kCFRunLoopRunTimedOut;
} else if (__CFRunLoopIsStopped(rl)) {
__CFRunLoopUnsetStopped(rl);
// 被外部調(diào)用者強(qiáng)制停止了
retVal = kCFRunLoopRunStopped;
} else if (rlm->_stopped) {
rlm->_stopped = false;
retVal = kCFRunLoopRunStopped;
} else if (__CFRunLoopModeIsEmpty(rl, rlm, previousMode)) {
// source/timer一個(gè)都沒有
retVal = kCFRunLoopRunFinished;
}
*/
/// Source1是runloop用來處理Mach port傳來的系統(tǒng)的事件的, Source0是用來處理用戶事件的
/// 檢測(cè)runloop,一旦發(fā)現(xiàn)在進(jìn)入睡眠前的KCFRunLoopBeforeSources到喚醒后 KCFRunLoopAfterWaiting的時(shí)間過長,說明Runloop卡頓了, 開啟一個(gè)子線程,不斷的去循環(huán)檢測(cè),如果在n次都超過了預(yù)定好的閾值, 認(rèn)為是卡頓了, 然后進(jìn)行dump當(dāng)前的堆棧并上報(bào)
/**
通過 long dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout) 方法判斷是否阻塞主線程,Returns zero on success, or non-zero if the timeout occurred. 返回非0則代表超時(shí)阻塞了主線程
*/
//MARK: 子線程ping主線程監(jiān)聽的方式
///開啟一個(gè)子線程,創(chuàng)建一個(gè)初始值為0的信號(hào)量, 一個(gè)bool標(biāo)志位,將設(shè)置為NO的標(biāo)志位的任務(wù)派發(fā)到主線程中去, 子線程休眠閾值到后判斷是否被主線程成功,如果沒成功認(rèn)為是主線程卡頓
/*
while (self.isCancelled == NO) {
@autoreleasepool {
__block BOOL isMainThreadNoRespond = YES;
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
dispatch_async(dispatch_get_main_queue(), ^{
isMainThreadNoRespond = NO;
dispatch_semaphore_signal(semaphore);
});
[NSThread sleepForTimeInterval:self.threshold];
if (isMainThreadNoRespond) {
if (self.handlerBlock) {
self.handlerBlock(); // 外部在 block 內(nèi)部 dump 堆棧(下面會(huì)講),數(shù)據(jù)上報(bào)
}
}
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
}
}
啟動(dòng)時(shí)間監(jiān)控
1 . pre-main階段影響因素
/**
1. 動(dòng)態(tài)庫越多,啟動(dòng)越慢
2. Objc類越好, 函數(shù)越多,啟動(dòng)越慢
3. 可執(zhí)行文件越大,啟動(dòng)越慢
4. Objc的load方法越多,啟動(dòng)越慢
*/
/**
struct thread_basic_info {
time_value_t user_time; /* user run time(用戶運(yùn)行時(shí)長) */
time_value_t system_time; /* system run time(系統(tǒng)運(yùn)行時(shí)長) */
integer_t cpu_usage; /* scaled cpu usage percentage(CPU使用率,上限1000) */
policy_t policy; /* scheduling policy in effect(有效調(diào)度策略) */
integer_t run_state; /* run state (運(yùn)行狀態(tài),見下) */
integer_t flags; /* various flags (各種各樣的標(biāo)記) */
integer_t suspend_count; /* suspend count for thread(線程掛起次數(shù)) */
integer_t sleep_time; /* number of seconds that thread
* has been sleeping(休眠時(shí)間) */
};
*/
OOM 超出了內(nèi)存分配
/// 收到低內(nèi)存警告不一定會(huì) Crash,因?yàn)橛?秒鐘的系統(tǒng)判斷時(shí)間,6秒內(nèi)內(nèi)存下降了則不會(huì) crash。發(fā)生 OOM 也不一定會(huì)收到低內(nèi)存警告
/**
合理使用autoreleasepool, autoreleasepool對(duì)象是在Runloop結(jié)束時(shí)釋放,在ARC下, 如果我們不斷的申請(qǐng)內(nèi)存, 我們需要手動(dòng)的添加autoreleasepool, 避免短時(shí)間內(nèi)內(nèi)存猛漲發(fā)生OOM
內(nèi)存分配: malloc用的是 malloc_zone_malloc, calloc用的是malloc_zone_calloc
在處理圖片縮放時(shí),ImageIO占用內(nèi)存會(huì)比直接處理圖片縮放小
*/