我们可以使用go tool pprof命令来交互式的访问概要文件的内容。命令将会分析指定的概要文件,并会根据我们的要求为我们提供高可读性的输出信息。
我们可以通过标准库的代码包runtime和runtime/pprof中的程序来生成三种包含实时性数据的概要文件,分别是CPU概要文件、内存概要文件和程序阻塞概要文件
func main() {
f, err := os.OpenFile("./cpu.prof", os.O_RDWR|os.O_CREATE, 0644)
if err != nil {
log.Fatal(err)
}
defer f.Close()
pprof.StartCPUProfile(f)
defer pprof.StopCPUProfile()
for i := 0; i < 1000000; i++ {
fmt.Println(i)
}
//注意,有时候defer f.Close(),defer pprof.StopCPUProfile() 会执行不到,这时候我们就会看到 prof文件是空的,我们需要在自己代码退出的地方,增加上下面两行,确保写文件内容了。
pprof.StopCPUProfile()
f.Close()
}
对产生的文件进行分析:
E:\GoWorks>go tool pprof ./cpu.prof
Entering interactive mode (type "help" for commands)
(pprof) top
45.40s of 45.40s total ( 100%)
flat flat% sum% cum cum%
45.40s 100% 100% 45.40s 100%
(pprof)
内存概要文件用于保存在用户程序执行期间的内存使用情况。这里所说的内存使用情况,其实就是程序运行过程中堆内存的分配情况。Go语言运行时系统会对用户程序运行期间的所有的堆内存分配进行记录。不论在取样的那一时刻、堆内存已用字节数是否有增长,只要有字节被分配且数量足够,分析器就会对其进行取样。开启内存使用情况记录的方式如下:
func startMemProfile() {
if *memProfile != "" && *memProfileRate > 0 {
runtime.MemProfileRate = *memProfileRate
}
}
在函数startMemProfile中,只有在memProfile和memProfileRate的值有效时才会进行后续操作。memProfile的含义是内存概要文件的绝对路径。memProfileRate的含义是分析器的取样间隔,单位是字节。当我们将这个值赋给int类型的变量runtime.MemProfileRate时,就意味着分析器将会在每分配指定的字节数量后对内存使用情况进行取样。实际上,即使我们不给runtime.MemProfileRate变量赋值,内存使用情况的取样操作也会照样进行。此取样操作会从用户程序开始时启动,且一直持续进行到用户程序结束。runtime.MemProfileRate变量的默认值是512 * 1024,即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函数没有返回错误,就说明数据已被写入到了内存概要文件中
func SaveProfile(workDir string, profileName string, ptype string, debug int) {
execFileAbsPath, _ := filepath.Abs(workDir)
if profileName == "" {
profileName = string(ptype)
}
profilePath := filepath.Join(execFileAbsPath, profileName)
f, err := os.Create(profilePath)
if err != nil {
fmt.Fprintf(os.Stderr, "Can not create profile output file: %s", err)
return
}
if err = pprof.Lookup(string(ptype)).WriteTo(f, debug); err != nil {
fmt.Fprintf(os.Stderr, "Can not write %s: %s", profilePath, err)
}
f.Close()
}
func main() {
//SaveProfile("E://GoWorks", "goroutine.prof", "goroutine", 1)
for i := 0; i < 10000; i++ {
fmt.Println(i)
}
}
函数SaveProfile有四个参数。第一个参数是概要文件的存放目录。第二个参数是概要文件的名称。第三个参数是概要文件的类型。其中,类型ProfileType只是为string类型起的一个别名而已。这样是为了对它的值进行限制。它的值必须为“goroutine”、“threadcreate”、“heap”或“block”中的一个。我们现在来重点说一下第四个参数。参数debug控制着概要文件中信息的详细程度。这个参数也就是传给结构体pprof.Profile的指针方法WriteTo的第二个参数。而pprof.Profile结构体的实例的指针由函数pprof.Lookup产生。
如果你的go程序是用http包启动的web服务器,你想查看自己的web服务器的状态。这个时候就可以选择net/http/pprof。你只需要引入包_”net/http/pprof”
go func() {
http.ListenAndServe("localhost:8090", nil)
}()
go run main.go 后就可以在浏览器中使用http://localhost:8080/debug/pprof/直接看到当前web服务的状态,包括CPU占用情况和内存使用情况等,例如: