pprof是GoLang程序性能分析工具,prof是profile(画像)的缩写 .通过pprof,我们可以得到程序执行的以下数据:
Profile Descriptions:
真正分析时常用4种
做性能分析,第一步需要先获取数据,然后对数据进行分析。所以下面展示一下如何进行数据获取。
go的运行有2种情况,一种是常驻内存 服务式运行,例如http,tcp服务,一直运行接收请求,一种是工具脚本形式,运行完则退出
工具型应用调用比较简单,主要是用 runtime/pprof库,将画像数据写入文件中:
package main
import (
"os"
"runtime/pprof"
_ "runtime/pprof"
)
func main() {
//获取cpu profile
cpuFile, _ := os.OpenFile("cpu.prof", os.O_CREATE|os.O_RDWR, 0644)
defer cpuFile.Close()
pprof.StartCPUProfile(cpuFile)
defer pprof.StopCPUProfile()
//获取内存profile
memFile, _ := os.OpenFile("mem.prof", os.O_CREATE|os.O_RDWR, 0644)
defer memFile.Close()
pprof.WriteHeapProfile(memFile)
//执行循环1000次
for i := 0; i < 1000; i++ {
}
}
执行完毕之后,可以直接看到cpu.prof和mem.prof文件,里面包含了画像数据:
(base) tioncico@appledeMacBook-Pro test % ls *.prof
cpu.prof mem.prof
文件不能直接查看,需要通过go tool pprof 工具读取显示,在下面会讲到
服务型应用通过 "net/http/pprof"库进行获取,在http库中,默认使用了defaultServerMux,可以直接使用即可获取:
package main
import (
"fmt"
"net/http"
_ "net/http/pprof"
)
func hello(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "hello world!")
}
func main() {
http.HandleFunc("/", hello) //设置访问的路由
err := http.ListenAndServe(":8080", nil) //设置监听的端口
if err != nil {
fmt.Printf("ListenAndServe: %s", err)
}
}
访问 http://localhost:8080 可以输出 hello world
访问 http://localhost:8080/debug/pprof/ 可以查看到画像信息:
可以看出,本身没有注册/debug/pprof,但是直接访问依然还是有数据的,那是因为在 http.ListenAndServe 时,没有传入自定义的serverMux进行路由处理,则pprof库自动进行了注册:
如果你使用了自定义的serverMux,则需要自己注册,才能获取到pprof
http.HandleFunc("/debug/ppprof/", pprof.Index)
这样的话,访问 http://localhost:8080/debug/ppprof/ 也是有用的
在不同的开源框架中,有提供自己封装好的pprof包,调用更加方便,具体使用请参考框架文档
pprof主要核心就是将pprof路由注册到服务中,并可以访问此服务即可
数据分析通过命令 go tool pprof 进行,主要有2种查看模式
1:通过http路径进入交互模式
2:通过文件进入交互模式或者进入web页面查看
进入命令行,基于http路径的数据分析:
(base) tioncico@appledeMacBook-Pro test % go tool pprof http://localhost:8080/debug/pprof/profile
Fetching profile over HTTP from http://localhost:8080/debug/pprof/profile
Saved profile in /Users/tioncico/pprof/pprof.samples.cpu.003.pb.gz
Type: cpu
Time: Nov 22, 2022 at 5:16pm (CST)
Duration: 30s, Total samples = 20ms (0.067%)
Entering interactive mode (type "help" for commands, "o" for options)
(pprof)
执行此命令后,会采集默认30秒之后的cpu数据,然后生成记录,进行数据分析,可以通过?=seconds=60改成60秒
也可以通过 go tool pprof cpu.prof(文件名) 方式进入
进入命令行交互模式之后,可以使用help查看所有命令,通过help <cmd|option> 查看子命令使用方法
(pprof) help
Commands:
callgrind Outputs a graph in callgrind format
comments Output all profile comments
disasm Output assembly listings annotated with samples
dot Outputs a graph in DOT format
eog Visualize graph through eog
evince Visualize graph through evince
gif Outputs a graph image in GIF format
gv Visualize graph through gv
kcachegrind Visualize report in KCachegrind
list Output annotated source for functions matching regexp
pdf Outputs a graph in PDF format
peek Output callers/callees of functions matching regexp
png Outputs a graph image in PNG format
proto Outputs the profile in compressed protobuf format
ps Outputs a graph in PS format
raw Outputs a text representation of the raw profile
svg Outputs a graph in SVG format
tags Outputs all tags in the profile
text Outputs top entries in text form
top Outputs top entries in text form
topproto Outputs top entries in compressed protobuf format
traces Outputs all profile samples in text form
tree Outputs a text rendering of call graph
web Visualize graph through web browser
weblist Display annotated source in a web browser
o/options List options and their current values
q/quit/exit/^D Exit pprof
可以自行研究
通过top,可以查看到cpu占用最高的排序:
(pprof) top
Showing nodes accounting for 20ms, 100% of 20ms total
flat flat% sum% cum cum%
10ms 50.00% 50.00% 10ms 50.00% runtime.libcCall
10ms 50.00% 100% 20ms 100% runtime.pthread_cond_wait
0 0% 100% 20ms 100% runtime.findrunnable
0 0% 100% 20ms 100% runtime.mPark
0 0% 100% 20ms 100% runtime.mcall
0 0% 100% 20ms 100% runtime.notesleep
0 0% 100% 20ms 100% runtime.park_m
0 0% 100% 20ms 100% runtime.schedule
0 0% 100% 20ms 100% runtime.semasleep
0 0% 100% 20ms 100% runtime.stopm
(pprof)
这个东西看不出啥,我们改造下http服务的helloworld,使其计算1000次快速排序:
package main
import (
"fmt"
"math/rand"
"net/http"
_ "net/http/pprof"
)
func hello(w http.ResponseWriter, r *http.Request) {
//生成一个100个随机数字的数组
arr := make([]int, 100)
for i := 0; i < 100; i++ {
arr[i] = rand.Intn(100)
}
for i := 0; i < 10000; i++ {
//快速排序
quickSort(arr, 0, len(arr)-1)
}
fmt.Fprintf(w, "hello world!")
}
func main() {
http.HandleFunc("/", hello) //设置访问的路由
err := http.ListenAndServe(":8080", nil) //设置监听的端口
if err != nil {
fmt.Printf("ListenAndServe: %s", err)
}
}
//快速排序
func quickSort(arr []int, left, right int) {
if left < right {
pivot := partition(arr, left, right)
quickSort(arr, left, pivot-1)
quickSort(arr, pivot+1, right)
}
}
func partition(arr []int, left int, right int) int {
pivot := arr[left]
for left < right {
for left < right && arr[right] >= pivot {
right--
}
arr[left] = arr[right]
for left < right && arr[left] <= pivot {
left++
}
arr[right] = arr[left]
}
arr[left] = pivot
return left
}
重新执行分析命令进行分析,top命令查看:
(pprof) top
Showing nodes accounting for 144.72s, 99.70% of 145.16s total
Dropped 21 nodes (cum <= 0.73s)
flat flat% sum% cum cum%
114.47s 78.86% 78.86% 114.83s 79.11% main.partition
30.03s 20.69% 99.55% 144.90s 99.82% main.quickSort
0.22s 0.15% 99.70% 117.12s 80.68% main.hello
0 0% 99.70% 114.42s 78.82% net/http.(*ServeMux).ServeHTTP
0 0% 99.70% 111.58s 76.87% net/http.(*conn).serve
0 0% 99.70% 115.71s 79.71% net/http.HandlerFunc.ServeHTTP
0 0% 99.70% 112.95s 77.81% net/http.serverHandler.ServeHTTP
(pprof)
每一行都代表一个函数的信息,每列的标识为:
flat:函数在 CPU 上运行的时间
flat%:函数在CPU上运行时间的百分比
sum%:是从上到当前行所有函数累加使用 CPU 的比例,如第二行sum=48.52=28.79+19.73
cum:这个函数以及子函数运行所占用的时间,应该大于等于flat
cum%:这个函数以及子函数运行所占用的比例,应该大于等于flat%
最后一列:函数的名字
如果应用程序有性能问题,上面这些信息应该能告诉我们时间都花费在哪些函数的执行上
通过 交互模式 list 函数名,即可查看到该函数的具体哪一行耗时:
(pprof) list hello
Total: 145.16s
ROUTINE ======================== main.hello in /Users/tioncico/go/src/test/server.go
220ms 117.12s (flat, cum) 80.68% of Total
. . 11: //生成一个100个随机数字的数组
. . 12: arr := make([]int, 100)
. . 13: for i := 0; i < 100; i++ {
. . 14: arr[i] = rand.Intn(100)
. . 15: }
210ms 210ms 16: for i := 0; i < 10000; i++ {
. . 17: //快速排序
. 116.90s 18: quickSort(arr, 0, len(arr)-1)
. . 19: }
10ms 10ms 20: fmt.Fprintf(w, "hello world!")
. . 21:}
. . 22:
. . 23:func main() {
. . 24: http.HandleFunc("/", hello) //设置访问的路由
. . 25: err := http.ListenAndServe(":8080", nil) //设置监听的端口
(pprof)
可看到,for循环里面的quickSort耗时比较长
可以通过svg命令,生成一个svg文件,拖动到浏览器打开即可查看函数调用图,但是需要安装 graphviz 才可以使用,具体安装方法可以自行百度
mac安装方法:
brew install graphviz
go get -u github.com/ofabry/go-callvis
执行go tool pprof http://localhost:8080/debug/pprof/heap 即可,其他和cpu操作同理
通过-http 选项进入web页面:
go tool pprof -http=:9091 http://localhost:8080/debug/pprof/profile
go tool pprof -http=:9091 文件名
其他可以执行了解探索
本文为仙士可原创文章,转载无需和我联系,但请注明来自仙士可博客www.php20.cn