什么是pprof
pprof 是 Go 提供的性能剖析工具,允许开发者查看 CPU、内存、Goroutine 以及其他资源的使用情况
启动pprof
import (
_ "net/http/pprof"
"log"
"net/http"
)
func main() {
go func() {
log.Println(http.ListenAndServe("localhost:6060", nil))
}()
// 其他代码...
}
一旦你的应用程序运行并且 pprof 启用后,你可以通过浏览器或命令行工具访问 pprof 数据。常见的端点包括:
- CPU profile:
http://localhost:6060/debug/pprof/profile?seconds=30 - Heap profile:
http://localhost:6060/debug/pprof/heap - Goroutine profile:
http://localhost:6060/debug/pprof/goroutine - Block profile:
http://localhost:6060/debug/pprof/block
使用pprof排查heap饱满
模拟heap爆满的场景,github地址为 codeCollection/pprof at main · Forrest-Tao/codeCollection (github.com) 其中的文件可以自己模拟一个非常大的文件
#运行go 程序
go mod tidy && go run .
//导出文件的方式 具体使用见下方
curl -o heap.out http://localhost:6060/debug/pprof/heap
#在本地8080端口发起请求
http://localhost:8080/readall
#不导出文件的方式,直接请求
go tool pprof http://localhost:6060/debug/pprof/heap
#进入pprof后,top 10 查看前10的heap分配情况
top 10
观察系统 内存变化
htop
查看具体的heap.out 文件
❯ curl -o heap.out http://127.0.0.1:6060/debug/pprof/heap
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 3257 0 3257 0 0 1060k 0 --:--:-- --:--:-- --:--:-- 1060k
❯ go tool pprof heap.out
File: demo
Type: inuse_space
Time: Aug 22, 2024 at 8:07pm (CST)
Entering interactive mode (type "help" for commands, "o" for options)
(pprof) top 10
Showing nodes accounting for 2.61GB, 100% of 2.61GB total
Dropped 9 nodes (cum <= 0.01GB)
flat flat% sum% cum cum%
2.61GB 100% 100% 2.61GB 100% io.ReadAll
0 0% 100% 2.61GB 100% github.com/gin-gonic/gin.(*Context).Next (inline)
0 0% 100% 2.61GB 100% github.com/gin-gonic/gin.(*Engine).ServeHTTP
0 0% 100% 2.61GB 100% github.com/gin-gonic/gin.(*Engine).handleHTTPRequest
0 0% 100% 2.61GB 100% github.com/gin-gonic/gin.CustomRecoveryWithWriter.func1
0 0% 100% 2.61GB 100% github.com/gin-gonic/gin.LoggerWithConfig.func1
0 0% 100% 2.61GB 100% io/ioutil.ReadAll (inline)
0 0% 100% 2.61GB 100% main.testReadAll
0 0% 100% 2.61GB 100% net/http.(*conn).serve
0 0% 100% 2.61GB 100% net/http.serverHandler.ServeHTTP
(pprof) list main.testReadAll # 列出方法
Total: 2.61GB
ROUTINE ======================== main.testReadAll in /mnt/c/Users/xiaoxi/GolandProjects/codeCollection/pprof/main.go
0 2.61GB (flat, cum) 100% of Total
. . 40:func testReadAll(c *gin.Context) {
. . 41: //fmt.Println("in testReadAll")
. . 42: file, err := os.Open("./cka.zip")
. . 43: if err != nil {
. . 44: panic(err)
. . 45: }
. . 46: defer file.Close()
. . 47: //simulate onLine err
. 2.61GB 48: bytes, err := ioutil.ReadAll(file) #这有写明白
. . 49: if err != nil {
. . 50: panic(err)
. . 51: }
. . 52: c.Writer.Write(bytes)
. . 53: if err != nil {
(pprof)
网页可视化
默认 net/http/pprof 挂在 /debug/pprof/。本机把 profile 拉下来用交互 UI:
go tool pprof -http=:8080 http://127.0.0.1:6060/debug/pprof/heap
浏览器里看 inuse_space 的火焰图 / top / source。线上不要把这个端口暴露到公网。
上面的 heap 案例已经能定位:ioutil.ReadAll 一次把文件读进内存,单次请求堆上 2.61GB。改成 io.Copy 流式写出,再抓一次 heap,这条栈应从 top 消失。
分析过程
- 先看
alloc_space和inuse_space是谁涨:前者是累计分配,后者是还活着的。 - 对这条接口打一次
heap,按inuse_space排序,对上源码行。 - 改掉整包读入后复测;RSS 下来且同一栈不再占 top,才算闭环。