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

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

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

  • CPU Profile:报告程序的CPU使用情况,按照一定频率去采集应用程序在CPU和寄存器上面的数据。
  • Memory Profile(Heap Profile):报告程序的内存使用情况。
  • Block Profiling: 报告goroutines不在运行状态的情况,可以用来分析和查找死锁等性能瓶颈。
  • Goroutine Profiling: 报告goroutines的使用情况,有哪些roroutines,它们的调用关系是怎样的。

注意:我们只应该在性能测试的时候才在代码中引入pprof

搭建

实例代码:

  1. package main
  2. import (
  3. "net/http"
  4. "github.com/gin-contrib/pprof"
  5. "github.com/gin-gonic/gin"
  6. )
  7. func main() {
  8. app := gin.Default()
  9. pprof.Register(app) // 性能
  10. app.GET("/test", func(c *gin.Context) {
  11. c.String(http.StatusOK, "test")
  12. })
  13. app.Run(":3000")
  14. }
  1. package pprof
  2. import (
  3. "net/http"
  4. "net/http/pprof"
  5. "github.com/gin-gonic/gin"
  6. )
  7. const (
  8. // DefaultPrefix url prefix of pprof
  9. DefaultPrefix = "/debug/pprof"
  10. )
  11. func getPrefix(prefixOptions ...string) string {
  12. prefix := DefaultPrefix
  13. if len(prefixOptions) > 0 {
  14. prefix = prefixOptions[0]
  15. }
  16. return prefix
  17. }
  18. // Register the standard HandlerFuncs from the net/http/pprof package with
  19. // the provided gin.Engine. prefixOptions is a optional. If not prefixOptions,
  20. // the default path prefix is used, otherwise first prefixOptions will be path prefix.
  21. func Register(r *gin.Engine, prefixOptions ...string) {
  22. RouteRegister(&(r.RouterGroup), prefixOptions...)
  23. }
  24. // RouteRegister the standard HandlerFuncs from the net/http/pprof package with
  25. // the provided gin.GrouterGroup. prefixOptions is a optional. If not prefixOptions,
  26. // the default path prefix is used, otherwise first prefixOptions will be path prefix.
  27. func RouteRegister(rg *gin.RouterGroup, prefixOptions ...string) {
  28. prefix := getPrefix(prefixOptions...)
  29. prefixRouter := rg.Group(prefix)
  30. {
  31. prefixRouter.GET("/", pprofHandler(pprof.Index))
  32. prefixRouter.GET("/cmdline", pprofHandler(pprof.Cmdline))
  33. prefixRouter.GET("/profile", pprofHandler(pprof.Profile))
  34. prefixRouter.POST("/symbol", pprofHandler(pprof.Symbol))
  35. prefixRouter.GET("/symbol", pprofHandler(pprof.Symbol))
  36. prefixRouter.GET("/trace", pprofHandler(pprof.Trace))
  37. prefixRouter.GET("/allocs", pprofHandler(pprof.Handler("allocs").ServeHTTP))
  38. prefixRouter.GET("/block", pprofHandler(pprof.Handler("block").ServeHTTP))
  39. prefixRouter.GET("/goroutine", pprofHandler(pprof.Handler("goroutine").ServeHTTP))
  40. prefixRouter.GET("/heap", pprofHandler(pprof.Handler("heap").ServeHTTP))
  41. prefixRouter.GET("/mutex", pprofHandler(pprof.Handler("mutex").ServeHTTP))
  42. prefixRouter.GET("/threadcreate", pprofHandler(pprof.Handler("threadcreate").ServeHTTP))
  43. }
  44. }
  45. func pprofHandler(h http.HandlerFunc) gin.HandlerFunc {
  46. handler := http.HandlerFunc(h)
  47. return func(c *gin.Context) {
  48. handler.ServeHTTP(c.Writer, c.Request)
  49. }
  50. }

编译并运行代码。代码运行之后可以看到系统自动增加了很多/debug/pprof的API。通过这些API我们可以看到需要的数据。
image.png
在浏览器里访问/debug/pprof, 通过这个页面我们可以看到我们需要的所有数据:
image.png这几个路径表示的是

  • /debug/pprof/profile:访问这个链接会自动进行 CPU profiling,持续 30s,并生成一个文件供下载
  • /debug/pprof/block:Goroutine阻塞事件的记录。默认每发生一次阻塞事件时取样一次。
  • /debug/pprof/goroutines:活跃Goroutine的信息的记录。仅在获取时取样一次。
  • /debug/pprof/heap: 堆内存分配情况的记录。默认每分配512K字节时取样一次。
  • /debug/pprof/mutex: 查看争用互斥锁的持有者。
  • /debug/pprof/threadcreate: 系统线程创建情况的记录。 仅在获取时取样一次。

使用go tool pprof采集数据

  1. go tool pprof http://localhost:3000/debug/pprof/profile?second=30

上述命令的意思是采集cpu数据并持续30S。执行结果如下:

image.png
top命令展示函数的5个数据:

  1. flat: 函数执行时间
  2. flat%: 函数执行时间与总时间的百分比
  3. sum%: 前面每一行flat%的和
  4. cum: 累计量,加入函数调用了其它函数,其它函数的执行时间也会被记入
  5. cum%: 累计量占总量的百分比

image.png