• 首页 首页 icon
  • 工具库 工具库 icon
    • IP查询 IP查询 icon
  • 内容库 内容库 icon
    • 快讯库 快讯库 icon
    • 精品库 精品库 icon
    • 问答库 问答库 icon
  • 更多 更多 icon
    • 服务条款 服务条款 icon

Go性能工具pprof

武飞扬头像
叨陪鲤
帮助7

1. 简介

pprof 是profile(画像)的缩写,是Go中很常用的获取数据、分析数据的工具。pprof有很多优点:可视化,除此之外,go原生,简单方便,很容易上手。

go tool pprof 是对应的命令行指令。它的源数据既可以是一个http地址,也可以是已经获取到的profile文件。使用go tool pprof命令时,既可以采用交互式终端,也可以采用web进行可视化分析,除此之外可以直接将数据生成svg图片,进行静态的分析。

pprof可以分析以下9中数据:
学新通

这9项对应的内容如下:

Profile项 说明 详情
allocs 内存分配 从程序启动开始,分配的全部内存
block 阻塞 导致同步原语阻塞的堆栈跟踪
cmdline 命令行调用 当前程序的命令行调用
goroutine gorouting 所有当前 goroutine 的堆栈跟踪
heap 活动对象的内存分配抽样。您可以指定 gc 参数以在获取堆样本之前运行 GC
mutex 互斥锁 争用互斥锁持有者的堆栈跟踪
profile CPU分析 CPU 使用率分析。可以在url中,通过seconds指定持续时间(默认30s)。获取配置文件后,使用 go tool pprof 命令分析CPU使用情况
threadcreate 线程创建 导致创建新操作系统线程的堆栈跟踪
trace 追踪 当前程序的执行轨迹。可以在url中,通过seconds指定持续时间(默认30s)。获取跟踪文件后,使用 go tool trace 命令调查跟踪

在性能分析时,使用最多有三种:内存分析(allocs,heap),CPU分析(profile), 阻塞分析(block), 互斥锁分析(mutex)。

下面详细介绍pprof的使用方法。

2. 数据获取

pprof的应用场景主要分为两种:

  • 服务型应用,例如web服务器等各种服务类型端的性能分析
  • 工具型应用,例如一些命令行工具,执行完毕后直接退出的应用

针对这两种不同的应用场景,pprof有不同的用法。下面做一个详细的介绍

2.1 工具型应用

🏆工具型应用使用runtime/pprof库,将CPU、内存信息手动写到文件中!!!

package main

import (
	"fmt"
	"os"
	"runtime/pprof"
)

func main() {

	cpuProfile, err := os.Create("./pprof/cpu_profile")
	if err != nil {
		fmt.Printf("创建文件失败:%s", err.Error())
		return
	}
	defer cpuProfile.Close()

	memProfile, err := os.Create("./pprof/mem_profile")
	if err != nil {
		fmt.Printf("创建文件失败:%s", err.Error())
		return
	}
	defer memProfile.Close()
	//采集CPU信息
	pprof.StartCPUProfile(cpuProfile)
	defer pprof.StopCPUProfile()

	//采集内存信息
	pprof.WriteHeapProfile(memProfile)

	for i := 0; i < 100; i   {
		fmt.Println("pprof 工具型测试")
	}
}

2.2 服务型应用

服务型应用使用net/http/pprof库。

服务型应用使用pprof工具时,首先需要将pprof提供路由注册到当前服务中。由于go的http/https服务框架,允许用户使用原生的http框架,也可以使用其他的框架(例如Gin框架);不同的http服务框架,pprof在处理上略有不同:关键在于是否使用默认的SeverMux结构;下图分别为go中原生net/http框架和Gin框架
学新通
学新通

2.2.1 使用Go原生http服务框架

当采用go原生的http服务框架,使用pprof非常的简单:只需要导入net/http/pprof包即可。代码如下:

package main

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

func HelloWorld(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintln(w, "hello world")
}

func main() {
	http.HandleFunc("/", HelloWorld)

	err := http.ListenAndServe(":8080", nil)
	if err != nil {
		fmt.Println(err)
	}
}

启动服务后,直接通过浏览器访问pprof路径即可获取采集的信息:

