From 465b4c348480ce792ece2b45b076595b7929432f Mon Sep 17 00:00:00 2001 From: Dieter Plaetinck Date: Wed, 19 Nov 2014 18:01:56 -0500 Subject: [PATCH 1/3] support serving up go-metrics as expvars. fix #68 --- README.md | 13 +++++ exp/exp.go | 148 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 161 insertions(+) create mode 100644 exp/exp.go diff --git a/README.md b/README.md index e0091a4..f1a2678 100644 --- a/README.md +++ b/README.md @@ -90,6 +90,19 @@ import "github.com/rcrowley/go-metrics/stathat" go stathat.Stathat(metrics.DefaultRegistry, 10e9, "example@example.com") ``` +Maintain all metrics along with expvars at `/debug/vars2`: + +This uses the same mechanism as [the official expvar](http://golang.org/pkg/expvar/) +but exposed under `/debug/vars2`, which shows a json representation of all your usual expvars +as well as all your go-metrics. + + +```go +import "github.com/rcrowley/go-metrics/exp" + +exp.Exp(metrics.DefaultRegistry) +``` + Installation ------------ diff --git a/exp/exp.go b/exp/exp.go new file mode 100644 index 0000000..c058981 --- /dev/null +++ b/exp/exp.go @@ -0,0 +1,148 @@ +// Hook go-metrics into expvar +// on any /debug/vars2 request, load all vars from the registry into expvar, and execute regular expvar handler +package exp + +import ( + "expvar" + "fmt" + "github.com/rcrowley/go-metrics" + "net/http" + "sync" +) + +type exp struct { + expvarLock sync.Mutex // expvar panics if you try to register the same var twice, so we must probe it safely + registry metrics.Registry +} + +func (exp *exp) expHandler(w http.ResponseWriter, r *http.Request) { + // load our variables into expvar + exp.syncToExpvar() + + // now just run the official expvar handler code (which is not publicly callable, so pasted inline) + w.Header().Set("Content-Type", "application/json; charset=utf-8") + fmt.Fprintf(w, "{\n") + first := true + expvar.Do(func(kv expvar.KeyValue) { + if !first { + fmt.Fprintf(w, ",\n") + } + first = false + fmt.Fprintf(w, "%q: %s", kv.Key, kv.Value) + }) + fmt.Fprintf(w, "\n}\n") +} + +func Exp(r metrics.Registry) { + e := exp{sync.Mutex{}, r} + // this would cause a panic: + // panic: http: multiple registrations for /debug/vars + // http.HandleFunc("/debug/vars", e.expHandler) + // haven't found an elegant way, so just use a different endpoint + http.HandleFunc("/debug/vars2", e.expHandler) +} + +func (exp *exp) getInt(name string) *expvar.Int { + var v *expvar.Int + exp.expvarLock.Lock() + p := expvar.Get(name) + if p != nil { + v = p.(*expvar.Int) + } else { + v = new(expvar.Int) + expvar.Publish(name, v) + } + exp.expvarLock.Unlock() + return v +} + +func (exp *exp) getFloat(name string) *expvar.Float { + var v *expvar.Float + exp.expvarLock.Lock() + p := expvar.Get(name) + if p != nil { + v = p.(*expvar.Float) + } else { + v = new(expvar.Float) + expvar.Publish(name, v) + } + exp.expvarLock.Unlock() + return v +} + +func (exp *exp) publishCounter(name string, metric metrics.Counter) { + v := exp.getInt(name) + v.Set(metric.Count()) +} + +func (exp *exp) publishGauge(name string, metric metrics.Gauge) { + v := exp.getInt(name) + v.Set(metric.Value()) +} +func (exp *exp) publishGaugeFloat64(name string, metric metrics.GaugeFloat64) { + exp.getFloat(name).Set(metric.Value()) +} + +func (exp *exp) publishHistogram(name string, metric metrics.Histogram) { + h := metric.Snapshot() + ps := h.Percentiles([]float64{0.5, 0.75, 0.95, 0.99, 0.999}) + exp.getInt(name + ".count").Set(h.Count()) + exp.getFloat(name + ".min").Set(float64(h.Min())) + exp.getFloat(name + ".max").Set(float64(h.Max())) + exp.getFloat(name + ".mean").Set(float64(h.Mean())) + exp.getFloat(name + ".std-dev").Set(float64(h.StdDev())) + exp.getFloat(name + ".50-percentile").Set(float64(ps[0])) + exp.getFloat(name + ".75-percentile").Set(float64(ps[1])) + exp.getFloat(name + ".95-percentile").Set(float64(ps[2])) + exp.getFloat(name + ".99-percentile").Set(float64(ps[3])) + exp.getFloat(name + ".999-percentile").Set(float64(ps[4])) +} + +func (exp *exp) publishMeter(name string, metric metrics.Meter) { + m := metric.Snapshot() + exp.getInt(name + ".count").Set(m.Count()) + exp.getFloat(name + ".one-minute").Set(float64(m.Rate1())) + exp.getFloat(name + ".five-minute").Set(float64(m.Rate5())) + exp.getFloat(name + ".fifteen-minute").Set(float64((m.Rate15()))) + exp.getFloat(name + ".mean").Set(float64(m.RateMean())) +} + +func (exp *exp) publishTimer(name string, metric metrics.Timer) { + t := metric.Snapshot() + ps := t.Percentiles([]float64{0.5, 0.75, 0.95, 0.99, 0.999}) + exp.getInt(name + ".count").Set(t.Count()) + exp.getFloat(name + ".min").Set(float64(t.Min())) + exp.getFloat(name + ".max").Set(float64(t.Max())) + exp.getFloat(name + ".mean").Set(float64(t.Mean())) + exp.getFloat(name + ".std-dev").Set(float64(t.StdDev())) + exp.getFloat(name + ".50-percentile").Set(float64(ps[0])) + exp.getFloat(name + ".75-percentile").Set(float64(ps[1])) + exp.getFloat(name + ".95-percentile").Set(float64(ps[2])) + exp.getFloat(name + ".99-percentile").Set(float64(ps[3])) + exp.getFloat(name + ".999-percentile").Set(float64(ps[4])) + exp.getFloat(name + ".one-minute").Set(float64(t.Rate1())) + exp.getFloat(name + ".five-minute").Set(float64(t.Rate5())) + exp.getFloat(name + ".fifteen-minute").Set(float64((t.Rate15()))) + exp.getFloat(name + ".mean-rate").Set(float64(t.RateMean())) +} + +func (exp *exp) syncToExpvar() { + exp.registry.Each(func(name string, i interface{}) { + switch i.(type) { + case metrics.Counter: + exp.publishCounter(name, i.(metrics.Counter)) + case metrics.Gauge: + exp.publishGauge(name, i.(metrics.Gauge)) + case metrics.GaugeFloat64: + exp.publishGaugeFloat64(name, i.(metrics.GaugeFloat64)) + case metrics.Histogram: + exp.publishHistogram(name, i.(metrics.Histogram)) + case metrics.Meter: + exp.publishMeter(name, i.(metrics.Meter)) + case metrics.Timer: + exp.publishTimer(name, i.(metrics.Timer)) + default: + panic("unsupported type for " + name) + } + }) +} From 3a7d7c52a40876a1e3b7c2bbafe20bee23cde9cb Mon Sep 17 00:00:00 2001 From: Dieter Plaetinck Date: Mon, 24 Nov 2014 18:08:10 -0500 Subject: [PATCH 2/3] more useful output --- exp/exp.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exp/exp.go b/exp/exp.go index c058981..3b7ef0b 100644 --- a/exp/exp.go +++ b/exp/exp.go @@ -142,7 +142,7 @@ func (exp *exp) syncToExpvar() { case metrics.Timer: exp.publishTimer(name, i.(metrics.Timer)) default: - panic("unsupported type for " + name) + panic(fmt.Sprintf("unsupported type for '%s': %T", name, i)) } }) } From 7da7ed57785040df39407030c3f8f39747383bed Mon Sep 17 00:00:00 2001 From: Dieter Plaetinck Date: Mon, 30 Nov 2015 16:37:52 +1300 Subject: [PATCH 3/3] use /debug/metrics as endpoint, seems more proper/elegant --- README.md | 4 ++-- exp/exp.go | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index f1a2678..d801aa4 100644 --- a/README.md +++ b/README.md @@ -90,10 +90,10 @@ import "github.com/rcrowley/go-metrics/stathat" go stathat.Stathat(metrics.DefaultRegistry, 10e9, "example@example.com") ``` -Maintain all metrics along with expvars at `/debug/vars2`: +Maintain all metrics along with expvars at `/debug/metrics`: This uses the same mechanism as [the official expvar](http://golang.org/pkg/expvar/) -but exposed under `/debug/vars2`, which shows a json representation of all your usual expvars +but exposed under `/debug/metrics`, which shows a json representation of all your usual expvars as well as all your go-metrics. diff --git a/exp/exp.go b/exp/exp.go index 3b7ef0b..09a496f 100644 --- a/exp/exp.go +++ b/exp/exp.go @@ -1,5 +1,5 @@ // Hook go-metrics into expvar -// on any /debug/vars2 request, load all vars from the registry into expvar, and execute regular expvar handler +// on any /debug/metrics request, load all vars from the registry into expvar, and execute regular expvar handler package exp import ( @@ -39,7 +39,7 @@ func Exp(r metrics.Registry) { // panic: http: multiple registrations for /debug/vars // http.HandleFunc("/debug/vars", e.expHandler) // haven't found an elegant way, so just use a different endpoint - http.HandleFunc("/debug/vars2", e.expHandler) + http.HandleFunc("/debug/metrics", e.expHandler) } func (exp *exp) getInt(name string) *expvar.Int {