gitlab.com/aquachain/aquachain@v1.17.16-rc3.0.20221018032414-e3ddf1e1c055/common/metrics/exp/exp.go (about)

     1  // Copyright 2018 The aquachain Authors
     2  // This file is part of the aquachain library.
     3  //
     4  // The aquachain library is free software: you can redistribute it and/or modify
     5  // it under the terms of the GNU Lesser General Public License as published by
     6  // the Free Software Foundation, either version 3 of the License, or
     7  // (at your option) any later version.
     8  //
     9  // The aquachain library is distributed in the hope that it will be useful,
    10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    12  // GNU Lesser General Public License for more details.
    13  //
    14  // You should have received a copy of the GNU Lesser General Public License
    15  // along with the aquachain library. If not, see <http://www.gnu.org/licenses/>.
    16  
    17  // Hook go-metrics into expvar
    18  // on any /debug/metrics request, load all vars from the registry into expvar, and execute regular expvar handler
    19  package exp
    20  
    21  import (
    22  	"expvar"
    23  	"fmt"
    24  	"net/http"
    25  	"sync"
    26  
    27  	"gitlab.com/aquachain/aquachain/common/metrics"
    28  )
    29  
    30  type exp struct {
    31  	expvarLock sync.Mutex // expvar panics if you try to register the same var twice, so we must probe it safely
    32  	registry   metrics.Registry
    33  }
    34  
    35  func (exp *exp) expHandler(w http.ResponseWriter, r *http.Request) {
    36  	// load our variables into expvar
    37  	exp.syncToExpvar()
    38  
    39  	// now just run the official expvar handler code (which is not publicly callable, so pasted inline)
    40  	w.Header().Set("Content-Type", "application/json; charset=utf-8")
    41  	fmt.Fprintf(w, "{\n")
    42  	first := true
    43  	expvar.Do(func(kv expvar.KeyValue) {
    44  		if !first {
    45  			fmt.Fprintf(w, ",\n")
    46  		}
    47  		first = false
    48  		fmt.Fprintf(w, "%q: %s", kv.Key, kv.Value)
    49  	})
    50  	fmt.Fprintf(w, "\n}\n")
    51  }
    52  
    53  // Exp will register an expvar powered metrics handler with http.DefaultServeMux on "/debug/vars"
    54  func Exp(r metrics.Registry) {
    55  	h := ExpHandler(r)
    56  	// this would cause a panic:
    57  	// panic: http: multiple registrations for /debug/vars
    58  	// http.HandleFunc("/debug/vars", e.expHandler)
    59  	// haven't found an elegant way, so just use a different endpoint
    60  	http.Handle("/debug/metrics", h)
    61  }
    62  
    63  // ExpHandler will return an expvar powered metrics handler.
    64  func ExpHandler(r metrics.Registry) http.Handler {
    65  	e := exp{sync.Mutex{}, r}
    66  	return http.HandlerFunc(e.expHandler)
    67  }
    68  
    69  func (exp *exp) getInt(name string) *expvar.Int {
    70  	var v *expvar.Int
    71  	exp.expvarLock.Lock()
    72  	p := expvar.Get(name)
    73  	if p != nil {
    74  		v = p.(*expvar.Int)
    75  	} else {
    76  		v = new(expvar.Int)
    77  		expvar.Publish(name, v)
    78  	}
    79  	exp.expvarLock.Unlock()
    80  	return v
    81  }
    82  
    83  func (exp *exp) getFloat(name string) *expvar.Float {
    84  	var v *expvar.Float
    85  	exp.expvarLock.Lock()
    86  	p := expvar.Get(name)
    87  	if p != nil {
    88  		v = p.(*expvar.Float)
    89  	} else {
    90  		v = new(expvar.Float)
    91  		expvar.Publish(name, v)
    92  	}
    93  	exp.expvarLock.Unlock()
    94  	return v
    95  }
    96  
    97  func (exp *exp) publishCounter(name string, metric metrics.Counter) {
    98  	v := exp.getInt(name)
    99  	v.Set(metric.Count())
   100  }
   101  
   102  func (exp *exp) publishGauge(name string, metric metrics.Gauge) {
   103  	v := exp.getInt(name)
   104  	v.Set(metric.Value())
   105  }
   106  func (exp *exp) publishGaugeFloat64(name string, metric metrics.GaugeFloat64) {
   107  	exp.getFloat(name).Set(metric.Value())
   108  }
   109  
   110  func (exp *exp) publishHistogram(name string, metric metrics.Histogram) {
   111  	h := metric.Snapshot()
   112  	ps := h.Percentiles([]float64{0.5, 0.75, 0.95, 0.99, 0.999})
   113  	exp.getInt(name + ".count").Set(h.Count())
   114  	exp.getFloat(name + ".min").Set(float64(h.Min()))
   115  	exp.getFloat(name + ".max").Set(float64(h.Max()))
   116  	exp.getFloat(name + ".mean").Set(h.Mean())
   117  	exp.getFloat(name + ".std-dev").Set(h.StdDev())
   118  	exp.getFloat(name + ".50-percentile").Set(ps[0])
   119  	exp.getFloat(name + ".75-percentile").Set(ps[1])
   120  	exp.getFloat(name + ".95-percentile").Set(ps[2])
   121  	exp.getFloat(name + ".99-percentile").Set(ps[3])
   122  	exp.getFloat(name + ".999-percentile").Set(ps[4])
   123  }
   124  
   125  func (exp *exp) publishMeter(name string, metric metrics.Meter) {
   126  	m := metric.Snapshot()
   127  	exp.getInt(name + ".count").Set(m.Count())
   128  	exp.getFloat(name + ".one-minute").Set(m.Rate1())
   129  	exp.getFloat(name + ".five-minute").Set(m.Rate5())
   130  	exp.getFloat(name + ".fifteen-minute").Set((m.Rate15()))
   131  	exp.getFloat(name + ".mean").Set(m.RateMean())
   132  }
   133  
   134  func (exp *exp) publishTimer(name string, metric metrics.Timer) {
   135  	t := metric.Snapshot()
   136  	ps := t.Percentiles([]float64{0.5, 0.75, 0.95, 0.99, 0.999})
   137  	exp.getInt(name + ".count").Set(t.Count())
   138  	exp.getFloat(name + ".min").Set(float64(t.Min()))
   139  	exp.getFloat(name + ".max").Set(float64(t.Max()))
   140  	exp.getFloat(name + ".mean").Set(t.Mean())
   141  	exp.getFloat(name + ".std-dev").Set(t.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  	exp.getFloat(name + ".one-minute").Set(t.Rate1())
   148  	exp.getFloat(name + ".five-minute").Set(t.Rate5())
   149  	exp.getFloat(name + ".fifteen-minute").Set(t.Rate15())
   150  	exp.getFloat(name + ".mean-rate").Set(t.RateMean())
   151  }
   152  
   153  func (exp *exp) syncToExpvar() {
   154  	exp.registry.Each(func(name string, i interface{}) {
   155  		switch i := i.(type) {
   156  		case metrics.Counter:
   157  			exp.publishCounter(name, i.(metrics.Counter))
   158  		case metrics.Gauge:
   159  			exp.publishGauge(name, i.(metrics.Gauge))
   160  		case metrics.GaugeFloat64:
   161  			exp.publishGaugeFloat64(name, i.(metrics.GaugeFloat64))
   162  		case metrics.Histogram:
   163  			exp.publishHistogram(name, i.(metrics.Histogram))
   164  		case metrics.Meter:
   165  			exp.publishMeter(name, i.(metrics.Meter))
   166  		case metrics.Timer:
   167  			exp.publishTimer(name, i.(metrics.Timer))
   168  		default:
   169  			panic(fmt.Sprintf("unsupported type for '%s': %T", name, i))
   170  		}
   171  	})
   172  }