当前位置:主页 > 查看内容

Go程序性能分析工具和方法

发布时间:2021-07-14 00:00| 位朋友查看

简介:作者:宁亮 一、常用分析命令和工具 pprof go tool [xxx] go test delve go race gdb 二、程序编译时的参数传递 1、gcflags //可使用go tool compile --help查看可用参数及含义go build -gcflags=-m? ? 比如 -N 禁用编译优化,-l 禁止内联,-m 打印编译优化……

作者:宁亮

一、常用分析命令和工具

  • pprof
  • go tool [xxx]
  • go test
  • delve
  • go race
  • gdb

二、程序编译时的参数传递

1、gcflags

//可使用go tool compile --help查看可用参数及含义
go build -gcflags="-m"? ?

比如 -N 禁用编译优化,-l 禁止内联,-m 打印编译优化策略(包括逃逸情况和函数是否内联,以及变量分配在堆或栈),-S 是打印汇编。

如果只在编译特定包时需要传递参数,格式应遵守“包名=参数列表”,如 go build -gcflags='log=-N -l' main.go

2、ldflags

go build 用 -ldflags 给 go 链接器传入参数,实际是给 go tool link 的参数,可以用 go tool link --help 查看可用的参数。

常用 -X 来指定版本号等编译时才决定的参数值。例如代码中定义var buildVer string,然后在编译时用go build -ldflags "-X main.buildVer=1.0" 来赋值。注意 -X 只能给string类型变量赋值。

三、go build -x

可以列出 go build 触发的所有命令,比如工具链、跨平台编译、传入外部编译器的 flags、链接器等,可使用 -x 来查看所有的触发。

四、竞争检测

使用 go run -race main.go 或 go build -race main.go 来进行竞争检测。

五、GC日志

  • 执行前添加系统环境变量 GODEBUG='gctrace=1' 来跟踪打印垃圾回收器信息
  • 在代码中使用 runtime.ReadMemStats 来获取程序当前内存的使用情况
  • 使用 pprof 工具

例如 GODEBUG=gctrace=1 go run main.go //跟踪打印垃圾回收器信息,Go 程序会每隔一段时间打印一些 gc 信息。

六、Pprof

Go 语言内置了获取程序运行数据的工具,包括以下两个标准库

(1)runtime/pprof:采集工具型应用运行数据进行分析
(2)net/http/pprof:采集服务型应用运行时数据进行分析

1、工具型应用分析

(1)CPU分析
f, err := os.Create(*cpuprofile)
...
pprof.StartCPUProfile(f)
defer pprof.StopCPUProfile()
(2)内存分析
f, err := os.Create(*memprofile)
pprof.WriteHeapProfile(f)
f.Close()

2、使用 net/http 包时启用 Pprof

package main

import (
"log"
"net/http"
_ "net/http/pprof"
)

func main() {
//... do something

log.Println(http.ListenAndServe("localhost:8090", nil))
}

3、使用Gin框架时启用Pprof

package main

import (
"github.com/gin-contrib/pprof"
"github.com/gin-gonic/gin"
)

func main() {
app := gin.Default()

pprof.Register(app)

app.Run(":8090")
}

七、访问web页获取分析结果

如在本机测试服务上启用 pprof,可以使用 http://127.0.0.1:8090/debug/p... http 服务:

/debug/pprof/

Types of profiles available:
Count Profile
3 allocs
0 block //goroutine的阻塞信息
0 cmdline
4 goroutine //此项可排查是否创建了大量的 goroutine
3 heap //堆内存的分配信息
0 mutex //锁的信息
0 profile
7 threadcreate //线程信息
0 trace
full goroutine stack dump //此项可排查是否有 goroutine 运行时间过长

1、pprof 支持四种类型的分析

pprof开启后,每隔一段时间(默认10ms)就会收集当前的堆栈信息,获取各个函数占用的CPU以及内存资源,然后通过对这些采样数据进行分析,形成一个性能分析报告。

(1)CPU Profile:CPU 分析,采样消耗 cpu 的调用,这个一般用来定位排查程序里耗费计算资源的地方
(2)Memory Profile(Heap Profile):内存分析,一般用来排查内存占用,内存泄露等问题。堆内存分配情况的记录。默认每分配512K字节时取样一次。
(3)Block Profile:阻塞分析,报告导致阻塞的同步原语的情况,可以用来分析和查找锁的性能瓶颈。Goroutine阻塞事件的堆栈跟踪记录。默认每发生一次阻塞事件时取样一次。
(4)Goroutine Profile :报告 goroutines 的使用情况,有哪些 goroutine,它们的调用关系是怎样的。活跃 Goroutine 的信息的记录,以及调用关系。仅在获取时取样一次。

2、Pprof可视化

如在 Mac 上通过 brew 安装 graphviz,可以执行以下命令 brew install graphviz

3、以本地文件形式获取分析结果

