“A Journey With Go” 专属插图,由 Renee French 根据原始 Go Gopher 制作。
:information_source: 本文基于 Go 1.13.
go test 命令提供了许多出色的功能,比如代码覆盖率,CPU 和 内存分析。要提供这些统计信息,Go 就需要一种方式来跟踪 CPU 使用率,或在代码覆盖中跟踪一个函数何时被用到。
Go 使用多种方式来产生这些统计信息:
我们来写一个简单的程序并回顾所有内容。这是我们在后面的章节将使用的代码:
package main
import "math/rand"
func main() {
\tprintln(run())
}
//go:noinline
func run() int {
\ta := 0
\tfor i:= 0; i < rand.Intn(100000); i++ {
\t\tif i % 2 == 0 {
\t\t\tadd(&a)
\t\t} else {
\t\t\tsub(&a)
\t\t}
\t}
\treturn a
}
//go:noinline
func add(a *int) {
\t*a += rand.Intn(10)
}
//go:noinline
func sub(a *int) {
\t*a -= rand.Intn(10)
}
main.go 托管在 [GitHub] (https://github.com/) 查看
通过 GOSSAFUNC=run Go test -cover 命令生成的 SSA 代码,我们可以查看 Go 对程序进行了什么样的修改:
变量 GoCover_0_313837343662366134383538 是一个标志数组,其中每个键是一个代码块,当代码实际进入这一块时对应的标志设置为 1.
你可以在我的文章 “Go: Compiler Phases” 中找到更多关于 SSA 的信息。
生成的代码将稍后在管理代码覆盖率报告的函数中使用。 我们可以通过使用 objdump 命令反汇编代码覆盖期间生成的目标文件来进行验证。 运行 go test -cover -o main.o && Go tool objdump main.go 将反汇编代码并显示缺少的部分。 它首先初始化并在自动生成的 init 函数中注册 coverage:
test.go 添加的 init 方法
然后,如前所述,测试将在执行期间收集覆盖率数据并且会触发一个方法来实际写入和显示覆盖率:
go test 调用的 after 函数
跟踪 CPU 使用率的策略则有所不同。Go 会停止程序并收集正在运行程序的样本。这里是未开启 CPU 分析的代码的 trace:
这里是相同代码开启了 CPU 分析的 trace:
增加的 trace 与 pprof 及性能分析相关。这里是其中一个的放大图:
profileWriter 方法将循环调用,每 100 毫秒收集 CPU 数据,以在性能分析结束时最终生成报告。
内存分析包含在源码中,并已集成在内存分配系统中。在使用 -memprofile 开启内存分析 的情况下,位于 malloc.go 中的内存分配器,将 对已分配的内存进行分析 。这里,依然可以通过反汇编代码进行验证。这里是内存分配器的使用:
开启了内存分配分析