exp.go 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148
  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. "github.com/rcrowley/go-metrics"
  8. "net/http"
  9. "sync"
  10. )
  11. type exp struct {
  12. expvarLock sync.Mutex // expvar panics if you try to register the same var twice, so we must probe it safely
  13. registry metrics.Registry
  14. }
  15. func (exp *exp) expHandler(w http.ResponseWriter, r *http.Request) {
  16. // load our variables into expvar
  17. exp.syncToExpvar()
  18. // now just run the official expvar handler code (which is not publicly callable, so pasted inline)
  19. w.Header().Set("Content-Type", "application/json; charset=utf-8")
  20. fmt.Fprintf(w, "{\n")
  21. first := true
  22. expvar.Do(func(kv expvar.KeyValue) {
  23. if !first {
  24. fmt.Fprintf(w, ",\n")
  25. }
  26. first = false
  27. fmt.Fprintf(w, "%q: %s", kv.Key, kv.Value)
  28. })
  29. fmt.Fprintf(w, "\n}\n")
  30. }
  31. func Exp(r metrics.Registry) {
  32. e := exp{sync.Mutex{}, r}
  33. // this would cause a panic:
  34. // panic: http: multiple registrations for /debug/vars
  35. // http.HandleFunc("/debug/vars", e.expHandler)
  36. // haven't found an elegant way, so just use a different endpoint
  37. http.HandleFunc("/debug/metrics", e.expHandler)
  38. }
  39. func (exp *exp) getInt(name string) *expvar.Int {
  40. var v *expvar.Int
  41. exp.expvarLock.Lock()
  42. p := expvar.Get(name)
  43. if p != nil {
  44. v = p.(*expvar.Int)
  45. } else {
  46. v = new(expvar.Int)
  47. expvar.Publish(name, v)
  48. }
  49. exp.expvarLock.Unlock()
  50. return v
  51. }
  52. func (exp *exp) getFloat(name string) *expvar.Float {
  53. var v *expvar.Float
  54. exp.expvarLock.Lock()
  55. p := expvar.Get(name)
  56. if p != nil {
  57. v = p.(*expvar.Float)
  58. } else {
  59. v = new(expvar.Float)
  60. expvar.Publish(name, v)
  61. }
  62. exp.expvarLock.Unlock()
  63. return v
  64. }
  65. func (exp *exp) publishCounter(name string, metric metrics.Counter) {
  66. v := exp.getInt(name)
  67. v.Set(metric.Count())
  68. }
  69. func (exp *exp) publishGauge(name string, metric metrics.Gauge) {
  70. v := exp.getInt(name)
  71. v.Set(metric.Value())
  72. }
  73. func (exp *exp) publishGaugeFloat64(name string, metric metrics.GaugeFloat64) {
  74. exp.getFloat(name).Set(metric.Value())
  75. }
  76. func (exp *exp) publishHistogram(name string, metric metrics.Histogram) {
  77. h := metric.Snapshot()
  78. ps := h.Percentiles([]float64{0.5, 0.75, 0.95, 0.99, 0.999})
  79. exp.getInt(name + ".count").Set(h.Count())
  80. exp.getFloat(name + ".min").Set(float64(h.Min()))
  81. exp.getFloat(name + ".max").Set(float64(h.Max()))
  82. exp.getFloat(name + ".mean").Set(float64(h.Mean()))
  83. exp.getFloat(name + ".std-dev").Set(float64(h.StdDev()))
  84. exp.getFloat(name + ".50-percentile").Set(float64(ps[0]))
  85. exp.getFloat(name + ".75-percentile").Set(float64(ps[1]))
  86. exp.getFloat(name + ".95-percentile").Set(float64(ps[2]))
  87. exp.getFloat(name + ".99-percentile").Set(float64(ps[3]))
  88. exp.getFloat(name + ".999-percentile").Set(float64(ps[4]))
  89. }
  90. func (exp *exp) publishMeter(name string, metric metrics.Meter) {
  91. m := metric.Snapshot()
  92. exp.getInt(name + ".count").Set(m.Count())
  93. exp.getFloat(name + ".one-minute").Set(float64(m.Rate1()))
  94. exp.getFloat(name + ".five-minute").Set(float64(m.Rate5()))
  95. exp.getFloat(name + ".fifteen-minute").Set(float64((m.Rate15())))
  96. exp.getFloat(name + ".mean").Set(float64(m.RateMean()))
  97. }
  98. func (exp *exp) publishTimer(name string, metric metrics.Timer) {
  99. t := metric.Snapshot()
  100. ps := t.Percentiles([]float64{0.5, 0.75, 0.95, 0.99, 0.999})
  101. exp.getInt(name + ".count").Set(t.Count())
  102. exp.getFloat(name + ".min").Set(float64(t.Min()))
  103. exp.getFloat(name + ".max").Set(float64(t.Max()))
  104. exp.getFloat(name + ".mean").Set(float64(t.Mean()))
  105. exp.getFloat(name + ".std-dev").Set(float64(t.StdDev()))
  106. exp.getFloat(name + ".50-percentile").Set(float64(ps[0]))
  107. exp.getFloat(name + ".75-percentile").Set(float64(ps[1]))
  108. exp.getFloat(name + ".95-percentile").Set(float64(ps[2]))
  109. exp.getFloat(name + ".99-percentile").Set(float64(ps[3]))
  110. exp.getFloat(name + ".999-percentile").Set(float64(ps[4]))
  111. exp.getFloat(name + ".one-minute").Set(float64(t.Rate1()))
  112. exp.getFloat(name + ".five-minute").Set(float64(t.Rate5()))
  113. exp.getFloat(name + ".fifteen-minute").Set(float64((t.Rate15())))
  114. exp.getFloat(name + ".mean-rate").Set(float64(t.RateMean()))
  115. }
  116. func (exp *exp) syncToExpvar() {
  117. exp.registry.Each(func(name string, i interface{}) {
  118. switch i.(type) {
  119. case metrics.Counter:
  120. exp.publishCounter(name, i.(metrics.Counter))
  121. case metrics.Gauge:
  122. exp.publishGauge(name, i.(metrics.Gauge))
  123. case metrics.GaugeFloat64:
  124. exp.publishGaugeFloat64(name, i.(metrics.GaugeFloat64))
  125. case metrics.Histogram:
  126. exp.publishHistogram(name, i.(metrics.Histogram))
  127. case metrics.Meter:
  128. exp.publishMeter(name, i.(metrics.Meter))
  129. case metrics.Timer:
  130. exp.publishTimer(name, i.(metrics.Timer))
  131. default:
  132. panic(fmt.Sprintf("unsupported type for '%s': %T", name, i))
  133. }
  134. })
  135. }