进入正题前,请允许我打个广告
阿里云2018双11云服务只需99.5元
- 1核2G内存,¥99.5/年
- 2核4G内存,¥545.00/1年
- 2核4G内存,¥927.00/2年
- 2核4G内存,¥1227.00/3年
- 2核8G内存,¥2070.00/3年(本人认为最划算)
直达入口:http://t.cn/EZ14u8r
go1.11 后自带了 pprof 工具,在 $GOPATH/bin 下可找到。
pprof允许收集Go程序CPU、栈和堆信息等,这些信息在后文中,还是沿用官方名词 profile。使用pprof的正常方法是:
import _ "net/http/pprof"
)curl localhost:$PORT/debug/pprof/$PROFILE_TYPE
以保存 profilego tool pprof
分析 profile您也可以使用
pprof
包在代码中生成pprof profile,但我还没有这样做。
以下是我迄今为止在互联网上发现的关于pprof的所有有用链接。基本上互联网上关于pprof的材料似乎是官方文档+ rakyll的惊人博客。
pprof
可以读取perf文件!!)go tool pprof --help
(我在这里将输出粘贴到我的系统上)(可能还有关于pprof的讨论,但是我太急于观看会谈,这也是为什么我写了很多博客文章并且几乎没有谈判的部分原因)
在了解事情如何运作时,我喜欢从头开始。究竟什么是“个人资料”?
好吧,让我们阅读文档吧!我第7次查看运行时/ pprof文档时,我读到了这个非常有用的句子:
profile是堆栈跟踪的集合,显示导致特定事件(例如分配)实例的调用序列。包可以创建和维护自己的profile; 最常见的用途是跟踪必须明确关闭的资源,例如文件或网络连接。
每个profile都有唯一的名称。预定义了一些profile:
goroutine - stack traces of all current goroutines
heap - a sampling of all heap allocations
threadcreate - stack traces that led to the creation of new OS threads
block - stack traces that led to blocking on synchronization primitives
mutex - stack traces of holders of contended mutexes
您可以在默认网络服务器中获取7个地方:上面提到的那些
还有2个:CPU profile和CPU trace。
要分析这些profile(堆栈跟踪列表),要使用的工具是go tool pprof
,这是一组用于可视化堆栈跟踪的工具。
超级混乱的注意事项:跟踪端点(/debug/pprof/trace?seconds=5
)与其他所有内容不同,输出的文件不是 pprof profile。相反,它是一个跟踪,您可以使用go tool trace
(而不是go tool pprof
)查看它。
您可以在浏览器中看到http:// localhost:6060 / debug / pprof /的可用profile。除非它没有告诉你/debug/pprof/profile
或/debug/pprof/trace
由于某种原因。
所有这些类型的profile(goroutine,堆分配等)都只是堆栈跟踪的集合,可能附加了一些元数据。如果我们看一下pprof protobuf定义,你会发现一个profile主要是一堆Sample
s。
样本基本上是堆栈跟踪。堆栈跟踪可能附加了一些额外的信息!例如,在堆profile中,堆栈跟踪具有附加到其上的多个字节的内存。我认为样本是概要中最重要的部分。
我们稍后将解构pprof文件中究竟是什么,但是现在让我们首先做一个分析堆profile的快速示例!
我现在最感兴趣的是调试内存问题。所以我决定编写一个程序,用pprof分配一堆内存来profile。
func main() {
// we need a webserver to get the pprof webserver
go func() {
log.Println(http.ListenAndServe("localhost:6060", nil))
}()
fmt.Println("hello world")
var wg sync.WaitGroup
wg.Add(1)
go leakyFunction(wg)
wg.Wait()
}
func leakyFunction(wg sync.WaitGroup) {
defer wg.Done()
s := make([]string, 3)
for i:= 0; i < 10000000; i++{
s = append(s, "magical pandas")
if (i % 100000) == 0 {
time.Sleep(500 * time.Millisecond)
}
}
}
基本上这只是启动一个goroutine leakyFunction
,分配一堆内存,然后最终退出。
获取此程序的堆profile非常简单 - 我们只需运行即可go tool pprof http://localhost:6060/debug/pprof/heap
。这使我们进入了一个我们运行的交互模式top
$ go tool pprof http://localhost:6060/debug/pprof/heap
Fetching profile from http://localhost:6060/debug/pprof/heap
Saved profile in /home/bork/pprof/pprof.localhost:6060.inuse_objects.inuse_space.004.pb.gz
Entering interactive mode (type "help" for commands)
(pprof) top
34416.04kB of 34416.04kB total ( 100%)
Showing top 10 nodes out of 16 (cum >= 512.04kB)
flat flat% sum% cum cum%
33904kB 98.51% 98.51% 33904kB 98.51% main.leakyFunction
我也可以在交互模式之外做同样的事情go tool pprof -top http://localhost:6060/debug/pprof/heap
。
这基本上告诉我们main.leakyFunction
使用339MB的内存。整齐!
我们还可以生成这样的PNGprofile:go tool pprof -png http://localhost:6060/debug/pprof/heap > out.png
。
这就是它的样子(我在不同的时间运行它,所以它只使用100MB内存)。
这并不复杂,但对我来说也不是100%明显。堆profile中的堆栈跟踪是分配时的堆栈跟踪。
因此,堆profile中的堆栈跟踪可能适用于不再运行的代码 - 例如,可能是一个分配了一堆内存,返回的函数,以及应该释放内存不正常的不同函数。因此,内存泄漏的责任函数可能与堆profile中列出的函数完全不同。
go tool pprof可以选择显示分配计数或使用内存。如果你关心的内存量被使用,你可能想的INUSE指标,但如果你担心垃圾收集所花费的时间,看一下分配!
-inuse_space Display in-use memory size
-inuse_objects Display in-use object counts
-alloc_space Display allocated memory size
-alloc_objects Display allocated object counts
我最初对此作品感到困惑 - 已经收集了个人资料!事后我怎么能做出这个选择呢?我认为堆profile的工作原理是 - 以某种采样率记录分配。然后每次释放其中一个分配时,也会记录下来。因此,您可以获得某些内存活动样本的分配和释放历史记录。然后,当分析您的内存使用情况时,您可以决定在哪里使用内存或总分配计数!
您可以在此处阅读内存分析器的源代码:https://golang.org/src/runtime/mprof.go。它有很多有用的评论!例如,以下是有关设置采样率的注释:
// MemProfileRate controls the fraction of memory allocations
// that are recorded and reported in the memory profile.
// The profiler aims to sample an average of
// one allocation per MemProfileRate bytes allocated.
// To include every allocated block in the profile, set MemProfileRate to 1.
// To turn off profiling entirely, set MemProfileRate to 0.
// The tools that process the memory profiles assume that the
// profile rate is constant across the lifetime of the program
// and equal to the current value. Programs that change the
// memory profiling rate should do so just once, as early as
// possible in the execution of the program (for example,
// at the beginning of main).
当我开始使用pprof时,我对实际发生的事情感到困惑。它生成了这些名为like的堆profilepprof.localhost:6060.inuse_objects.inuse_space.004.pb.gz
- 这是什么?我怎样才能看到内容?
好吧,我们来看看!! 我写了一个更简单的Go程序来获得最简单的堆profile。
package main
import "runtime"
import "runtime/pprof"
import "os"
import "time"
func main() {
go leakyFunction()
time.Sleep(500 * time.Millisecond)
f, _ := os.Create("/tmp/profile.pb.gz")
defer f.Close()
runtime.GC()
pprof.WriteHeapProfile(f);
}
func leakyFunction() {
s := make([]string, 3)
for i:= 0; i < 10000000; i++{
s = append(s, "magical pprof time")
}
}
该程序只分配一些内存,写入堆profile,然后退出。很简单。我们来看看这个文件/tmp/profile.pb.gz
吧!你可以profile.pb
在这里下载一个gunzipped版本:profile.pb。我使用这些指示安装了protoc 。
profile.pb
是一个protobuf文件,事实证明你可以使用protoc
protobuf编译器查看protobuf文件。
go get github.com/google/pprof/proto
protoc --decode=perftools.profiles.Profile $GOPATH/src/github.com/google/pprof/proto/profile.proto --proto_path $GOPATH/src/github.com/google/pprof/proto/
这个输出有点长,你可以在这里查看:输出。
以下是此profile文件中的内容摘要!这包含1个样本。样本是堆栈跟踪,此堆栈跟踪有2个位置:1和2.什么是位置1和2?它们对应于映射1和2,映射又对应于文件名7和8。
如果我们查看字符串表,我们会看到文件名7和8是这两个:
string_table: "/home/bork/work/experiments/golang-pprof/leak_simplest"
string_table: "[vdso]"
sample {
location_id: 1
location_id: 2
value: 1
value: 34717696
value: 1
value: 34717696
}
mapping {
id: 1
memory_start: 4194304
memory_limit: 5066752
filename: 7
}
mapping {
id: 2
memory_start: 140720922800128
memory_limit: 140720922808320
filename: 8
}
location {
id: 1
mapping_id: 1
address: 5065747
}
location {
id: 2
mapping_id: 1
address: 4519969
}
string_table: ""
string_table: "alloc_objects"
string_table: "count"
string_table: "alloc_space"
string_table: "bytes"
string_table: "inuse_objects"
string_table: "inuse_space"
string_table: "/home/bork/work/experiments/golang-pprof/leak_simplest"
string_table: "[vdso]"
string_table: "[vsyscall]"
string_table: "space"
time_nanos: 1506268926947477256
period_type {
type: 10
unit: 4
}
period: 524288
关于这个pprof文件的一个有趣的事情profile.pb
是它不包含我们正在运行的函数的名称!但如果我运行go tool pprof
它,它会打印出泄漏函数的名称。你是怎么做到的,go tool pprof
?!
go tool pprof -top profile.pb
59.59MB of 59.59MB total ( 100%)
flat flat% sum% cum cum%
59.59MB 100% 100% 59.59MB 100% main.leakyFunction
0 0% 100% 59.59MB 100% runtime.goexit
我用strace回答了这个问题,很明显 - 我stra go tool pprof
and这就是我所看到的:
5015 openat(AT_FDCWD, "/home/bork/pprof/binaries/leak_simplest", O_RDONLY|O_CLOEXEC
5015 openat(AT_FDCWD, "/home/bork/work/experiments/golang-pprof/leak_simplest", O_RDONLY|O_CLOEXEC) = 3
因此,似乎go tool pprof
注意到文件名profile.pb
是/ home / bork / work / experiments / golang -pprof / leak_simplest,然后它只是在我的计算机上打开该文件并使用它来获取函数名称。整齐!
您也可以将二进制文件传递给go tool pprof
喜欢go tool pprof -out $BINARY_FILE myprofile.pb.gz
。有时pprof文件包含函数名称,有时它们没有,我还没弄清楚是什么决定了它。
我也发现,感谢rakyll等人的出色工作,pprof不断变得更好!例如,有这个拉取请求https://github.com/google/pprof/pull/188正在正确使用,它为pprof web界面添加了火焰图支持。火焰图是宇宙中最好的东西,所以我很高兴能够获得它。