Go pprof 性能分析实践

2,510 阅读4分钟

准备工作

下载 go-wrk(an HTTP benchmarking tool)

GitHub:github.com/tsliwowicz/…

安装

go install github.com/tsliwowicz/go-wrk@latest

基本使用

使用 80 个 Go 协程(连接)运行 5 秒钟的基准测试:

./go-wrk -c 80 -d 5  http://192.168.1.118:8080/json

输出:

Running 10s test @ http://192.168.1.118:8080/json
  80 goroutine(s) running concurrently
   142470 requests in 4.949028953s, 19.57MB read
     Requests/sec:		28787.47
     Transfer/sec:		3.95MB
     Avg Req Time:		0.0347ms
     Fastest Request:	0.0340ms
     Slowest Request:	0.0421ms
     Number of Errors:	0

性能分析

开启性能分析

开启性能分析只需要导入下面这个包即可:

import _ "net/http/pprof"

pprof 包通过它的 HTTP 服务端提供 pprof 可视化工具期望格式的运行时剖面文件数据服务。

本包一般只需导入获取其注册 HTTP 处理器的副作用。处理器的路径以 /debug/pprof/ 开始。

官方文档:pkg.go.dev/net/http/pp…

中文文档:studygolang.com/pkgdoc

压力测试

启动 main.go

package main

import (
   "fmt"
   "log"
   "net/http"
   _ "net/http/pprof"
   "os"
   "path/filepath"
   "time"

   "github.com/varstr/uaparser"
)

const hostPort = ":8080"

func main() {
   http.HandleFunc("/hello", Hello)
   fmt.Println("Starting server on", hostPort)
   if err := http.ListenAndServe(hostPort, nil); err != nil {
      log.Fatalf("HTTP server failed: %v", err)
   }
}

func Hello(w http.ResponseWriter, r *http.Request) {
   start := time.Now()
   tags := getStatsTags(r)
   duration := time.Since(start)
   fmt.Println(tags, duration)
}

func getStatsTags(r *http.Request) map[string]string {
   userBrowser, userOS := parseUserAgent(r.UserAgent())
   stats := map[string]string{
      "browser":  userBrowser,
      "os":       userOS,
      "endpoint": filepath.Base(r.URL.Path),
   }

   hostName, _ := os.Hostname()

   if hostName != "" {
      stats["host"] = hostName
   }
   return stats
}

func parseUserAgent(uaString string) (browser, os string) {
   ua := uaparser.Parse(uaString)

   if ua.Browser != nil {
      browser = ua.Browser.Name
   }
   if ua.OS != nil {
      os = ua.OS.Name
   }

   return browser, os
}

使用「go-wrk」进行压测

go-wrk -d 500 http://localhost:8080/hello

image.png

Web 监控

浏览器访问:http://localhost:8080/debug/pprof/

image.png 简单查看,以 goroutine 为例,参数有 debug=1 与 debug = 2:

image.png

image.png

采样分析

下面命令会打开一个交互页面:

go tool pprof --seconds 5 http://localhost:8080/debug/pprof/profile

image.png

常用命令:

  • top n:n 不写默认显示 10 个占用 CPU 时间最多的函数。
  • top -cum:将数据累计查看各个函数 CPU 占用。
  • tree:树形结构查看 goroutine 情况。
  • list 方法名:查看方法名里面具体调用耗时时长。
  • web:生成 SVG 函数调用图(需安装 graphviz)。
  • exit:退出分析。

打开 pprof 可视化页面,与上面可交互页面中 web 命令效果一样:

go tool pprof -http=:8888 http://localhost:8080/debug/pprof/profile?seconds=60

image.png 链路图: image.png 火焰图:

image.png 其他参数:

  • -inuse_space:分析程序常驻内存的占用情况
  • -alloc_objects:分析内存的临时分配情况
go tool pprof -inuse_space http://localhost:8080/debug/pprof/heap

go tool pprof -alloc_space http://localhost:8080/debug/pprof/heap

链路追踪分析

curl http://localhost:8080/debug/pprof/trace?seconds=30 > trace.out

image.png

go tool trace trace.out

image.png 上面的命令会打开一个 Web 页面:

image.png 点击 View trace 可以看到整个链路追踪页面。 按 W 可以将时间线放大,S 将时间线缩小。

  • Goroutine analysis:查看每个方法的 Goroutine 数量。
  • Network blocking profile:查看 IO 阻塞情况。
  • Synchronization blocking profile:查看系统同步阻塞情况。
  • Syscall blocking profile:查看系统调用阻塞情况。
  • Scheduler latency profile:查看调度程序延迟情况。

image.png 如果只想针对某个方法进行分析可以在方法内第一行加上下面代码:

f, err := os.Create("trace.out")
if err != nil {
    panic(err)
}

defer f.Close()

err = trace.Start(f)
if err != nil {
    panic(err)
}

defer trace.Stop()

常用解释:

名称含义
Execution Time执行时间
Network Wait Time网络等待时间
Sync Block Time同步阻塞时间
Blocking Syscall Time调用阻塞时间
Scheduler Wait Time调度等待时间
GC SweepingGC 清扫
GC PauseGC 暂停

打点对比分析

下文以 goroutine 为例,想比较「内存」的话,可把 url 后缀改成 heap

打第一个时间点:

go tool pprof http://localhost:8080/debug/pprof/goroutine

image.png 等待一会,再打第二个时间点:

go tool pprof http://localhost:8080/debug/pprof/goroutine

image.png 会生成两个采样文件:

  • pprof.goroutine.001.pb.gz
  • pprof.goroutine.002.pb.gz

对比分析:

go tool pprof -base pprof.goroutine.001.pb.gz pprof.goroutine.002.pb.gz

会和之前一样出现一个命令行交互界面,不同的是这个里面的信息是两者的差异比较。可以通过 top 查看两者 goroutine 差异最大之处是在哪里,然后通过 traces 查看栈调用信息,也可以通过 list 方法名 查看某个方法具体哪一行出了问题。

image.png

其他

生成 pprof.cpu 文件

// hello_test.go
import (
   "fmt"
   "testing"
)

func BenchmarkHello(b *testing.B) {
   for i := 0; i < b.N; i++ {
      fmt.Sprintf("hello")
   }
}
go test -bench . -benchmem -cpuprofile pprof.cpu

image.png

分析 pprof.cpu 文件

go tool pprof pprof.cpu

image.png

使用 ab 压测(ab - Apache HTTP server benchmarking tool)

ab 的原理:ab 命令会创建多个并发访问线程,模拟多个访问者同时对某一 URL 地址进行访问。它的测试目标是基于 URL 的,因此,它既可以用来测试 apache 的负载压力,也可以测试 nginx、lighthttp、tomcat、IIS 等其它 Web 服务器的压力。

ab 命令对发出负载的计算机要求很低,它既不会占用很高 CPU,也不会占用很多内存。但却会给目标服务器造成巨大的负载,其原理类似「CC攻击」。自己测试使用也需要注意,否则一次上太多的负载。可能造成目标服务器资源耗完,严重时甚至导致死机。

ab -n 200 -c 20 -p data.txt http://localhost:8080/hello 
  • -n:一共请求多少次。
  • -c:每次请求多少个。

image.png