使用pprof进行性能卡点分析(生成 trace 图与 火焰图)

背景 : 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 ,二者使用场景略有不同。

  1. runtime/pprof 适用于一次运行就可以分析出结果的程序, 比如在某个函数内进行埋点, 运行一次就可以输出结果。
  2. 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/, 然后你将看到一幅函数调用链路的图,大概这样:

关于这张图怎么看,你只要记住这下面两点就差不多了:

  1. 框框越大,颜色越红, 代表越消耗时间
  2. 实线箭头代表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, 这也和代码结构相符合。

火焰图大概长这样:

巨人的肩膀

  1. github.com/google/ppro...
  2. www.cnblogs.com/onemorepoin...
相关推荐
青云交16 小时前
大数据新视界 -- 大数据大厂之 Impala 性能优化:为企业决策加速的核心力量(下)(14/30)
大数据·性能优化·impala·查询优化·企业决策·数据整合·系统融合
橘色的喵16 小时前
嵌入式ARM平台Linux网络实时性能优化
linux·网络·arm开发·性能优化·实时·内核优化
杰克逊的日记16 小时前
‌MySQL 5.7和8.0版本在多个方面存在显著区别,主要包括性能优化、新特性引入以及安全性提升
数据库·mysql·性能优化
货拉拉技术18 小时前
货拉拉是如何实现symbolic demangle?
ios·性能优化
我怀里的猫21 小时前
glide性能优化实战
性能优化·glide
27669582921 天前
阿里1688 阿里滑块 231滑块 x5sec分析
java·python·go·验证码·1688·阿里滑块·231滑块
键盘上的蚂蚁-1 天前
独立站 API 接口的性能优化策略
性能优化
斗-匕1 天前
MySQL 高性能优化规范建议总结
mysql·oracle·性能优化
kida_yuan2 天前
【从零开始】6. RAG 应用性能压测工具(番外篇)
后端·llm·测试
Moment2 天前
在 NodeJs 中如何通过子进程与 Golang 进行 IPC 通信 🙄🙄🙄
前端·后端·go