Tags: #go #performance
package main
import (
"runtime"
"time"
"buzzfeed/metrics"
)
type starter interface {
Start() error
}
type stopper interface {
Stop() error
}
// Profiler is a type which emits metrics to a metrics.Writer for memory metrics
// relating to the garbage collector and overall resource usage.
type Profiler interface {
starter
stopper
}
// NewProfiler creates a new Profiler that records metrics on an interval
func NewProfiler(metricWriter metrics.Writer, interval time.Duration) Profiler {
return &profiler{
metricWriter: metricWriter,
interval: interval,
ch: make(chan struct{}),
}
}
type profiler struct {
metricWriter metrics.Writer
interval time.Duration
ch chan struct{}
}
func (p *profiler) Start() error {
go p.profilerLoop()
return nil
}
func (p *profiler) Stop() error {
close(p.ch)
return nil
}
func (p *profiler) profilerLoop() {
ticker := time.NewTicker(p.interval)
for {
select {
case <-p.ch:
ticker.Stop()
return
case <-ticker.C:
p.emitStats()
}
}
}
func (p *profiler) emitStats() {
// See the MemStats docs for the meaning of the various stats recorded
// below:
//
// https://golang.org/pkg/runtime/#MemStats
m := &runtime.MemStats{}
runtime.ReadMemStats(m)
p.gauge("runtime.goroutines", uint64(runtime.NumGoroutine()))
p.gauge("runtime.cgo_calls", uint64(runtime.NumCgoCall()))
// General
p.gauge("runtime.mem.alloc_bytes", m.Alloc)
p.gauge("runtime.mem.alloc_bytes_total", m.TotalAlloc)
p.gauge("runtime.mem.sys_bytes", m.Sys)
p.gauge("runtime.mem.pointer_lookup_count", m.Lookups)
p.gauge("runtime.mem.malloc_count_total", m.Mallocs)
p.gauge("runtime.mem.free_count_total", m.Frees)
// Heap
p.gauge("runtime.mem.heap.alloc_bytes", m.HeapAlloc)
p.gauge("runtime.mem.heap.sys_bytes", m.HeapSys)
p.gauge("runtime.mem.heap.idle_bytes", m.HeapIdle)
p.gauge("runtime.mem.heap.inuse_bytes", m.HeapInuse)
p.gauge("runtime.mem.heap.released_bytes", m.HeapReleased)
p.gauge("runtime.mem.heap.object_count", m.HeapObjects)
// Stack
p.gauge("runtime.mem.stack.inuse_bytes", m.StackInuse)
p.gauge("runtime.mem.stack.sys_bytes", m.StackSys)
p.gauge("runtime.mem.stack.mspan_inuse_bytes", m.MSpanInuse)
p.gauge("runtime.mem.stack.mspan_sys_bytes", m.MSpanSys)
p.gauge("runtime.mem.stack.mcache_inuse_bytes", m.MCacheInuse)
p.gauge("runtime.mem.stack.mcache_sys_bytes", m.MCacheSys)
// Garbage Collection
p.gauge("runtime.gc.sys_metadata_bytes", m.GCSys)
p.gauge("runtime.gc.next_target_heap_bytes", m.NextGC)
p.gauge("runtime.gc.last_finished_ts", m.LastGC)
p.gauge("runtime.gc.pause_total_ns", m.PauseTotalNs)
p.gauge("runtime.gc.pause_ns", m.PauseNs[(m.NumGC+255)%256])
p.gauge("runtime.gc.count", uint64(m.NumGC))
p.metricWriter.Gauge("runtime.gc.cpu_use", m.GCCPUFraction)
}
func (p *profiler) gauge(key string, val uint64) {
p.metricWriter.Gauge(key, float64(val))
}