可以先把信息 dump 到本地文件,然后用 go tool 去分析。

4、debugcharts

一个可以实时查看 go 程序内存、CPU、GC、协程等变化情况的可视化工具。启用方式跟 pprof 类似,都是先 import 引入,然后开启端口监听即可。

package main

import (
_ "github.com/mkevac/debugcharts"
"log"
"net/http"
)

func main() {
//... do something

log.Println(http.ListenAndServe("localhost:8090", nil))
}

然后在浏览器中打开查看:http://127.0.0.1:8090/debug/c...

5、第三方工具prometheus

prometheus 是 grafana 的插件,支持 go 监控的可视化。

启用方式也是先引入包:

import (

"github.com/prometheus/client_golang/prometheus/promhttp"

)

然后增加路由:

//prometheus
http.Handle("/metrics", promhttp.Handler())
http.ListenAndServe(":8090", nil)

最后,通过访问 http://127.0.0.1:8090/metrics 查看采集到的指标数据。
image.png

注:也可以通过一个端口同时开启 pprof + charts + prometheus。

八、go tool [xxx]系列命令

1、输入 go tool 可查看内置的所有[xxx]工具命令

addr2line
api
asm
buildid
cgo
compile:代码汇编
cover:生成代码覆盖率
dist
doc
fix
link
nm:查看符号表(等同于系统 nm 命令)
objdump:反汇编工具,分析二进制文件(等同于系统 objdump 命令)
oldlink
pack
pprof:性能和指标分析工具
test2json
trace:采样一段时间,指标跟踪分析工具
vet

2、go tool nm 查看符号表的命令

在断点的时候,如果不知道断点的函数符号,可以用这个命令进行查询(命令处理的是二进制程序文件)。
go tool nm ./main
输出的第一列是地址,第二列是类型,第三列是符号。

115aa00 T bufio.(*ReadWriter).Available
115aa20 T bufio.(*ReadWriter).Discard
115aa60 T bufio.(*ReadWriter).Flush
115aa80 T bufio.(*ReadWriter).Peek

3、go tool compile 汇编某个文件

go tool compile -N -l -S main.go

4、go tool objdump 反汇编二进制的工具

go tool objdump main.o
go tool objdump -s DoFunc main.o //反汇编具体函数

5、go tool pprof 性能指标分析工具

5.1 命令行分析模式

go tool pprof http://localhost:8090/debug/p... 分析heap,进入命令行模式,输入 web 即可以web方式打开(前提是安装了graphviz)。

或者继续输入命令:
在命令行输入top默认查看程序中占用内存前10位的函数,在命令行输入top 3可以查看程序中占用内存前3位的函数。同样,如果采集的是cpu使用top命令可以看占用cpu的函数

(1)输入top后显示的最后一列为函数名称,其他各项内容意义如下:

flat:当前函数占用CPU的耗时
flat%:当前函数占用CPU的耗时百分比
sum%:函数占用CPU的累积耗时百分比
cum:当前函数+调用的子函数 占用CPU总耗时
cum%:当前函数+调用的子函数 占用CPU总耗时百分比

(2)可以在命令行输入 list+函数名 命令查看具体的函数分析
(3)可以在命令行输入 pdf 生成可视化的pdf文件
(4)可以在命令行输入 help 提供所有pprof支持的命令说明

5.2 web分析模式

(1)go tool pprof -http=:1234 http://localhost:8090/debug/p... 会直接以web方式打开。或者:http://localhost:1234/ui/ 也可以直接打开,从中可以直接筛选查看火焰图(Flame Graph)。-http 表示使用交互式web接口查看获取的性能信息,指定可用的端口即可。debug/pprof/需要查看的指标(allocs,block,goroutine,heap等)。火焰图从上往下是方法的调用栈,长度代表使用的cpu时长。

5.3 go tool pprof 分析命令
go tool pprof -http=:1234 http://localhost:8090/debug/pprof/goroutine?second=10
go tool pprof --seconds 10 http://localhost:8090/debug/pprof/goroutine

如果应用比较复杂,生成的调用图特别大,看起来很乱,有两个办法可以优化:
(1)使用 web [funcName] 的方式,只打印和某个函数相关的内容。
(2)运行 go tool pprof 命令时加上 --nodefration 参数,可以忽略内存使用较少的函数,比如--nodefration=0.05表示如果调用的子函数使用的 CPU、memory 不超过 5%,就忽略它,不要显示在图片中。

九、go test 单元测试

(1)本质上,golang 跑单测是先编译 *_test.go 文件,编译成二进制后,再运行这个二进制文件

go test . //直接在本目录中运行go test
go test -run=TestPutAndGetKeyValue //指定运行函数
go test -v //提供详细的测试输出,打印测试名称、状态(通过或者失败)、耗时、测试用例的日志等

