Step 1: user, system, or iowait
Read the CPU line at the top of `top` first: high us means application code is burning CPU (business logic, hot loops, GC); high sy means kernel overhead (syscall storms, lock contention, context switching); high wa is not CPU business at all — slow disk IO inflates load. The directions are totally different, and the wrong tool wastes an afternoon.
Compare load average against core count: `nproc` prints cores; only load persistently above that means real queuing.
top # 看 %Cpu(s) 行的 us / sy / wa
nproc # 核数,用于对照 load averageStep 2: pin the process, then drill to threads
After `top` (press P to sort by CPU) identifies the hot process, most people stop there. But the hotspot is often just a few threads inside one process — `top -H -p <pid>` shows a per-thread view; note the thread IDs burning CPU.
An even handier form is `pidstat -t -p <pid> 1`, which prints per-thread CPU usage once per second without an interactive terminal.
top -H -p <pid> # 该进程的线程级视图
pidstat -t -p <pid> 1 # 逐秒采样各线程 CPUStep 3: capture what that thread is executing
Java: convert the thread ID to hexadecimal and search `jstack <pid>` output for nid=0x<hex>; the stack frames appear directly. Capture two or three times — frames stuck at the top are the hotspot. Python: `py-spy dump --pid` gives the call stack without code changes or restarts. Go: grab a CPU profile if pprof is wired in. Universal fallback: `perf top -p <pid>` for kernel-side hot symbols.
printf '%x\n' <tid> # 线程号转十六进制
jstack <pid> | grep -A 20 nid=0x<hex>
py-spy dump --pid <pid> # Python 进程调用栈
perf top -p <pid> # 通用内核态热点Match against the usual root causes
① Infinite loops or a while whose exit condition never holds; ② catastrophic regex backtracking (nested quantifiers over long text); ③ frequent Young/Full GC (memory starvation masquerading as a CPU problem); ④ crypto, compression or serialization doing heavy work on the hot path; ⑤ synchronous log flushing at high QPS. Whatever the stack shows usually maps to one of these.
jstat -gcutil <pid> 1000 # 每秒看 GC 频率,判断是否 GC 风暴Stop the bleeding first, cure later
Bleeding-stop actions for a live incident: drain traffic (remove the node from the load balancer), throttle, or kill the runaway process so the supervisor restarts it. Before killing, capture all evidence — thread stacks, GC logs — otherwise the clues vanish with the restart and the issue becomes "spooky and random".
The cure goes back to code: fix loop conditions, rewrite regexes, add caching, move heavy work off the hot path, size the heap or fix the leak. If the root cause is traffic growth, it is a capacity problem — scaling out is more honest than patching code.
jstack <pid> > /tmp/stack-$(date +%s).txt # 先留证据
kill <pid> # 再重启(supervisor 会拉起)One-page recap: the chain
top to classify us/sy/wa → top -H / pidstat -t to pin the thread → jstack / py-spy / perf to capture the stack → match the five root causes → preserve evidence then stop the bleeding → cure in code. Drill this chain and average CPU triage drops under ten minutes.
top → top -H -p <pid> → jstack/py-spy → 定位 → 止血 → 根治