Go 语言编程 — Profiling 和 Debug

目录

Profiling

Golang 提供了友好的工程化支持,其中之一就是 Profiling(分析)工具。例如:Golang 自带的 runtime 包,就可以轻松获取程序运行期间的各种内存或 CPU 的使用状态信息。

runtime

MemStat

查看内存使用情况:

package main

import (
	"fmt"
	"runtime"
)

func main() {
	var m runtime.MemStats
	runtime.ReadMemStats(&m)
	fmt.Println(m.Alloc, m.HeapAlloc, m.HeapObjects, m.Frees)
}
  • Alloc/HeapAlloc:表示当前 heap 的大小。
  • HeapObjects:表示当前 heap 中有多少个对象。
  • Frees:表示一共释放过多少的对象。程序可能会出现当前内存使用已经降下来了但过去某个时间曾经升高过的情况。

GC

// disable gc when start
GOGC=off go run main.go
 

// disable gc and manually trigger gc
debug.SetGCPercent(-1)
runtime.GC()

查看 GC 状态:

package main

import (
    "fmt"
    "runtime/debug"
)

func main() {
    var d debug.GCStats
    debug.ReadGCStats(&d)
    fmt.Println(d.LastGC, d.NumGC, d.PauseTotal)
}
  • LastGC:表示上次 GC 时间。
  • NumGC:表示一共 GC 了过多少次。
  • PauseTotal:总暂停时间以及 Pause 暂停历史。

pprof

pprof 可以查看程序运行过程中更加详细的 CPU、Mem 损耗信息:

package main

import (
	"os"
	"runtime/pprof"
)

func main() {
    // CPU
	pprof.StartCPUProfile(os.Stdout)
	defer pprof.StopCPUProfile()
}

输出 CPU Profile 文件并查看:

go run main.go > cpu.profile
go tool pprof cpu.profile

打印内存使用信息:

package main

import (
	"os"
	"runtime/pprof"
)

func main() {
    // Mem
	pprof.WriteHeapProfile(os.Stdout)
}

输出 Mem Profile 文件并查看:

go run main.go > memory.profile
go tool pprof memory.profile

通常 memory.profile 记录的是当前的内存使用情况,有些时候并不适合 DEBUG,如果想知道全部的分配情况:

go tool pprof --alloc_space memory.profile

在一些问题原因不明确也不太好复现的场景中,上面输出 memory 和 cpu profile 的情况有些时候并不那么实用,这个时候一方面我们可以结合上面的 MemStats 使用,如果达到某个值就输出一份 profile,或者直接使用下面的通过 Web UI 把 profile 信息实时输出:

package main

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

func main() {
	http.ListenAndServe("localhost:8082", nil)
}

浏览器访问:http://localhost:8082/debug/pprof/
在这里插入图片描述

  • allocs:与 --alloc_space 是一样的,同时还可以查看到 MemStats。
  • block:没有在运行的时间,例如:等待 channel、等待 mutex 等。
  • cmdline:当前程序的启动命令。
  • goroutine:Goroutine 的信息。
  • heap:与 Memory Profle 一样。
  • mutex:Stack traces of holders of contended mutexes.
  • profile:与 CPU Profile 一样。
  • threadcreate:线程。
  • trace:A trace of execution of the current program. You can specify the duration in the seconds GET parameter. After you get the trace file, use the go tool trace command to investigate the trace.

依旧可以使用工具来查看:

go tool pprof --alloc_space http://localhost:8082/debug/pprof/heap

trace

trace 和 pprof 的区别在于两者关注的维度不同,后者更关注代码栈层面,而 trace 更关注于 latency(延迟)。

比如说一个请求在客户端观察从发送到完成经过了 5s,做 profile 可能发现这个请求的 CPU 时间只有 2s,那剩下的 3s 就不是很清楚了, profile 更侧重的是我们代码执行了多久,至于其他的,例如:网络 IO,系统调用,Goroutine 调度,GC 时间等,很难反映出来。这是就应该使用 trace 了。

生成 trace:

package main

import (
	_ "net/http/pprof"
	"os"
	"runtime/trace"
)

func main() {
	trace.Start(os.Stdout)
	defer trace.Stop()
}

或者上面的 pprof Web UI 也支持下载 trace 文件。

$ go tool trace app.trace
2020/07/17 17:57:17 Parsing trace...
2020/07/17 17:57:17 Splitting trace...
2020/07/17 17:57:17 Opening browser. Trace viewer is listening on http://127.0.0.1:51252

在 trace 上可以清楚的看到每个 Goroutine 的起始,怎么生成的,每个 CPU 内核在做什么这些。

在这里插入图片描述

DEBUG

Golang 支持使用 GDB 来进行调试,这与 C 语言几乎是一致的。但 GDB 是一个通用性调试工具,不非常能直接地反映出 Golang 的特点,例如:goroutine 语句的调试。

delve

所以也推荐使用 delve(https://github.com/go-delve/delve),可以理解为是 Golang 版本的 GDB,使用方式大致与 GBD 一致。

安装:

go get -u github.com/derekparker/delve/cmd/dlv

测试程序:

package main

import (
	"fmt"
	"log"
	"net/http"
	"os"
)

const port = "8000"

func hi(w http.ResponseWriter, r *http.Request) {
	hostname, _ := os.Hostname()
	fmt.Fprintf(w, "HostName: %s", hostname)
}

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

	fmt.Println("runing on port: " + port)
	log.Fatal(http.ListenAndServe(":"+port, nil))
}

进入调试环境:

$ dlv debug ./main.go
Type 'help' for list of commands.
(dlv)

MAC pro 可能需要验证:
在这里插入图片描述

$ dlv debug ./main.go
Type 'help' for list of commands.
(dlv) b hi
Breakpoint 1 set at 0x135a078 for main.hi() ./main.go:12
(dlv) c
runing on port: 8000
> main.hi() ./main.go:12 (hits goroutine(6):1 total:1) (PC: 0x135a078)
Warning: listing may not match stale executable
     7:		"os"
     8:	)
     9:
    10:	const port = "8000"
    11:
=>  12:	func hi(w http.ResponseWriter, r *http.Request) {
    13:		hostname, _ := os.Hostname()
    14:		fmt.Fprintf(w, "HostName: %s", hostname)
    15:	}
    16:
    17:	func main() {
(dlv) p w

常用选型:

  • b:设置断点。
  • n:执行到下一行。
  • s:单步执行。
  • p:输出变量值。
  • args:打印所有参数信息。
  • locals:打印所有本地变量。

挂载(Attach)形式的调试

dlv attach <pid>

参考文章

https://mp.weixin.qq.com/s/rydO2JK-r8JjG9v_Uy7gXg

猜你喜欢

转载自blog.csdn.net/Jmilk/article/details/107413101