pprof是google官方提供的golang内存/CPU/goroutine分析工具, 不可谓不强大, 简单记录下最近用到的几种调试技巧.
使用前
pprof可以用来分析go程序(非Server)的运行时数据(runtime/pprof
)和http server的运行时数据(net/http/pprof
), 本文主要针对后一种.
如果程序本身使用了net/http
来建立http server, 例如:
import (
"net/http"
)
func main() {
http.ListenAndServe("0.0.0.0:8080", nil)
}
只需引入net/http/pprof
即可:
import (
"net/http"
_ "net/http/pprof" //加入这一行即可
)
pprof会自动注册/debug/pprof/*
相关的api path. 但如果使用的是GIN框架, 则需要引入"github.com/gin-contrib/pprof"
这个包, 并手动注册一下路由:
import (
"github.com/gin-contrib/pprof"
"github.com/gin-gonic/gin"
)
func main() {
route = gin.Default()
pprof.Register(route)
}
别的类似http框架可能也需要特殊处理一下, 此处不赘述.
分析方法
1. 直接访问web路径
上文提到, pprof会注册一系列/debug/pprof/*
的api路径, 这些路径可以直接访问, 获取有限的信息.
例如直接访问http://your-host:port/debug/pprof
, 可以看到类似此画面:
这里比较有用的信息有:
-
goroutine的数量, 如你所见, 图上说当前有26个goroutine
-
点进
heap
连接, 拉到页面最下方, 可以看到runtime.MemStats
相关信息:
关于此处各项数值的解读, 可以参考这篇文章的第三节.
- 点击
profile
链接, 会进行默认为期30s的cpu profiling, 30s后会在浏览器中下载一个profile文件, 可以本地调用pprof工具的命令行进一步分析程序的CPU占用信息.
2. pprof 交互式命令行工具
要使用pprof 交互式命令行工具, 前提是机器已经配置好了go环境. go版本高的话, pprof cli提供的功能似乎会更丰富些.
go环境配置就不介绍了, 配置完之后安装pprof:
go get -u github.com/google/pprof
如果执行go tool pprof
会蹦出一大堆帮助信息, 那大概是安装成功了.
简单地讲使用方法, 先进入交互界面:
# 分析堆内存信息
go tool pprof http://your-host:port/debug/pprof/heap/
# 分析历史内存分配
go tool pprof http://your-host:port/debug/pprof/allocs/
# 分析CPU Profile, 默认等待30s, 可以用 -seconds n 参数指定采样时间
go tool pprof http://your-host:port/debug/pprof/profile/
# 当然也可以直接使用上面提到的下载的profile文件
go tool pprof /local/path/to/file/profile
# 或者是dump内存信息到文件, 再进行分析
curl -s http://your-host:port/debug/pprof/heap > ~/xxxx.heap
go tool pprof ~/xxxx.heap
当你看到(pprof)
开头一行闪着光标, 就可以继续输命令了, 常用的有:
top
命令可以查看各项占用排名
(pprof) top
Showing nodes accounting for 57.19MB, 97.44% of 58.69MB total
Showing top 10 nodes out of 53
flat flat% sum% cum cum%
27.01MB 46.02% 46.02% 27.01MB 46.02% reflect.mapassign
11MB 18.75% 64.77% 11MB 18.75% reflect.New
5.50MB 9.37% 74.14% 5.50MB 9.37% container/list.(*List).insertValue
5MB 8.52% 82.66% 5MB 8.52% reflect.makemap
4MB 6.82% 89.47% 4MB 6.82% encoding/gob.decString
1.34MB 2.28% 91.76% 1.34MB 2.28% github.com/open-falcon/falcon-plus/modules/graph/index.(*IndexCacheBase).Put
1MB 1.70% 93.46% 1MB 1.70% bytes.(*Buffer).String
1MB 1.70% 95.16% 1MB 1.70% container/list.New
0.84MB 1.43% 96.59% 0.84MB 1.43% github.com/open-falcon/falcon-plus/vendor/github.com/toolkits/container/nmap.(*SafeMap).Put
0.50MB 0.85% 97.44% 0.50MB 0.85% github.com/open-falcon/falcon-plus/modules/graph/store.(*GraphItemMap).Set
list + 函数名
可以看到这个函数调用的代码上下文:
(pprof) list reflect.mapassign
Total: 58.69MB
ROUTINE ======================== reflect.mapassign in /usr/local/go/src/runtime/map.go
27.01MB 27.01MB (flat, cum) 46.02% of Total
. . 1314: return elem
. . 1315:}
. . 1316:
. . 1317://go:linkname reflect_mapassign reflect.mapassign
. . 1318:func reflect_mapassign(t *maptype, h *hmap, key unsafe.Pointer, elem unsafe.Pointer) {
27.01MB 27.01MB 1319: p := mapassign(t, h, key)
. . 1320: typedmemmove(t.elem, p, elem)
. . 1321:}
. . 1322:
. . 1323://go:linkname reflect_mapdelete reflect.mapdelete
. . 1324:func reflect_mapdelete(t *maptype, h *hmap, key unsafe.Pointer) {
交互式终端功能也有限, 适用于线上紧急的分析, 目前我大概只用到上述的两种方法.
3. pprof web可视化界面
终于讲到最有用的部分了...
如果说前两个方法只是开胃小菜, pprof本地建立起http server提供的web可视化页面绝对是大杀器了.
使用pprof web可视化界面前需要先安装Graphviz, 请根据你的Linux发行版自行安装.
开启的方式与上一节交互式命令行方法类似, 仅增加一个参数:
# 使用 --http :port参数开启本地http server
go tool pprof --http :8081 http://your-host:port/debug/pprof/heap/
# 也可以对0.0.0.0开放监听端口
go tool pprof --http 0.0.0.0:8081 http://your-host:port/debug/pprof/heap/
# 或者是对于dump内存文件/profile文件进行分析
go tool pprof --http :8081 /local/path/to/file/profile
curl -s http://your-host:port/debug/pprof/heap > ~/xxxx.heap
go tool pprof --http :8081 ~/xxxx.heap
然后打开浏览器, 访问你在--http
参数中指定的ip和端口号, 就能看到web可视化界面了!
顶部菜单栏的VIEW
中, 可以选择看top信息, 火焰图, 各函数调用上下文等等. SAMPLE
中可以选择alloc_objects, alloc_space, inuse_objects, inuse_space
四种采样结果.
(falcon-graph的火焰图有些一言难尽...)
另外提个隐藏技巧: 在Top
和Graph
页面可以单击指定函数名, 再跳转到Flame Graph
, Source
, Peek
页面, 可以仅查看该函数的相关内容, 高效地过滤一波无用信息.
好家伙, 我tm直接好家伙.jpg
鉴于内容已经相当直观, 就不一一介绍各个页面了, 不明之处可参见golang官方的这篇博文.
4. 比较两个时间点的内存升降
很多时候, 只查看某一个时间点的内存使用状况, 结果并不能很好的协助定位是什么地方发生了内存泄露, 但如果对比两个时间点的函数使用的内存升降, 就一目了然了. 感谢smallnest
的此文提供思路.
pprof可以使用--base
参数比较不同时间点的两个dump文件:
# 在时间点1 dump内存
curl -s http://your-host:port/debug/pprof/heap > previous.heap
# 在时间点2 dump内存
curl -s http://your-host:port/debug/pprof/heap > current.heap
# 加上 --base 参数进行比较
go tool pprof --http :8081 --base previous.heap current.heap
后面的操作与第三节无异.