github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/util/metric/registry.go (about)

     1  // Copyright 2015 The Cockroach Authors.
     2  //
     3  // Use of this software is governed by the Business Source License
     4  // included in the file licenses/BSL.txt.
     5  //
     6  // As of the Change Date specified in that file, in accordance with
     7  // the Business Source License, use of this software will be governed
     8  // by the Apache License, Version 2.0, included in the file
     9  // licenses/APL.txt.
    10  
    11  package metric
    12  
    13  import (
    14  	"context"
    15  	"encoding/json"
    16  	"reflect"
    17  	"regexp"
    18  
    19  	"github.com/cockroachdb/cockroach/pkg/util/log"
    20  	"github.com/cockroachdb/cockroach/pkg/util/syncutil"
    21  	"github.com/gogo/protobuf/proto"
    22  	prometheusgo "github.com/prometheus/client_model/go"
    23  )
    24  
    25  // A Registry is a list of metrics. It provides a simple way of iterating over
    26  // them, can marshal into JSON, and generate a prometheus format.
    27  //
    28  // A registry can have label pairs that will be applied to all its metrics
    29  // when exported to prometheus.
    30  type Registry struct {
    31  	syncutil.Mutex
    32  	labels  []*prometheusgo.LabelPair
    33  	tracked []Iterable
    34  }
    35  
    36  // Struct can be implemented by the types of members of a metric
    37  // container so that the members get automatically registered.
    38  type Struct interface {
    39  	MetricStruct()
    40  }
    41  
    42  // NewRegistry creates a new Registry.
    43  func NewRegistry() *Registry {
    44  	return &Registry{
    45  		labels:  []*prometheusgo.LabelPair{},
    46  		tracked: []Iterable{},
    47  	}
    48  }
    49  
    50  // AddLabel adds a label/value pair for this registry.
    51  func (r *Registry) AddLabel(name, value string) {
    52  	r.Lock()
    53  	defer r.Unlock()
    54  	r.labels = append(r.labels,
    55  		&prometheusgo.LabelPair{
    56  			Name:  proto.String(exportedLabel(name)),
    57  			Value: proto.String(value),
    58  		})
    59  }
    60  
    61  func (r *Registry) getLabels() []*prometheusgo.LabelPair {
    62  	r.Lock()
    63  	defer r.Unlock()
    64  	return r.labels
    65  }
    66  
    67  // AddMetric adds the passed-in metric to the registry.
    68  func (r *Registry) AddMetric(metric Iterable) {
    69  	r.Lock()
    70  	defer r.Unlock()
    71  	r.tracked = append(r.tracked, metric)
    72  	if log.V(2) {
    73  		log.Infof(context.TODO(), "Added metric: %s (%T)", metric.GetName(), metric)
    74  	}
    75  }
    76  
    77  // AddMetricStruct examines all fields of metricStruct and adds
    78  // all Iterable or metric.Struct objects to the registry.
    79  func (r *Registry) AddMetricStruct(metricStruct interface{}) {
    80  	v := reflect.ValueOf(metricStruct)
    81  	if v.Kind() == reflect.Ptr {
    82  		v = v.Elem()
    83  	}
    84  	t := v.Type()
    85  
    86  	for i := 0; i < v.NumField(); i++ {
    87  		vfield, tfield := v.Field(i), t.Field(i)
    88  		if !vfield.CanInterface() {
    89  			if log.V(2) {
    90  				log.Infof(context.TODO(), "Skipping unexported field %s", tfield.Name)
    91  			}
    92  			continue
    93  		}
    94  		val := vfield.Interface()
    95  		switch typ := val.(type) {
    96  		case Iterable:
    97  			r.AddMetric(typ)
    98  		case Struct:
    99  			r.AddMetricStruct(typ)
   100  		default:
   101  			if log.V(2) {
   102  				log.Infof(context.TODO(), "Skipping non-metric field %s", tfield.Name)
   103  			}
   104  		}
   105  	}
   106  }
   107  
   108  // WriteMetricsMetadata writes metadata from all tracked metrics to the
   109  // parameter map.
   110  func (r *Registry) WriteMetricsMetadata(dest map[string]Metadata) {
   111  	for _, v := range r.tracked {
   112  		dest[v.GetName()] = v.GetMetadata()
   113  	}
   114  }
   115  
   116  // Each calls the given closure for all metrics.
   117  func (r *Registry) Each(f func(name string, val interface{})) {
   118  	r.Lock()
   119  	defer r.Unlock()
   120  	for _, metric := range r.tracked {
   121  		metric.Inspect(func(v interface{}) {
   122  			f(metric.GetName(), v)
   123  		})
   124  	}
   125  }
   126  
   127  // MarshalJSON marshals to JSON.
   128  func (r *Registry) MarshalJSON() ([]byte, error) {
   129  	m := make(map[string]interface{})
   130  	for _, metric := range r.tracked {
   131  		metric.Inspect(func(v interface{}) {
   132  			m[metric.GetName()] = v
   133  		})
   134  	}
   135  	return json.Marshal(m)
   136  }
   137  
   138  var (
   139  	// Prometheus metric names and labels have fairly strict rules, they
   140  	// must match the regexp [a-zA-Z_:][a-zA-Z0-9_:]*
   141  	// See: https://prometheus.io/docs/concepts/data_model/
   142  	prometheusNameReplaceRE  = regexp.MustCompile("^[^a-zA-Z_:]|[^a-zA-Z0-9_:]")
   143  	prometheusLabelReplaceRE = regexp.MustCompile("^[^a-zA-Z_]|[^a-zA-Z0-9_]")
   144  )
   145  
   146  // exportedName takes a metric name and generates a valid prometheus name.
   147  func exportedName(name string) string {
   148  	return prometheusNameReplaceRE.ReplaceAllString(name, "_")
   149  }
   150  
   151  // exportedLabel takes a metric name and generates a valid prometheus name.
   152  func exportedLabel(name string) string {
   153  	return prometheusLabelReplaceRE.ReplaceAllString(name, "_")
   154  }