学新通

虽然可以通过web查看每一项信息,但是很不友好。我并不喜欢直接使用web查看pprof采用的数据方式,而是采用先通过go tool pprof将数据采集下来,然后再通过go tool pprof开启一个http服务进行,最后通过web访问这个服务,这个时候你的选项非常多,各种图像化输出非常的友好。

go tool pprof http://192.168.1.27:8080/debug/pprof/allocs
go tool pprof http://192.168.1.27:8080/debug/pprof/block
go tool pprof http://192.168.1.27:8080/debug/pprof/cmdline
go tool pprof http://192.168.1.27:8080/debug/pprof/heap
go tool pprof http://192.168.1.27:8080/debug/pprof/mutex
go tool pprof http://192.168.1.27:8080/debug/pprof/profile
go tool pprof http://192.168.1.27:8080/debug/pprof/threadcreate
go tool pprof http://192.168.1.27:8080/debug/pprof/trace

上面的命令,我全部列出来了。不过每次只需要选择需要分析的项进行数据采集即可。命令执行完毕后,会生成对应的pb.gz的文件。之后再使用go tool pprof工具开启一个服务便大功告成。

$ go tool pprof http://192.168.1.27:8080/debug/pprof/allocs
Fetching profile over HTTP from http://192.168.1.27:8080/debug/pprof/allocs
Saved profile in /home/toney/pprof/pprof.server.alloc_objects.alloc_space.inuse_objects.inuse_space.002.pb.gz

开启http服务,通过浏览器图形化分析采集的数据:

$ go tool pprof -http=192.168.1.27:8081 pprof.server.alloc_objects.alloc_space.inuse_objects.inuse_space.001.pb.gz 

此命令执行后,如果本设备有浏览器,会自动跳转到浏览器中;如果没有浏览器,可以通过http协议远程访问。
学新通

除此之外,可以通过左上角的“VIEW”菜单栏,选择不同的视图,其中比较逼格很高的是:火焰图(Flame Graph),它真是一个好东西…
学新通


在原生http框架中的用法已经说完了,下面简单介绍下:为何只需要导入net/http/pprof包即可完成这么牛逼的功能?

直接追踪net/http/pprof包,就会发现:

/net/http/pprof包中有一个init()函数。此函数中注册了5个路由,用来获取不同的profile信息

func init() {
	http.HandleFunc("/debug/pprof/", Index)
	http.HandleFunc("/debug/pprof/cmdline", Cmdline)
	http.HandleFunc("/debug/pprof/profile", Profile)
	http.HandleFunc("/debug/pprof/symbol", Symbol)
	http.HandleFunc("/debug/pprof/trace", Trace)
}

由于init()函数在导入包的过程中会自动执行。由于我们采用原生的http框架时(路由注册到DefaultServeMux中),http.HandleFunc函数会将路由注册到默认的DefaultServeMux中,此时pprof路径便可以生效,无需再做任何其他操作。

由此联想到其他的web框架,如Gin框架,由于不采用默认的DefaultServerMux结构,因此无法通过直接导入pprof包完成pprof路由的注册。

对于net/http包的web框架,gin框架不熟的可以看看上面两个大图。

2.2.2 使用Gin框架

Gin框架如果要添加pprof, 可以借助github.com/gin-contrib/pprof包

Gin中pprof的使用方式如下:

package main

import (
	"fmt"
	"net/http"

	"github.com/gin-contrib/pprof"
	"github.com/gin-gonic/gin"
)

func helloGin(c *gin.Context) {
	fmt.Println("hello Gin")
	c.String(http.StatusOK, "欢迎来到三体世界")
}

func GinMain(r *gin.RouterGroup) {
	ginGroup := r.Group("/gin") //gin框架前缀
	{
		ginGroup.GET("/", helloGin)
	}
}

func main() {
	r := gin.Default()
	pprof.Register(r)
	web := r.Group("/golang/web") //公共前缀
	GinMain(web)

	r.Run(":8080")
}

启动程序后,可以看出已经成功注册pprof的相关路由。

学新通

之后的步骤就是借助go tool pprof工具采集信息,分析数据。


