exp.go 5.8 KB

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