likes
comments
collection
share

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

作者站长头像
站长
· 阅读数 18

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

背景: pprof 是一个基于go语言的性能分析工具它提供了一系列用于分析和可视化应用程序性能的功能。本贴致力于最快速让你上手pprof, 并且使用使用 runtime/pprof 与 net/http/pprof 两个工具快速 生成 trace 图与 火焰图

安装 graphviz

fot -version

如果有输出graphviz信息,说明安装成功。

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 mod init pprof_demo 
go mod tidy

随后创建各目录与文件如下:

-- 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:

go install github.com/google/pprof@latest

runtime/pprof

main.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 目录下输入

go run main.go

然后就可以看到生成了一个 runtime_demo.prof的文件, 接着使用 graphviz 查看生成的 trace 图和 火焰图。同样在 runtime_demo 目录下输入

 go tool pprof -http=:8081  .\runtime_demo.prof

这将唤起浏览器http://localhost:8081/ui/, 然后你将看到一幅函数调用链路的图,大概这样:

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

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

  1. 框框越大,颜色越红, 代表越消耗时间
  2. 实线箭头代表CPU密集形任务, 虚线箭头代表IO密集型任务(比如查数据库)

点击左上角的 VIEW -> Fame Graph 就可以查看火焰图, 大概长这样:

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

可以看到 main 函数的周期包含了 fib1 和 fib2, 其中 fib1的调用先与 fib2, 这又是和代码相符合的, 火焰图与trace图都能分析出程序耗时的位置,但二者侧重略有不同, trace 图可以很清楚看到哪个函数调用了哪个函数, 但没有各个函数的时序关系。火焰图看不出来谁调用了谁,但可以看出所有函数被调用的时间先后

net/http/pprof

server/main.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

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 run main.go

然后在pprof_demo/net_demo/server下运行pprof监听命令:

 go tool pprof http://localhost:2000/debug/pprof/profile -seconds=10 

注意这里的2000端口要与http服务端的监听端口保持一致, 这表示监听该端口进来的请求在10秒的性能指标

然后迅速到 pprof_demo/net_demo/client目录下运行客户端:

go run main.go

10秒后在运行pprof监听命令行下应该会输出下面这几行提示

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 文件

go tool pprof -http=:8081 .\pprof.main.exe.samples.cpu.001.pb.gz

这将唤起浏览器http://localhost:8081/ui/, 然后你将看到一幅函数调用链路的图,大概这样:

使用 pprof 进行性能卡点分析(生成 trace图 与 火焰图) 可以看出调用 fib1 和 fib2 之前多了一个 helloHandler, 这也和代码结构相符合。

火焰图大概长这样:

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

巨人的肩膀

  1. github.com/google/ppro…
  2. www.cnblogs.com/onemorepoin…