github.com/amazechain/amc@v0.1.3/internal/metrics/prometheus/set.go (about)

     1  package prometheus
     2  
     3  import (
     4  	"fmt"
     5  	"sort"
     6  	"strings"
     7  	"sync"
     8  	"time"
     9  
    10  	"github.com/prometheus/client_golang/prometheus"
    11  )
    12  
    13  type namedMetric struct {
    14  	name   string
    15  	metric prometheus.Metric
    16  	isAux  bool
    17  }
    18  
    19  // Set is a set of metrics.
    20  //
    21  // Metrics belonging to a set are exported separately from global metrics.
    22  //
    23  // Set.WritePrometheus must be called for exporting metrics from the set.
    24  type Set struct {
    25  	mu sync.Mutex
    26  	a  []*namedMetric
    27  	m  map[string]*namedMetric
    28  }
    29  
    30  var defaultSet = NewSet()
    31  
    32  // NewSet creates new set of metrics.
    33  //
    34  // Pass the set to RegisterSet() function in order to export its metrics via global WritePrometheus() call.
    35  func NewSet() *Set {
    36  	return &Set{
    37  		m: make(map[string]*namedMetric),
    38  	}
    39  }
    40  
    41  func (s *Set) Describe(ch chan<- *prometheus.Desc) {
    42  	lessFunc := func(i, j int) bool {
    43  		return s.a[i].name < s.a[j].name
    44  	}
    45  	s.mu.Lock()
    46  	if !sort.SliceIsSorted(s.a, lessFunc) {
    47  		sort.Slice(s.a, lessFunc)
    48  	}
    49  	sa := append([]*namedMetric(nil), s.a...)
    50  	s.mu.Unlock()
    51  	for _, nm := range sa {
    52  		ch <- nm.metric.Desc()
    53  	}
    54  }
    55  
    56  func (s *Set) Collect(ch chan<- prometheus.Metric) {
    57  	lessFunc := func(i, j int) bool {
    58  		return s.a[i].name < s.a[j].name
    59  	}
    60  	s.mu.Lock()
    61  	if !sort.SliceIsSorted(s.a, lessFunc) {
    62  		sort.Slice(s.a, lessFunc)
    63  	}
    64  	sa := append([]*namedMetric(nil), s.a...)
    65  	s.mu.Unlock()
    66  	for _, nm := range sa {
    67  		ch <- nm.metric
    68  	}
    69  }
    70  
    71  // NewHistogram creates and returns new histogram in s with the given name.
    72  //
    73  // name must be valid Prometheus-compatible metric with possible labels.
    74  // For instance,
    75  //
    76  //   - foo
    77  //   - foo{bar="baz"}
    78  //   - foo{bar="baz",aaa="b"}
    79  //
    80  // The returned histogram is safe to use from concurrent goroutines.
    81  func (s *Set) NewHistogram(name string, help ...string) (prometheus.Histogram, error) {
    82  	h, err := NewHistogram(name, help...)
    83  
    84  	if err != nil {
    85  		return nil, err
    86  	}
    87  
    88  	s.registerMetric(name, h)
    89  	return h, nil
    90  }
    91  
    92  func NewHistogram(name string, help ...string) (prometheus.Histogram, error) {
    93  	name, labels, err := parseMetric(name)
    94  
    95  	if err != nil {
    96  		return nil, err
    97  	}
    98  
    99  	return prometheus.NewHistogram(prometheus.HistogramOpts{
   100  		Name:        name,
   101  		ConstLabels: labels,
   102  		Help:        strings.Join(help, " "),
   103  	}), nil
   104  }
   105  
   106  // GetOrCreateHistogram returns registered histogram in s with the given name
   107  // or creates new histogram if s doesn't contain histogram with the given name.
   108  //
   109  // name must be valid Prometheus-compatible metric with possible labels.
   110  // For instance,
   111  //
   112  //   - foo
   113  //   - foo{bar="baz"}
   114  //   - foo{bar="baz",aaa="b"}
   115  //
   116  // The returned histogram is safe to use from concurrent goroutines.
   117  //
   118  // Performance tip: prefer NewHistogram instead of GetOrCreateHistogram.
   119  func (s *Set) GetOrCreateHistogram(name string, help ...string) prometheus.Histogram {
   120  	s.mu.Lock()
   121  	nm := s.m[name]
   122  	s.mu.Unlock()
   123  	if nm == nil {
   124  		metric, err := NewHistogram(name, help...)
   125  
   126  		if err != nil {
   127  			panic(fmt.Errorf("BUG: invalid metric name %q: %s", name, err))
   128  		}
   129  
   130  		nmNew := &namedMetric{
   131  			name:   name,
   132  			metric: metric,
   133  		}
   134  
   135  		s.mu.Lock()
   136  		nm = s.m[name]
   137  		if nm == nil {
   138  			nm = nmNew
   139  			s.m[name] = nm
   140  			s.a = append(s.a, nm)
   141  		}
   142  		s.mu.Unlock()
   143  	}
   144  	h, ok := nm.metric.(prometheus.Histogram)
   145  	if !ok {
   146  		panic(fmt.Errorf("BUG: metric %q isn't a Histogram. It is %T", name, nm.metric))
   147  	}
   148  	return h
   149  }
   150  
   151  // NewCounter registers and returns new counter with the given name in the s.
   152  //
   153  // name must be valid Prometheus-compatible metric with possible labels.
   154  // For instance,
   155  //
   156  //   - foo
   157  //   - foo{bar="baz"}
   158  //   - foo{bar="baz",aaa="b"}
   159  //
   160  // The returned counter is safe to use from concurrent goroutines.
   161  func (s *Set) NewCounter(name string, help ...string) (prometheus.Counter, error) {
   162  	c, err := NewCounter(name, help...)
   163  
   164  	if err != nil {
   165  		return nil, err
   166  	}
   167  
   168  	s.registerMetric(name, c)
   169  	return c, nil
   170  }
   171  
   172  func NewCounter(name string, help ...string) (prometheus.Counter, error) {
   173  	name, labels, err := parseMetric(name)
   174  
   175  	if err != nil {
   176  		return nil, err
   177  	}
   178  
   179  	return prometheus.NewCounter(prometheus.CounterOpts{
   180  		Name:        name,
   181  		Help:        strings.Join(help, " "),
   182  		ConstLabels: labels,
   183  	}), nil
   184  }
   185  
   186  // GetOrCreateCounter returns registered counter in s with the given name
   187  // or creates new counter if s doesn't contain counter with the given name.
   188  //
   189  // name must be valid Prometheus-compatible metric with possible labels.
   190  // For instance,
   191  //
   192  //   - foo
   193  //   - foo{bar="baz"}
   194  //   - foo{bar="baz",aaa="b"}
   195  //
   196  // The returned counter is safe to use from concurrent goroutines.
   197  //
   198  // Performance tip: prefer NewCounter instead of GetOrCreateCounter.
   199  func (s *Set) GetOrCreateCounter(name string, help ...string) prometheus.Counter {
   200  	s.mu.Lock()
   201  	nm := s.m[name]
   202  	s.mu.Unlock()
   203  	if nm == nil {
   204  		// Slow path - create and register missing counter.
   205  		metric, err := NewCounter(name, help...)
   206  
   207  		if err != nil {
   208  			panic(fmt.Errorf("BUG: invalid metric name %q: %s", name, err))
   209  		}
   210  
   211  		nmNew := &namedMetric{
   212  			name:   name,
   213  			metric: metric,
   214  		}
   215  		s.mu.Lock()
   216  		nm = s.m[name]
   217  		if nm == nil {
   218  			nm = nmNew
   219  			s.m[name] = nm
   220  			s.a = append(s.a, nm)
   221  		}
   222  		s.mu.Unlock()
   223  	}
   224  	c, ok := nm.metric.(prometheus.Counter)
   225  	if !ok {
   226  		panic(fmt.Errorf("BUG: metric %q isn't a Counter. It is %T", name, nm.metric))
   227  	}
   228  	return c
   229  }
   230  
   231  // NewGauge registers and returns gauge with the given name in s, which calls f
   232  // to obtain gauge value.
   233  //
   234  // name must be valid Prometheus-compatible metric with possible labels.
   235  // For instance,
   236  //
   237  //   - foo
   238  //   - foo{bar="baz"}
   239  //   - foo{bar="baz",aaa="b"}
   240  //
   241  // f must be safe for concurrent calls.
   242  //
   243  // The returned gauge is safe to use from concurrent goroutines.
   244  func (s *Set) NewGauge(name string, help ...string) (prometheus.Gauge, error) {
   245  	g, err := NewGauge(name, help...)
   246  
   247  	if err != nil {
   248  		return nil, err
   249  	}
   250  
   251  	s.registerMetric(name, g)
   252  	return g, nil
   253  }
   254  
   255  func NewGauge(name string, help ...string) (prometheus.Gauge, error) {
   256  
   257  	name, labels, err := parseMetric(name)
   258  
   259  	if err != nil {
   260  		return nil, err
   261  	}
   262  
   263  	return prometheus.NewGauge(prometheus.GaugeOpts{
   264  		Name:        name,
   265  		Help:        strings.Join(help, " "),
   266  		ConstLabels: labels,
   267  	}), nil
   268  }
   269  
   270  // GetOrCreateGaugeFunc returns registered gauge with the given name in s
   271  // or creates new gauge if s doesn't contain gauge with the given name.
   272  //
   273  // name must be valid Prometheus-compatible metric with possible labels.
   274  // For instance,
   275  //
   276  //   - foo
   277  //   - foo{bar="baz"}
   278  //   - foo{bar="baz",aaa="b"}
   279  //
   280  // The returned gauge is safe to use from concurrent goroutines.
   281  //
   282  // Performance tip: prefer NewGauge instead of GetOrCreateGauge.
   283  func (s *Set) GetOrCreateGauge(name string, help ...string) prometheus.Gauge {
   284  	s.mu.Lock()
   285  	nm := s.m[name]
   286  	s.mu.Unlock()
   287  	if nm == nil {
   288  		// Slow path - create and register missing gauge.
   289  		metric, err := NewGauge(name, help...)
   290  
   291  		if err != nil {
   292  			panic(fmt.Errorf("BUG: invalid metric name %q: %s", name, err))
   293  		}
   294  
   295  		nmNew := &namedMetric{
   296  			name:   name,
   297  			metric: metric,
   298  		}
   299  		s.mu.Lock()
   300  		nm = s.m[name]
   301  		if nm == nil {
   302  			nm = nmNew
   303  			s.m[name] = nm
   304  			s.a = append(s.a, nm)
   305  		}
   306  		s.mu.Unlock()
   307  	}
   308  	g, ok := nm.metric.(prometheus.Gauge)
   309  	if !ok {
   310  		panic(fmt.Errorf("BUG: metric %q isn't a Gauge. It is %T", name, nm.metric))
   311  	}
   312  	return g
   313  }
   314  
   315  // NewGaugeFunc registers and returns gauge with the given name in s, which calls f
   316  // to obtain gauge value.
   317  //
   318  // name must be valid Prometheus-compatible metric with possible labels.
   319  // For instance,
   320  //
   321  //   - foo
   322  //   - foo{bar="baz"}
   323  //   - foo{bar="baz",aaa="b"}
   324  //
   325  // f must be safe for concurrent calls.
   326  //
   327  // The returned gauge is safe to use from concurrent goroutines.
   328  func (s *Set) NewGaugeFunc(name string, f func() float64, help ...string) (prometheus.GaugeFunc, error) {
   329  	g, err := NewGaugeFunc(name, f, help...)
   330  
   331  	if err != nil {
   332  		return nil, err
   333  	}
   334  
   335  	s.registerMetric(name, g)
   336  	return g, nil
   337  }
   338  
   339  func NewGaugeFunc(name string, f func() float64, help ...string) (prometheus.GaugeFunc, error) {
   340  	if f == nil {
   341  		return nil, fmt.Errorf("BUG: f cannot be nil")
   342  	}
   343  
   344  	name, labels, err := parseMetric(name)
   345  
   346  	if err != nil {
   347  		return nil, err
   348  	}
   349  
   350  	return prometheus.NewGaugeFunc(prometheus.GaugeOpts{
   351  		Name:        name,
   352  		Help:        strings.Join(help, " "),
   353  		ConstLabels: labels,
   354  	}, f), nil
   355  }
   356  
   357  // GetOrCreateGaugeFunc returns registered gauge with the given name in s
   358  // or creates new gauge if s doesn't contain gauge with the given name.
   359  //
   360  // name must be valid Prometheus-compatible metric with possible labels.
   361  // For instance,
   362  //
   363  //   - foo
   364  //   - foo{bar="baz"}
   365  //   - foo{bar="baz",aaa="b"}
   366  //
   367  // The returned gauge is safe to use from concurrent goroutines.
   368  //
   369  // Performance tip: prefer NewGauge instead of GetOrCreateGauge.
   370  func (s *Set) GetOrCreateGaugeFunc(name string, f func() float64, help ...string) prometheus.GaugeFunc {
   371  	s.mu.Lock()
   372  	nm := s.m[name]
   373  	s.mu.Unlock()
   374  	if nm == nil {
   375  		// Slow path - create and register missing gauge.
   376  		if f == nil {
   377  			panic(fmt.Errorf("BUG: f cannot be nil"))
   378  		}
   379  
   380  		metric, err := NewGaugeFunc(name, f, help...)
   381  
   382  		if err != nil {
   383  			panic(fmt.Errorf("BUG: invalid metric name %q: %s", name, err))
   384  		}
   385  
   386  		nmNew := &namedMetric{
   387  			name:   name,
   388  			metric: metric,
   389  		}
   390  		s.mu.Lock()
   391  		nm = s.m[name]
   392  		if nm == nil {
   393  			nm = nmNew
   394  			s.m[name] = nm
   395  			s.a = append(s.a, nm)
   396  		}
   397  		s.mu.Unlock()
   398  	}
   399  	g, ok := nm.metric.(prometheus.GaugeFunc)
   400  	if !ok {
   401  		panic(fmt.Errorf("BUG: metric %q isn't a Gauge. It is %T", name, nm.metric))
   402  	}
   403  	return g
   404  }
   405  
   406  const defaultSummaryWindow = 5 * time.Minute
   407  
   408  var defaultSummaryQuantiles = map[float64]float64{0.5: 0.05, 0.9: 0.01, 0.97: 0.003, 0.99: 0.001}
   409  
   410  // NewSummary creates and returns new summary with the given name in s.
   411  //
   412  // name must be valid Prometheus-compatible metric with possible labels.
   413  // For instance,
   414  //
   415  //   - foo
   416  //   - foo{bar="baz"}
   417  //   - foo{bar="baz",aaa="b"}
   418  //
   419  // The returned summary is safe to use from concurrent goroutines.
   420  func (s *Set) NewSummary(name string, help ...string) (prometheus.Summary, error) {
   421  	sm, err := NewSummary(name, defaultSummaryWindow, defaultSummaryQuantiles, help...)
   422  
   423  	if err != nil {
   424  		return nil, err
   425  	}
   426  	s.mu.Lock()
   427  	// defer will unlock in case of panic
   428  	// checks in tests
   429  	defer s.mu.Unlock()
   430  
   431  	s.registerMetric(name, sm)
   432  	return sm, nil
   433  }
   434  
   435  // NewSummary creates and returns new summary in s with the given name,
   436  // window and quantiles.
   437  //
   438  // name must be valid Prometheus-compatible metric with possible labels.
   439  // For instance,
   440  //
   441  //   - foo
   442  //   - foo{bar="baz"}
   443  //   - foo{bar="baz",aaa="b"}
   444  //
   445  // The returned summary is safe to use from concurrent goroutines.
   446  func NewSummary(name string, window time.Duration, quantiles map[float64]float64, help ...string) (prometheus.Summary, error) {
   447  	name, labels, err := parseMetric(name)
   448  
   449  	if err != nil {
   450  		return nil, err
   451  	}
   452  
   453  	return prometheus.NewSummary(prometheus.SummaryOpts{
   454  		Name:        name,
   455  		ConstLabels: labels,
   456  		Objectives:  quantiles,
   457  		MaxAge:      window,
   458  	}), nil
   459  }
   460  
   461  // GetOrCreateSummary returns registered summary with the given name in s
   462  // or creates new summary if s doesn't contain summary with the given name.
   463  //
   464  // name must be valid Prometheus-compatible metric with possible labels.
   465  // For instance,
   466  //
   467  //   - foo
   468  //   - foo{bar="baz"}
   469  //   - foo{bar="baz",aaa="b"}
   470  //
   471  // The returned summary is safe to use from concurrent goroutines.
   472  //
   473  // Performance tip: prefer NewSummary instead of GetOrCreateSummary.
   474  func (s *Set) GetOrCreateSummary(name string, help ...string) prometheus.Summary {
   475  	return s.GetOrCreateSummaryExt(name, defaultSummaryWindow, defaultSummaryQuantiles, help...)
   476  }
   477  
   478  // GetOrCreateSummaryExt returns registered summary with the given name,
   479  // window and quantiles in s or creates new summary if s doesn't
   480  // contain summary with the given name.
   481  //
   482  // name must be valid Prometheus-compatible metric with possible labels.
   483  // For instance,
   484  //
   485  //   - foo
   486  //   - foo{bar="baz"}
   487  //   - foo{bar="baz",aaa="b"}
   488  //
   489  // The returned summary is safe to use from concurrent goroutines.
   490  //
   491  // Performance tip: prefer NewSummaryExt instead of GetOrCreateSummaryExt.
   492  func (s *Set) GetOrCreateSummaryExt(name string, window time.Duration, quantiles map[float64]float64, help ...string) prometheus.Summary {
   493  	s.mu.Lock()
   494  	nm := s.m[name]
   495  	s.mu.Unlock()
   496  	if nm == nil {
   497  		// Slow path - create and register missing summary.
   498  		metric, err := NewSummary(name, window, quantiles, help...)
   499  
   500  		if err != nil {
   501  			panic(fmt.Errorf("BUG: invalid metric name %q: %s", name, err))
   502  		}
   503  
   504  		nmNew := &namedMetric{
   505  			name:   name,
   506  			metric: metric,
   507  		}
   508  		s.mu.Lock()
   509  		nm = s.m[name]
   510  		if nm == nil {
   511  			nm = nmNew
   512  			s.m[name] = nm
   513  			s.a = append(s.a, nm)
   514  		}
   515  		s.mu.Unlock()
   516  	}
   517  	sm, ok := nm.metric.(prometheus.Summary)
   518  	if !ok {
   519  		panic(fmt.Errorf("BUG: metric %q isn't a Summary. It is %T", name, nm.metric))
   520  	}
   521  
   522  	return sm
   523  }
   524  
   525  func (s *Set) registerMetric(name string, m prometheus.Metric) {
   526  	if _, _, err := parseMetric(name); err != nil {
   527  		panic(fmt.Errorf("BUG: invalid metric name %q: %s", name, err))
   528  	}
   529  	s.mu.Lock()
   530  	// defer will unlock in case of panic
   531  	// checks in test
   532  	defer s.mu.Unlock()
   533  	s.mustRegisterLocked(name, m)
   534  }
   535  
   536  // mustRegisterLocked registers given metric with the given name.
   537  //
   538  // Panics if the given name was already registered before.
   539  func (s *Set) mustRegisterLocked(name string, m prometheus.Metric) {
   540  	_, ok := s.m[name]
   541  	if !ok {
   542  		nm := &namedMetric{
   543  			name:   name,
   544  			metric: m,
   545  		}
   546  		s.m[name] = nm
   547  		s.a = append(s.a, nm)
   548  	}
   549  	if ok {
   550  		panic(fmt.Errorf("BUG: metric %q is already registered", name))
   551  	}
   552  }
   553  
   554  // UnregisterMetric removes metric with the given name from s.
   555  //
   556  // True is returned if the metric has been removed.
   557  // False is returned if the given metric is missing in s.
   558  func (s *Set) UnregisterMetric(name string) bool {
   559  	s.mu.Lock()
   560  	defer s.mu.Unlock()
   561  
   562  	nm, ok := s.m[name]
   563  	if !ok {
   564  		return false
   565  	}
   566  	return s.unregisterMetricLocked(nm)
   567  }
   568  
   569  func (s *Set) unregisterMetricLocked(nm *namedMetric) bool {
   570  	name := nm.name
   571  	delete(s.m, name)
   572  
   573  	deleteFromList := func(metricName string) {
   574  		for i, nm := range s.a {
   575  			if nm.name == metricName {
   576  				s.a = append(s.a[:i], s.a[i+1:]...)
   577  				return
   578  			}
   579  		}
   580  		panic(fmt.Errorf("BUG: cannot find metric %q in the list of registered metrics", name))
   581  	}
   582  
   583  	// remove metric from s.a
   584  	deleteFromList(name)
   585  
   586  	return true
   587  }
   588  
   589  // UnregisterAllMetrics de-registers all metrics registered in s.
   590  func (s *Set) UnregisterAllMetrics() {
   591  	metricNames := s.ListMetricNames()
   592  	for _, name := range metricNames {
   593  		s.UnregisterMetric(name)
   594  	}
   595  }
   596  
   597  // ListMetricNames returns sorted list of all the metrics in s.
   598  func (s *Set) ListMetricNames() []string {
   599  	s.mu.Lock()
   600  	defer s.mu.Unlock()
   601  	metricNames := make([]string, 0, len(s.m))
   602  	for _, nm := range s.m {
   603  		if nm.isAux {
   604  			continue
   605  		}
   606  		metricNames = append(metricNames, nm.name)
   607  	}
   608  	sort.Strings(metricNames)
   609  	return metricNames
   610  }