
1. 先把 On-CPU 和 Off-CPU 這兩個詞掰開揉碎性能分析這件事做得久了會發(fā)現(xiàn)一個很尷尬的現(xiàn)象很多人拿到一臺卡頓的機(jī)器第一反應(yīng)是 top、ps、然后開 perf 抓火焰圖看到 CPU 使用率不高、火焰圖也沒幾個尖峰就得出系統(tǒng)沒問題的結(jié)論。但業(yè)務(wù)方還在催接口還是慢。問題出在哪出在只看了線程跑在 CPU 上的那段時間忽略了線程不在 CPU 上的那段更漫長的時間。Linux 性能分析里的 On-CPU 和 Off-CPU說的就是把一個線程的完整生命周期切成兩半來看On-CPU 是線程真正占用處理器執(zhí)行指令的時間Off-CPU 是線程雖然活著、但沒有被處理器執(zhí)行的時間。這兩塊時間加起來才是一個請求從進(jìn)來到返回的真實(shí)墻鐘耗時。我做過的排查里至少有六成的性能問題根子在 Off-CPU 上等鎖、等磁盤 IO、等網(wǎng)絡(luò)回包、等定時器、等被喚醒調(diào)度。而大家習(xí)慣用的工具幾乎全是為 On-CPU 設(shè)計的。這就導(dǎo)致一個結(jié)構(gòu)性盲區(qū)——你越用 On-CPU 的工具越會把注意力放在計算熱點(diǎn)上越容易漏掉阻塞點(diǎn)。所以這篇內(nèi)容想聊的就是怎么把這兩半時間都量出來、怎么看、怎么改。適合已經(jīng)會用 top、perf 這類基礎(chǔ)工具但遇到CPU 不高卻很慢的場景會卡住的運(yùn)維、后端開發(fā)和 SRE也適合剛接觸 Linux 性能調(diào)優(yōu)、想建立一套完整分析框架的朋友。1.1 一個類比餐廳后廚和等位區(qū)把服務(wù)器想象成一家餐廳CPU 核心就是廚師線程就是一道道待做的菜。On-CPU 時間相當(dāng)于廚師正在灶臺前顛勺的時間Off-CPU 時間是這道菜從點(diǎn)單到出餐之間廚師沒碰它的所有時間——可能是在等配菜送過來等 IO可能是灶臺被別的菜占著等鎖也可能是廚師被叫去處理別的單子、這道菜就擱在一邊調(diào)度延遲。只統(tǒng)計廚師顛勺的時長你會覺得后廚效率挺高但顧客感受到的是從點(diǎn)單到上菜的完整等待。這兩者之間的差距就是 Off-CPU 分析要填的坑。理解了這層類比后面所有的工具和指標(biāo)其實(shí)都是在回答兩個問題廚師到底在哪個灶臺上花了多少時間On-CPU 熱點(diǎn)以及這道菜在沒被烹飪的時候究竟卡在哪一步Off-CPU 阻塞點(diǎn)。1.2 為什么只盯 On-CPU 會系統(tǒng)性誤判CPU 使用率是個比值分子是忙的時間分母是總時間。當(dāng)系統(tǒng)大量線程都在睡覺等待時這個比值天然就低看起來很閑。但這恰恰可能是最糟的狀態(tài)線程數(shù)開了一堆每個都在等同一個資源吞吐上不去延遲還高。這類問題在 On-CPU 視角里幾乎隱身因?yàn)閴焊鶝]有多少指令在跑。更麻煩的是一些常見結(jié)論會把人帶偏。比如CPU 使用率低說明負(fù)載輕可以再壓一壓實(shí)際可能是下游某個依賴把請求全堵住了再比如火焰圖沒熱點(diǎn)所以代碼沒問題實(shí)際瓶頸在 syscall 睡眠或者 futex 等待上采樣器根本沒采到什么棧。所以建立 On-CPU 和 Off-CPU 的雙視角不是為了多學(xué)幾個命令而是為了堵住這個判斷漏洞。1.3 把線程時間賬本拆開一個線程從被創(chuàng)建到退出它的時間可以粗略拆成三塊Running在 CPU 上跑、Runnable就緒但在等 CPU、Sleeping睡眠等某個事件。Running 就是 On-CPU后兩者合起來是 Off-CPU。這里有個容易被忽視的細(xì)節(jié)Runnable 狀態(tài)本身也是一種浪費(fèi)它說明 CPU 不夠分或者調(diào)度策略讓某些線程遲遲輪不上。很多偶發(fā)尖刺就是 Runnable 排隊造成的。把這三塊量化出來你就能給任何一次慢請求做一個耗時歸因表多少時間在算多少時間在等鎖多少時間在等 IO多少時間在等調(diào)度。歸因清楚了優(yōu)化方向基本就是明牌。接下來的章節(jié)我會先講 On-CPU 怎么采得準(zhǔn)再講 Off-CPU 怎么把等待棧抓出來最后給一個從現(xiàn)象到修復(fù)的完整推演。2. On-CPU 分析采樣、火焰圖與熱點(diǎn)定位On-CPU 分析的目標(biāo)很明確找出線程把時間花在了哪些代碼路徑上。主流做法是基于定時中斷的采樣sampling而不是插樁計數(shù)因?yàn)椴蓸訉π阅苡绊懶 ⒛芊从痴鎸?shí)分布也不需要改代碼。采樣器每隔一個固定周期打斷一次 CPU抓下當(dāng)前的調(diào)用棧攢夠樣本數(shù)之后按棧聚合就得到了調(diào)用分布的近似。樣本越多統(tǒng)計越穩(wěn)。2.1 perf 采樣機(jī)制和幾個必須懂的參數(shù)Linux 上最通用的采樣工具就是 perf。它依賴內(nèi)核的 perf_event 子系統(tǒng)和硬件/軟件 PMU能按時間頻率或事件計數(shù)觸發(fā)采樣。一條典型的抓取命令是這樣perf record -F 99 -g -p 12345 -- sleep 30 perf script perf.out ./stackcollapse-perf.pl perf.out perf.folded ./flamegraph.pl perf.folded oncpu.svg幾個參數(shù)值得掰扯清楚。-F 99 表示每秒采樣 99 次。為什么不是 100因?yàn)檎麛?shù) 100 容易和系統(tǒng)里其他 100Hz 的周期任務(wù)比如某些定時器產(chǎn)生鎖相采出來的樣本會偏向某個固定相位導(dǎo)致分布失真。99 是個質(zhì)數(shù)能打散這種共振。-g 是抓完整調(diào)用棧沒有它你只能看到最頂層的函數(shù)看不到是誰調(diào)用的定位就沒有上下文。-- sleep 30 是限定采集時長用 sleep 而不是直接 Ctrl-C是為了讓腳本化更穩(wěn)定、退出更干凈。還有兩個參數(shù)在排查特定問題時很關(guān)鍵-e 可以指定事件默認(rèn)是 cycles但你也可以換成 cache-misses、branch-misses 來抓特定瓶頸--call-graph dwarf 會在用戶態(tài)棧難以回溯時改用 DWARF 調(diào)試信息展開代價是開銷更大、產(chǎn)出更大。一般先默認(rèn) fpframe pointer跑一遍棧斷了再考慮 dwarf。注意perf 的內(nèi)核棧符號來自 /proc/kallsyms用戶態(tài)符號需要帶調(diào)試信息的二進(jìn)制。生產(chǎn)環(huán)境經(jīng)常遇到符號被 strip 掉的情況采出來全是十六進(jìn)制地址白忙一場。動手前先確認(rèn)目標(biāo)進(jìn)程有沒有 debuginfo 或者帶符號的構(gòu)建產(chǎn)物。2.2 火焰圖怎么讀哪些形狀代表什么問題火焰圖的橫軸是樣本占比不是時間順序縱軸是調(diào)用深度每一塊是一個棧幀塊越寬說明該函數(shù)在采樣里出現(xiàn)得越多。讀圖的核心心法就一句先找最寬的葉子再看它是被誰調(diào)用的。最寬的葉子通常是實(shí)際的 CPU 消耗大戶但它的元兇可能在它的調(diào)用者那一層——比如一個通用的序列化函數(shù)很寬但真正的問題是某個業(yè)務(wù)邏輯反復(fù)調(diào)它。幾種典型形狀對應(yīng)不同問題。平頂山形狀底部寬、頂層一排差不多的寬塊通常是循環(huán)或批處理在反復(fù)執(zhí)行同類操作優(yōu)化點(diǎn)是降復(fù)雜度或加緩存。塔尖形狀某一層特別寬下面迅速收窄說明有個單點(diǎn)函數(shù)吃滿了 CPU直接盯它。斷層形狀中間某層突然很窄說明采樣在這個函數(shù)里分布稀疏要么是它執(zhí)行快要么是棧在那斷了需要配合 dwarf 或檢查符號。2.3 符號丟失、棧截斷和 JIT 代碼這三類坑實(shí)話說On-CPU 分析最容易翻車的不是命令不會敲而是采出來的東西沒法看。第一類坑是符號丟失解決方式是裝對應(yīng)版本的 debuginfo 包或者用perf buildid-list確認(rèn) build-id 能不能對上 debug 文件。第二類坑是棧截斷frame pointer 被編譯器優(yōu)化掉了-fomit-frame-pointer 是很多發(fā)行版的默認(rèn)這時候要么重新編譯加 -fno-omit-frame-pointer要么切到 dwarf 模式。深度太深時 perf 默認(rèn)只抓一部分??梢杂?-call-graph dwarf,65528加大棧大小。第三類坑是 JIT 代碼Java、Node、部分 Go 場景都會遇到采出來是一堆[JIT]或者匿名地址。Java 的解法是掛 perf-map-agent讓 JIT 編譯時把方法地址映射寫到 /tmp/perf-PID.mapperf 就能解析更省事的做法是直接用 async-profiler 抓 CPU 火焰圖它對 JIT 代碼的映射是內(nèi)建的。Node 可以用--perf-basic-prof之類的方式輸出映射表。這類工作如果一開始沒規(guī)劃好事后補(bǔ)會很難受建議在部署階段就把符號和映射準(zhǔn)備好。3. Off-CPU 分析把等待這件事量化聊完 On-CPU重頭戲來了。Off-CPU 分析要回答的是線程沒在跑的時候到底在等什么等了多久。傳統(tǒng)思路是插樁記錄每個阻塞點(diǎn)的進(jìn)入和退出時間但這要求改代碼、而且覆蓋不到內(nèi)核路徑。更優(yōu)雅的做法同樣是跟蹤切換內(nèi)核每次發(fā)生上下文切換時都會記錄誰被換下、誰被換上如果我們在這兩個時刻打點(diǎn)就能算出每個線程兩次被調(diào)度之間隔了多久并且抓下它被換下時的調(diào)用棧——那個棧就是它睡覺的原因。3.1 線程離開 CPU 的幾種典型原因歸納一下線程從 Running 變成 Off-CPU常見就這幾類。第一類是睡眠等待主動調(diào)用會睡眠的 syscall 或同步原語比如 read/write 阻塞、futex 等待、nanosleep。第二類是等鎖包括用戶態(tài)自旋鎖的退化、互斥量、文件鎖內(nèi)核里 futex 是重災(zāi)區(qū)。第三類是等 IO包括磁盤、網(wǎng)絡(luò)、管道很多時候表現(xiàn)為 epoll_wait 或 read 阻塞。第四類是被搶占時間片用完或被高優(yōu)先級線程擠下去這時候線程還是 Runnable只是沒輪上。前幾類是 Sleeping第四類是 Runnable。區(qū)分它們很重要Sleeping 說明有外部依賴拖著優(yōu)化方向是縮短依賴或并發(fā)化Runnable 說明 CPU 資源或調(diào)度配置有問題優(yōu)化方向是加資源或調(diào)優(yōu)先級、綁核。很多分析工具會把兩者混在總 Off-CPU 時間里看的時候要留意能不能拆開。3.2 offcputime 與 wakeuptime等待棧和喚醒鏈BCC 工具集里的 offcputime 是最常用的入口。它會跟蹤上下文切換統(tǒng)計每個線程 Off-CPU 的累計時長并按被換下時的棧聚合offcputime -df -p 12345 30 offcpu.folded flamegraph.pl --colorio --titleOff-CPU Time --countnameus offcpu.folded offcpu.svg參數(shù)含義-d 打印分隔符方便后續(xù)折疊-f 輸出折疊格式直接喂給火焰圖腳本-p 限定進(jìn)程最后一個數(shù)字是采集秒數(shù)。得到的 Off-CPU 火焰圖跟 On-CPU 長得像但橫軸寬度是阻塞時長微秒讀法是哪個棧把線程卡住得最久。offcputime 解決的是線程在哪睡但它不回答誰把它叫醒的。這時候就要請出 wakeuptime。它把阻塞棧和喚醒棧拼在一起直接告訴你A 線程在某個 futex 上睡了 200ms是被 B 線程喚醒的。這個信息非常值錢因?yàn)樗岩蚬溄o串起來了——很多鎖競爭的根因不是持鎖線程慢而是持鎖線程被別的更慢的操作卡住了。wakeuptime -p 12345 30實(shí)測下來wakeuptime 在排查線程池互相等待、上游把下游拖死的場景里特別好用。缺點(diǎn)是對復(fù)雜調(diào)用棧的輸出會比較長需要配合過濾看。3.3 runqlat被忽視的調(diào)度延遲前面說過 Runnable 狀態(tài)是獨(dú)立的一類浪費(fèi)量化它主要靠 runqlat老版本叫 runqslower。它統(tǒng)計任務(wù)從進(jìn)入就緒隊列到真正被調(diào)度執(zhí)行之間的延遲分布輸出是直方圖runqlat -m -P 10-m用毫秒做單位-P按進(jìn)程聚合。看這個輸出主要看長尾如果 p99 到了幾十毫秒說明某些線程經(jīng)常在就緒隊列里等很久CPU 飽和或者有 CPU 密集任務(wù)在搶。這類延遲在應(yīng)用層往往表現(xiàn)為莫名的抖動而且因?yàn)樗幌?CPU 時間常規(guī)監(jiān)控根本看不到。線上偶發(fā)毛刺排查runqlat 是我必開的一個工具。注意offcputime 統(tǒng)計的時長里其實(shí)已經(jīng)隱含包含了 Runnable 的等待因?yàn)榫€程從被換下到再次被換上中間可能既有睡眠也有排隊。要精確拆開需要同時看 offcputime 和 runqlat用差值估算。睡了 100ms 里有 80ms 是在排隊這種結(jié)論只能靠兩個工具配合才能得出。4. 一次真實(shí)排查的完整推演講完工具來走一遍完整流程比干講參數(shù)有用得多。以下是我處理過的一個典型場景做了脫敏和簡化但思路和步驟是原樣的。4.1 現(xiàn)象描述和初步假設(shè)現(xiàn)象某服務(wù)接口在壓測時 p99 從 20ms 漲到 900ms但機(jī)器 CPU 使用率只有 25%IO 也不高監(jiān)控上看不出明顯異常。單看資源指標(biāo)這臺機(jī)器簡直空閑得可以去度假。這種資源不緊張但延遲爆炸的形態(tài)基本可以判定瓶頸在 Off-CPU而且大概率是排隊或者鎖不是計算。先立幾個假設(shè)一是線程池被某個慢調(diào)用占滿新請求只能排隊二是某個共享資源連接池、緩存、鎖成了串行點(diǎn)三是 GC 或者定時任務(wù)周期性搶占導(dǎo)致抖動。接下來逐個驗(yàn)證。4.2 用 On-CPU 排除計算密集第一步還是先排除計算問題畢竟它最好查。抓 30 秒 On-CPU 火焰圖perf record -F 99 -g -p $(pgrep -f myapp | head -1) -- sleep 30 perf script | ./stackcollapse-perf.pl | ./flamegraph.pl oncpu.svg結(jié)果很干凈最寬的葉子占不到 5%沒有任何函數(shù)吃滿。這就基本排除了 CPU 熱點(diǎn)也側(cè)面印證問題在等待。同時看了一眼 On-CPU 的采樣總數(shù)30 秒 99Hz 應(yīng)該接近 3000 個樣本實(shí)際只采到不到 800 個——這個差值本身就是信號大量時間線程根本沒在跑采樣器采不到。4.3 用 Off-CPU 定位到鎖競爭接下來抓 Off-CPUoffcputime -df -p $(pgrep -f myapp | head -1) 30 offcpu.folded flamegraph.pl --colorio --titleOff-CPU --countnameus offcpu.folded offcpu.svg火焰圖一出來最寬的一條棧非常顯眼從業(yè)務(wù)入口一路到pthread_mutex_lock再往下是futex_wait累計阻塞時間占了總 Off-CPU 時間的六成以上。也就是說大量線程都堵在同一把鎖上。順著棧找到那個 mutex 對應(yīng)的代碼位置是一個本以為讀多寫少的共享配置緩存實(shí)際每次刷新都會整表加寫鎖而刷新又調(diào)用了下游接口一旦下游慢鎖就持很久。再用 wakeuptime 確認(rèn)喚醒鏈wakeuptime -p $(pgrep -f myapp | head -1) 30輸出清楚地顯示喚醒這些等鎖線程的正是那個持鎖的刷新線程而刷新線程自己又阻塞在下游網(wǎng)絡(luò)讀上。到這里因果鏈完整了下游慢 → 刷新線程持鎖時間長 → 其他線程全堵在鎖上 → 請求排隊 → p99 爆炸。4.4 驗(yàn)證與修復(fù)驗(yàn)證方式很直接給共享緩存的刷新邏輯改成先算好再原子替換指針也就是讀路徑無鎖、寫路徑只做一次指針交換把持鎖時間從整個下游調(diào)用縮到指針賦值。改完復(fù)壓p99 回落到 30ms 以內(nèi)Off-CPU 火焰圖上那條 futex_wait 的寬度基本消失了。這次排查的價值不在于用了多高級的工具而在于先用 On-CPU 排除、再用 Off-CPU 定位、最后用 wakeuptime 串因果這個固定套路。你把這套順序記下來遇到類似問題就不用靠猜了。5. 環(huán)境準(zhǔn)備與工具選型的一些硬門檻工具再好環(huán)境不支持也白搭。Off-CPU 這類分析工具大多基于 eBPF對內(nèi)核版本和權(quán)限有實(shí)打?qū)嵉囊筇崆按_認(rèn)能省掉很多抓耳撓腮的時間。5.1 內(nèi)核版本與 eBPF 能力自檢先看內(nèi)核版本uname -roffcputime、wakeuptime 這類工具依賴掛載 kprobe 的能力實(shí)踐上建議內(nèi)核 4.15 以上5.x 更穩(wěn)。4.9 到 4.14 之間有些工具能跑但偶有兼容問題。檢查內(nèi)核配置grep -E CONFIG_BPF|CONFIG_BPF_SYSCALL|CONFIG_KPROBE /boot/config-$(uname -r)幾個關(guān)鍵項要能查到BCC 工具集才能正常工作。如果是最小化安裝的發(fā)行版可能連 bcc-tools 包都沒裝。5.2 安裝 BCC 與依賴主流發(fā)行版基本都有現(xiàn)成包# Debian/Ubuntu 系 sudo apt-get install bpfcc-tools linux-headers-$(uname -r) # RHEL/CentOS 系 sudo yum install bcc-tools kernel-devel-$(uname -r)裝完工具一般在/usr/sbin或者/usr/share/bcc/tools下可能帶-bpfcc后綴。perf 和火焰圖腳本另裝perf 跟內(nèi)核版本綁定最好用包管理器裝避免自己編譯出坑FlameGraph 是一堆 Perl 腳本clone 下來即可。5.3 容器和虛擬化環(huán)境的特殊處理容器環(huán)境里有兩個必踩的坑。第一是 PID 命名空間容器里看到的 PID 和宿主機(jī)不是一回事perf 和 bcc 工具如果跑在宿主機(jī)上得用宿主機(jī)的 PID用docker top或者ps -eLf | grep去定位。BCC 的-p參數(shù)接受的是它所在命名空間里的 PID跑在容器內(nèi)就填容器內(nèi) PID。第二是權(quán)限eBPF 傳統(tǒng)上要 root5.8 以后有了 CAP_BPF 和 CAP_PERFMON 可以降權(quán)但生產(chǎn)上多數(shù)還是用 root 或者 privileged 容器。虛擬化環(huán)境里硬件 PMU 事件可能不可用perf 的部分功能會退化但軟件事件cpu-clock和采樣依然可用Off-CPU 工具基于 kprobe 基本不受影響。這一點(diǎn)在云主機(jī)上不需要太擔(dān)心。提示如果 offcputime 報 HINT: ... failed to attach 之類的錯誤先確認(rèn)內(nèi)核開了 CONFIG_BPF 相關(guān)選項再確認(rèn)自己沒有在受限的 seccomp 環(huán)境里跑。有些安全加固過的環(huán)境會禁用 bpf() 系統(tǒng)調(diào)用。6. 常見問題與排查技巧速查把經(jīng)常遇到的坑整理成一張表排查時直接對號入座比翻文檔快?,F(xiàn)象可能原因排查手段處理方式On-CPU 火焰圖全是地址符號二進(jìn)制被 strip 或缺 debuginfoperf buildid-list對比安裝 debuginfo 包或帶符號重編采樣樣本數(shù)遠(yuǎn)低于預(yù)期線程大量 Off-CPU對比預(yù)期樣本數(shù)轉(zhuǎn) Off-CPU 分析Java 棧顯示為[JIT]JIT 代碼無符號映射檢查/tmp/perf-PID.map掛 perf-map-agent 或換 async-profileroffcputime 棧斷在中間棧展開深度不足看棧頂部是否有斷裂dwarf 模式或加大棧參數(shù)Off-CPU 圖里大量 idle/swapper采集到空閑任務(wù)觀察最寬塊的名字用-p限定進(jìn)程或加過濾p99 抖動但 CPU 不高調(diào)度延遲長尾runqlat -m -P查 CPU 飽和或綁核調(diào)優(yōu)先級等鎖時間長但持鎖者不慢持鎖者被下游卡住wakeuptime看喚醒鏈縮短持鎖范圍或改無鎖容器內(nèi)工具報 PID 找不到命名空間不一致docker top核對用對應(yīng)命名空間的 PIDeBPF 工具 attach 失敗內(nèi)核能力或 seccomp 限制查內(nèi)核 config 和安全策略換環(huán)境或提權(quán)這張表里我最想強(qiáng)調(diào)的兩行是采樣樣本數(shù)遠(yuǎn)低于預(yù)期和等鎖時間長但持鎖者不慢。前者是一個很隱蔽的信號很多人看到 CPU 不高就直接放棄 perf 了其實(shí)差值本身就在告訴你答案后者是翻身常犯的錯——盯著等鎖的人看忽略了叫醒他們的人。把這兩個視角建立起來絕大多數(shù)詭異延遲都能找到入口。再補(bǔ)幾個實(shí)操心得。第一采集時長別太短Off-CPU 事件往往稀疏10 秒可能采不到幾次建議至少 30 秒長尾問題可以拉到幾分鐘。第二采樣時盡量復(fù)現(xiàn)問題場景空跑采集沒意義要壓測就邊壓邊采。第三火焰圖看的是分布不是順序別試圖從圖里讀出時間先后要看先后得用 trace 類工具。第四perf script產(chǎn)出的中間文件可能很大幾十秒采集幾百 MB 是常事注意磁盤空間和后續(xù)處理的內(nèi)存占用。7. 一些踩坑之后才明白的經(jīng)驗(yàn)最后聊點(diǎn)工具之外的東西都是實(shí)打?qū)嵅瘸鰜淼?。剛接觸 Off-CPU 分析時我老想找一個命令搞定后來發(fā)現(xiàn)這套分析的價值恰恰在于組合On-CPU 排除計算、offcputime 找阻塞點(diǎn)、wakeuptime 串因果、runqlat 補(bǔ)調(diào)度四個環(huán)節(jié)缺一個結(jié)論就可能偏。還有一個體會是Off-CPU 時間不是越低越好而是越可解釋越好。有些等待是設(shè)計使然比如正常的網(wǎng)絡(luò) RTT、必要的批量攢批這些等待是劃算的真正要干掉的是那些沒必要的、可以并發(fā)化的、因?yàn)閷?shí)現(xiàn)缺陷被放大的等待。所以別追求把 Off-CPU 壓到零先追求能說清楚每一段等待花在哪、值不值。另一個容易忽略的點(diǎn)是采樣開銷。eBPF 工具在高頻事件上比如上下文切換特別頻繁的系統(tǒng)本身也會產(chǎn)生可觀測的額外負(fù)載采集的時候要留意對被分析進(jìn)程的影響別把觀察行為變成了干擾因素。壓測對比時最好先跑一次不采集的基線再跑采集版本把采集開銷從結(jié)果里扣掉不然容易冤枉代碼。再分享一個小技巧如果線上不方便跑 root 工具可以先從/proc/PID/status里的 voluntary_ctxt_switches 和 nonvoluntary_ctxt_switches 看趨勢兩個值漲得快說明上下文切換頻繁O(jiān)ff-CPU 問題概率大這就是個零成本的先行指標(biāo)用來決定要不要上重武器很夠用。這套東西我用了好幾年越用越覺得它不是什么高深技術(shù)就是把線程到底在干嘛這個問題拆成兩半來回答的樸素方法。工具會隨內(nèi)核版本變理解On-CPU 加 Off-CPU 等于完整墻鐘時間這個賬本關(guān)系才是長期管用的東西。