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  }