性能分析工具pprof找出程序潜在的bug

2023/03/18 go

性能分析工具pprof的使用

一、什么是pprof?

pprof可以对程序运行时占用CPU、内存、磁盘 IO、网络这些指标数据进行采样分析,并且提供了可视化的功能。
它包含了两个库:
1、runtime/pprof 对于只跑一次的程序,例如每天只跑一次的离线预处理程序,调用 pprof 包提供的函数,手动开启性能数据采集。
2、net/http/pprof 对于在线服务,对于一个 HTTP Server,访问 pprof 提供的 HTTP 接口,获得性能数据。
当然,实际上这里底层也是调用的 runtime/pprof 提供的函数,封装成接口对外提供网络访问。

二、影响程序性能指标数据有哪些?

ppwLHsA.png

1、当 CPU 性能分析启用后,Go runtime 会每 10ms 就暂停一下,记录当前运行的 goroutine 的调用堆栈及相关数据。
   当性能分析数据保存到硬盘后,我们就可以分析代码中的热点了。
2、内存性能分析则是在堆(Heap)分配的时候,记录一下调用堆栈。默认情况下,是每 1000 次分配,取样一次,这个数值可以改变。
  栈(Stack)分配 由于会随时释放,因此不会被内存分析所记录。由于内存分析是取样方式,并且也因为其记录的是分配内存,而不是使用内存。
  因此使用内存性能分析工具来准确判断程序具体的内存使用是比较困难的。   
3、阻塞分析是一个很独特的分析,它有点儿类似于 CPU 性能分析,但是它所记录的是 goroutine 等待资源所花的时间。
  阻塞分析对分析程序并发瓶颈非常有帮助,阻塞性能分析可以显示出什么时候出现了大批的 goroutine 被阻塞了。阻塞性能分析是特殊的分析工具,
  在排除 CPU 和内存瓶颈前,不应该用它来分析。  

三、pprof如何使用?

runtime/pprof

1、创建一个main.go文件,编写如下代码

func main(){
	f, _ := os.OpenFile("cpu.profile", os.O_CREATE|os.O_RDWR, 0644)
	defer f.Close()
	pprof.StartCPUProfile(f)
	defer pprof.StopCPUProfile()
	sayHellowold()
 
 }
 func sayHellowold() {
	hellowold(10000)
	say(100000)
}

func hellowold(times int) {
	time.Sleep(time.Second * 2)
	var counter int
	for i := 0; i < times; i++ {
		for j := 0; j < times; j++ {
			counter++
		}
	}
}

func say(times int) {
	time.Sleep(time.Second * 2)
	var counter int
	for i := 0; i < times; i++ {
		for j := 0; j < times; j++ {
			counter++
		}
	}
}

2、使用go run main.go执行程序

 go run main.go 生成需要分析的cpu或内存的文件 
 example: cpu.profile

ppNJssJ.png

3、对采集的程序指标数据进行分析

 另启一个终端 go tool pprof [文件路径] 分析cpu 或内存的使用情况
 example:  go tool pprof  cpu.profile|pprof.samples.cpu.032.pb.gz

ppNY3Y6.png

发现cpu执行时间较长的是say函数,使用list main.say查看详情: ppNYDtP.png

net/http/pprof

1、创建一个main.go文件,编写如下代码

func main() {

	http.HandleFunc("/", sayHelloHandler) //	设置访问路由
	log.Fatal(http.ListenAndServe(":9999", nil))

}

func sayHelloHandler(w http.ResponseWriter, r *http.Request) {

	hellowold(10000)
	fmt.Println("path", r.URL.Path)
	fmt.Println("scheme", r.URL.Scheme)
	fmt.Fprintf(w, "Hello world!\n") //这个写入到w的是输出到客户端的
	say(100000)
}

func hellowold(times int) {
	time.Sleep(time.Second * 2)
	var counter int
	for i := 0; i < times; i++ {
		for j := 0; j < times; j++ {
			counter++
		}
	}
}

func say(times int) {
	time.Sleep(time.Second * 2)
	var counter int
	for i := 0; i < times; i++ {
		for j := 0; j < times; j++ {
			counter++
		}
	}
}


