说明:本文测试环境 go version go1.6.2 darwin/amd64
本地开发环境的监控
如果是本地开发环境, 可以利用 GODEBUG=gctrace=1 /path/to/binary
的方式输出 GC 信息,然后用 gcvis 作可视化。
GODEBUG=gctrace=1 会输出如下格式的信息(输出到 stderr):
1 | gc 1 @17.471s 0%: 0.22+4.4+0.19 ms clock, 0.66+0/1.8/10+0.57 ms cpu, 4->4->2 MB, 5 MB goal, 4 P |
关于其资料,可以查阅:https://godoc.org/runtime ,这里引用下:
1 | gctrace: setting gctrace=1 causes the garbage collector to emit a single line to standard |
为了方便对应上字段,我简单标记了一下:
GODEBUG=gctrace=1
对应的实现在 src/runtime/mheap.go
(go 1.6)
据说目前是每两分钟
进行一次 GC forced, 关于其定义,我估计是下面这个,但是不太确定:
1 | runtime/proc.go |
从下图来看,还真是每两分钟一次 force GC 。
gcvis 的原理很简单, 就是逐行解析目标程序的 GC 输出,然后用正则匹配相关的数据,然后生成 JSON 数据,另外也会起一个协程开启 HTTP 服务,用于图表展示。
gcvis 主要有两种用法:
1、 直接运行
1 | gcvis /path/to/binary |
2、 管道重定向方式(standard error)
1 | GODEBUG=gctrace=1 /path/to/binary |& gcvis |
gcvis 的图标输出效果如下:
线上环境的监控
上面这种是不修改一行代码的情况下,完全使用外部工具/参数,无侵入式的 GC 监控。
另一种办法是直接读取 runtime.MemStats
(runtime/mstats.go) 的内容。其实上面这种办法也是读取了 runtime.memstats
(跟 runtime.MemStats
是同一个东西,一个对内,一个对外)。这也意味着要修改我们的程序代码。
我逛了一圈,发现不少人也是这么干的。
- NSQ 对 GC 监控 https://github.com/nsqio/nsq/blob/master/nsqd/statsd.go#L117
- beego 对 GC 的监控: https://github.com/astaxie/beego/blob/master/toolbox/profile.go#L96
- Go port of Coda Hale’s Metrics library https://github.com/rcrowley/go-metrics
- A Golang library for exporting performance and runtime metrics to external metrics systems (i.e. statsite, statsd)
https://github.com/armon/go-metrics/
总之,都是读取了 runtime.MemStats
,然后定时发往一些时序数据库之类的,然后展示。
代码基本都是这样:
1 | memStats := &runtime.MemStats{} |
如果希望获取 gcstats:
1 | gcstats := &debug.GCStats{PauseQuantiles: make([]time.Duration, 100)} |
如果你用了 open-falcon
作为监控工具的话,还可以用 github.com/niean/goperfcounter, 配置一下即可使用。
{
"bases": [“runtime”, “debug”], // 分别对应 runtime.MemStats, debug.GCStats
}
如果读者看过 ReadMemStats
的实现的话,应该知道里面调用了 stopTheWorld
。 卧槽,这会不会出事啊!
Russ Cox 说,
We use ReadMemStats internally at Google. I am not sure of the period but it’s something like what you’re talking about (maybe up to once a minute, I forget).
Stopping the world is not a huge problem; stopping the world for a long time is. ReadMemStats stops the world for only a fixed, very short amount of time. So calling it every 10-20 seconds should be fine.
Don’t take my word for it: measure how long it takes and decide whether you’re willing to give up that much of every 10-20 seconds. I expect it would be under 1/1000th of that time (10 ms).
refer: https://groups.google.com/forum/#!searchin/golang-nuts/ReadMemStats/golang-nuts/mTnw5k4pZdo/rpK69Fns2MsJ
另外, https://github.com/rcrowley/go-metrics 也提到了(go-metrics/runtime.go L:68)
runtime.ReadMemStats(&memStats) // This takes 50-200us.
我觉得一般业务,只要对性能没有很变态的要求,1毫秒内都还能接受吧,也看你读取的频率有多高。
由于每家公司使用的监控系统大相径庭,很难有大一统的解决办法,所以本文只是提供思路以及不严谨的考证。
祝大家玩的开心!
小结:
- 基本都是围绕 runtime.MemStats 做文章;
- 没事多看看 runtime 代码,加深理解。