Golang pprof 使用

什么是 Profile?

在计算机性能调试领域里,profile 就是对应用的画像,这里画像就是应用使用 CPU 和内存等情况,也就是说应用使用了多少 CPU 资源、都是哪些部分在使用、每个函数使用的比例是多少、有哪些函数在等待 CPU 资源等等。知道了这些,我们就能对应用进行规划,也能快速定位性能瓶颈。

Golang 是一个对性能特别看重的语言,因此语言中自带了 profile 的库,这篇文章就要讲解怎么在 golang 中做 profile。

在 Golang 中,主要关注的应用运行情况主要包括以下几种:

  • CPU profile:报告程序的 CPU 使用情况,按照一定频率去采集应用程序在 CPU 和寄存器上面的数据
  • Memory profile(Heap profile):报告程序的内存使用情况
  • Block profile:报告 goroutines 不在运行状态的情况,可以用来分析和查找死锁等性能瓶颈
  • Goroutine profile:报告 goroutines 的使用情况,有哪些 goroutine,它们的调用关系是怎样的

两种收集方式

分析 profile 第一步就是怎么获取应用程序的运行情况数据。Golang 提供了 runtime/pprof 和 net/http/pprof 两个库,分别应用于两种不同的应用。

工具型应用

如果你的应用是一次性的,运行一段时间就结束,那么最好的办法就是在应用退出时把 profile 的报告保存到文件中,进行分析。对于这种情况,可以使用 runtime/pprof 库。

pprof 封装了很好的接口供我们使用,比如要想进行 CPU profile,可以调用 pprof.StartCPUProfile() 方法,它会对当前应用程序进行 CPU profile,并写入到提供的参数中(w io.Writer)。要停止写入,调用 StopCPUProfile() 即可。

去除错误处理只需要三行内容,一般把它们写在 main.go 文件中,应用程序启动之后就开始执行:

f, err := os.Create(*cpuprofile)
...
pprof.StartCPUProfile(f)
defer pprof.StopCPUProfile()

应用执行结束后,就会生成一个文件,保存了我们的 CPU profile 数据。

要获得内存的数据,直接使用 WriteHeapProfile 即可,不用 start 和 stop 这两个步骤:

f, err := os.Create(*memprofile)
pprof.WriteHeapProfile(f)
f.Close()

服务型应用

如果你的应用是一直运行的,比如 web 应用,那么可以使用 net/http/pprof 库,它能够对 Http 服务进行分析。

在 import 里添加一行:

import _ "net/http/pprof"

在主函数中启动服务监听端口:

go func() {
    http.ListenAndServe(":6060", nil)
}()

访问 /debug/pprof 即可得到下面的内容: 

/debug/pprof/

profiles:
0	block
756	goroutine
16100	heap
0	mutex
94	threadcreate

full goroutine stack dump

go tool ppof 获取和分析 profile 数据

有了 profile 数据之后(不管是文件还是网络请求),下一步就是要对这些数据进行分析。我们可以使用 go tool pprof 命令行工具。

在后面我们会生成调用关系图和火焰图,需要安装 graphviz 软件包,在 ubuntu 系统可以使用下面的命令:

$ sudo apt-get install -y graphviz

注意获取的 profile 数据是动态的,要想获得有效的数据,请保证应用处于较大的负载(比如正在运行的服务,或者通过其他工具模拟访问压力)。否则如果应用处于空闲状态,得到的结果可能没有任何意义。

我们以 CPU profile 分析为例介绍两种分析方法。

交互式终端

go tool pprof 最简单的使用方式为 go tool pprof [binary] [source]binary 是应用的二进制文件,用来解析各种符号;source 表示 profile 数据的来源,可以是本地的文件,也可以是 http 地址。比如:

 ➜  go tool pprof ./hyperkube http://172.16.3.232:10251/debug/pprof/profile
Fetching profile from http://172.16.3.232:10251/debug/pprof/profile
Please wait... (30s)
Saved profile in /home/cizixs/pprof/pprof.hyperkube.172.16.3.232:10251.samples.cpu.002.pb.gz
Entering interactive mode (type "help" for commands)
(pprof) 

这个命令会进行 CPU profile 分析,等待一段时间(默认是 30s,如果在 url 最后加上 ?seconds=60 参数可以调整采集数据的时间为 60s)之后,我们就进入了一个交互式命令行,可以对解析的结果进行查看和导出。可以通过 help 来查看支持的命令有哪些。

一个有用的命令是 topN,它列出最耗时间的地方:

(pprof) top10
130ms of 360ms total (36.11%)
Showing top 10 nodes out of 180 (cum >= 10ms)
      flat  flat%   sum%        cum   cum%
      20ms  5.56%  5.56%      100ms 27.78%  encoding/json.(*decodeState).object
      20ms  5.56% 11.11%       20ms  5.56%  runtime.(*mspan).refillAllocCache
      20ms  5.56% 16.67%       20ms  5.56%  runtime.futex
      10ms  2.78% 19.44%       10ms  2.78%  encoding/json.(*decodeState).literalStore
      10ms  2.78% 22.22%       10ms  2.78%  encoding/json.(*decodeState).scanWhile
      10ms  2.78% 25.00%       40ms 11.11%  encoding/json.checkValid
      10ms  2.78% 27.78%       10ms  2.78%  encoding/json.simpleLetterEqualFold
      10ms  2.78% 30.56%       10ms  2.78%  encoding/json.stateBeginValue
      10ms  2.78% 33.33%       10ms  2.78%  encoding/json.stateEndValue
      10ms  2.78% 36.11%       10ms  2.78%  encoding/json.stateInString

每一行表示一个函数的信息。前两列表示函数在 CPU 上运行的时间以及百分比;第三列是当前所有函数累加使用 CPU 的比例;第四列和第五列代表这个函数以及子函数运行所占用的时间和比例(也被称为累加值 cumulative),应该大于等于前两列的值;最后一列就是函数的名字。如果应用程序有性能问题,上面这些信息应该能告诉我们时间都花费在哪些函数的执行上了。

pprof 不仅能打印出最耗时的地方(top),还能列出函数代码以及对应的取样数据、汇编代码以及对应的取样数据。list 命令后面跟着一个正则表达式,就能查看匹配函数的代码以及每行代码的耗时:

 (pprof) list podFitsOnNode
Total: 120ms
ROUTINE ======================== k8s.io/kubernetes/plugin/pkg/scheduler.podFitsOnNode in /home/cizixs/go/src/k8s.io/kubernetes/_output/local/go/src/k8s.io/kubernetes/plugin/pkg/scheduler/generic_scheduler.go
         0       20ms (flat, cum) 16.67% of Total
         .          .    230:
         .          .    231:// Checks whether node with a given name and NodeInfo satisfies all predicateFuncs.
         .          .    232:func podFitsOnNode(pod *api.Pod, meta interface{}, info *schedulercache.NodeInfo, predicateFuncs map[string]algorithm.FitPredicate) (bool, []algorithm.PredicateFailureReason, error) {
         .          .    233:	var failedPredicates []algorithm.PredicateFailureReason
         .          .    234:	for _, predicate := range predicateFuncs {
         .       20ms    235:		fit, reasons, err := predicate(pod, meta, info)
         .          .    236:		if err != nil {
         .          .    237:			err := fmt.Errorf("SchedulerPredicates failed due to %v, which is unexpected.", err)
         .          .    238:			return false, []algorithm.PredicateFailureReason{}, err
         .          .    239:		}
         .          .    240:		if !fit {

如果想要了解对应的汇编代码,可以使用 disadm  命令。

可视化

pprof 能以各种样式输出数据,比如 svg、gv、callgrind、png、gif 等等。其中一个非常便利的方法是在交互式终端中输入 web 命令,就能自动生成一个 svg 文件,并跳转到浏览器打开,生成了一个函数调用图:

Golang pprof 使用_第1张图片

这个调用图包含了更多的信息,而且可视化的图像能让我们更清楚地理解整个应用程序的全貌。图中每个方框对应一个函数,方框越大代表执行的时间越久(包括它调用的子函数执行时间,但并不是正比的关系);方框之间的箭头代表着调用关系,箭头上的数字代表被调用函数的执行时间。

因为原图比较大,这里只截取了其中一部分,但是能明显看到 encoding/json.(*decodeState).object 是这里耗时比较多的地方,而且能看到它调用了哪些函数。这些信息对于定位和调优性能是非常有帮助的。如果想进一步在浏览器中查看源代码和汇编代码,可以使用 weblist 命令,和 list、disadm 的用法相同,它能够同时显示源代码和汇编代码

此外还可以输入 pdf 命令生成一个 pdf 文件。更详细的 pprof 使用方法可以参考 pprof --help 或者 pprof 文档。

另一个可视化的方法是直接启动一个 http 服务:

go tool pprof -http="10.224.27.152:8081" ./hyperkube http://172.16.3.232:10251/debug/pprof/profile

在浏览器上访问 10.224.27.152:8081 即可看到各种界面。

Golang pprof 使用_第2张图片

Golang pprof 使用_第3张图片

注:本文大量使用了 使用 pprof 和火焰图调试 golang 应用 的内容,并结合了笔者平时的实践。

你可能感兴趣的:(Golang)