2、使用go run main.go执行程序

服务启动后,直接在浏览器上访问:

http://localhost:9999/debug/pprof/

得到了一个性能指标数据的汇总页面。

ppwOOX9.png

可以直接点击上面的链接,进入子页面,查看相关的汇总信息。

关于 goroutine 的信息有两个链接,goroutine 和 full goroutine stack dump,前者是一个汇总的消息,可以查看 goroutines 的总体情况,后者则可以看到每一个 goroutine 的状态
点击 profile 和 trace 则会在后台进行一段时间的数据采样(默认30秒),采样完成后,返回给浏览器一个 profile 文件,之后在本地通过 go tool pprof 工具进行分析。

ppwXntf.png 然后使用list查看命令占用cpu时间最长的函数的时间消耗详情 ppwX4jH.png

当然如果不想通过浏览器下载,也可以通过交互模式采集并下载要分析的指标数据文件

go tool pprof http://localhost:9999/debug/pprof/profile

默认采集时间30秒,当然你可以重新设置采集时间,像下面这样:
go tool pprof http://localhost:9999/debug/pprof/profile\?seconds\=60

其他采集命令:
# 下载 cpu profile,默认从当前开始收集 30s 的 cpu 使用情况,需要等待 30s
go tool pprof http://localhost:9999/debug/pprof/profile
# wait 120s
go tool pprof http://localhost:9999/debug/pprof/profile?seconds=120     

# 下载 heap profile
go tool pprof -inuse_space http://127.0.0.1:9999/debug/pprof/heap\?seconds\=60
go tool pprof http://localhost:9999/debug/pprof/heap

# 下载 goroutine profile
go tool pprof http://localhost:9999/debug/pprof/goroutine

# 下载 block profile
go tool pprof http://localhost:9999/debug/pprof/block

# 下载 mutex profile
go tool pprof http://localhost:9999/debug/pprof/mutex



在采集的过程中,也别忘了访问你觉得有性能的接口,比如

http://localhost:9999/

采集完成后会直接进入交互模式。如下图所示:
红框部分就行采集后生成的cpu性能指标数据文件

ppwvFeA.png

如果在以上交互模式下退出,还可以继续使用如下命令进入交互模式
go tool pprof [文件路径] 分析cpu 或内存的使用情况,比如下面例子:
go tool pprof /Users/liufangting/pprof/pprof.samples.cpu.042.pb.gz  

如果不想在终端交互模式下做指标数据性能分析,那还可以在浏览器里进行交互
go tool pprof -http :8081  [采集信息文件路径]或者 采集url

example:
go tool pprof -http :8081 cpu.profile
go tool pprof -http :8081 /Users/liufangting/pprof/pprof.samples.cpu.042.pb.gz
go tool pprof -http :8081  http://localhost:8080/debug/pprof/profile\?seconds\=60

ppwzVxS.png

3 如果查看火焰图?

  y 轴表示调用栈,每一层都是一个函数。调用栈越深,火焰就越高,下方就是正在执行的函数,顶部都是它的父函数
  x 轴表示抽样数,如果一个函数在 x 轴占据的宽度越宽,就表示它被抽到的次数多,即执行的时间长。注意,x 轴不代表时间,而是所有的调用栈合并后,按字母顺序排列的。
  火焰图就是看顶层的哪个函数占据的宽度最大。只要有"平顶"(plateaus),就表示该函数可能存在性能问题
  
  //https://www.ruanyifeng.com/blog/2017/09/flame-graph.html
  

ppwzOds.png

4 测试代码地址

示例完整代码

总结 使用pprof进行性能指标分析两大步:

第一步:性能指标数据采集
方法一、代码中采集并保存文件;runtime/pprof
方法二、终端采集自动生成可分析的指标数据文件;net/http/pprof
方法三、web端访问击 profile 和 trace 采集;net/http/pprof

第二步:对采集的性能指标数据进行分析
方法一、终端交互模式下使用top,list,traces 分析;
方法二、web端交互模式下,查看火焰图,程序的调用图进行性能分析;

Search

    Table of Contents