golang编写的程序在开发过程、运行过程中可能会出现一些意想不到的问题,诸如:cpu暴涨、内存吃紧、接口响应时间过长、goroutine数量暴涨等等问题,这个时候就涉及到性能分析和问题定位排查。

pprofprofile.protoprofile.protoProtobuf v3callstacksymbolization

pprof

runtime/pprofnet/http/pprofgo test

通过pprof这个工具也就是使用go官方提供的两个包可得到一些分析报告或执行一些分析:

  • 生成报告:即生成一个后续可用来分析的性能分析报告文件
  • 交互式终端里直接输入命令查看各项指标
  • web界面报告:即打开一个web网页,在页面里点点点就可以看到各种图形化一目了然的指标

pprof的分析报告可以为我们提供如下类型的分析指标<这段是摘除>:

runtime.SetBlockProfileRateruntime.SetMutexProfileFraction

http服务的pprof

go原生提供http便捷服务开发包,一个典型的http服务结构如下:

package main

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

func main()  {
	srv := &http.Server{
		Addr:           ":9080",
		Handler:        nil,
		ReadTimeout:    60 * time.Second,
		WriteTimeout:   65 * time.Second,
		MaxHeaderBytes: 1 << 20, //1MB
	}

	http.HandleFunc("/hello", func(writer http.ResponseWriter, request *http.Request) {
		_, _ = writer.Write([]byte("hello world!"))
	})

	_ = srv.ListenAndServe()
}
_ "net/http/pprof"initinit
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)
}
net/http/pprof

一些指标的说明<这段是摘除>:

$HOST/debug/pprof/allocs$HOST/debug/pprof/block$HOST/debug/pprof/goroutine$HOST/debug/pprof/heap$HOST/debug/pprof/mutex$HOST/debug/pprof/profile$HOST/debug/pprof/threadcreate

可视化图形分析

graphvizgraphvizCould not execute dot; may need to install graphviz.

1、获取profile文件

启动已加载pprof的服务,终端里通过wget命令获取profile文件:

wget http://127.0.0.1:9080/debug/pprof/profile\?seconds\=20
secondsWriteTimeoutprofile duration exceeds server's WriteTimeoutsecondsWriteTimeoutsecondsWriteTimeout

上述wget命令会阻塞采样,阻塞采样期间如果要查看某个接口的采样分析结果,这个时候需要去访问这个接口以便执行这个接口相关的逻辑。

2、生成可视化网页和图

profile
go tool pprof -http=:6001 profile

以及所谓的火焰图

自定义抓取profile

net/http/pprofProfileruntime/pprofnet/http/pprofruntime/pprofruntime/pprof
// Profile responds with the pprof-formatted cpu profile.
// Profiling lasts for duration specified in seconds GET parameter, or for 30 seconds if not specified.
// The package initialization registers it as /debug/pprof/profile.
func Profile(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("X-Content-Type-Options", "nosniff")
	sec, err := strconv.ParseInt(r.FormValue("seconds"), 10, 64)
	if sec <= 0 || err != nil {
		sec = 30
	}

	if durationExceedsWriteTimeout(r, float64(sec)) {
		serveError(w, http.StatusBadRequest, "profile duration exceeds server's WriteTimeout")
		return
	}

	// Set Content Type assuming StartCPUProfile will work,
	// because if it does it starts writing.
	w.Header().Set("Content-Type", "application/octet-stream")
	w.Header().Set("Content-Disposition", `attachment; filename="profile"`)
	if err := pprof.StartCPUProfile(w); err != nil {
		// StartCPUProfile failed, so no writes yet.
		serveError(w, http.StatusInternalServerError,
			fmt.Sprintf("Could not enable CPU profiling: %s", err))
		return
	}
	sleep(r, time.Duration(sec)*time.Second)
	pprof.StopCPUProfile()
}
runtime/pprof
// 启动cpu采样,参数为一个io.Writer接口,通过参数可以指定将采样写到何处,譬如:文件
pprof.StartCPUProfile(writer)
// 采样开始执行后会启动一个协程去持续的采样获取样本数据
// 故而当前协程需要暂停等待协程去执行采样
time.sleep(t)
// 采样结束调用停止方法,本质是通过chan传递一个消息告诉上述采样协程需要停止
pprof.StopCPUProfile()

---

参考资料:

① https://golang2.eddycjy.com/posts/ch6/01-pprof-1/