使用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...
相关推荐
泯泷3 小时前
关于优化 LCP 的常见误解
前端·性能优化·浏览器
傻啦嘿哟17 小时前
性能优化与资源管理:优化Selenium脚本的执行效率,合理管理浏览器实例和系统资源
selenium·测试工具·性能优化
一丝晨光1 天前
标准输入输出
java·c++·python·c#·go·c·io
__water1 天前
『网络游戏』GoLand服务器框架【01】
服务器·unity·go
拉不拉斯1 天前
Goweb---Gorm操作Mysql数据库(一)
数据库·mysql·oracle·go·gorm·goweb
青云交1 天前
大数据新视界 --大数据大厂之基于 MapReduce 的大数据并行计算实践
大数据·搜索引擎·性能优化·mapreduce·并行计算·数据倾斜·电商数据
人工智能培训咨询叶梓1 天前
参数高效的迁移学习在自然语言处理中的应用
人工智能·自然语言处理·性能优化·微调·迁移学习·大模型微调
WMYeah1 天前
Goland使用SSH远程Linux进行断点调试 (兼容私有库)
linux·golang·go·ssh·goland
EterNity_TiMe_2 天前
【Linux基础IO】深入解析Linux基础IO缓冲区机制:提升文件操作效率的关键
linux·运维·服务器·开发语言·学习·性能优化·学习方法
_Soy_Milk2 天前
后端学习路线
java·学习·go