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