1 介绍
pprof 是一个可视化和分析数据的工具,pprof 读取 profile.proto 格式的分析样本集合并生成报告以可视化和帮助分析数据,它可以生成文本和图形报告,是用来分析应用程序的性能重要工具,也可以作为其他用途,例如查看反汇编、和Linux perf工具结合使用。
2 安装
golang本身自带pprof工具,pprof可视化时需要依赖Graphviz工具。
linux环境下安装Graphviz:
yum install -y graphviz
windows环境安装Graphviz:
# Graphviz下载地址: https://graphviz.org/download/
# (1) 安装,在界面默认是没有选择加入系统path,选择添加到系统path即可。(注:如果是默认安装,需要手动把`Graphviz/bin`目录放到系统path。)
# (2) 生成配置文件,进去 `Graphviz/bin` 安装目录,执行命令:
dot -c
3 使用模式
3.1 报告生成
pprof 生成指定格式的报告并退出,格式可以是文本或图形。
pprof <format> [options] source
3.2 交互式终端使用
pprof 启动一个交互式 shell,用户可以在其中键入命令,输入help
获取命令帮助信息。
pprof [options] source
3.3 网页界面
pprof 可以指定端口上开启 HTTP 服务,在浏览器中访问该端口(例如 http://localhost:7778 ),相对前两种模式,比较常用。
pprof -http=[host]:[port] [options] source
4 一个go程序性能分析示例
下面代码是分配栈对象和堆对象测试代码,可以调整heapGSize
和stackGSize
变量大小来调整栈对象和堆对象数量,随着goroutine数量增大,gc压力增大,GC阶段占CPU时间越来越大,CPU执行用户逻辑占比变小,说明程序性能下降。
package main
import (
"fmt"
"math/rand"
"net/http"
_ "net/http/pprof"
"time"
)
var chOut = make(chan string, 100)
func main() {
// 开启pprof
go func() {
err := http.ListenAndServe(":7777", nil)
if err != nil {
panic(err)
}
}()
// 20个goroutine创建堆对象
heapGSize := 20
for i := 0; i < heapGSize; i++ {
NO := i
go func() {
for {
heapVar(NO)
sleep()
}
}()
}
// 20个goroutine创建栈对象
stackGSize := 20
for i := 0; i < stackGSize; i++ {
NO := i
go func() {
for {
stackVar(NO)
sleep()
}
}()
}
go output(chOut)
<-time.After(time.Hour)
}
func heapVar(NO int) {
for i := 0; i < 2000; i++ {
s := make([]int, 10240) // 10K
s[0] = i
if i == 0 { // 每次只把2000个堆对象中的第一个堆对象输出查看
s[0] = time.Now().Second()
chOut <- fmt.Sprintf("heap%d:%d", NO, s[0])
}
}
}
type User struct {
ID int
Name string
Age int
Email string
}
func stackVar(NO int) {
for i := 0; i < 100000; i++ {
number := &User{ID: i}
if i == 0 {
number.ID = time.Now().Second()
chOut <- fmt.Sprintf("stack%d:%d", NO, number.ID)
}
}
}
// 随机睡眠1~1.5秒
func sleep() {
n := rand.Intn(500) + 1000
time.Sleep(time.Millisecond * time.Duration(n))
}
// 打印输出
func output(ch <-chan string) {
maxSize := 100
msgs := make([]string, maxSize, maxSize)
var v string
var count int
for {
select {
case v = <-ch:
count++
if count == maxSize {
count = 0
fmt.Printf(msgs[0] + " ") // 打印第0个位置消息,为了减少终端输出
}
msgs[count] = v
}
}
}
编译后运行测试程序,在浏览器打开 http://localhost:7777/debug/pprof
可以查看不同类型采集数据。
count Profile
15 allocs // 过去所有内存分配的样本
0 block // 同步阻塞
0 cmdline // 当前程序的命令行调用
47 goroutine // 当前所有goroutine的堆栈痕迹
15 heap // 现场对象的内存分配样本。你可以指定gc GET参数,在进行堆采样之前运行GC
0 mutex // 争夺锁的持有者
0 profile // CPU概况。你可以在seconds GET参数中指定持续时间。在你得到概况文件后,使用go工具pprof命令来调查概况
16 threadcreate // 操作系统创建新线程
0 trace // 当前程序的执行跟踪。你可以在秒的GET参数中指定持续时间。在你得到跟踪文件后,使用go tool trace命令来调查该跟踪
页面只能查看这些数据简单分析,但可视化不够直观,使用go tool pprof
更方便查看对应每个类型profile,在菜单【View】可以查看类型包括:
- TOP:排名
- Graph:关系图
- Flame Graph:火焰图
- Peek:类似TOP,信息更详细
- Source:源码
4.1 CPU分析
在实际中,如果发现程序的CPU使用率高时,可以使用go tool pprof
采集CPU分析数据。下面命令是采集最近20秒(默认是30秒)CPU执行分析数据,http端口是7778。
go tool pprof -http=:7778 -seconds=20 http://localhost:7777/debug/pprof/profile
等待20秒之后自动在浏览器展示,点击菜单【View】–> 【Flame Graph】查看CPU火焰图,如下图所示:
从图中可以看到heapVar
和stackVar
只占用1/3左右的CPU,2/3的CPU时间用来创建对象和回收对象了,说明有优化空间。
4.2 内存分析
在实际中,如果发现程序的内存使用比较高时,可以使用go tool pprof
采集内存分析数据,下面命令是查看堆分配情况,http端口是7779。
go tool pprof -http=:7779 http://localhost:7777/debug/pprof/heap
执行命令之后自动在浏览器展示,点击菜单【SAMPLE】 –> 【inuse_space】,然后点击菜单【View】–> 【Flame Graph】查看内存火焰图,如下图所示:
从图中可以看到heapVar
占用比较多,说明需要优化heapVar
函数,其他系统底层调用,通常是用户程序调用导致,无法优化。
4.3 下载采集数据到本地分析
除了直接在浏览器分析,还可以把采集的数据下载到本地后再分析,命令如下:
# 下载到本地
wget <url> -O <filename>
# 下载的文件
go tool pprof -http=<:prort> <filename>
下载采集数据到本地之后,可以做优化前后对比,优化前测试程序的heapVar
和stackVar
变量值为20,优化后heapVar
和stackVar
变量值为1.
CPU优化前后对比
# 下载优化前采集数据
wget http://localhost:7777/debug/pprof/profile -O cpu_before.out
# 下载优化后数据
wget http://localhost:7777/debug/pprof/profile -O cpu_after.out
# 比较优化前后
go tool pprof -http=:7778 --base cpu_before.out cpu_after.out
执行命令后,查看Graph图,绿色是优化节省的CPU资源,如下图所示:
内存优化前后对比
# 下载优化前采集数据
wget http://localhost:7777/debug/pprof/heap -O heap_before.out
# 下载优化后数据
wget http://localhost:7777/debug/pprof/heap -O heap_after.out
# 优化前后比较
go tool pprof -http=:7779 --base heap_before.out heap_after.out
执行命令后,查看Graph图,绿色是优化节省的内存资源,如下图所示:
注:切换到【VIEW】 –> 【Source】源码界面,可以展示哪个函数节省了多少资源。
5 trace分析
通过trace可以看到goroutine数量、线程数量、堆数量、GC、各个goroutine调度情况,可以查看各个线程当前时刻执行哪个goroutine,当前时刻各个goroutine在干什么,使用trace需要两个步骤:
(1) 下载trace数据到trace.out文件,只有1秒内trace数据
wget http://localhost:7777/debug/pprof/trace -O trace.out
(2) 查看trace信息,http端口为7780
go tool trace -http=:7780 trace.out
执行命令之后在浏览界面点击View Trace
查看具体跟踪信息,如下图所示:
从图中看出在1秒内,GC占用时间超过30%,说明这个程序是需要优化的。点击界面右边菜单File Size Stats
,从统计中可以看到GC相关的事件数量非常多,例如 runtime.bgsweep、SWEEP、GC (dedicated)。
可以选择zoom
进行放大看各个goroutine执行细节,在界面右上角搜索事件名称,例如STW、SWEEP等,下面是搜索STW的细节:
从图中看到在8ms内出现了不少STW(stop the world),在一个GC完整周期出现两次STW,其中绿色是sweep termination阶段的STW,红色是mark termination阶段的STW。
6 总结
通过pprof作为go程序定位问题、性能优化的工具。建议生产环境的go服务开启pprof,正常情况下是不需要实时去采集内存、CPU数据来分析。因为不知道什么时候应该采集数据,建议写一个看门狗程序,检测CPU、内存或其他指标达到阈值时,自动采集保存现场数据,避免出现问题了,没有留下现场数据。
专题「golang相关」的其它文章 »
- 使用开发框架sponge快速把单体web服务拆分为微服务 (Sep 18, 2023)
- 使用开发框架sponge一天多开发完成一个简单版社区后端服务 (Jul 30, 2023)
- 一个提高开发项目效率的开发框架sponge,支持mysql、mongodb、postgresql、tidb、sqlite (Jan 06, 2023)
- go test命令 (Apr 15, 2022)
- channel原理和应用 (Mar 22, 2022)
- go runtime (Mar 14, 2022)
- go调试工具 (Mar 13, 2022)
- cobra (Mar 10, 2022)
- grpc使用实践 (Nov 27, 2020)
- 配置文件viper库 (Nov 22, 2020)
- 根据服务名称查看golang程序的profile信息 (Sep 03, 2019)
- go语言开发规范 (Aug 28, 2019)
- goroutine和channel应用——处理队列 (Sep 06, 2018)
- golang中的context包 (Aug 28, 2018)