Golang程序)官方正规快三81512073

前言

最近计划用三篇文章讲述一下Golang应用性能分析,本文是第一篇,先来介绍Go语言自带的性能分析库pprof怎么使用,后面两篇会讲解怎么用pprofEcho或者Gin框架开发的应用进行性能分析以及如何使用pprof对gRPC 服务进行性能分析。node

有兴趣追更的同窗欢迎微信关注「网管叨bi叨」

Golang是一个很是注重性能的语言,所以语言的内置库里就自带了性能分析库pprofgit

性能分析和采集在计算机性能调试领域使用的术语是profile,或者有时候也会使用profiling表明性能分析这个行为。因此pprof名字里的prof来源于对单词profile的缩写,profile这个单词的原意是画像,那么针对程序性能的画像就是应用使用 CPU 和内存等等这些资源的状况。都是哪些函数在使用这些资源?每一个函数使用的比例是多少?github

Go 语言中,主要关注的程序运行状况包括如下几种:golang

  • CPU profile:报告程序的 CPU 使用状况,按照必定频率去采集应用程序在 CPU 和寄存器上面的数据
  • Memory Profile(Heap Profile):报告程序的内存使用状况
  • Block Profile:报告致使阻塞的同步原语的状况,能够用来分析和查找锁的性能瓶颈
  • Goroutine Profile:报告 goroutines 的使用状况,有哪些 goroutine,它们的调用关系是怎样的

工具型应用的性能分析

若是你的应用是工具类应用,执行完任务就结束退出,可使用 runtime/pprof [1]库。web

好比要想进行 CPU Profiling,能够调用 pprof.StartCPUProfile() 方法,它会对当前应用程序进行CPU使用状况分析,并写入到提供的参数中(w io.Writer),要中止调用 StopCPUProfile() 便可。正则表达式

去除错误处理只须要三行内容,通常把部份内容写在 main.go 文件中,应用程序启动以后就开始执行:json

1.  f, err := os.Create(*cpuprofile)
    
2.  ...
    
3.  pprof.StartCPUProfile(f)
    
4.  defer pprof.StopCPUProfile()

应用执行结束后,就会生成一个文件,保存了咱们应用的 CPU使用状况数据。api

想要得到内存的数据,直接使用 WriteHeapProfile 就行,不用 startstop 这两个步骤了:浏览器

1.  f, err := os.Create(*memprofile)
    
2.  pprof.WriteHeapProfile(f)
    
3.  f.Close()

咱们的文章会把重点篇幅放在服务型应用的性能分析,因此关于工具型应用的性能分析就说这么多。安全

服务型应用性能分析

若是你的应用是一直运行的,好比 web 应用或者gRPC服务等,那么可使用 net/http/pprof 库,它可以在应用提供 HTTP 服务时进行分析。

若是使用了默认的 http.DefaultServeMux(一般是代码直接使用 http.ListenAndServe("0.0.0.0:8000", nil)),只须要在代码中添加一行,匿名引用net/http/pprof

import _ "net/http/pprof"

若是你使用自定义的 ServerMux复用器,则须要手动注册一些路由规则:

1.  r.HandleFunc("/debug/pprof/", pprof.Index)
    
2.  r.HandleFunc("/debug/pprof/heap", pprof.Index)
    
3.  r.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline)
    
4.  r.HandleFunc("/debug/pprof/profile", pprof.Profile)
    
5.  r.HandleFunc("/debug/pprof/symbol", pprof.Symbol)
    
6.  r.HandleFunc("/debug/pprof/trace", pprof.Trace)

注册完后访问http://localhost/debug/pprof端点,它会获得相似下面的页面内容:

1.  Types of profiles available:
    
2.  Count Profile
    
3.  // 下面是一些可访问的/debug/pprof/目录下的路由
    
4.  2 allocs
    
5.  0 block
    
6.  0 cmdline
    
7.  36 goroutine
    
8.  2 heap
    
9.  0 mutex
    
10.  0 profile
    
11.  13 threadcreate
    
12.  0 trace
    
13.  full goroutine stack dump
    
14.  Profile Descriptions:
    

16.  // 下面是对上面那些路由页面里展现的性能分析数据的解释
    
17.  allocs: A sampling of all past memory allocations
    
18.  block: Stack traces that led to blocking on synchronization primitives
    
19.  cmdline: The command line invocation of the current program
    
20.  goroutine: Stack traces of all current goroutines
    
21.  heap: A sampling of memory allocations of live objects. You can specify the gc GET parameter to run GC before taking the heap sample.
    
22.  mutex: Stack traces of holders of contended mutexes
    
23.  profile: CPU profile. You can specify the duration in the seconds GET parameter. After you get the profile file, use the go tool pprof command to investigate the profile.
    
24.  threadcreate: Stack traces that led to the creation of new OS threads
    
25.  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.

这个路径下几个须要重点关注的子页面有:

  • /debug/pprof/profile:访问这个连接会自动进行 CPU profiling,持续 30s,并生成一个文件供下载,能够经过带参数?=seconds=60进行60秒的数据采集
  • /debug/pprof/heap:Memory Profiling 的路径,访问这个连接会获得一个内存 Profiling 结果的文件
  • /debug/pprof/block:block Profiling 的路径
  • /debug/pprof/goroutines:运行的 goroutines 列表,以及调用关系

直接访问这些页面产生的性能分析数据咱们是分析不过来什么的,Go在1.11版本后在它自带的工具集go tool里内置了pprof工具来分析由pprof库生成的数据文件。

使用go tool pprof

经过上面的设置能够获取服务的性能数据后,接下来就可使用go tool pprof工具对这些数据进行分析和保存了,通常都是使用pprof经过HTTP访问上面列的那些路由端点直接获取到数据后再进行分析,获取到数据后pprof会自动让终端进入交互模式。在交互模式里pprof为咱们提供了很多分析各类指标的子命令,在交互模式下键入help后就会列出全部子命令。

NOTE pprof子命令的使用方法能够参考 pprof --help 或者 pprof 文档[2]。

CPU性能分析

进行CPU性能分析直接用go tool pprof访问上面说的/debug/pprof/profile端点便可,等数据采集完会自动进入命令行交互模式。

1.  ➜ go tool pprof http://localhost/debug/pprof/profile
    
2.  Fetching profile over HTTP from http://localhost/debug/pprof/profile
    
3.  Saved profile in /Users/Kev/pprof/pprof.samples.cpu.005.pb.gz
    
4.  Type: cpu
    
5.  Time: Nov 15, 2020 at 3:32pm (CST)
    
6.  Duration: 30.01s, Total samples = 0
    
7.  No samples were found with the default sample value type.
    
8.  Try "sample_index" command to analyze different sample values.
    
9.  Entering interactive mode (type "help" for commands, "o" for options)
    
10.  (pprof)

默认采集时长是 30s,若是在 url 最后加上 ?seconds=60 参数能够调整采集数据的时间为 60s。

采集完成咱们就进入了一个交互式命令行,能够对解析的结果进行查看和导出。能够经过 help 来查看支持的子命令有哪些。

NOTE: 若是 pprof用性能数据生成分析图的话、包括后面的go-torch火焰图都依赖软件 graphviz

Mac 用户直接brew install graphviz就能安装,其余系统官网下载页面也有提供安装包,请访问https://graphviz.org/download/

列出最耗时的地方

一个有用的命令是 topN,它列出最耗时间的地方:

1.  (pprof) top10
    
2.  130ms of 360ms total (36.11%)
    
3.  Showing top 10 nodes out of 180 (cum >= 10ms)
    
4.        flat  flat%   sum%        cum   cum%
    
5.        20ms  5.56%  5.56%      100ms 27.78%  encoding/json.(*decodeState).object
    
6.        20ms  5.56% 11.11%       20ms  5.56%  runtime.(*mspan).refillAllocCache
    
7.        20ms  5.56% 16.67%       20ms  5.56%  runtime.futex
    
8.        10ms  2.78% 19.44%       10ms  2.78%  encoding/json.(*decodeState).literalStore
    
9.        10ms  2.78% 22.22%       10ms  2.78%  encoding/json.(*decodeState).scanWhile
    
10.        10ms  2.78% 25.00%       40ms 11.11%  encoding/json.checkValid
    
11.        10ms  2.78% 27.78%       10ms  2.78%  encoding/json.simpleLetterEqualFold
    
12.        10ms  2.78% 30.56%       10ms  2.78%  encoding/json.stateBeginValue
    
13.        10ms  2.78% 33.33%       10ms  2.78%  encoding/json.stateEndValue
    
14.        10ms  2.78% 36.11%       10ms  2.78%  encoding/json.stateInString

每一行表示一个函数的信息。前两列表示函数在 CPU 上运行的时间以及百分比;第三列是当前全部函数累加使用 CPU 的比例;第四列和第五列表明这个函数以及子函数运行所占用的时间和比例(也被称为累加值 cumulative),应该大于等于前两列的值;最后一列就是函数的名字。若是应用程序有性能问题,上面这些信息应该能告诉咱们时间都花费在哪些函数的执行上。

生成函数调用图

pprof 不只能打印出最耗时的地方(top),还能列出函数代码以及对应的取样数据(list)、汇编代码以及对应的取样数据(disasm),并且能以各类样式进行输出,好比 svggifpng等等。

其中一个很是便利的是 web 命令,在交互模式下输入 web,就能自动生成一个 svg 文件,并跳转到浏览器打开,生成了一个函数调用图(这个功能须要安装graphviz后才能使用)。

图中每一个方框对应应用程序运行的一个函数,方框越大表明函数执行的时间越久(函数执行时间会包含它调用的子函数的执行时间,但并非正比的关系);方框之间的箭头表明着调用关系,箭头上的数字表明被调用函数的执行时间。

这里还要提两个比较有用的方法,若是应用比较复杂,生成的调用图特别大,看起来很乱,有两个办法能够优化:

  • 使用 web funcName 的方式,只打印和某个函数相关的内容
  • 运行 go tool pprof 命令时加上 --nodefration 参数,能够忽略内存使用较少的函数,好比--nodefration=0.05表示若是调用的子函数使用的 CPU、memory 不超过 5%,就忽略它,不要显示在图片中。

分析函数性能

想更细致分析,就要精确到代码级别了,看看每行代码的耗时,直接定位到出现性能问题的那行代码。pprof 也能作到,list 命令后面跟着一个正则表达式,就能查看匹配函数的代码以及每行代码的耗时:

1.  (pprof) list podFitsOnNode
    
2.  Total: 120ms
    
3.  ROUTINE ======================== k8s.io/kubernetes/plugin/pkg/scheduler.podFitsOnNode in /home/cizixs/go/src/k8s.io/kubernetes/_output/local/go/src/k8s.io/kubernetes/plugin/pkg/scheduler/generic_scheduler.go
    
4.           0       20ms (flat, cum) 16.67% of Total
    
5.           .          .    230:
    
6.           .          .    231:// Checks whether node with a given name and NodeInfo satisfies all predicateFuncs.
    
7.           .          .    232:func podFitsOnNode(pod *api.Pod, meta interface{}, info *schedulercache.NodeInfo, predicateFuncs map[string]algorithm.FitPredicate) (bool, []algorithm.PredicateFailureReason, error) {
    
8.           .          .    233:    var failedPredicates []algorithm.PredicateFailureReason
    
9.           .          .    234:    for _, predicate := range predicateFuncs {
    
10.           .       20ms    235:        fit, reasons, err := predicate(pod, meta, info)
    
11.           .          .    236:        if err != nil {
    
12.           .          .    237:            err := fmt.Errorf("SchedulerPredicates failed due to %v, which is unexpected.", err)
    
13.           .          .    238:            return false, []algorithm.PredicateFailureReason{}, err
    
14.           .          .    239:        }
    
15.           .          .    240:        if !fit {

内存性能分析

要想得到内存使用 Profiling 信息,只须要把数据源修改一下就行(对于 HTTP 方式来讲就是修改 url 的地址,从 /debug/pprof/profile改为 /debug/pprof/heap):

1.  ➜  go tool pprof  http://localhost/debug/pprof/heap 
    
2.  Fetching profile from http://localhost/debug/pprof/heap 
    
3.  Saved profile in 
    
4.  ......
    
5.  (pprof)

和 CPU Profiling 使用同样,使用 top N 能够打印出使用内存最多的函数列表:

1.  (pprof) top
    
2.  11712.11kB of 14785.10kB total (79.22%)
    
3.  Dropped 580 nodes (cum <= 73.92kB)
    
4.  Showing top 10 nodes out of 146 (cum >= 512.31kB)
    
5.        flat  flat%   sum%        cum   cum%
    
6.   2072.09kB 14.01% 14.01%  2072.09kB 14.01%  k8s.io/kubernetes/vendor/github.com/beorn7/perks/quantile.NewTargeted
    
7.   2049.25kB 13.86% 27.87%  2049.25kB 13.86%  k8s.io/kubernetes/pkg/api/v1.(*ResourceRequirements).Unmarshal
    
8.   1572.28kB 10.63% 38.51%  1572.28kB 10.63%  k8s.io/kubernetes/vendor/github.com/beorn7/perks/quantile.(*stream).merge
    
9.   1571.34kB 10.63% 49.14%  1571.34kB 10.63%  regexp.(*bitState).reset
    
10.   1184.27kB  8.01% 57.15%  1184.27kB  8.01%  bytes.makeSlice
    
11.   1024.16kB  6.93% 64.07%  1024.16kB  6.93%  k8s.io/kubernetes/pkg/api/v1.(*ObjectMeta).Unmarshal
    
12.    613.99kB  4.15% 68.23%  2150.63kB 14.55%  k8s.io/kubernetes/pkg/api/v1.(*PersistentVolumeClaimList).Unmarshal
    
13.    591.75kB  4.00% 72.23%  1103.79kB  7.47%  reflect.Value.call
    
14.    520.67kB  3.52% 75.75%   520.67kB  3.52%  k8s.io/kubernetes/vendor/github.com/gogo/protobuf/proto.RegisterType
    
15.    512.31kB  3.47% 79.22%   512.31kB  3.47%  k8s.io/kubernetes/pkg/api/v1.(*PersistentVolumeClaimStatus).Unmarshal

每一列的含义也是相似的,只不过从 CPU 使用时长变成了内存使用大小,就很少解释了。

相似的,web 命令也能生成 svg 图片在浏览器中打开,从中能够看到函数调用关系,以及每一个函数的内存使用多少。

须要注意的是,默认状况下,统计的是内存使用大小,若是执行命令的时候加上 --inuse_objects 能够查看每一个函数分配的对象数;--alloc-space 查看分配的内存空间大小。

go-torch 和火焰图

火焰图(Flame Graph)是 Bredan Gregg 建立的一种性能分析图表,由于它的样子近似火焰而得名。上面的 profiling结果也转换成火焰图,这里咱们要介绍一个工具:go-torch[3]。这是 uber 开源的一个工具,能够直接读取 pprofprofiling 数据,并生成一个火焰图的 svg 文件。

img

火焰图 svg 文件能够经过浏览器打开,它对于调用图的优势是:能够经过点击每一个方块来分析它上面的内容

火焰图的调用顺序从下到上,每一个方块表明一个函数,它上面一层表示这个函数会调用哪些函数,方块的大小表明了占用 CPU 使用的长短。火焰图的配色并无特殊的意义,默认的红、黄配色是为了更像火焰而已。

go-torch 工具的使用很是简单,没有任何参数的话,它会尝试从 http://localhost/debug/pprof/profile 获取 profiling 数据。它有三个经常使用的参数能够调整:

  • -u --url:要访问的 URL,这里只是主机和端口部分
  • -s --suffix:pprof profile 的路径,默认为 /debug/pprof/profile
  • --seconds:要执行 profiling 的时间长度,默认为 30s

要生成火焰图,须要事先安装 FlameGraph[4]工具,这个工具的安装很简单,只要把对应的可执行文件放到 $PATH 目录下就行。

总结

今天的文章把Go语言的性能分析库pprof的安装和使用方法大致流程走了一遍,重点讲解了一下最经常使用的几个性能分析命令以及如何用pprof采集的profile数据找出程序里最耗费性能的部分。相信有了pprof的帮助在遇到须要优化程序性能的时候咱们能有更多的参照指标从而有的放矢地对程序性能进行优化改进。

在使用pprof采集数据的时候必定要注意下面两点:

  1. 只有在有访问量的时候才能采集到这些性能指标数据。我是在公司的压测环境对接口压测时用pprof拿到的数据,若是你是在本地运行程序的话最好用Postman或者Jmeter这些工具作个简单的并发访问。
  2. 除非有健全的安全策略,不然最好只在测试和压测环境加上pprof使用的那些路由,不要在生产环境上应用。

这篇文章就说这么多,后面的文章会介绍怎么在EchoGin框架下使用pprof,以及如何用pprof分析gRPC服务的性能。求关注、求点赞、求转发!我是网管,会在这里每周坚持输出原创,咱们下期再见吧。

引用连接

[1]

runtime/pprof : https://golang.org/pkg/runtim...

[2]

pprof 文档: https://github.com/google/ppr...

[3]

go-torch: https://github.com/uber/go-torch

[4]

FlameGraph: https://github.com/brendangre...

文章推荐

三种传递gRPC动态参数方式的使用体验

看Kubernetes源码,学习怎么用Go实现调度队列

  • END -
相关文章
相关标签/搜索