虽然Gin框架中使用的是"github.com/gin-contrib/pprof",实际上这个包就是在net/http/pprof基础上做的gin封装(将func(w http.ResponseWriter, r *http.Request)格式函数转换为gin.HandlerFunc),方便gin框架调用而已。

// Register the standard HandlerFuncs from the net/http/pprof package with
// the provided gin.Engine. prefixOptions is a optional. If not prefixOptions,
// the default path prefix is used, otherwise first prefixOptions will be path prefix.
func Register(r *gin.Engine, prefixOptions ...string) {
	RouteRegister(&(r.RouterGroup), prefixOptions...)
}

// RouteRegister the standard HandlerFuncs from the net/http/pprof package with
// the provided gin.GrouterGroup. prefixOptions is a optional. If not prefixOptions,
// the default path prefix is used, otherwise first prefixOptions will be path prefix.
func RouteRegister(rg *gin.RouterGroup, prefixOptions ...string) {
	prefix := getPrefix(prefixOptions...)

	prefixRouter := rg.Group(prefix)
	{
		prefixRouter.GET("/", pprofHandler(pprof.Index))
		prefixRouter.GET("/cmdline", pprofHandler(pprof.Cmdline))
		prefixRouter.GET("/profile", pprofHandler(pprof.Profile))
		prefixRouter.POST("/symbol", pprofHandler(pprof.Symbol))
		prefixRouter.GET("/symbol", pprofHandler(pprof.Symbol))
		prefixRouter.GET("/trace", pprofHandler(pprof.Trace))
		prefixRouter.GET("/allocs", pprofHandler(pprof.Handler("allocs").ServeHTTP))
		prefixRouter.GET("/block", pprofHandler(pprof.Handler("block").ServeHTTP))
		prefixRouter.GET("/goroutine", pprofHandler(pprof.Handler("goroutine").ServeHTTP))
		prefixRouter.GET("/heap", pprofHandler(pprof.Handler("heap").ServeHTTP))
		prefixRouter.GET("/mutex", pprofHandler(pprof.Handler("mutex").ServeHTTP))
		prefixRouter.GET("/threadcreate", pprofHandler(pprof.Handler("threadcreate").ServeHTTP))
	}
}

func pprofHandler(h http.HandlerFunc) gin.HandlerFunc {
	handler := http.HandlerFunc(h)
	return func(c *gin.Context) {
		handler.ServeHTTP(c.Writer, c.Request)
	}
}

2.2.3 Grpc类服务

单独开启一个协程用来采集数据信息即可。

3. 数据分析

以内存分配(allocs)分配的内存为例学习go tool pprof数据分析

3.1 TOP图

用来查看CPU/内存占有率最高的接口。

学新通

名称 说明
Flat CPU在此函数上的运行时间/消耗的内存
Flat% CPU在此函数上运行时间/消耗的内存所占用比例
Sum% 从第一行到当前行CPU占有率总和
Cum 此函数及其子函数运行所占用时间/消耗的内存
Cum% 此函数及其子函数运行所占用时间/消耗的内存的比例
Name 函数名称

3.2 函数调用图

通过函数调用图可以很直观形象的看出:哪些函数内存消耗较多
学新通

3.3 火焰图

火焰图很直观形象看出整体的时间/内存的消耗情况,重点分析占重比比较高的部分。
学新通

3.4 Peek图

没玩明白,略。
学新通

3.5 Go源码分析图

列出占比比较高的函数接口在go代码中的位置。
学新通

3.6 汇编代码分析图

超出知识范围,略。

4. 参考

📖 pprof 的使用

📖 源码包中用法

📖 一文搞懂pprof

[外链图片转存中…(img-KJ4qitqL-1649862180770)]

3.6 汇编代码分析图

超出知识范围,略。

4. 参考

📖 pprof 的使用

📖 源码包中用法

📖 一文搞懂pprof

这篇好文章是转载于:学新通技术网

  • 版权申明: 本站部分内容来自互联网,仅供学习及演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,请提供相关证据及您的身份证明,我们将在收到邮件后48小时内删除。
  • 本站站名: 学新通技术网
  • 本文地址: /boutique/detail/tanhfhkjjh
系列文章
更多 icon
同类精品
更多 icon
继续加载