Skip to content

pprof

bash
brew install graphviz

CPU

目标:CPU 明显吃满的热点函数

bash
go run burn_cpu/burn_cpu.go

采集 CPU profile(10 秒)

bash
go tool pprof http://localhost:6060/debug/pprof/profile?seconds=10

然后敲:

bash
(pprof) top
Showing nodes accounting for 8.71s, 99.43% of 8.76s total
Dropped 7 nodes (cum <= 0.04s)
      flat  flat%   sum%        cum   cum%
     8.30s 94.75% 94.75%      8.71s 99.43%  main.burnCPU
     0.41s  4.68% 99.43%      0.41s  4.68%  runtime.asyncPreempt
         0     0% 99.43%      0.05s  0.57%  runtime.findRunnable
         0     0% 99.43%      0.05s  0.57%  runtime.mcall
         0     0% 99.43%      0.05s  0.57%  runtime.park_m
         0     0% 99.43%      0.05s  0.57%  runtime.schedule
(pprof)

burnCPU 会稳稳地排第一!这是保证的,因为它 100% 吃你的 CPU。

看源码级别的热点行

bash
(pprof) list burnCPU
Total: 8.76s
ROUTINE ======================== main.burnCPU in /Users/luca/dev-test/gogc/burn_cpu/burn_cpu.go
     8.30s      8.71s (flat, cum) 99.43% of Total
         .          .     10:func burnCPU() {
         .          .     11:   for {
         .          .     12:           // 这里故意做大循环,让 CPU 明显上升
         .          .     13:           x := 0
     8.28s      8.68s     14:           for i := 0; i < 500_000_000; i++ {
      20ms       30ms     15:                   x += i
         .          .     16:           }
         .          .     17:
         .          .     18:           // 防止被内联(没什么必要,但更保险)
         .          .     19:           if x < 0 {
         .          .     20:                   panic("never happen")
(pprof)

看 flame graph / 调用图

bash
web

查看火焰图

bash
go tool pprof -http=:8011 http://localhost:6060/debug/pprof/profile?seconds=20

内存泄漏

目标:让 heap profile 的 top 第一名永远是你写的函数 leakMem,占非常高的比例(几十 MB)

bash
go run leak_mem/leak_mem.go

几秒后抓

bash
go tool pprof http://localhost:6060/debug/pprof/heap

pprof 里敲 top

bash
(pprof) top
Showing nodes accounting for 640.03MB, 99.53% of 643.03MB total
Dropped 23 nodes (cum <= 3.22MB)
      flat  flat%   sum%        cum   cum%
  640.03MB 99.53% 99.53%   640.03MB 99.53%  main.leakMem
(pprof)

看具体行

bash
(pprof) list leakMem
Total: 643.03MB
ROUTINE ======================== main.leakMem in /Users/luca/dev-test/gogc/leak_mem/leak_mem.go
  640.03MB   640.03MB (flat, cum) 99.53% of Total
         .          .     12:func leakMem() {
         .          .     13:   for {
  640.03MB   640.03MB     14:           b := make([]byte, 5*1024*1024) // 每次分 5MB
         .          .     15:           global = append(global, b)     // 不释放 永久持有
         .          .     16:           time.Sleep(200 * time.Millisecond)
         .          .     17:   }
         .          .     18:}
         .          .     19:
(pprof)

查看火焰图

bash
go tool pprof -http=:8012 http://localhost:6060/debug/pprof/heap

goroutine 泄漏

目标:让 goroutine profile 里清晰出现几十条 goroutine,全部卡死在同一个点

bash
go run goroutine_leak/goroutine_leak.go

抓 goroutine profile 并进入交互:

bash
go tool pprof http://localhost:6060/debug/pprof/goroutine

按累积调用统计

bash
(pprof) top -cum
Showing nodes accounting for 1003, 99.90% of 1004 total
Dropped 30 nodes (cum <= 5)
      flat  flat%   sum%        cum   cum%
      1003 99.90% 99.90%       1003 99.90%  runtime.gopark
         0     0% 99.90%       1000 99.60%  main.leakGoroutine
         0     0% 99.90%       1000 99.60%  runtime.chanrecv
         0     0% 99.90%       1000 99.60%  runtime.chanrecv1
(pprof)

分析:

  • flat:直接在这个函数里消耗的时间/事件数
  • cum:包含它下面所有调用的总和
  • runtime.gopark:表示 goroutine 被阻塞(park)
  • main.leakGoroutine:你自己的函数,它创建了阻塞 goroutine
  • runtime.chanrecv / chanrecv1:说明 goroutine 正在 <-ch 上等待
  • 为什么 flat=0 但 cum=1000

结论:top 输出是正常的,cum 才是你要看的

  • goroutine profile 并不是 CPU 时间,而是“事件快照”(采样 goroutine 堆栈)
  • 你看到 flat=0 是因为泄漏的 goroutine 并没有在消耗 CPU
  • cum=1000 才是重点:有 1000 个 goroutine 堆栈顶层是 main.leakGoroutine,全部被阻塞
  • 这就是典型泄漏:goroutine 很多,但 flat=0 因为它们没在跑(被 <-ch 卡住了)

看源码哪一行造成阻塞

bash
(pprof) list leakGoroutine
Total: 1004
ROUTINE ======================== main.leakGoroutine in /Users/luca/dev-test/gogc/goroutine_leak/goroutine_leak.go
         0       1000 (flat, cum) 99.60% of Total
         .          .      9:func leakGoroutine(ch chan struct{}) {
         .       1000     10:   <-ch // 永远阻塞
         .          .     11:}
         .          .     12:
         .          .     13:func manyLeaks() {
         .          .     14:   ch := make(chan struct{})
         .          .     15:   for i := 0; i < 1000; i++ {
(pprof)

top 找数量/热点,list 找具体行,web 看全局堆栈

查看火焰图

bash
go tool pprof -http=:8013 http://localhost:6060/debug/pprof/goroutine?debug=2