前言
在了解开源项目-夜莺的alert告警引擎模块时候,我觉得有部分存疑的代码,于是打算抓取对应证据来证明猜想,pprof是go语言官方提供的profile工具,本文打算初步介绍一下,收集样本方式,指标类型中常用指标类型(profile,heap),以及采集数据的常用视图解读(top,graph,flame),最后介绍怎么使用。
收集样本
目前在golang中支持的收集数据方式有三种,分别是:
-
net/http/pprof,并在程序中开启 HTTP 服务器,启动后查看简单,通过访问http://127.0.0.1:6060/debug/pprof/
import _ "net/http/pprof" .... go func() { if err := http.ListenAndServe(":6060", nil); err != nil { log.Fatal(err) } }()
-
runtime/pprof, 指定的程序采集数据进行分析
f, err := os.Create(cpuProfile) if err != nil { log.Fatal("could not create CPU profile: ", err) } defer f.Close() if err := pprof.StartCPUProfile(f); err != nil { log.Fatal("could not start CPU profile: ", err) } defer pprof.StopCPUProfile()
-
通过运行性能测试,并指定所需标识来进行采集,具体用法可以最新参考 go help testflag
go test -bench=".*" -cpuprofile cpu.profile -memprofile mem.profile
上述这些方式都能够采集到性能数据可以按需采用。
指标类型
目前支持的指标类型有9种,具体类型和解释搬运如下:
Profile Descriptions:
• allocs: A sampling of all past memory allocations
• block: Stack traces that led to blocking on synchronization primitives • cmdline: The command line invocation of the current program
• goroutine: Stack traces of all current goroutines. Use debug=2 as a query parameter to export in the same format as an unrecovered panic.
• heap: A sampling of memory allocations of live objects. You can specify the gc GET parameter to run GC before taking the heap sample.
• mutex: Stack traces of holders of contended mutexes
• profile: CPU profile. You can specify the duration in the seconds GET parameter. After you get the profile file, use the go tool pprof command to investigate the profile.
• threadcreate: Stack traces that led to the creation of new OS threads
• trace: A trace of execution of the current program. You can specify the duration in the seconds GET parameter. After you get the trace file, use the go tool trace command to investigate the trace.
不过这里我们先分析常用的 CPU 占用分析(profile) 和 堆内存分析(heap) 。
profile 可以通过获取数据下面两种命令下载到数据:
go tool pprof "http://localhost:6060/debug/pprof/profile?seconds=30"
或者
curl -o cpu.out http://localhost:6060/debug/pprof/profile?seconds=30
通过Web页面查看命令如下:
go tool pprof -http=:8080 "cpu.out"
profile支持的分析类型:
- samples:采样期间运行次数
- cpu:采样期间运行时间,默认
heap可以通过获取数据下面两种命令下载数据:
curl -o heap.out http://localhost:6060/debug/pprof/heap
或
go tool pprof http://localhost:6060/debug/pprof/heap
通过Web页面查看命令如下:
go tool pprof -http=:8080 "http://localhost:6060/debug/pprof/heap"
heap支持的分析类型:
- alloc_objects:已分配的对象总量(不管是否已释放)
- alloc_space:已分配的内存总量(不管是否已释放)
- inuse_objects: 已分配但尚未释放的对象数量
- inuse_sapce:已分配但尚未释放的内存数量,默认
其他几种类型的获取方式应该都是类似的,例如 goroutine ,也是 curl -o goroutine.out http://localhost:6060/debug/pprof/goroutine
或者go tool pprof http://localhost:6060/debug/pprof/goroutine
,大家可以自行尝试。
视图
视图是指web页面的分析页面,可以在视图中选择节点/行然后切换视图过滤查看,方便用户使用,达到不用命令行工具的效果。
- Top:类似于 linux top 的形式, 默认按flat%从高到低排序
- Graph:采样图,web浏览默认展示模式
- Flame Graph:pprof 火焰图
- Flame Graph(New):pprof 火焰图,更易用,同样方法在图中各处调用标记明显且合并查看
- Peek:类似于 Top 也是从高到底的排序,多了 calls,calls% ,context ,可以看到正则匹配的函数的调用函数和被调用函数
- Source:和交互命令式(list <regexp>)的类似,正则匹配的函数代码
- Disassemble:显示汇编代码输出
其中常用的的三种视图分别是:
- Top :cum%最大
- Graph:矩形面积最大
- Flame:格子最宽
本文以cpu类型采样数据为例,来解释这些视图中含义(如果是内存分析其中时间改为内存分配)。
简单来说,flat越大,其代表一个函数可能非常耗时,或者调用了非常多次,或者两者兼而有之,从而导致这个函数消耗了最多的时间;累计采样时间越大,花费cpu时间越多,但是不一定是函数本身问题,也能是函数所调用的函数性能存在瓶颈,需要找到函数直接或间接调用中最耗费cpu的时间的函数。
Top显示靠前的N个样本,输出一个表格,默认按 flat%排序,表头含义如下:
- flat:函数自身(去除调用其他函数)在采样期耗时
- flat%:函数自身在采样期时间占比
- sum%:累计flat%值,上一项累计百分比
- cum:函数内所有操作(含调用其他函数)在采样期耗时
- cum%:函数所有操作在采样期时间占比
- name:函数名
Graph显示的是一个矩形和线段组成的调用关系拓扑图含义如下:
- 线段: 有向线段描述了函数的调用关系,线段旁边数字表明耗时,例如10ms
- 矩形:有多个信息
- 函数名/方法名 ,包含包名,结构体名,函数方法名
- 本地采样时间 ,函数自身耗时(去除调用其他函数耗时),在采用总数的占比
- 累计采样时间 ,函数的总调用耗时和占比
例如一个矩形的内容如下所示:
mysql
(*mysqlConn)
PrepareContext
0 of 20ms (6%)
这个矩形中的 mysql (*mysqlConn) PrepareContext
都属于函数名/方法名部分,of之前的0
属于本地采样时间,这部分如果不是0会跟着一个占比数例如0.15ms(1%)
,在of 后面的部分20ms (6%)
属于累计采样时间,不过当这个函数没有调用其他函数的情况下,矩形中只会有本地采样时间,例如 10ms(5%)
。
Flame显示的是一个函数调用情况的火焰图含义如下:
- 纵轴展示栈深度,按调用关系从上到下排列,图的最下面是采样时占用cpu的函数
- 横向遇到相同调用栈会做合并,格子越宽瓶颈可能越大
使用
做性能分析时,一般先按top20 -cum,找到函数总耗时最多的,然后再参考函数flat和调用关系判断,是函数本身耗时还是调用其他函数耗时;
在找到怀疑的函数后,通过list 函数名,查看函数内部耗时情况,分析代码针对性修改,例如是不是在for循环中串行调用,修改成多个goroutine是否可行。