在Golang中,可以通过pprof工具对应于程序的运行时进行性能分析,包括CPU、内存、Goroutine等实时信息。
Golang内置了获取程序运行数据的工具,包括以下两个标准库:
- runtime/pprof: 采集工具型应用运行数据进行分析
- net/http/pprof: 采集服务型应用运行时数据进行分析
pprof开启后,每隔一段时间(10ms)就会收集当前的堆栈信息,获取各个函数占用的CPU以及内存资源,然后通过对这些采样数据进行分析,形成一个性能分析报告。性能优化主要有以下几个方面:
- CPU Profile:报告程序的CPU使用情况,按照100 Hz频率去采集应用程序在CPU和寄存器上面的数据。
- Memory Profile(Heap Profile):报告程序的内存使用情况,其实就是程序运行过程中堆内存的分配情况,默认每分配512K字节时取样一次。
- Block Profiling: 报告goroutines不在运行状态的情况,保存用户程序中的Goroutine阻塞事件的记录,可以用来分析和查找死锁等性能瓶颈。
- Goroutine Profiling: 报告goroutines的使用情况,有哪些roroutines,它们的调用关系是怎样的。
默认情况下是不追踪block和mutex的信息的,如果想要看这两个信息,需要在代码中加上两行:
引入pprof
在浏览器中输入地址:http://127.0.0.1:6060/debug/pprof/,即可看到监控的数据。
profiles解释如下:
- allocs:查看过去所有内存分配的样本。
- block:查看导致阻塞同步的堆栈跟踪。
- cmdline: 当前程序的命令行的完整调用路径。
- goroutine:查看当前所有运行的 goroutines 堆栈跟踪。
- heap:查看活动对象的内存分配情况。
- mutex:查看导致互斥锁的竞争持有者的堆栈跟踪。
- profile: 默认进行 30s 的 CPU Profiling,得到一个分析用的 profile 文件。
- threadcreate:查看创建新 OS 线程的堆栈跟踪。
- trace:能够辅助我们跟踪程序的执行情况。
线上分析用的更多的是go tool pprof,因为本地浏览器不能直接访问线上服务地址,而是通过线上服务生成分析文件,下载文件到本地进行分析,需要在本地安装graphviz工具(https://graphviz.org/download/)。
线上使用go tool pprof采集的时候可以通过加入?seconds=30参数来指定采集时间。
2.1 查看CPU使用情况
查看CPU使用率排行命令如下:
默认30秒后会进入交互模式,常见命令如下
- help可以查看使用说明
- top 可以查看前10个的CPU使用情况
- tree 以树状显示
- png 以图片格式输出
- svg 生成浏览器可以识别的svg文件
可使用top命令查看CPU使用率排行,top N显示前N个函数,默认显示10个。
输出内容最后一列为函数名称,其他各项内容意义如下:
- flat:当前函数占用CPU的耗时
- flat%:当前函数占用CPU的耗时百分比
- sum%:函数占用CPU的累积耗时百分比
- cum:当前函数+调用当前函数的占用CPU总耗时
- cum%:当前函数+调用当前函数的占用CPU总耗时百分比 list命令可以查看函数的cpu使用情况,形式为 list 函数名,这种需要调用源码,在线上打包后的一般看不到,本地运行可查看具体耗时情况。
traces命令可以调出函数的调用栈,形式为traces 函数名。
若没有安装Golang及相关工具,可以直接wget获取profile信息到文件中进行本地分析。
浏览器输入http://127.0.0.1:8080/ui/
菜单解释
- Top:类和命令行中的top命令类似
- Graph:函数调用的连线图
- Flame Graph:火焰图
- Peek:文本形式的top函数调用栈
- Source:显示top函数源码文件位置
图中的部分信息解释
- 节点的颜色越红,其cum和cum%越大。其颜色越灰白,则cum和cum%越小。
- 节点越大,其flat和flat%越大;其越小,则flat和flat%越小。
- 线条代表了函数的调用链,线条越粗,代表指向的函数消耗了越多的资源。反之亦然。
- 线条的样式代表了调用关系。实线代表直接调用;虚线代表中间少了几个节点;带有inline字段表示该函数被内联进了调用方。
Source可以列出函数调⽤的源代码,为了让该视图正确的显⽰,我们必须使⽤go tool pprof命令,⽽不能直接使⽤pprof命令,命令⾏的pprof版本有可能⽆法使源码正确的呈现出来。如果要使⽤Source视图,还需要注意将编译好的⼆进制⽂件作为参数传⼊,如:
Disassemable和Source类似,也是显⽰源代码的,但是Disassemable显⽰的是汇编语⾔。Disassemable的正确展⽰也和Source⼀样的要求。
2.2 查看内存使用情况
查看内存使用排行命令如下:
会很快进入交互模式,可使用top命令查看内存使用排行,top N显示前N个函数,默认显示10个。
浏览器输入http://localhost:8081/ui/
2.3 查看trace使用情况
查看trace命令如下:
打开浏览器输入http://127.0.0.1:8082/,即可看到如下信息
链接解释
- View trace:以图形页面的形式渲染和展示tracer的数据,这是最常用的功能。
- Goroutine analysis:以表格形式记录执行同一个函数的多个goroutine的各项trace数据
- Network blocking profile:用pprof profile形式的调用关系图展示网络I/O阻塞的情况
- Synchronization blocking profile:用pprof profile形式的调用关系图展示同步阻塞耗时情况
- Syscall blocking profile:用pprof profile形式的调用关系图展示系统调用阻塞耗时情况
- Scheduler latency profile:用pprof profile形式的调用关系图展示调度器延迟情况
- User-defined tasks和User-defined regions:用户自定义trace的task和region
- Minimum mutator utilization:分析GC对应用延迟和吞吐影响情况的曲线图
时间线
为View trace提供了时间参照系,View trace的时间线始于Tracer开启时,各个区域记录的事件的时间都是基于时间线的起始时间的相对时间。
时间线的时间精度最高为纳秒,但View trace视图支持自由缩放时间线的时间标尺,我们可以在秒、毫秒的“宏观尺度”查看全局,我们亦可以将时间标尺缩放到微秒、纳秒的“微观尺度”来查看某一个极短暂事件的细节。
采样状态区(Stats)
这个区内展示了三个指标:Goroutines、Heap和Threads,某个时间点上的这三个指标的数据是这个时间点上的状态快照采样:
- Goroutines:某一时间点上应用中启动的goroutine的数量,当我们点击某个时间点上的goroutines采样状态区域时(我们可以用快捷键m来准确标记出那个时间点),事件详情区会显示当前的goroutines指标采样状态。
- Heap指标则显示了某个时间点上Go应用heap分配情况(包括已经分配的Allocated和下一次GC的目标值NextGC)。
- Threads指标显示了某个时间点上Go应用启动的线程数量情况,事件详情区将显示处于InSyscall(整阻塞在系统调用上)和Running两个状态的线程数量情况。
P区(PROCS)
我们能看到Go应用中每个P(Goroutine调度概念中的P)上发生的所有事件,包括:EventProcStart、EventProcStop、EventGoStart、EventGoStop、EventGoPreempt、Goroutine辅助GC的各种事件以及Goroutine的GC阻塞(STW)、系统调用阻塞、网络阻塞以及同步原语阻塞(mutex)等事件。除了每个P上发生的事件,我们还可以看到以单独行显示的GC过程中的所有事件。
另外我们看到每个Proc对应的条带都有两行,上面一行表示的是运行在该P上的Goroutine的主事件,而第二行则是一些其他事件,比如系统调用、运行时事件等,或是goroutine代表运行时完成的一些任务,比如代表GC进行并行标记。
在G区,每个G都会对应一个单独的条带(和P区一样,每个条带都有两行),通过这一条带我们可以按时间线看到这个G的全部执行情况。通常我们仅需在goroutine analysis的表格页面找出执行最快和最慢的两个goroutine,在Go视角视图中沿着时间线对它们进行对比,以试图找出执行慢的goroutine究竟出了什么问题。
2.4 Go test 使用pprof
Golang在运行测试用例或压测时也可以通过添加参加输出测试过程中的CPU、内存和trace情况,分析过程同上,参数如下:
-bench=. // 进行性能测试,“.”是正则匹配,匹配了所有的测试函数
-benchmem // 打印出申请内存的次数。一般用于简单的性能测试,不会导出数据文件
-blockprofile block.out // 将协程的阻塞数据写入特定的文件(block.out),如果-c,则写成二进制文件
-cpuprofile cpu.out // 将协程的CPU使用数据写入特定的文件(cpu.out),如果-c,则写成二进制文件
-memprofile mem.out // 将协程的内存申请数据写入特定的文件(mem.out),如果-c,则写成二进制文件
-mutexprofile mutex.out // 将协程的互斥数据写入特定的文件(mutex.out),如果-c,则写成二进制文件
-trace trace.out // 将执行调用链写入特定文件(trace.out)
3 参考3.1 top输出列解释
通过一个简单示例函数来解释flat、cum、sum等参数含义。
flat flat%
flat只会记录step2和step3的时间;flat%即是flat/总运行时间。内存等参数同理。所有的flat相加即是总采样时间,所有的flat%相加应该等于100%。
flat一般是我们最关注的。其代表一个函数可能非常耗时,或者调用了非常多次,或者两者兼而有之,从而导致这个函数消耗了最多的时间。
如果是我们自己编写的代码,则很可能有一些无脑for循环、复杂的计算、字符串操作、频繁申请内存等等。
如果是第三方库的代码,则很可能我们过于频繁地调用了这些第三方库,或者以不正确的方式使用了这些第三方库。
cum cum%
相比flat,cum则是这个函数内所有操作的物理耗时,比如包括了上述的step1、2、3、4。
cum%即是cum的时间/总运行时间。内存等参数同理。
一般cum是我们次关注的,且需要结合flat来看。flat可以让我们知道哪个函数耗时多,而cum可以帮助我们找到是哪些函数调用了这些耗时的(flat值大的)函数。
sum%
其上所有行的flat%的累加。可以视为,这一行及其以上行,其所有的directly操作一共占了多少物理时间。
3.2 内存字段解释
在Golang内存分析的过程中经常会出现很多内部相关内存字段,具体解释如下: