前言
最近打算用三篇文章讲述一下 Golang
利用性能剖析,本文是第一篇,先来介绍 Go
语言自带的性能剖析库 pprof
怎么应用,前面两篇会解说怎么用 pprof
对Echo
或者 Gin
框架开发的利用进行性能剖析以及如何应用 pprof
对 gRPC 服务进行性能剖析。
有趣味追更的同学欢送微信关注「网管叨 bi 叨」
Golang
是一个十分重视性能的语言,因而语言的内置库里就自带了性能剖析库pprof
。
性能剖析和采集在计算机性能调试畛域应用的术语是 profile
,或者有时候也会应用profiling
代表性能剖析这个行为。所以 pprof
名字里的 prof
来源于对单词 profile
的缩写,profile
这个单词的原意是 画像,那么针对程序性能的画像就是利用应用 CPU 和内存等等这些资源的状况。都是哪些函数在应用这些资源?每个函数应用的比例是多少?
在Go
语言中,次要关注的程序运行状况包含以下几种:
- CPU profile:报告程序的 CPU 应用状况,依照肯定频率去采集应用程序在 CPU 和寄存器下面的数据
- Memory Profile(Heap Profile):报告程序的内存应用状况
- Block Profile:报告导致阻塞的同步原语的状况,能够用来剖析和查找锁的性能瓶颈
- Goroutine Profile:报告 goroutines 的应用状况,有哪些 goroutine,它们的调用关系是怎么的
工具型利用的性能剖析
如果你的利用是工具类利用,执行完工作就完结退出,能够应用 runtime/pprof
[1]库。
比方要想进行 CPU Profiling,能够调用 pprof.StartCPUProfile()
办法,它会对以后应用程序进行 CPU 应用状况剖析,并写入到提供的参数中(w io.Writer
),要进行调用 StopCPUProfile()
即可。
去除错误处理只须要三行内容,个别把局部内容写在 main.go
文件中,应用程序启动之后就开始执行:
1. f, err := os.Create(*cpuprofile)
2. ...
3. pprof.StartCPUProfile(f)
4. defer pprof.StopCPUProfile()
利用执行完结后,就会生成一个文件,保留了咱们利用的 CPU 应用状况数据。
想要取得内存的数据,间接应用 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
通过下面的设置能够获取服务的性能数据后,接下来就能够应用 go tool pprof
工具对这些数据进行剖析和保留了,个别都是应用 pprof
通过 HTTP
拜访下面列的那些路由端点间接获取到数据后再进行剖析,获取到数据后 pprof
会主动让终端进入交互模式。在交互模式里 pprof
为咱们提供了不少剖析各种指标的子命令,在交互模式下键入 help
后就会列出所有子命令。
NOTE pprof 子命令的应用办法能够参考
pprof --help
或者 pprof 文档[2]。
CPU 性能剖析
进行 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
查看调配的内存空间大小。
go-torch 和火焰图
火焰图(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…
文章举荐
三种传递 gRPC 动静参数形式的应用体验
看 Kubernetes 源码,学习怎么用 Go 实现调度队列
- END –