最近计划用三篇文章讲述一下Golang
应用性能分析,本文是第一篇,先来介绍Go
语言自带的性能分析库pprof
怎么使用,后面两篇会讲解怎么用pprof
对Echo
或者Gin
框架开发的应用进行性能分析以及如何使用pprof
对gRPC 服务进行性能分析。node
有兴趣追更的同窗欢迎微信关注「网管叨bi叨」
Golang
是一个很是注重性能的语言,所以语言的内置库里就自带了性能分析库pprof
。git
性能分析和采集在计算机性能调试领域使用的术语是profile
,或者有时候也会使用profiling
表明性能分析这个行为。因此pprof
名字里的prof
来源于对单词profile
的缩写,profile
这个单词的原意是画像,那么针对程序性能的画像就是应用使用 CPU 和内存等等这些资源的状况。都是哪些函数在使用这些资源?每一个函数使用的比例是多少?github
在Go
语言中,主要关注的程序运行状况包括如下几种:golang
若是你的应用是工具类应用,执行完任务就结束退出,可使用 runtime/pprof
[1]库。web
好比要想进行 CPU Profiling,能够调用 pprof.StartCPUProfile()
方法,它会对当前应用程序进行CPU使用状况分析,并写入到提供的参数中(w io.Writer
),要中止调用 StopCPUProfile()
便可。正则表达式
去除错误处理只须要三行内容,通常把部份内容写在 main.go
文件中,应用程序启动以后就开始执行:json
1. f, err := os.Create(*cpuprofile) 2. ... 3. pprof.StartCPUProfile(f) 4. defer pprof.StopCPUProfile()
应用执行结束后,就会生成一个文件,保存了咱们应用的 CPU使用状况数据。api
想要得到内存的数据,直接使用 WriteHeapProfile
就行,不用 start
和 stop
这两个步骤了:浏览器
1. f, err := os.Create(*memprofile) 2. pprof.WriteHeapProfile(f) 3. f.Close()
咱们的文章会把重点篇幅放在服务型应用的性能分析,因此关于工具型应用的性能分析就说这么多。安全
若是你的应用是一直运行的,好比 web 应用或者gRPC
服务等,那么可使用 net/http/pprof
库,它可以在应用提供 HTTP 服务时进行分析。
若是使用了默认的 http.DefaultServeMux
(一般是代码直接使用 http.ListenAndServe("0.0.0.0:8000", nil)
),只须要在代码中添加一行,匿名引用net/http/pprof
:
import _ "net/http/pprof"
若是你使用自定义的 ServerMux
复用器,则须要手动注册一些路由规则:
1. r.HandleFunc("/debug/pprof/", pprof.Index) 2. r.HandleFunc("/debug/pprof/heap", pprof.Index) 3. r.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline) 4. r.HandleFunc("/debug/pprof/profile", pprof.Profile) 5. r.HandleFunc("/debug/pprof/symbol", pprof.Symbol) 6. r.HandleFunc("/debug/pprof/trace", pprof.Trace)
注册完后访问http://localhost/debug/pprof
端点,它会获得相似下面的页面内容:
1. Types of profiles available: 2. Count Profile 3. // 下面是一些可访问的/debug/pprof/目录下的路由 4. 2 allocs 5. 0 block 6. 0 cmdline 7. 36 goroutine 8. 2 heap 9. 0 mutex 10. 0 profile 11. 13 threadcreate 12. 0 trace 13. full goroutine stack dump 14. Profile Descriptions: 16. // 下面是对上面那些路由页面里展现的性能分析数据的解释 17. allocs: A sampling of all past memory allocations 18. block: Stack traces that led to blocking on synchronization primitives 19. cmdline: The command line invocation of the current program 20. goroutine: Stack traces of all current goroutines 21. heap: A sampling of memory allocations of live objects. You can specify the gc GET parameter to run GC before taking the heap sample. 22. mutex: Stack traces of holders of contended mutexes 23. profile: CPU profile. You can specify the duration in the seconds GET parameter. After you get the profile file, use the go tool pprof command to investigate the profile. 24. threadcreate: Stack traces that led to the creation of new OS threads 25. trace: A trace of execution of the current program. You can specify the duration in the seconds GET parameter. After you get the trace file, use the go tool trace command to investigate the trace.
这个路径下几个须要重点关注的子页面有:
/debug/pprof/profile
:访问这个连接会自动进行 CPU profiling,持续 30s,并生成一个文件供下载,能够经过带参数?=seconds=60
进行60秒的数据采集/debug/pprof/heap
:Memory Profiling 的路径,访问这个连接会获得一个内存 Profiling 结果的文件/debug/pprof/block
:block Profiling 的路径/debug/pprof/goroutines
:运行的 goroutines 列表,以及调用关系直接访问这些页面产生的性能分析数据咱们是分析不过来什么的,Go在1.11
版本后在它自带的工具集go tool
里内置了pprof
工具来分析由pprof
库生成的数据文件。
经过上面的设置能够获取服务的性能数据后,接下来就可使用go tool pprof
工具对这些数据进行分析和保存了,通常都是使用pprof
经过HTTP
访问上面列的那些路由端点直接获取到数据后再进行分析,获取到数据后pprof
会自动让终端进入交互模式。在交互模式里pprof
为咱们提供了很多分析各类指标的子命令,在交互模式下键入help
后就会列出全部子命令。
NOTE pprof子命令的使用方法能够参考
pprof --help
或者 pprof 文档[2]。
进行CPU
性能分析直接用go tool pprof
访问上面说的/debug/pprof/profile
端点便可,等数据采集完会自动进入命令行交互模式。
1. ➜ go tool pprof http://localhost/debug/pprof/profile 2. Fetching profile over HTTP from http://localhost/debug/pprof/profile 3. Saved profile in /Users/Kev/pprof/pprof.samples.cpu.005.pb.gz 4. Type: cpu 5. Time: Nov 15, 2020 at 3:32pm (CST) 6. Duration: 30.01s, Total samples = 0 7. No samples were found with the default sample value type. 8. Try "sample_index" command to analyze different sample values. 9. Entering interactive mode (type "help" for commands, "o" for options) 10. (pprof)
默认采集时长是 30s,若是在 url 最后加上 ?seconds=60
参数能够调整采集数据的时间为 60s。
采集完成咱们就进入了一个交互式命令行,能够对解析的结果进行查看和导出。能够经过 help
来查看支持的子命令有哪些。
NOTE: 若是pprof
用性能数据生成分析图的话、包括后面的go-torch火焰图都依赖软件graphviz
Mac 用户直接
brew install graphviz
就能安装,其余系统官网下载页面也有提供安装包,请访问https://graphviz.org/download/
一个有用的命令是 topN
,它列出最耗时间的地方:
1. (pprof) top10 2. 130ms of 360ms total (36.11%) 3. Showing top 10 nodes out of 180 (cum >= 10ms) 4. flat flat% sum% cum cum% 5. 20ms 5.56% 5.56% 100ms 27.78% encoding/json.(*decodeState).object 6. 20ms 5.56% 11.11% 20ms 5.56% runtime.(*mspan).refillAllocCache 7. 20ms 5.56% 16.67% 20ms 5.56% runtime.futex 8. 10ms 2.78% 19.44% 10ms 2.78% encoding/json.(*decodeState).literalStore 9. 10ms 2.78% 22.22% 10ms 2.78% encoding/json.(*decodeState).scanWhile 10. 10ms 2.78% 25.00% 40ms 11.11% encoding/json.checkValid 11. 10ms 2.78% 27.78% 10ms 2.78% encoding/json.simpleLetterEqualFold 12. 10ms 2.78% 30.56% 10ms 2.78% encoding/json.stateBeginValue 13. 10ms 2.78% 33.33% 10ms 2.78% encoding/json.stateEndValue 14. 10ms 2.78% 36.11% 10ms 2.78% encoding/json.stateInString
每一行表示一个函数的信息。前两列表示函数在 CPU 上运行的时间以及百分比;第三列是当前全部函数累加使用 CPU 的比例;第四列和第五列表明这个函数以及子函数运行所占用的时间和比例(也被称为累加值 cumulative
),应该大于等于前两列的值;最后一列就是函数的名字。若是应用程序有性能问题,上面这些信息应该能告诉咱们时间都花费在哪些函数的执行上。
pprof
不只能打印出最耗时的地方(top
),还能列出函数代码以及对应的取样数据(list
)、汇编代码以及对应的取样数据(disasm
),并且能以各类样式进行输出,好比 svg
、gif
、png
等等。
其中一个很是便利的是 web
命令,在交互模式下输入 web
,就能自动生成一个 svg
文件,并跳转到浏览器打开,生成了一个函数调用图(这个功能须要安装graphviz
后才能使用)。
图中每一个方框对应应用程序运行的一个函数,方框越大表明函数执行的时间越久(函数执行时间会包含它调用的子函数的执行时间,但并非正比的关系);方框之间的箭头表明着调用关系,箭头上的数字表明被调用函数的执行时间。
这里还要提两个比较有用的方法,若是应用比较复杂,生成的调用图特别大,看起来很乱,有两个办法能够优化:
web funcName
的方式,只打印和某个函数相关的内容go tool pprof
命令时加上 --nodefration
参数,能够忽略内存使用较少的函数,好比--nodefration=0.05
表示若是调用的子函数使用的 CPU、memory 不超过 5%,就忽略它,不要显示在图片中。想更细致分析,就要精确到代码级别了,看看每行代码的耗时,直接定位到出现性能问题的那行代码。pprof
也能作到,list
命令后面跟着一个正则表达式,就能查看匹配函数的代码以及每行代码的耗时:
1. (pprof) list podFitsOnNode 2. Total: 120ms 3. 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 4. 0 20ms (flat, cum) 16.67% of Total 5. . . 230: 6. . . 231:// Checks whether node with a given name and NodeInfo satisfies all predicateFuncs. 7. . . 232:func podFitsOnNode(pod *api.Pod, meta interface{}, info *schedulercache.NodeInfo, predicateFuncs map[string]algorithm.FitPredicate) (bool, []algorithm.PredicateFailureReason, error) { 8. . . 233: var failedPredicates []algorithm.PredicateFailureReason 9. . . 234: for _, predicate := range predicateFuncs { 10. . 20ms 235: fit, reasons, err := predicate(pod, meta, info) 11. . . 236: if err != nil { 12. . . 237: err := fmt.Errorf("SchedulerPredicates failed due to %v, which is unexpected.", err) 13. . . 238: return false, []algorithm.PredicateFailureReason{}, err 14. . . 239: } 15. . . 240: if !fit {
要想得到内存使用 Profiling 信息,只须要把数据源修改一下就行(对于 HTTP 方式来讲就是修改 url 的地址,从 /debug/pprof/profile
改为 /debug/pprof/heap
):
1. ➜ go tool pprof http://localhost/debug/pprof/heap 2. Fetching profile from http://localhost/debug/pprof/heap 3. Saved profile in 4. ...... 5. (pprof)
和 CPU Profiling 使用同样,使用 top N
能够打印出使用内存最多的函数列表:
1. (pprof) top 2. 11712.11kB of 14785.10kB total (79.22%) 3. Dropped 580 nodes (cum <= 73.92kB) 4. Showing top 10 nodes out of 146 (cum >= 512.31kB) 5. flat flat% sum% cum cum% 6. 2072.09kB 14.01% 14.01% 2072.09kB 14.01% k8s.io/kubernetes/vendor/github.com/beorn7/perks/quantile.NewTargeted 7. 2049.25kB 13.86% 27.87% 2049.25kB 13.86% k8s.io/kubernetes/pkg/api/v1.(*ResourceRequirements).Unmarshal 8. 1572.28kB 10.63% 38.51% 1572.28kB 10.63% k8s.io/kubernetes/vendor/github.com/beorn7/perks/quantile.(*stream).merge 9. 1571.34kB 10.63% 49.14% 1571.34kB 10.63% regexp.(*bitState).reset 10. 1184.27kB 8.01% 57.15% 1184.27kB 8.01% bytes.makeSlice 11. 1024.16kB 6.93% 64.07% 1024.16kB 6.93% k8s.io/kubernetes/pkg/api/v1.(*ObjectMeta).Unmarshal 12. 613.99kB 4.15% 68.23% 2150.63kB 14.55% k8s.io/kubernetes/pkg/api/v1.(*PersistentVolumeClaimList).Unmarshal 13. 591.75kB 4.00% 72.23% 1103.79kB 7.47% reflect.Value.call 14. 520.67kB 3.52% 75.75% 520.67kB 3.52% k8s.io/kubernetes/vendor/github.com/gogo/protobuf/proto.RegisterType 15. 512.31kB 3.47% 79.22% 512.31kB 3.47% k8s.io/kubernetes/pkg/api/v1.(*PersistentVolumeClaimStatus).Unmarshal
每一列的含义也是相似的,只不过从 CPU
使用时长变成了内存使用大小,就很少解释了。
相似的,web
命令也能生成 svg
图片在浏览器中打开,从中能够看到函数调用关系,以及每一个函数的内存使用多少。
须要注意的是,默认状况下,统计的是内存使用大小,若是执行命令的时候加上 --inuse_objects
能够查看每一个函数分配的对象数;--alloc-space
查看分配的内存空间大小。
火焰图(Flame Graph)是 Bredan Gregg 建立的一种性能分析图表,由于它的样子近似火焰而得名。上面的 profiling
结果也转换成火焰图,这里咱们要介绍一个工具:go-torch[3]。这是 uber 开源的一个工具,能够直接读取 pprof
的 profiling
数据,并生成一个火焰图的 svg 文件。
img
火焰图 svg 文件能够经过浏览器打开,它对于调用图的优势是:能够经过点击每一个方块来分析它上面的内容。
火焰图的调用顺序从下到上,每一个方块表明一个函数,它上面一层表示这个函数会调用哪些函数,方块的大小表明了占用 CPU 使用的长短。火焰图的配色并无特殊的意义,默认的红、黄配色是为了更像火焰而已。
go-torch 工具的使用很是简单,没有任何参数的话,它会尝试从 http://localhost/debug/pprof/profile
获取 profiling 数据。它有三个经常使用的参数能够调整:
-u --url
:要访问的 URL,这里只是主机和端口部分-s --suffix
:pprof profile 的路径,默认为 /debug/pprof/profile
--seconds
:要执行 profiling 的时间长度,默认为 30s要生成火焰图,须要事先安装 FlameGraph[4]工具,这个工具的安装很简单,只要把对应的可执行文件放到 $PATH
目录下就行。
今天的文章把Go语言的性能分析库pprof
的安装和使用方法大致流程走了一遍,重点讲解了一下最经常使用的几个性能分析命令以及如何用pprof
采集的profile
数据找出程序里最耗费性能的部分。相信有了pprof
的帮助在遇到须要优化程序性能的时候咱们能有更多的参照指标从而有的放矢地对程序性能进行优化改进。
在使用pprof
采集数据的时候必定要注意下面两点:
pprof
拿到的数据,若是你是在本地运行程序的话最好用Postman
或者Jmeter
这些工具作个简单的并发访问。pprof
使用的那些路由,不要在生产环境上应用。这篇文章就说这么多,后面的文章会介绍怎么在Echo
和Gin
框架下使用pprof
,以及如何用pprof
分析gRPC
服务的性能。求关注、求点赞、求转发!我是网管,会在这里每周坚持输出原创,咱们下期再见吧。
[1]
runtime/pprof
: https://golang.org/pkg/runtim...
[2]
pprof 文档: https://github.com/google/ppr...
[3]
go-torch: https://github.com/uber/go-torch
[4]
FlameGraph: https://github.com/brendangre...
文章推荐