go test -race //测试时支持对竞争进行检测和报告
go test -coverprofile=c.out && go tool cover -html=c.out //输出一个覆盖信息结果并可在浏览器上可视化观看
(2)统计代码覆盖率

加一个 -coverprofile 的参数,声明在跑单测的时候,记录代码覆盖率。例如 go test -coverprofile=coverage.out
使用 go tool cover 命令分析,可以得出覆盖率报告 go tool cover -func=coverage.out

十、Delve

delve 当前是最友好的 golang 调试程序,ide 调试其实也是调用 dlv 而已,比如 goland使用的调试。
安装dlv:go get -u github.com/go-delve/delve/cmd/dlv
检查安装版本信息:dlv version

把程序加载进 Delve 调试器的两种方式(事先需要有go.mod)

1、加载源码进行调试

dlv debug
(1)执行 dlv debug 进入命令行模式,此时同目录下会自动生成一个 __debug_bin 文件。这个文件是由源码编译生成的,并会自动加载进调试器。
(2)Delve 期望的是从单个程序或项目中构建出单个二进制文件,如果目录中有多个源文件且每个文件都有自己的主函数, Delve 则可能抛出错误。此种情况下应该使用下面第二种方式,加载二进制文件进行调试。

2、加载二进制文件进行调试

dlv exec ./main
(1)使用 dlv exec 命令将二进制文件加载进调试器。
(2)在命令行模式下输入 help 查看可用命令。
(3)其常使用的一些命令:

b main.main //在 main 函数处设置断点,等同于 break main.main
b func.go:5 //使用 文件名:行号 的格式来设置断点,也可以直接用行号设置断点
bp //查看设置的断点,等同于 breakpoints
clear [断点标号如2] //清除单个断点
clearall //清除所有断点
on //设置一段命令,当断点命中的时候
c //继续运行程序,运行到断点处中止,等同于 continue
n //单步调试下一行源码,等同于 next。默认情况下,Delve不会更深入地调试函数调用。
s //单步调试下一个函数,等同于 step
step-instruction //单步调试某个汇编指令
stack //打印当前堆栈的内容信息,可以看到0、1、2、3...等栈位置的函数
frame 0 //实现帧之间的跳转,可以使用 stack 输出的位置序号
args //打印出命令行传给函数的参数
disassemble //查看编译器生成的汇编语言指令
stepout //跳回到函数被调用的地方
print [var_name] //打印变量的值
whatis [var_name] //打印变量的类型
locals //打印函数内的所有局部变量
regs //打印寄存器的信息
x //等同于examinemem,这个是解析内存用的,和 gdb 的 x 命令一样
set //set赋值
vars //打印全局变量(包变量)
whatis //打印类型信息
r //重新启动并调试执行程序,等同于restart
call //整个程序执行
quit //退出调试器

协程相关

goroutine (alias: gr) //打印某个特定协程的信息
goroutines (alias: grs) //列举所有的协程
goroutines -t //展开所有协程详细信息
thread (alias: tr) //切换到某个线程
threads //打印所有的线程信息

栈相关

deferred //在 defer 函数上下文里执行命令
down //上堆栈
frame //跳到某个具体的堆栈
stack (alias: bt) //打印堆栈信息
up //下堆栈

其他命令

config //配置变更
disassemble (alias: disass) //反汇编
funcs //打印所有函数符号
libraries //打印所有加载的动态库
list (alias: ls | l) //显示源码
source //加载命令
sources //打印源码
types //打印所有类型信息

(4)dlv的其它命令

dlv debug:使用dlv debug可以在main函数文件所在目录直接对main函数进行调试,也可以在根目录以指定包路径的方式对main函数进行调试
dlv exec:使用dlv exec可以对编译好的二进制进行调试
dlv test:使用dlv test可以对test包进行调试
dlv attach:使用dlv attach可以附加到一个已在运行的进程进行调试
dlv connect:使用dlv connect可以连接到调试服务器进行调试
dlv trace:使用dlv trace可以追踪程序

十一、go race

1、Go 语言提供了 race 检测(Go race detector)来进行竞争分析和发现。
2、go run -race main.go 是运行时检测,并不是编译时。使用 race 时存在明显的性能开销,因此尽量不要在生产环境中使用这个。

十二、GDB

1、gdb的相关命令

info goroutines //打印所有的goroutines
goroutine ${id} bt //打印一个goroutine的堆栈
iface //打印静态或者动态的接口类型

2、gdb的相关函数

len //打印string,slices,map,channels 这四种类型的长度
cap //打印slices,channels 这两种类型的cap
dtype //强制转换接口到动态类型

扫码加好友拉你入技术交流群,加微暗号:思否

image.png


本文转自网络,版权归原作者所有,原文链接:https://segmentfault.com/a/1190000040043628
本站部分内容转载于网络,版权归原作者所有,转载之目的在于传播更多优秀技术内容,如有侵权请联系QQ/微信:153890879删除,谢谢!

推荐图文


随机推荐