go的性能分析:pprof工具

简介: go的性能分析:pprof工具

pprof

pprof是GoLang程序性能分析工具,prof是profile(画像)的缩写 .通过pprof,我们可以得到程序执行的以下数据:

Profile Descriptions:

  • allocs:
    内存分配数据采样信息
  • block:
    导致同步原语阻塞的堆栈跟踪
  • cmdline:
    当前程序的命令行调用
    goroutine:
    所有当前goroutine的堆栈跟踪
  • heap:
    活动对象的内存分配采样。您可以指定gcGET参数以在获取堆样本之前运行gc。
  • mutex:
    争用互斥锁持有者的堆栈跟踪
  • profile:
    CPU配置文件。可以在秒GET参数中指定持续时间。获取配置文件后,使用go工具pprof命令调查配置文件。
  • threadcreate:
    导致创建新操作系统线程的堆栈跟踪
  • trace:
    A trace of execution of the current program. You can specify the duration in the seconds GET parameter. After you get the trace file, use the go tool trace command to investigate the trace.

真正分析时常用4种

  • CPU Profiling:CPU 分析,按照一定的频率采集所监听的应用程序 CPU(含寄存器)的使用情况,可确定应用程序在主动消耗 CPU 周期时花费时间的位置
  • Memory Profiling:内存分析,在应用程序进行堆分配时记录堆栈跟踪,用于监视当前和历史内存使用情况,以及检查内存泄漏
  • Block Profiling:阻塞分析,记录 goroutine 阻塞等待同步(包括定时器通道)的位置
  • Mutex Profiling:互斥锁分析,报告互斥锁的竞争情况

做性能分析,第一步需要先获取数据,然后对数据进行分析。所以下面展示一下如何进行数据获取。

数据获取方式

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/  可以查看到画像信息:

serverMux

可以看出,本身没有注册/debug/pprof,但是直接访问依然还是有数据的,那是因为在 http.ListenAndServe 时,没有传入自定义的serverMux进行路由处理,则pprof库自动进行了注册:

image.png

如果你使用了自定义的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

image.png

内存画像

执行go tool pprof http://localhost:8080/debug/pprof/heap  即可,其他和cpu操作同理

web页面模式

通过-http 选项进入web页面:

go tool pprof  -http=:9091 http://localhost:8080/debug/pprof/profile 
go tool pprof  -http=:9091 文件名


image.png

其他可以执行了解探索

目录
相关文章
|
1月前
|
缓存 监控 Linux
Linux性能分析利器:全面掌握perf工具
【10月更文挑战第18天】 在Linux系统中,性能分析是确保软件运行效率的关键步骤。`perf`工具,作为Linux内核自带的性能分析工具,为开发者提供了强大的性能监控和分析能力。本文将全面介绍`perf`工具的使用,帮助你成为性能优化的高手。
147 1
|
1月前
|
缓存 监控 Linux
掌握Linux性能分析:深入探索perf工具
【10月更文挑战第26天】
59 1
|
1月前
|
JSON 监控 安全
go语言选择合适的工具和库
【10月更文挑战第17天】
14 2
|
2月前
|
Web App开发 监控 JavaScript
一些常用的 Vue 性能分析工具
【10月更文挑战第2天】
131 1
|
3月前
|
SQL 缓存 关系型数据库
MySQL高级篇——性能分析工具
MySQL的慢查询日志,用来记录在MySQL中响应时间超过阀值的语句,具体指运行时间超过long-query_time值的SQL,则会被记录到慢查询日志中。long_query_time的默认值为 10,意思是运行10秒以上(不含10秒)的语句,认为是超出了我们的最大忍耐时间值。它的主要作用是,帮助我们发现那些执行时间特别长的 SOL 查询,并且有针对性地进行优化,从而提高系统的整体效率。当我们的数据库服务器发生阻塞、运行变慢的时候,检查一下慢查询日志,找到那些慢查询,对解决问题很有帮助。
MySQL高级篇——性能分析工具
|
3月前
|
监控 IDE Java
【Java性能调优新工具】JDK 22性能分析器:深度剖析,优化无死角!
【9月更文挑战第9天】JDK 22中的性能分析器为Java应用的性能调优提供了强大的支持。通过深度集成、全面监控、精细化分析和灵活报告生成等核心优势,性能分析器帮助开发者实现了对应用性能的全面掌控和深度优化。在未来的Java开发过程中,我们期待性能分析器能够继续发挥重要作用,为Java应用的性能提升贡献更多力量。
|
4月前
|
存储 缓存 监控
Linux性能分析工具-perf并生成火焰图
Linux性能分析工具-perf并生成火焰图
|
4月前
|
Kubernetes 数据可视化 Java
|
4月前
|
SQL 存储 监控
|
4月前
|
算法 测试技术 Go