Ver código fonte

support serving up go-metrics as expvars. fix #68

Dieter Plaetinck 11 anos atrás
pai
commit
465b4c3484
2 arquivos alterados com 161 adições e 0 exclusões
  1. 13 0
      README.md
  2. 148 0
      exp/exp.go

+ 13 - 0
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
 ------------
 

+ 148 - 0
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)
+		}
+	})
+}