背景 : pprof 是一个基于go语言的性能分析工具它提供了一系列用于分析和可视化应用程序性能的功能。本贴致力于最快速让你上手pprof, 并且使用使用 runtime/pprof 与 net/http/pprof 两个工具快速 生成 trace 图与 火焰图。
安装 graphviz
Graphviz 是一个开源的图形可视化工具集,用于绘制和可视化图形和网络结构。它提供了一组命令行工具和库,可以根据给定的描述文件自动生成各种类型的图形。后续的 trace 图和 火焰图的展示都需要 graphviz 的参与。大家可以去官网直接下载对应的版本即可。 如果你是 win系统, 记得下载完成后将安装目录下的 bin 目录加到环境变量中, 重启即可。如果不重启,在浏览器可能仍然无法使用,这是个大坑。 重启完成后在命令行输入下面这条命令
fot -version
如果有输出graphviz信息,说明安装成功。
scss
dot - graphviz version 5.0.0 (20220707.1540)
runtime/pprof 与 net/http/pprof
pprof 实际上分为两个包, runtime/pprof 与 net/http/pprof ,二者使用场景略有不同。
- runtime/pprof 适用于一次运行就可以分析出结果的程序, 比如在某个函数内进行埋点, 运行一次就可以输出结果。
- net/http/pprof适用于 http 服务端 端的运行数据进行分析, 比如压测场景下,需要在5分钟内统计一下运行的结果。
本帖中会以实际例子演示两个包的使用场景。
新建工程
创建新目录 pprof_demo 在该目录下打开命令行输入:
go
go
复制代码
go mod init pprof_demo
go mod tidy
随后创建各目录与文件如下:
lua
-- pprof_demo
--net_demo net/http/pprof 展示
--client http客户端
--main.go
--server http服务端
--main.go
--pprof.main.exe.samples.cpu.001.pb.gz 无需创建, 生成的性能文件
--runtime_demo runtime/pprof 展示
-- main
--runtime_demo.prof 无需创建, 生成的性能文件
--go.mod
--go.sum
然后命令行输入下面这条命令, 用于安装pprof:
bash
go install github.com/google/pprof@latest
runtime/pprof
main.go
go
package main
import (
"fmt"
"os"
"runtime/pprof"
)
func main() {
f1, err := os.Create("./runtime_demo/runtime_demo.prof")
if err != nil {
panic(err)
}
pprof.StartCPUProfile(f1)
defer pprof.StopCPUProfile()
var n = []int{15, 20, 25, 30, 35}
for i := 0; i < len(n); i++ {
fmt.Printf("fib1(%d)=%d \n", n[i], fib1(n[i]))
}
for i := 0; i < len(n); i++ {
fmt.Printf("fib2(%d)=%d \n", n[i], fib2(n[i]))
}
}
func fib1(n int) int {
if n < 2 {
return 1
}
return fib1(n-1) + fib1(n-2)
}
func fib2(n int) int {
if n < 2 {
return 1
}
return fib2(n-1) + fib2(n-2)
}
这个文件非常简单, 一开始设置了生成的profile文件的路径, 然后调用了两个功能完全一样的函数fib1和fib2, fib1 和 fib2 都是计算斐波那契数列。之所以创建两个功能一样的函数, 是为了方便等等生成的 trace 图和 火焰图的展示。
随后在 runtime_demo 目录下输入
sh
go run main.go
然后就可以看到生成了一个 runtime_demo.prof的文件, 接着使用 graphviz 查看生成的 trace 图和 火焰图。同样在 runtime_demo 目录下输入
ini
go tool pprof -http=:8081 .\runtime_demo.prof
这将唤起浏览器http://localhost:8081/ui/, 然后你将看到一幅函数调用链路的图,大概这样:
关于这张图怎么看,你只要记住这下面两点就差不多了:
- 框框越大,颜色越红, 代表越消耗时间
- 实线箭头代表CPU密集形任务, 虚线箭头代表IO密集型任务(比如查数据库)
点击左上角的 VIEW -> Fame Graph 就可以查看火焰图, 大概长这样:
可以看到 main 函数的周期包含了 fib1 和 fib2, 其中 fib1的调用先与 fib2, 这又是和代码相符合的, 火焰图与trace图都能分析出程序耗时的位置,但二者侧重略有不同, trace 图可以很清楚看到哪个函数调用了哪个函数, 但没有各个函数的时序关系。火焰图看不出来谁调用了谁,但可以看出所有函数被调用的时间先后。
net/http/pprof
server/main.go
go
package main
import (
"fmt"
"net/http"
_ "net/http/pprof"
)
func main() {
http.HandleFunc("/hello", helloHandler)
errChan := make(chan error)
go func() {
errChan <- http.ListenAndServe(":2000", nil)
}()
err := <-errChan
if err != nil {
fmt.Println("Server stop running.")
}
}
func helloHandler(w http.ResponseWriter, r *http.Request) {
var n = []int{15, 20, 25, 30, 35}
for i := 0; i < len(n); i++ {
fmt.Printf("fib1(%d)=%d \n", n[i], fib1(n[i]))
}
for i := 0; i < len(n); i++ {
fmt.Printf("fib2(%d)=%d \n", n[i], fib2(n[i]))
}
w.Write([]byte("hello from hello handler"))
}
func fib1(n int) int {
if n < 2 {
return 1
}
return fib1(n-1) + fib1(n-2)
}
func fib2(n int) int {
if n < 2 {
return 1
}
return fib2(n-1) + fib2(n-2)
}
server/main.go 引入了 _ "net/http/pprof", 然后注册了一个 hello路径, 里面的逻辑和 runtime_demo 中的调用两个计算斐波那契的函数一模一样。
client/main.go
go
package main
import (
"fmt"
"net/http"
)
func main() {
resp, err := http.Get("http://localhost:2000/hello")
if err != nil {
fmt.Println(err)
return
}
if resp.StatusCode == 200 {
fmt.Println("OK")
}
}
client/main.go 是一个 http 客户端, 没有什么好说的,直接贴上去。
然后我们打开三个命令行, 看看 net/http/pprof 如何生成用于性能分析的文件 首先在 pprof_demo/net_demo/server下启动服务端
go
go run main.go
然后在pprof_demo/net_demo/server下运行pprof监听命令:
bash
go tool pprof http://localhost:2000/debug/pprof/profile -seconds=10
注意这里的2000端口要与http服务端的监听端口保持一致, 这表示监听该端口进来的请求在10秒的性能指标
然后迅速到 pprof_demo/net_demo/client目录下运行客户端:
go
go run main.go
10秒后在运行pprof监听命令行下应该会输出下面这几行提示
makefile
Saved profile in C:\Users\Chester_Zhang\pprof\pprof.main.exe.samples.cpu.001.pb.gz
File: main.exe
Build ID: C:\Users\CHESTE~1\AppData\Local\Temp\go-build3192772992\b001\exe\main.exe2023-11-26 23:09:06.5838025 +0800 CST
Type: cpu
Time: Nov 27, 2023 at 12:09am (CST)
Duration: 30.01s, Total samples = 120ms ( 0.4%)
Entering interactive mode (type "help" for commands, "o" for options)
(pprof)
我们在 (pprof)后面输入 quit 即可退出 pprof 命令行交互模式. 第一行提示生成的 pprof.main.exe.samples.cpu.001.pb.gz 就是最终我们需要拿到的用于 pprof 展示的profile文件, 我们可以把它copy到 pprof_demo/net_demo/server 路径下, 然后使用下面这条命令来分析这个 pprof.main.exe.samples.cpu.001.pb.gz 文件
ini
go tool pprof -http=:8081 .\pprof.main.exe.samples.cpu.001.pb.gz
这将唤起浏览器http://localhost:8081/ui/, 然后你将看到一幅函数调用链路的图,大概这样:
可以看出调用 fib1 和 fib2 之前多了一个 helloHandler, 这也和代码结构相符合。
火焰图大概长这样: