Golang pprof

文章目录

  • 1. 简介
  • 2. 使用方法
  • 3. Profile文件产生方法
    • 3.1 Web应用
    • 3.2 普通应用
      • 3.2.1 CPU概要文件
      • 3.2.2 内存概要文件
      • 3.2.3 程序阻塞概要文件
  • 4. 效果展示
    • 4.1 Web应用
    • 4.2 普通应用
  • 5. go test
  • 6. 火焰图
    • 6.1 原生 PProf 工具
    • 6.2 安装FlameGraph
    • 6.3 安装graphviz
    • 6. 4 安装go-torch
    • 6.5 结果
  • 7. 参考文献

1. 简介

Golang 是一个对性能特别看重的语言,因此语言中自带了 一些性能分析工具。可以生成相应的Profile(概要文件),譬如CPU Profile、Memory Profile、Block Profile、Mutex Profile、Goroutine Profile等等

CPU Profile:报告程序的 CPU 使用情况,按照一定频率采集应用程序在 CPU 和寄存器上面的数据

Memory Profile(Heap Profile):报告程序的内存使用情况

Block Profile:报告 goroutines 不在运行状态的情况,可以用来分析和查找死锁等性能瓶颈问题

Goroutine Profile:报告 goroutines 的使用情况,有哪些 goroutine,它们的调用关系是怎样的

2. 使用方法

pprof命令:

usage:

Produce output in the specified format.

   pprof  [options] [binary]  ...

Omit the format to get an interactive shell whose commands can be used
to generate various views of a profile

   pprof [options] [binary]  ...

Omit the format and provide the "-http" flag to get an interactive web
interface at the specified host:port that can be used to navigate through
various views of a profile.

   pprof -http [host]:[port] [options] [binary]  ...

Details:
  Output formats (select at most one):
    -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

  Options:
    -call_tree       Create a context-sensitive call tree
    -compact_labels  Show minimal headers
    -divide_by       Ratio to divide all samples before visualization
    -drop_negative   Ignore negative differences
    -edgefraction    Hide edges below *total
    -focus           Restricts to samples going through a node matching regexp
    -hide            Skips nodes matching regexp
    -ignore          Skips paths going through any nodes matching regexp
    -mean            Average sample value over first value (count)
    -nodecount       Max number of nodes to show
    -nodefraction    Hide nodes below *total
    -normalize       Scales profile based on the base profile.
    -output          Output filename for file-based outputs
    -positive_percentages Ignore negative samples when computing percentages
    -prune_from      Drops any functions below the matched frame.
    -relative_percentages Show percentages relative to focused subgraph
    -sample_index    Sample value to report (0-based index or name)
    -show            Only show nodes matching regexp
    -source_path     Search path for source files
    -tagfocus        Restricts to samples with tags in range or matched by regex
p
    -taghide         Skip tags matching this regexp
    -tagignore       Discard samples with tags in range or matched by regexp
    -tagshow         Only consider tags matching this regexp
    -trim            Honor nodefraction/edgefraction/nodecount defaults
    -unit            Measurement units to display

  Option groups (only set one per group):
    cumulative
      -cum             Sort entries based on cumulative weight
      -flat            Sort entries based on own weight
    granularity
      -addresses       Aggregate at the function level.
      -addressnoinlines Aggregate at the function level, including functions' ad
dresses in the output.
      -files           Aggregate at the file level.
      -functions       Aggregate at the function level.
      -lines           Aggregate at the source code line level.
      -noinlines       Aggregate at the function level.

binary 是应用的二进制文件,用来解析各种符号;source 表示 profile 数据的来源,可以是本地的文件,也可以是 http 地址

在pprof界面,通过 TopN 命令显示重要信息。

如果我们在top命令后加入标签–cum,那么输出的列表就是以累积取样计数为顺序的

当开启CPU Profile时,Go程序每秒钟进行100次采样。

The web command writes a graph of the profile data in SVG format and opens it in a web browser. (There is also a gv command that writes PostScript and opens it in Ghostview. For either command, you need graphviz installed.)

通过 Web 命令以SVG格式创建 Profile数据表,并且在web浏览器中进行展示。

同时,也可以通过gv命令写PostScript,并且在Ghostview中打开。不管哪一个命令,都需要安装graphviz。

在图中看到耗时的操作时,譬如 function1,那么可以通过如下命令仔细观察:

web function1

可以通过如下命令查看源码

list function1

go tool pprof 最简单的使用方式为 go tool pprof [binary] [source],binary 是应用的二进制文件,用来解析各种符号;source 表示 Profile 数据的来源,可以是本地的文件,也可以是 http 地址。

如果程序名为testDemo,profile文件为testDemo.prof,那么通过如下命令就能展示程序的一些运行信息:

go tool pprof testDemo testDemo.prof

那么问题来了,如何生成Profile文件?

3. Profile文件产生方法

根据程序的性质不同,生成profile文件的方式也不同。

3.1 Web应用

对于Web应用,非常简单,如果使用了默认的 http.DefaultServeMux(通常是代码直接使用 http.ListenAndServe(“0.0.0.0:8000”, nil)),在命令源码文件中导入net/http/pprof包就可以,具体形式如下:

import _ "net/http/pprof"

如果你使用自定义的 Mux,则需要手动注册一些路由规则:

r.HandleFunc("/debug/pprof/", pprof.Index)
r.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline)
r.HandleFunc("/debug/pprof/profile", pprof.Profile)
r.HandleFunc("/debug/pprof/symbol", pprof.Symbol)
r.HandleFunc("/debug/pprof/trace", pprof.Trace)

3.2 普通应用

方法一

对于普通的应用程序,可以采用和Web应用相同的方法,在main函数中添加如下代码:

import "net/http"
import "log"
go func() {
	log.Println(http.ListenAndServe("localhost:6060", nil))
}()

或者

package main

import (
    "net/http"
    _ "net/http/pprof"
)

func hiHandler(w http.ResponseWriter, r *http.Request) {
    w.Write([]byte("hi"))
}

func main() {
    http.HandleFunc("/", hiHandler)
    http.ListenAndServe(":8080", nil)
}

**方法二 **

主动将各种概要文件写入文件。

3.2.1 CPU概要文件

对于CPU Profile,可以采用如下方式,在main函数的入口处调用 WriteCPUProfile 函数:

var cpuProfile = flag.String("cpuProfile", "", "write cpu profile to file")
func WriteCPUProfile() {
	if *cpuProfile != "" {
		f, err := os.Create(*cpuProfile)
		if err != nil {
			fmt.Fprintf(os.Stderr, "Can not create cpu profile output file: %s",
				err)
			return
		}
		if err := pprof.StartCPUProfile(f); err != nil {
			fmt.Fprintf(os.Stderr, "Can not start cpu profile: %s", err)
			f.Close()
			return
		}
		
		defer pprof.StopCPUProfile() // 把记录的概要信息写到已指定的文件
	}
}

或者


func startCPUProfile() {
	if *cpuProfile != "" {
		f, err := os.Create(*cpuProfile)
		if err != nil {
			fmt.Fprintf(os.Stderr, "Can not create cpu profile output file: %s",
				err)
			return
		}
		if err := pprof.StartCPUProfile(f); err != nil {
			fmt.Fprintf(os.Stderr, "Can not start cpu profile: %s", err)
			f.Close()
			return
		}
	}
}

停止函数

func stopCPUProfile() {
	if *cpuProfile != "" {
		pprof.StopCPUProfile() // 把记录的概要信息写到已指定的文件
	}
}

调用方法

var cpuProfile  = flag.String("cpuprofile", "", "write a cpu profile to the named file during execution")
func main(){
    startCPUProfile()
    defer stopCPUProfile()
}

-cpuprofile 指定CPU概要文件的保存路径。该路径可以是相对路径也可以是绝对路径,但其父路径必须已存在。

使用方法:

go run testDemo.go -p="runtime" -cpuprofile="./pprof/cpu.pprof"

-p是对命令源码文件 testDemo.go 有效的。其含义是指定要解析依赖关系的代码包的导入路径。(明确)

3.2.2 内存概要文件

对于内存概要函数,分为start和stop两个函数:

var memProfile       = flag.String("memprofile", "", "write a memory profile to the named file after execution")
var memProfileRate   = flag.Int("memprofilerate", 0, "if > 0, sets runtime.MemProfileRate")
func startMemProfile() {
	if *memProfile != "" && *memProfileRate > 0 {
		runtime.MemProfileRate = *memProfileRate
	}
}

memProfile是内存概要文件的路径。memProfileRate的含义是分析器的取样间隔,单位是字节。如果我们不给runtime.MemProfileRate变量赋值,内存使用情况的取样操作也会照样进行。此取样操作会从用户程序开始时启动,且一直持续进行到用户程序结束。runtime.MemProfileRate变量的默认值是512K字节。只有当我们显式的将0赋给runtime.MemProfileRate变量之后,才会取消取样操作。

在默认情况下,内存使用情况的取样数据只会被保存在运行时内存中,而保存到文件的操作只能由我们自己来完成。

停止函数:

func stopMemProfile() {
	if *memProfile != "" {
		f, err := os.Create(*memProfile)
		if err != nil {
			fmt.Fprintf(os.Stderr, "Can not create mem profile output file: %s", err)
			return
		}
		if err = pprof.WriteHeapProfile(f); err != nil {
			fmt.Fprintf(os.Stderr, "Can not write %s: %s", *memProfile, err)
		}
		f.Close()
	}
}

stopMemProfile函数的功能是停止对内存使用情况的取样操作。但是,它只做了将取样数据保存到内存概要文件的操作。在stopMemProfile函数中,我们调用了函数pprof.WriteHeapProfile,并把代表内存概要文件的文件实例作为了参数。如果pprof.WriteHeapProfile函数没有返回错误,就说明数据已被写入到了内存概要文件中。

-memprofile 指定内存概要文件的保存路径。该路径可以是相对路径也可以是绝对路径,但其父路径必须已存在。

-memprofilerate 定义其值为n。此标记指定每分配n个字节的堆内存时,进行一次取样操作。

使用方法:

go run testDemo.go -p="runtime" -memprofile="./pprof/mem.pprof" -memprofilerate=10

3.2.3 程序阻塞概要文件

程序阻塞概要文件用于保存用户程序中的Goroutine阻塞事件的记录。

var blockProfile = flag.String("blockprofile", "", "write a goroutine blocking profile to the named file after execution")

var blockProfileRate = flag.Int("blockprofilerate", 1, "if > 0, calls runtime.SetBlockProfileRate()")
func startBlockProfile() {
	if *blockProfile != "" && *blockProfileRate > 0 {
		runtime.SetBlockProfileRate(*blockProfileRate)
	}
}

blockProfile的含义为程序阻塞概要文件的路径。blockProfileRate的含义是分析器的取样间隔,单位是次。默认取样间隔为1,即默认情况下,每发生一次Goroutine阻塞事件,分析器就会取样一次。与内存使用情况记录一样,运行时系统对Goroutine阻塞事件的取样操作也会贯穿于用户程序的整个运行期。但是,如果我们通过runtime.SetBlockProfileRate函数将这个取样间隔设置为0或者负数,那么这个取样操作就会被取消。

将保存在运行时内存中的Goroutine阻塞事件记录存放到指定的文件,代码如下:

func stopBlockProfile() {
	if *blockProfile != "" && *blockProfileRate >= 0 {
		f, err := os.Create(*blockProfile)
		if err != nil {
			fmt.Fprintf(os.Stderr, "Can not create block profile output file: %s", err)
			return
		}
		if err = pprof.Lookup("block").WriteTo(f, 0); err != nil {
			fmt.Fprintf(os.Stderr, "Can not write %s: %s", *blockProfile, err)
		}
		f.Close()
	}
}

在创建程序阻塞概要文件之后,stopBlockProfile函数会先通过函数pprof.Lookup将保存在运行时内存中的内存使用情况记录取出,并在记录的实例上调用WriteTo方法将记录写入到文件中。

-blockprofile 指定程序阻塞概要文件的保存路径。该路径可以是相对路径也可以是绝对路径,但其父路径必须已存在。

-blockprofilerate 定义其值为n。此标记指定每发生n次Goroutine阻塞事件时,进行一次取样操作。

使用方法:

go run testDemo.go -p="runtime" -blockprofile="./pprof/block.pprof" -blockprofilerate=10

通过pprof.Lookup函数取出更多种类的取样记录,具体如下:

名称 说明 取样频率
goroutine 活跃Goroutine的信息的记录。 仅在获取时取样一次。
threadcreate 系统线程创建情况的记录。 仅在获取时取样一次。
heap 堆内存分配情况的记录。 默认每分配512K字节时取样一次。
block Goroutine阻塞事件的记录。 默认每发生一次阻塞事件时取样一次

4. 效果展示

4.1 Web应用

为方便展示效果,先以Web应用展示Pprof的使用,示例程序见Github:

查看堆信息:

go tool pprof http://localhost:9090/debug/pprof/heap

在pprof界面中,输入web命令:

web

查看阻塞信息:

go tool pprof http://localhost:9090/debug/pprof/block

查看锁信息(to look at the holders of contended mutexes, after calling runtime.SetMutexProfileFraction in your program)

go tool pprof http://localhost:9090/debug/pprof/mutex

查看60s的CPU信息

go tool pprof http://localhost:9090/debug/pprof/profile?seconds=60

输入web指令,输出信息:

web_cpu
Golang pprof_第1张图片

采集5s内的程序执行路径信息:

wget http://localhost:9090/debug/pprof/trace?seconds=5

如果需要查看所有可用的profile,在浏览器中打开:

http://localhost:9090/debug/pprof/

结果:
Golang pprof_第2张图片

heap信息如下:
Golang pprof_第3张图片

4.2 普通应用

测试程序如下见Github,只进行cpu性能分析.

通过 go install havlak1.go生成可执行程序havlak1

首先产生prof文件:

havlak1 -cpuprofile=havlak1.prof

其次,对prof文件进行分析:

go tool pprof havlak1 havlak1.prof

在pprof操作界面:

F:\ProgramTest\Golang\goBin>go tool pprof havlak1.exe havlak1.prof
File: havlak1.exe
Type: cpu
Time: Jul 25, 2019 at 6:34am (CST)
Duration: 20.10s, Total samples = 25.87s (128.68%)
Entering interactive mode (type "help" for commands, "o" for options)
(pprof)

输入以下指令查看最占CPU的10个函数:

top 10

结果如下:

Showing nodes accounting for 19400ms, 74.99% of 25870ms total
Dropped 107 nodes (cum <= 129.35ms)
Showing top 10 nodes out of 55
      flat  flat%   sum%        cum   cum%
    3540ms 13.68% 13.68%     3620ms 13.99%  runtime.mapaccess1_fast64
    3100ms 11.98% 25.67%     6690ms 25.86%  runtime.scanobject
    2420ms  9.35% 35.02%    12460ms 48.16%  main.FindLoops
    2070ms  8.00% 43.02%     3410ms 13.18%  runtime.mallocgc
    2020ms  7.81% 50.83%     2020ms  7.81%  runtime.heapBitsForObject
    1840ms  7.11% 57.94%     1860ms  7.19%  runtime.greyobject
    1410ms  5.45% 63.39%     2800ms 10.82%  runtime.mapassign_fast64ptr
    1330ms  5.14% 68.53%     3530ms 13.65%  main.DFS
     890ms  3.44% 71.98%      890ms  3.44%  runtime.heapBitsSetType
     780ms  3.02% 74.99%      780ms  3.02%  runtime.memclrNoHeapPointers
top 5 -cum

结果如下:

Showing nodes accounting for 2.50s, 9.66% of 25.87s total
Dropped 107 nodes (cum <= 0.13s)
Showing top 5 nodes out of 55
      flat  flat%   sum%        cum   cum%
         0     0%     0%     12.53s 48.43%  main.main
         0     0%     0%     12.53s 48.43%  runtime.main
         0     0%     0%     12.46s 48.16%  main.FindHavlakLoops
     2.42s  9.35%  9.35%     12.46s 48.16%  main.FindLoops
     0.08s  0.31%  9.66%      9.06s 35.02%  runtime.systemstack

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

