trpc.group/trpc-go/trpc-go@v1.0.3/metrics/metrics.go (about)

     1  //
     2  //
     3  // Tencent is pleased to support the open source community by making tRPC available.
     4  //
     5  // Copyright (C) 2023 THL A29 Limited, a Tencent company.
     6  // All rights reserved.
     7  //
     8  // If you have downloaded a copy of the tRPC source code from Tencent,
     9  // please note that tRPC source code is licensed under the  Apache 2.0 License,
    10  // A copy of the Apache 2.0 License is included in this file.
    11  //
    12  //
    13  
    14  // Package metrics defines some common metrics, such as Counter, IGauge, ITimer and IHistogram.
    15  // The method MetricsSink is used to adapt to external monitor systems, such as monitors in our
    16  // company or open source prometheus.
    17  //
    18  // For convenience, we provide two sorts of methods:
    19  //
    20  //  1. counter
    21  //     - reqNumCounter := metrics.Counter("req.num")
    22  //     reqNumCounter.Incr()
    23  //     - metrics.IncrCounter("req.num", 1)
    24  //
    25  //  2. gauge
    26  //     - cpuAvgLoad := metrics.Gauge("cpu.avgload")
    27  //     cpuAvgLoad.Set(0.75)
    28  //     - metrics.SetGauge("cpu.avgload", 0.75)
    29  //
    30  //  3. timer
    31  //     - timeCostTimer := metrics.Timer("req.proc.timecost")
    32  //     timeCostTimer.Record()
    33  //     - timeCostDuration := time.Millisecond * 2000
    34  //     metrics.RecordTimer("req.proc.timecost", timeCostDuration)
    35  //
    36  //  4. histogram
    37  //     - buckets := metrics.NewDurationBounds(time.Second, time.Second*2, time.Second*5),
    38  //     timeCostDist := metrics.Histogram("timecost.distribution", buckets)
    39  //     timeCostDist.AddSample(float64(time.Second*3))
    40  //     - metrics.AddSample("timecost.distribution", buckets, float64(time.Second*3))
    41  package metrics
    42  
    43  import (
    44  	"fmt"
    45  	"sync"
    46  	"time"
    47  )
    48  
    49  var (
    50  	// metricsSinks emits same metrics information to multi external system at the same time.
    51  	metricsSinksMutex = sync.RWMutex{}
    52  	metricsSinks      = map[string]Sink{}
    53  
    54  	countersMutex = sync.RWMutex{}
    55  	counters      = map[string]ICounter{}
    56  
    57  	gaugesMutex = sync.RWMutex{}
    58  	gauges      = map[string]IGauge{}
    59  
    60  	timersMutex = sync.RWMutex{}
    61  	timers      = map[string]ITimer{}
    62  
    63  	histogramsMutex = sync.RWMutex{}
    64  	histograms      = map[string]IHistogram{}
    65  )
    66  
    67  // RegisterMetricsSink registers a Sink.
    68  func RegisterMetricsSink(sink Sink) {
    69  	metricsSinksMutex.Lock()
    70  	metricsSinks[sink.Name()] = sink
    71  	metricsSinksMutex.Unlock()
    72  	if histSink, ok := sink.(HistogramSink); ok {
    73  		histogramsMutex.Lock()
    74  		for _, hist := range histograms {
    75  			if h, ok := hist.(*histogram); ok {
    76  				histSink.Register(h.Name, HistogramOption{BucketBounds: h.Spec})
    77  			}
    78  		}
    79  		histogramsMutex.Unlock()
    80  	}
    81  }
    82  
    83  // GetMetricsSink gets a Sink by name
    84  func GetMetricsSink(name string) (Sink, bool) {
    85  	metricsSinksMutex.RLock()
    86  	sink, ok := metricsSinks[name]
    87  	metricsSinksMutex.RUnlock()
    88  	return sink, ok
    89  }
    90  
    91  // Counter creates a named counter.
    92  func Counter(name string) ICounter {
    93  	countersMutex.RLock()
    94  	c, ok := counters[name]
    95  	countersMutex.RUnlock()
    96  	if ok && c != nil {
    97  		return c
    98  	}
    99  
   100  	countersMutex.Lock()
   101  	c, ok = counters[name]
   102  	if ok && c != nil {
   103  		countersMutex.Unlock()
   104  		return c
   105  	}
   106  	c = &counter{name: name}
   107  	counters[name] = c
   108  	countersMutex.Unlock()
   109  
   110  	return c
   111  }
   112  
   113  // Gauge creates a named gauge.
   114  func Gauge(name string) IGauge {
   115  	gaugesMutex.RLock()
   116  	c, ok := gauges[name]
   117  	gaugesMutex.RUnlock()
   118  	if ok && c != nil {
   119  		return c
   120  	}
   121  
   122  	gaugesMutex.Lock()
   123  	c, ok = gauges[name]
   124  	if ok && c != nil {
   125  		gaugesMutex.Unlock()
   126  		return c
   127  	}
   128  	c = &gauge{name: name}
   129  	gauges[name] = c
   130  	gaugesMutex.Unlock()
   131  
   132  	return c
   133  }
   134  
   135  // Timer creates a named timer.
   136  func Timer(name string) ITimer {
   137  	timersMutex.RLock()
   138  	t, ok := timers[name]
   139  	timersMutex.RUnlock()
   140  	if ok && t != nil {
   141  		return t
   142  	}
   143  
   144  	timersMutex.Lock()
   145  	t, ok = timers[name]
   146  	if ok && t != nil {
   147  		timersMutex.Unlock()
   148  		return t
   149  	}
   150  	t = &timer{name: name, start: time.Now()}
   151  	timers[name] = t
   152  	timersMutex.Unlock()
   153  
   154  	return t
   155  }
   156  
   157  // NewTimer creates a named timer whose start is set to time.Now().
   158  func NewTimer(name string) ITimer {
   159  	t := Timer(name)
   160  	t.Reset()
   161  	return t
   162  }
   163  
   164  // Histogram creates a named histogram with buckets.
   165  func Histogram(name string, buckets BucketBounds) IHistogram {
   166  	h, ok := GetHistogram(name)
   167  	if ok && h != nil {
   168  		return h
   169  	}
   170  
   171  	// histogramsMutex 的锁范围不应该包括 metricsSinksMutex 的锁。
   172  	histogramsMutex.Lock()
   173  	h, ok = histograms[name]
   174  	if ok && h != nil {
   175  		histogramsMutex.Unlock()
   176  		return h
   177  	}
   178  	h = newHistogram(name, buckets)
   179  	histograms[name] = h
   180  	histogramsMutex.Unlock()
   181  
   182  	metricsSinksMutex.Lock()
   183  	for _, sink := range metricsSinks {
   184  		if histSink, ok := sink.(HistogramSink); ok {
   185  			histSink.Register(name, HistogramOption{BucketBounds: buckets})
   186  		}
   187  	}
   188  	metricsSinksMutex.Unlock()
   189  
   190  	return h
   191  }
   192  
   193  // GetHistogram gets the histogram by key.
   194  func GetHistogram(key string) (v IHistogram, ok bool) {
   195  	histogramsMutex.RLock()
   196  	h, ok := histograms[key]
   197  	histogramsMutex.RUnlock()
   198  	if !ok {
   199  		return nil, false
   200  	}
   201  
   202  	hist, ok := h.(*histogram)
   203  	if !ok {
   204  		return nil, false
   205  	}
   206  	return hist, true
   207  }
   208  
   209  // IncrCounter increases counter key by value. Counters should accumulate values.
   210  func IncrCounter(key string, value float64) {
   211  	Counter(key).IncrBy(value)
   212  }
   213  
   214  // SetGauge sets gauge key to value. An IGauge retains the last set value.
   215  func SetGauge(key string, value float64) {
   216  	Gauge(key).Set(value)
   217  }
   218  
   219  // RecordTimer records timer named key with duration.
   220  func RecordTimer(key string, duration time.Duration) {
   221  	Timer(key).RecordDuration(duration)
   222  }
   223  
   224  // AddSample adds one sample key with value.
   225  func AddSample(key string, buckets BucketBounds, value float64) {
   226  	h := Histogram(key, buckets)
   227  	h.AddSample(value)
   228  }
   229  
   230  // Report reports a multi-dimension record.
   231  func Report(rec Record, opts ...Option) (err error) {
   232  	var errs []error
   233  	for _, sink := range metricsSinks {
   234  		err = sink.Report(rec, opts...)
   235  		if err != nil {
   236  			errs = append(errs, fmt.Errorf("sink-%s error: %v", sink.Name(), err))
   237  		}
   238  	}
   239  
   240  	if len(errs) == 0 {
   241  		return nil
   242  	}
   243  	return fmt.Errorf("metrics sink error: %v", errs)
   244  }