github.com/theQRL/go-zond@v0.1.1/metrics/exp/exp.go (about) 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 "net/http" 9 "sync" 10 11 "github.com/theQRL/go-zond/log" 12 "github.com/theQRL/go-zond/metrics" 13 "github.com/theQRL/go-zond/metrics/prometheus" 14 ) 15 16 type exp struct { 17 expvarLock sync.Mutex // expvar panics if you try to register the same var twice, so we must probe it safely 18 registry metrics.Registry 19 } 20 21 func (exp *exp) expHandler(w http.ResponseWriter, r *http.Request) { 22 // load our variables into expvar 23 exp.syncToExpvar() 24 25 // now just run the official expvar handler code (which is not publicly callable, so pasted inline) 26 w.Header().Set("Content-Type", "application/json; charset=utf-8") 27 fmt.Fprintf(w, "{\n") 28 first := true 29 expvar.Do(func(kv expvar.KeyValue) { 30 if !first { 31 fmt.Fprintf(w, ",\n") 32 } 33 first = false 34 fmt.Fprintf(w, "%q: %s", kv.Key, kv.Value) 35 }) 36 fmt.Fprintf(w, "\n}\n") 37 } 38 39 // Exp will register an expvar powered metrics handler with http.DefaultServeMux on "/debug/vars" 40 func Exp(r metrics.Registry) { 41 h := ExpHandler(r) 42 // this would cause a panic: 43 // panic: http: multiple registrations for /debug/vars 44 // http.HandleFunc("/debug/vars", e.expHandler) 45 // haven't found an elegant way, so just use a different endpoint 46 http.Handle("/debug/metrics", h) 47 http.Handle("/debug/metrics/prometheus", prometheus.Handler(r)) 48 } 49 50 // ExpHandler will return an expvar powered metrics handler. 51 func ExpHandler(r metrics.Registry) http.Handler { 52 e := exp{sync.Mutex{}, r} 53 return http.HandlerFunc(e.expHandler) 54 } 55 56 // Setup starts a dedicated metrics server at the given address. 57 // This function enables metrics reporting separate from pprof. 58 func Setup(address string) { 59 m := http.NewServeMux() 60 m.Handle("/debug/metrics", ExpHandler(metrics.DefaultRegistry)) 61 m.Handle("/debug/metrics/prometheus", prometheus.Handler(metrics.DefaultRegistry)) 62 log.Info("Starting metrics server", "addr", fmt.Sprintf("http://%s/debug/metrics", address)) 63 go func() { 64 if err := http.ListenAndServe(address, m); err != nil { 65 log.Error("Failure in running metrics server", "err", err) 66 } 67 }() 68 } 69 70 func (exp *exp) getInt(name string) *expvar.Int { 71 var v *expvar.Int 72 exp.expvarLock.Lock() 73 p := expvar.Get(name) 74 if p != nil { 75 v = p.(*expvar.Int) 76 } else { 77 v = new(expvar.Int) 78 expvar.Publish(name, v) 79 } 80 exp.expvarLock.Unlock() 81 return v 82 } 83 84 func (exp *exp) getFloat(name string) *expvar.Float { 85 var v *expvar.Float 86 exp.expvarLock.Lock() 87 p := expvar.Get(name) 88 if p != nil { 89 v = p.(*expvar.Float) 90 } else { 91 v = new(expvar.Float) 92 expvar.Publish(name, v) 93 } 94 exp.expvarLock.Unlock() 95 return v 96 } 97 98 func (exp *exp) getInfo(name string) *expvar.String { 99 var v *expvar.String 100 exp.expvarLock.Lock() 101 p := expvar.Get(name) 102 if p != nil { 103 v = p.(*expvar.String) 104 } else { 105 v = new(expvar.String) 106 expvar.Publish(name, v) 107 } 108 exp.expvarLock.Unlock() 109 return v 110 } 111 112 func (exp *exp) publishCounter(name string, metric metrics.CounterSnapshot) { 113 v := exp.getInt(name) 114 v.Set(metric.Count()) 115 } 116 117 func (exp *exp) publishCounterFloat64(name string, metric metrics.CounterFloat64Snapshot) { 118 v := exp.getFloat(name) 119 v.Set(metric.Count()) 120 } 121 122 func (exp *exp) publishGauge(name string, metric metrics.GaugeSnapshot) { 123 v := exp.getInt(name) 124 v.Set(metric.Value()) 125 } 126 func (exp *exp) publishGaugeFloat64(name string, metric metrics.GaugeFloat64Snapshot) { 127 exp.getFloat(name).Set(metric.Value()) 128 } 129 130 func (exp *exp) publishGaugeInfo(name string, metric metrics.GaugeInfoSnapshot) { 131 exp.getInfo(name).Set(metric.Value().String()) 132 } 133 134 func (exp *exp) publishHistogram(name string, metric metrics.Histogram) { 135 h := metric.Snapshot() 136 ps := h.Percentiles([]float64{0.5, 0.75, 0.95, 0.99, 0.999}) 137 exp.getInt(name + ".count").Set(h.Count()) 138 exp.getFloat(name + ".min").Set(float64(h.Min())) 139 exp.getFloat(name + ".max").Set(float64(h.Max())) 140 exp.getFloat(name + ".mean").Set(h.Mean()) 141 exp.getFloat(name + ".std-dev").Set(h.StdDev()) 142 exp.getFloat(name + ".50-percentile").Set(ps[0]) 143 exp.getFloat(name + ".75-percentile").Set(ps[1]) 144 exp.getFloat(name + ".95-percentile").Set(ps[2]) 145 exp.getFloat(name + ".99-percentile").Set(ps[3]) 146 exp.getFloat(name + ".999-percentile").Set(ps[4]) 147 } 148 149 func (exp *exp) publishMeter(name string, metric metrics.Meter) { 150 m := metric.Snapshot() 151 exp.getInt(name + ".count").Set(m.Count()) 152 exp.getFloat(name + ".one-minute").Set(m.Rate1()) 153 exp.getFloat(name + ".five-minute").Set(m.Rate5()) 154 exp.getFloat(name + ".fifteen-minute").Set(m.Rate15()) 155 exp.getFloat(name + ".mean").Set(m.RateMean()) 156 } 157 158 func (exp *exp) publishTimer(name string, metric metrics.Timer) { 159 t := metric.Snapshot() 160 ps := t.Percentiles([]float64{0.5, 0.75, 0.95, 0.99, 0.999}) 161 exp.getInt(name + ".count").Set(t.Count()) 162 exp.getFloat(name + ".min").Set(float64(t.Min())) 163 exp.getFloat(name + ".max").Set(float64(t.Max())) 164 exp.getFloat(name + ".mean").Set(t.Mean()) 165 exp.getFloat(name + ".std-dev").Set(t.StdDev()) 166 exp.getFloat(name + ".50-percentile").Set(ps[0]) 167 exp.getFloat(name + ".75-percentile").Set(ps[1]) 168 exp.getFloat(name + ".95-percentile").Set(ps[2]) 169 exp.getFloat(name + ".99-percentile").Set(ps[3]) 170 exp.getFloat(name + ".999-percentile").Set(ps[4]) 171 exp.getFloat(name + ".one-minute").Set(t.Rate1()) 172 exp.getFloat(name + ".five-minute").Set(t.Rate5()) 173 exp.getFloat(name + ".fifteen-minute").Set(t.Rate15()) 174 exp.getFloat(name + ".mean-rate").Set(t.RateMean()) 175 } 176 177 func (exp *exp) publishResettingTimer(name string, metric metrics.ResettingTimer) { 178 t := metric.Snapshot() 179 ps := t.Percentiles([]float64{0.50, 0.75, 0.95, 0.99}) 180 exp.getInt(name + ".count").Set(int64(t.Count())) 181 exp.getFloat(name + ".mean").Set(t.Mean()) 182 exp.getFloat(name + ".50-percentile").Set(ps[0]) 183 exp.getFloat(name + ".75-percentile").Set(ps[1]) 184 exp.getFloat(name + ".95-percentile").Set(ps[2]) 185 exp.getFloat(name + ".99-percentile").Set(ps[3]) 186 } 187 188 func (exp *exp) syncToExpvar() { 189 exp.registry.Each(func(name string, i interface{}) { 190 switch i := i.(type) { 191 case metrics.Counter: 192 exp.publishCounter(name, i.Snapshot()) 193 case metrics.CounterFloat64: 194 exp.publishCounterFloat64(name, i.Snapshot()) 195 case metrics.Gauge: 196 exp.publishGauge(name, i.Snapshot()) 197 case metrics.GaugeFloat64: 198 exp.publishGaugeFloat64(name, i.Snapshot()) 199 case metrics.GaugeInfo: 200 exp.publishGaugeInfo(name, i.Snapshot()) 201 case metrics.Histogram: 202 exp.publishHistogram(name, i) 203 case metrics.Meter: 204 exp.publishMeter(name, i) 205 case metrics.Timer: 206 exp.publishTimer(name, i) 207 case metrics.ResettingTimer: 208 exp.publishResettingTimer(name, i) 209 default: 210 panic(fmt.Sprintf("unsupported type for '%s': %T", name, i)) 211 } 212 }) 213 }