1// Hook go-metrics into expvar
2// on any /debug/metrics request, load all vars from the registry into expvar, and execute regular expvar handler
3package exp
4
5import (
6	"expvar"
7	"fmt"
8	"net/http"
9	"sync"
10
11	"github.com/rcrowley/go-metrics"
12)
13
14type exp struct {
15	expvarLock sync.Mutex // expvar panics if you try to register the same var twice, so we must probe it safely
16	registry   metrics.Registry
17}
18
19func (exp *exp) expHandler(w http.ResponseWriter, r *http.Request) {
20	// load our variables into expvar
21	exp.syncToExpvar()
22
23	// now just run the official expvar handler code (which is not publicly callable, so pasted inline)
24	w.Header().Set("Content-Type", "application/json; charset=utf-8")
25	fmt.Fprintf(w, "{\n")
26	first := true
27	expvar.Do(func(kv expvar.KeyValue) {
28		if !first {
29			fmt.Fprintf(w, ",\n")
30		}
31		first = false
32		fmt.Fprintf(w, "%q: %s", kv.Key, kv.Value)
33	})
34	fmt.Fprintf(w, "\n}\n")
35}
36
37// Exp will register an expvar powered metrics handler with http.DefaultServeMux on "/debug/vars"
38func Exp(r metrics.Registry) {
39	h := ExpHandler(r)
40	// this would cause a panic:
41	// panic: http: multiple registrations for /debug/vars
42	// http.HandleFunc("/debug/vars", e.expHandler)
43	// haven't found an elegant way, so just use a different endpoint
44	http.Handle("/debug/metrics", h)
45}
46
47// ExpHandler will return an expvar powered metrics handler.
48func ExpHandler(r metrics.Registry) http.Handler {
49	e := exp{sync.Mutex{}, r}
50	return http.HandlerFunc(e.expHandler)
51}
52
53func (exp *exp) getInt(name string) *expvar.Int {
54	var v *expvar.Int
55	exp.expvarLock.Lock()
56	p := expvar.Get(name)
57	if p != nil {
58		v = p.(*expvar.Int)
59	} else {
60		v = new(expvar.Int)
61		expvar.Publish(name, v)
62	}
63	exp.expvarLock.Unlock()
64	return v
65}
66
67func (exp *exp) getFloat(name string) *expvar.Float {
68	var v *expvar.Float
69	exp.expvarLock.Lock()
70	p := expvar.Get(name)
71	if p != nil {
72		v = p.(*expvar.Float)
73	} else {
74		v = new(expvar.Float)
75		expvar.Publish(name, v)
76	}
77	exp.expvarLock.Unlock()
78	return v
79}
80
81func (exp *exp) publishCounter(name string, metric metrics.Counter) {
82	v := exp.getInt(name)
83	v.Set(metric.Count())
84}
85
86func (exp *exp) publishGauge(name string, metric metrics.Gauge) {
87	v := exp.getInt(name)
88	v.Set(metric.Value())
89}
90func (exp *exp) publishGaugeFloat64(name string, metric metrics.GaugeFloat64) {
91	exp.getFloat(name).Set(metric.Value())
92}
93
94func (exp *exp) publishHistogram(name string, metric metrics.Histogram) {
95	h := metric.Snapshot()
96	ps := h.Percentiles([]float64{0.5, 0.75, 0.95, 0.99, 0.999})
97	exp.getInt(name + ".count").Set(h.Count())
98	exp.getFloat(name + ".min").Set(float64(h.Min()))
99	exp.getFloat(name + ".max").Set(float64(h.Max()))
100	exp.getFloat(name + ".mean").Set(float64(h.Mean()))
101	exp.getFloat(name + ".std-dev").Set(float64(h.StdDev()))
102	exp.getFloat(name + ".50-percentile").Set(float64(ps[0]))
103	exp.getFloat(name + ".75-percentile").Set(float64(ps[1]))
104	exp.getFloat(name + ".95-percentile").Set(float64(ps[2]))
105	exp.getFloat(name + ".99-percentile").Set(float64(ps[3]))
106	exp.getFloat(name + ".999-percentile").Set(float64(ps[4]))
107}
108
109func (exp *exp) publishMeter(name string, metric metrics.Meter) {
110	m := metric.Snapshot()
111	exp.getInt(name + ".count").Set(m.Count())
112	exp.getFloat(name + ".one-minute").Set(float64(m.Rate1()))
113	exp.getFloat(name + ".five-minute").Set(float64(m.Rate5()))
114	exp.getFloat(name + ".fifteen-minute").Set(float64((m.Rate15())))
115	exp.getFloat(name + ".mean").Set(float64(m.RateMean()))
116}
117
118func (exp *exp) publishTimer(name string, metric metrics.Timer) {
119	t := metric.Snapshot()
120	ps := t.Percentiles([]float64{0.5, 0.75, 0.95, 0.99, 0.999})
121	exp.getInt(name + ".count").Set(t.Count())
122	exp.getFloat(name + ".min").Set(float64(t.Min()))
123	exp.getFloat(name + ".max").Set(float64(t.Max()))
124	exp.getFloat(name + ".mean").Set(float64(t.Mean()))
125	exp.getFloat(name + ".std-dev").Set(float64(t.StdDev()))
126	exp.getFloat(name + ".50-percentile").Set(float64(ps[0]))
127	exp.getFloat(name + ".75-percentile").Set(float64(ps[1]))
128	exp.getFloat(name + ".95-percentile").Set(float64(ps[2]))
129	exp.getFloat(name + ".99-percentile").Set(float64(ps[3]))
130	exp.getFloat(name + ".999-percentile").Set(float64(ps[4]))
131	exp.getFloat(name + ".one-minute").Set(float64(t.Rate1()))
132	exp.getFloat(name + ".five-minute").Set(float64(t.Rate5()))
133	exp.getFloat(name + ".fifteen-minute").Set(float64((t.Rate15())))
134	exp.getFloat(name + ".mean-rate").Set(float64(t.RateMean()))
135}
136
137func (exp *exp) syncToExpvar() {
138	exp.registry.Each(func(name string, i interface{}) {
139		switch i.(type) {
140		case metrics.Counter:
141			exp.publishCounter(name, i.(metrics.Counter))
142		case metrics.Gauge:
143			exp.publishGauge(name, i.(metrics.Gauge))
144		case metrics.GaugeFloat64:
145			exp.publishGaugeFloat64(name, i.(metrics.GaugeFloat64))
146		case metrics.Histogram:
147			exp.publishHistogram(name, i.(metrics.Histogram))
148		case metrics.Meter:
149			exp.publishMeter(name, i.(metrics.Meter))
150		case metrics.Timer:
151			exp.publishTimer(name, i.(metrics.Timer))
152		default:
153			panic(fmt.Sprintf("unsupported type for '%s': %T", name, i))
154		}
155	})
156}
157