Skip to content
This repository was archived by the owner on Apr 1, 2025. It is now read-only.

Commit 51425a2

Browse files
committed
Merge pull request #96 from Dieterbe/expvar
support serving up go-metrics as expvars
2 parents 7839c01 + 7da7ed5 commit 51425a2

File tree

2 files changed

+161
-0
lines changed

2 files changed

+161
-0
lines changed

README.md

+13
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,19 @@ import "github.com/rcrowley/go-metrics/stathat"
103103
go stathat.Stathat(metrics.DefaultRegistry, 10e9, "[email protected]")
104104
```
105105

106+
Maintain all metrics along with expvars at `/debug/metrics`:
107+
108+
This uses the same mechanism as [the official expvar](http://golang.org/pkg/expvar/)
109+
but exposed under `/debug/metrics`, which shows a json representation of all your usual expvars
110+
as well as all your go-metrics.
111+
112+
113+
```go
114+
import "github.com/rcrowley/go-metrics/exp"
115+
116+
exp.Exp(metrics.DefaultRegistry)
117+
```
118+
106119
Installation
107120
------------
108121

exp/exp.go

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

0 commit comments

Comments
 (0)