« Back to Index

Runtime Profiler

View original Gist on GitHub

Tags: #go #performance

Runtime Profiler.go

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))
}