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 }