遵从代码最佳实践或者代码规范指导,可以避免一些参见的坑,但在运行代码时遇到各种问题后,还是需要深入代码了解代码性能和瓶颈, 以便能够发现问题、解决问题和做出相应的优化。

1、profiler

性能分析器(Profiler)是一种动态的性能分析工具,可以提供程序在运行过程中的各种维度的关键性能分析,可以用来解决各种性能问题,定位内存泄露, 线程竞争死锁等问题。

Go运行时以pprof可视化工具预期的格式提供性能分析数据,分析数据可以在测试期间通过go test,或者从net/http/pprof包提供的endpoint收集。我们需要收集性能分析数据,并通过pprof工具对程序进行分析,并支持可视化数据。

Go 通过runtime/pprof包提对profile提供了内置的支持:

  • CPU: CPU分析采集一定周期内程序对CPU的使用情况,确定程序在主动消耗CPU周期是如何消耗时间的
  • Heap: Heap分析,记录内存分配,用于监视当前和历史内存使用情况,以及检查内存泄露
  • Goroutine: Goroutine分析报告所有当前正在运行goroutine的堆栈跟踪信息
  • threadcreate: 报告程序中导致创建新OS线程的堆栈跟踪信息
  • Block: 阻塞分析,显示goroutine阻塞等待同步(包括定时器通道)的位置,默认没有启用,可以使用 runtime.SetBlockProfileRate 启用它
  • Mutex: 互斥锁分析,报告互斥锁的竞争情况,当认为互斥锁争用导致CPU没有得到充分使用时,可以使用这种分析,默认没有启动,可以使用runtime.SetMutexProfileFraction 启用它

2、采集分析数据

目前有三种方式进行数据采集:

  • go test: 通过运行测试用例继续采集
  • runtime/pprof: 直接在代码中指定采集程序运行数据
  • net/http/pprof: 采集http server运行时数据进行分析

2.1、go test进行采样

测试标准库在go test中内置了对采集性能分析数据的内置支持,如下面的命令,在当前目录中运行测试,并将CPU和内存采样数据分别写入cpu.prof和 mem.prof:

1
go test -cpuprofile cpu.prof -memprofile mem.prof .

2.2、通过HTTP加载实时采样数据

在长时间运行的http服务中,只需要导入net/http/pprof包,服务便可以进行采集数据,并多出一个终结点(endpoint)/debug/pprof,可用于观察采集到的 性能分析数据。包导入语句如:

1
_ "net/http/pprof"

net/http/pprof 包初始化时在DefaultServeMux中为给定的URL注册了处理程序

1
2
3
4
5
6
7
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)
}

这些handler将采样数据写入到http.ResponseWriter,导入net/http/pprof后需要做的,只是启动一个HTTP server就可以了,如:

1
http.ListenAndServe(":8080", nil)

完整的最小采集demo:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
package main

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

func main() {
	http.HandleFunc("/", func(rw http.ResponseWriter, r *http.Request) {
		c, _ := httputil.DumpRequest(r, true)
		log.Println(string(c))

		rw.Header().Set("Content-Type", "application/json")
		rw.WriteHeader(201)
		rw.Write([]byte("{}"))
	})
	http.ListenAndServe(":8080", nil)
}

运行程序后,就可以在浏览器访问localhost:8080/debug/pprof实时采样数据总览页面:

pprof

  • allocs: 所有已分配内存的采样
  • block: 导致阻塞同步的堆栈跟踪
  • cmdline: 显示程序启动的路径命令和参数
  • goroutine: 所有当前goroutine的堆栈跟踪
  • heap: 当前活动对象的内存采样
  • mutext: 互斥锁持有者的堆栈跟踪
  • profile: 进行CPU采样,采样完成得到一个供分析用的profile文件,默认采集间隔是30秒
  • threadcreate: 创建新OS线程的堆栈跟踪
  • trace: 程序的执行跟踪信息

直接通过上面的方式查看采样数据枯燥且缺乏直观性,后面介绍可以借助go tool pprof命令,可以更直观方便的分析采样数据。

2.3、在代码中指定采样

通过使用runtime/pprof包,可以直接在代码中进行采样,如可以使用pprof.StartCPUProfile(io.Writer)开始CPU采样, 使用pprof.StopCPUProfile() 停止CPU采样,如:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
var cpuprofile = flag.String("cpuprofile", "", "write cpu profile to `file`")
var memprofile = flag.String("memprofile", "", "write memory profile to `file`")

func main() {
    flag.Parse()
    if *cpuprofile != "" {
        f, err := os.Create(*cpuprofile)
        if err != nil {
            log.Fatal("could not create CPU profile: ", err)
        }
        defer f.Close() // error handling omitted for example
        if err := pprof.StartCPUProfile(f); err != nil {
            log.Fatal("could not start CPU profile: ", err)
        }
        defer pprof.StopCPUProfile()
    }

    // ... rest of the program ...

    if *memprofile != "" {
        f, err := os.Create(*memprofile)
        if err != nil {
            log.Fatal("could not create memory profile: ", err)
        }
        defer f.Close() // error handling omitted for example
        runtime.GC() // get up-to-date statistics
        if err := pprof.WriteHeapProfile(f); err != nil {
            log.Fatal("could not write memory profile: ", err)
        }
    }
}

运行程序:

1
go run -cpuprofile cpu.prof -memprofile mem.prof .

将CPU和内存采样数据分别写入文件cpu.prof 和 mem.prof。

3、使用采样数据

3.1、通过交互命令行终端

通过go tool pprof命令在交互命令行终端中使用采用数据,对于采样数据文件profile可以使用:

1
go tool pprof profile

profile

对于通过「采样方式2.2」HTTP 终结点方式,可以使用go tool pprof url的方式,如:

1
go tool pprof http://localhost:8080/debug/pprof/profile?seconds=30

profile-2

采样url,在可以在采样总览页面中获取到:

ulr

3.2、pprof可视化界面

使用pprof可视化界面时,在go tool pprof命令通过指定端口参数-http,运行一个pprof分析用的站点,如:

  • 查看当前所有goroutine的堆栈跟踪

    1
    
    go tool pprof -http :9090 'http://localhost:8080/debug/pprof/goroutine'

pprof ui

通过pprof提供的可视化界面,可以更清晰直观的查看调用链等信息。

  • Top top

  • Graph graph

  • Flame Graph flame

  • Peek peek

  • Source source

  • Disassemble disassemble

参考资源