说明
- 希望知道应用在什么地方耗费了多少CPU、Memeory、IO等资源
- pprof是用于可视化和分析性能分析数据的工具
pprof功能简介
分析-Profile
- 网页
- 可视化终端
工具-Tool
- runtime/pprof
- net/http/pprof
展示-View
- Top
- 调用图-Graph
- 火焰图-Flame Graph
- Peek
- 源码-Source
- 反汇编-Disassemble
采样-Sample
- CPU
- 堆内存-Heap
- 协程-Goroutine
- 锁-Mutex
- 阻塞-Block
- 线程创建-ThreadCreate
在WSL2环境下,分析工具使用简单示例
终端里运行git clone便可获取示例源代码,接着编译一下源代码并运行,该示例无需担心依赖问题,程序没有任何外部依赖:
git clone https://github.com/wolfogre/go-pprof-practice.git
cd go-pprof-practice
go build
./go-pprof-practice
终端会一直输出日志,可以通过这些日志来判断程序是否在运行
保持程序运行,打开浏览器访问http://localhost:6060/debug/pprof/,可以看到如下界面:
查看CPU的占用情况
通过运行go tool pprof http://localhost:6060/debug/pprof/profile命令,进入一个交互式终端,也可以通过参数 seconds 指定采样时间。
go tool pprof http://localhost:6060/debug/pprof/profile\?seconds=60,查看CPU的占用情况:
输入top命令会对函数的CPU耗时和百分比排序后输出
注:flat:给定函数上运行耗时;flat%:同上的CPU运行耗时总比例;sum%:给定函数累积使用CPU总比例;cum:当前函数加上它之上的调用运行总耗时;cum%:同上的CPU运行耗时总比例。
输入list 函数名,可以列出函数最耗时的代码部分
很明显,CPU占用过高是github.com/wolfogre/go-pprof-practice/animal/felidae/tiger.(*Tiger).Eat造成的。
终端运行go tool pprof -http=:8080 "http://localhost:6060/debug/pprof/profile",在浏览器使用http://localhost:8080/ui/进行可视化分析:
堆上内存使用情况的采样信息
再次运行使用pprof命令,注意这次使用的URL的结尾是heap:
go tool pprof http://localhost:6060/debug/pprof/heap
再一次使用 top、list 来定问问题代码:
终端运行go tool pprof -http=:8080 "http://localhost:6060/debug/pprof/heap",在浏览器使用http://localhost:8080/ui/进行可视化分析:
可以看到这次出问题的地方在 github.com/wolfogre/go-pprof-practice/animal/muridae/mouse.(*Mouse).Steal。
内存分配情况的采样信息
运行使用pprof命令,注意这次使用的URL的结尾是allocs:
go tool pprof http://localhost:6060/debug/pprof/allocs
同样使用 top、list 来定位问题代码:
终端运行go tool pprof -http=:8080 "http://localhost:6060/debug/pprof/allocs",在浏览器使用http://localhost:8080/ui/进行可视化分析:
可以看到 github.com/wolfogre/go-pprof-practice/animal/canidae/dog.(*Dog).Run 会进行无意义的内存申请,而这个函数又会被频繁调用,这才导致程序不停地进行 GC
当前所有协程的堆栈信息
运行使用pprof命令,注意这次使用的URL的结尾是goroutine:
go tool pprof http://localhost:6060/debug/pprof/goroutine
使用 top、list 来定位问题代码:
终端运行go tool pprof -http=:8080 "http://localhost:6060/debug/pprof/goroutine",在浏览器使用http://localhost:8080/ui/进行可视化分析:
可以看出问题在于 github.com/wolfogre/go-pprof-practice/animal/canidae/wolf.(*Wolf).Drink 在不停地创建没有实际作用的协程
锁争用情况的采样信息
运行使用pprof命令,注意这次使用的URL的结尾是mutex:
go tool pprof http://localhost:6060/debug/pprof/mutex
使用 top、list 来定位问题代码:
终端运行go tool pprof -http=:8080 "http://localhost:6060/debug/pprof/mutex",在浏览器使用http://localhost:8080/ui/进行可视化分析:
可以看出来这问题出在 github.com/wolfogre/go-pprof-practice/animal/canidae/wolf.(*Wolf).Howl。但要知道,在代码中使用锁是无可非议的,并不是所有的锁都会被标记有问题。
阻塞操作情况的采样信息
运行使用pprof命令,注意这次使用的URL的结尾是block:
go tool pprof http://localhost:6060/debug/pprof/block
使用 top、list 来定位问题代码:
终端运行go tool pprof -http=:8080 "http://localhost:6060/debug/pprof/block",在浏览器使用http://localhost:8080/ui/进行可视化分析:
可以看到,阻塞操作位于 github.com/wolfogre/go-pprof-practice/animal/felidae/cat.(*Cat).Pee
pprof的采样过程和原理
CPU采样
-
采样对象:函数调用和他们占用的时间
-
采样率:100次/秒,固定值
-
采样时间:从手动启动到手动结束
-
采样结果:每个函数的调用次数和占用CPU时间
-
操作系统
- 每10ms向进程发送一次SIGPROF信号
-
进程
- 每次接收到SIGPROF信号时,记录当前的调用栈
-
写缓冲
- 每100ms读取已经记录的调用栈并写入输出流
堆内存
- 采样程序通过内存分配器在堆上分配和释放的内存,记录分配/释放的堆内存大小
- 采样率:每分配512KB记录一次,可在运行开头修改,1为每次分配均记录
- 采样时间:从程序运行开始到采样时
- 采样指标:alloc_space, alloc_objects, inuse_space, inuse_objects
- 计算方式:inuse_space = alloc_space - free_space
Goroutine-协程 & ThreadCreate-线程创建
- Goroutine-协程
- 记录所有用户发起且在运行中的goroutine(既非入口runtime开头的)runtime.main的调用栈信息
- ThreadCreate-线程创建
- 记录程序创建的所有系统线程的信息
Block-阻塞 & Mutex-锁
- 阻塞操作
- 采样阻塞操作的次数和耗时
- 采样率:阻塞耗时超过阈值的才会被记录,1为每次阻塞均记录
锁竞争
- 采样争抢锁的次数和耗时
- 采样率:只记录固定比例的锁操作,1为每次加锁均记录