GO 性能分析
简介
go提供了内存分析工具,pprof
利用它可以看cpu和内存的情况。
包含下面的几种类型:
- cpu
- 内存
- 阻塞
- 锁
pprof分为大体分为两个部分
- 数据采集
- 数据分析
数据采集有两种方式:
-
官方
官方提供了两种方式
-
runtime/pprof
这是用来给没http服务来使用
https://pkg.go.dev/runtime/pprof
-
net/http/pprof
给http服务来使用的,提供了路由来方便访问。
https://pkg.go.dev/net/http/pprof
-
-
三方库
-
profile
https://github.com/pkg/profile
-
数据分析:
-
go tool pprof
官方文档:https://github.com/google/pprof
使用
数据采集
cpu
启动 CPU 分析时,运行时(runtime) 将每隔 10ms 中断一次,记录协程(goroutines) 的堆栈信息。一个函数在性能分析数据中出现的次数越多,说明执行该函数的代码路径(code path)花费的时间占总运行时间的比重越大。
-
代码
package main import ( "fmt" _ "net/http/pprof" "os" "runtime/pprof" ) func main() { // 手动创建pprof文件 f, err := os.Create("cpu_test.pprof") if err != nil { return } // 启动 if err := pprof.StartCPUProfile(f); err != nil { return } //defer 停止 defer pprof.StopCPUProfile() var a []int for i := 0; i < 100000; i++ { a = append(a, i) } res := twnSum(a, len(a)*3) fmt.Printf("%v",res) } func twnSum(data []int,sum int) []int { for i, itemA := range data { for i2, itemB := range data { if i == i2{ continue } if itemA + itemB == sum{ return []int{i,i2} } } } return nil }
这种方式是我用原生库实现的,开启方式在原生库的文档里面有,分为下面三步:
- 创建pprof文件
- 启动cpu profile
- 在defer中停止cpu profile
运行此代码
go run .\main.go
在项目的根目录下面会有
cpu_test.pprof
,到此数据采集一级成功,分析在后面章节会介绍。三方库也是对基本代码的再次包装
profile库实现方式
// 主体代码都一样,采集的代码不一样
defer profile.Start().Stop()
就这一行,开启了cpu profile,看看它做了什么事情。
- 返回了一个接口,接口里面有stop方法
- 通过
options
(入参)来配置prof - 选择模式
- 这代码是上面用pprof包做的事情
它支持的模式有:
从代码可以看到,在调用start方法的时候可以通过入参来配置Profile
,它的入参是func的可变切片。我们可以自定义,同时它提供了一些方法便于我们操作:
使用方式如下:
defer profile.Start(
profile.CPUProfile,
profile.ProfilePath("D:/GolandProjects/base_go/pprof"),
).Stop() // 指定采集类型为CPU,并且指定pprof文件的路径
内存
内存性能分析(Memory profiling) 记录堆内存分配时的堆栈信息,忽略栈内存分配信息。
它也有采样比,默认是每分配512KB的时候采样一次,每次都需要采样的时候设置为1,不需要的时候设置为0。
这里我们直接用profile包来做
//主体代码都一样,只是profile变化
defer profile.Start(profile.MemProfile).Stop()
在log里面会显示pprof文件的目录。
数据分析
有两种方式
- web界面
- 交互界面
文档:https://github.com/google/pprof/blob/main/doc/README.md
这个文档解释了命令行和图形化中字段的含义。在下面的例子中我会对部分做解释说明。
web界面
需要安装Graphviz
官网:https://graphviz.org/
启动方式:
go tool pprof -http=:9989 .\cpu_test.pprof
// 格式如下
go tool pprof -http=主机:端口 pprof文件
访问localhost:9989界面如下:
说明:web界面也是将命令行的显示图形化,两者是一样,在这里做详细的解释之后,命令行就不多做解释了。
界面元素解释如下:
官网:https://github.com/google/pprof/blob/main/doc/README.md#interpreting-the-callgraph
view界面
文档:https://github.com/google/pprof/blob/main/doc/README.md#view
top
Graph
Flame
命令行交互界面
go tool pprof C:\Users\liuchen\AppData\Local\Temp\profile4178991121\cpu.pprof
其余的命令行操作看官方文档
要解释一下Flat和Cum
文档上是这么说的:
我理解是:
pprof的数据是通过采样获取的,并且数据要有垂直结构,每个采样点都有自己的代价和它所有的调用方法包括一块的代价
flat:采样点自己的代价。
cum:采样点调用方法一块的代价。
http服务
数据采集
原生go http服务方式
前提:启动http服务,用go原生的(非必须,之后可以自己做自定义)
package main
import (
"fmt"
"log"
"net/http"
_ "net/http/pprof"
)
func handler(w http.ResponseWriter, r * http.Request) {
fmt.Fprintf(w, "Hi there, I love %s!", r.URL.Path[1:])
}
func main() {
http.HandleFunc("/", handler)
log.Fatal(http.ListenAndServe(":8080", nil))
}
导入net/http/pprof
包
import _ "net/http/pprof"
访问 http://localhost:8080/debug/pprof/
看到下面的页面就说明ok了。我们就可以获取对应的pprof报告了。
使用方式和源码分析
看到这种导包方式,就知道在init方法里面干了一些事情。
可以看到,在init里面它自己注册了几个路由,每一个路由对应页面上的操作。只要看一下这些方法就知道可以传什么参数了,本质上来说,这里数据采集的方法也是和上面的一样,只不过提供了http接口,我们还是以cpu为例来看一下。
可以传递收集时间,默认30s 。这里收集的方法和一开始的例子是一样的。
和三方http库结合
按照上面的思路,和三方http结合就是将init方法中注册的路由注册到三方库的http中就好。这里以echo
为例子
echo: https://echo.labstack.com/guide/
代码:
package main
import (
"net/http"
"net/http/pprof"
"github.com/labstack/echo/v4"
)
func main() {
e := echo.New()
e.GET("/", func(c echo.Context) error {
return c.String(http.StatusOK, "Hello, World!")
})
e.GET("/debug/pprof",echo.WrapHandler(http.HandlerFunc(pprof.Index)))
e.GET("/debug/pprof", echo.WrapHandler(http.HandlerFunc(pprof.Index)))
e.GET("/debug/pprof/allocs", echo.WrapHandler(http.HandlerFunc(pprof.Index)))
e.GET("/debug/pprof/block", echo.WrapHandler(http.HandlerFunc(pprof.Index)))
e.GET("/debug/pprof/goroutine", echo.WrapHandler(http.HandlerFunc(pprof.Index)))
e.GET("/debug/pprof/heap", echo.WrapHandler(http.HandlerFunc(pprof.Index)))
e.GET("/debug/pprof/mutex", echo.WrapHandler(http.HandlerFunc(pprof.Index)))
e.GET("/debug/pprof/cmdline", echo.WrapHandler(http.HandlerFunc(pprof.Cmdline)))
e.GET("/debug/pprof/profile", echo.WrapHandler(http.HandlerFunc(pprof.Profile)))
e.GET("/debug/pprof/symbol", echo.WrapHandler(http.HandlerFunc(pprof.Symbol)))
e.GET("/debug/pprof/trace", echo.WrapHandler(http.HandlerFunc(pprof.Trace)))
e.Logger.Fatal(e.Start(":1323"))
}
echo中注册路由的方法是:
WrapHandler
做了一层适配。
数据分析
-
查看heap profile
go tool pprof http://localhost:8080/debug/pprof/heap
-
查看30s cpu profile
go tool pprof http://localhost:6060/debug/pprof/profile?seconds=30
-
查看阻塞的goroutine
go tool pprof http://localhost:6060/debug/pprof/block
-
查看mutexes
go tool pprof http://localhost:6060/debug/pprof/mutex
-
查看trace
curl -o trace.out http://localhost:6060/debug/pprof/trace?seconds=5 go tool trace trace.out
-
查看pprof
curl -o profile.out http://localhost:8080/debug/pprof/profile?debug=1 go tool pprof profile.out
引用下面文章:
https://geektutu.com/post/hpg-pprof.html
到这里结束了。