pprof 不仅能打印出最耗时的地方(top),还能列出函数代码以及对应的取样数据(list)、汇编代码以及对应的取样数据(disasm),而且能以各种样式进行输出,比如 svg、gv、callgrind、png、gif等等。

其中一个非常便利的是 web 命令,在交互模式下输入 web,就能自动生成一个 svg 文件,并跳转到浏览器打开,生成了一个函数调用图(havlak1_web):

web

Golang pprof_第4张图片

Web生成的图包含了更多的信息,而且可视化的图像能让我们更清楚地理解整个应用程序的全貌。图中每个方框对应一个函数,方框越大代表执行的时间越久(包括它调用的子函数执行时间,但并不是正比的关系);方框之间的箭头代表着调用关系,箭头上的数字代表被调用函数的执行时间。

web mapaccess1

Golang pprof_第5张图片

5. go test

go test 命令有两个参数和 pprof 相关,它们分别指定生成的 CPU 和 Memory profiling 保存的文件:

-cpuprofile:cpu profiling 数据要保存的文件地址
-memprofile:memory profiling 数据要报文的文件地址

go test -bench=. -cpuprofile=cpu.prof
go test -bench=. -memprofile=mem.prof
1. go tool pprof cpu.prof 
2. go tool pprof -http=:8080 cpu.prof

6. 火焰图

火焰图(Flame Graph)是 Bredan Gregg 创建的一种性能分析图表,因为它的样子近似火而得名。上面的 profiling 结果也转换成火焰图。Uber开源工具 go-torch可以直接读取 golang profiling 数据,并生成一个火焰图的 svg 文件。

使用火焰图之前,需要安装如下工具

6.1 原生 PProf 工具

go get github.com/google/pprof

需要将产生的pprof可执行程序所在路径添加到环境变量中。

用法

pprof -http=:8080 cpu.prof

6.2 安装FlameGraph

git clone https://github.com/brendangregg/FlameGraph.git

将FlameGraph所在路径添加到环境变量中。

export PATH=$PATH:$GOPATH/FlameGraph

6.3 安装graphviz

graphviz 用于绘图

CentOS:

yum install graphviz

Ubuntu

sudo apt-get install graphviz

6. 4 安装go-torch

安装go-torch用于采集数据,生产火焰图。

go get github.com/uber/go-torch

需要将产生的pprof可执行程序所在路径添加到环境变量中。

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

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

火焰图 svg 文件可以通过浏览器打开,它对于调用图的最优点是它是动态的:可以通过点击每个方块来 zoom in 分析它上面的内容。

火焰图的调用顺序从下到上,每个方块代表一个函数,它上面一层表示这个函数会调用哪些函数,方块的大小代表了占用 CPU 使用的长短,火焰图的配色并没有特殊的意义。

6.5 结果

运行程序(监听于9090端口),输入如下指令

go-torch -u http://localhost:9090 -t 30

随后在执行go-torch的目录下会生成torch.svg文件,通过浏览器打开,如下:
Golang pprof_第6张图片
点击图中的任意部分,可以看到更详细信息:Golang pprof_第7张图片
备注:
在Windows下安装同样方式操作时,会有如下错误:

could not generate flame graph: fork/exec F:\Pr
ogramTest\Golang\src\github.com\FlameGraph\flamegraph.pl: %1 is not a valid Win32 application.

问题原因是windows不能直接执行pl文件。pl文件是perl文件,于是安装perl,并且将路径添加到环境变量中。依然报错。因为windows直接可执行的是exe文件。可能需要将下载的flamegraph进行编译,然后进行安装。或者直接下载flamegraph可执行程序,并进行安装。这部分后面完善。

7. 参考文献

Package pprof

package pprof

Profiling Go Programs

Go 大杀器之性能剖析 PProf

go tool pprof

Go的pprof使用

使用 pprof 和火焰图调试 golang 应用

Profiling and optimizing Go web applications

google/pprof

实战Go内存泄露

扫描二维码,关注“清远的梦呓”公众号,手机端查看文章

Golang pprof_第8张图片

你可能感兴趣的:(Golang)