Skip to content

Commit ecbb837

Browse files
authored
experimental/stats: Add metrics registry (#7349)
1 parent c5c0e18 commit ecbb837

File tree

7 files changed

+634
-79
lines changed

7 files changed

+634
-79
lines changed
Lines changed: 230 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,230 @@
1+
/*
2+
*
3+
* Copyright 2024 gRPC authors.
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*
17+
*/
18+
19+
package stats
20+
21+
import (
22+
"maps"
23+
"testing"
24+
25+
"google.golang.org/grpc/grpclog"
26+
)
27+
28+
var logger = grpclog.Component("metrics-registry")
29+
30+
// DefaultMetrics are the default metrics registered through global metrics
31+
// registry. This is written to at initialization time only, and is read only
32+
// after initialization.
33+
var DefaultMetrics = NewMetrics()
34+
35+
// MetricDescriptor is the data for a registered metric.
36+
type MetricDescriptor struct {
37+
// The name of this metric. This name must be unique across the whole binary
38+
// (including any per call metrics). See
39+
// https://github.com/grpc/proposal/blob/master/A79-non-per-call-metrics-architecture.md#metric-instrument-naming-conventions
40+
// for metric naming conventions.
41+
Name Metric
42+
// The description of this metric.
43+
Description string
44+
// The unit (e.g. entries, seconds) of this metric.
45+
Unit string
46+
// The required label keys for this metric. These are intended to
47+
// metrics emitted from a stats handler.
48+
Labels []string
49+
// The optional label keys for this metric. These are intended to attached
50+
// to metrics emitted from a stats handler if configured.
51+
OptionalLabels []string
52+
// Whether this metric is on by default.
53+
Default bool
54+
// The type of metric. This is set by the metric registry, and not intended
55+
// to be set by a component registering a metric.
56+
Type MetricType
57+
}
58+
59+
// MetricType is the type of metric.
60+
type MetricType int
61+
62+
const (
63+
MetricTypeIntCount MetricType = iota
64+
MetricTypeFloatCount
65+
MetricTypeIntHisto
66+
MetricTypeFloatHisto
67+
MetricTypeIntGauge
68+
)
69+
70+
// Int64CountHandle is a typed handle for a int count metric. This handle
71+
// is passed at the recording point in order to know which metric to record
72+
// on.
73+
type Int64CountHandle MetricDescriptor
74+
75+
// Record records the int64 count value on the metrics recorder provided.
76+
func (h *Int64CountHandle) Record(recorder MetricsRecorder, incr int64, labels ...string) {
77+
recorder.RecordInt64Count(h, incr, labels...)
78+
}
79+
80+
// Float64CountHandle is a typed handle for a float count metric. This handle is
81+
// passed at the recording point in order to know which metric to record on.
82+
type Float64CountHandle MetricDescriptor
83+
84+
// Record records the float64 count value on the metrics recorder provided.
85+
func (h *Float64CountHandle) Record(recorder MetricsRecorder, incr float64, labels ...string) {
86+
recorder.RecordFloat64Count(h, incr, labels...)
87+
}
88+
89+
// Int64HistoHandle is a typed handle for an int histogram metric. This handle
90+
// is passed at the recording point in order to know which metric to record on.
91+
type Int64HistoHandle MetricDescriptor
92+
93+
// Record records the int64 histo value on the metrics recorder provided.
94+
func (h *Int64HistoHandle) Record(recorder MetricsRecorder, incr int64, labels ...string) {
95+
recorder.RecordInt64Histo(h, incr, labels...)
96+
}
97+
98+
// Float64HistoHandle is a typed handle for a float histogram metric. This
99+
// handle is passed at the recording point in order to know which metric to
100+
// record on.
101+
type Float64HistoHandle MetricDescriptor
102+
103+
// Record records the float64 histo value on the metrics recorder provided.
104+
func (h *Float64HistoHandle) Record(recorder MetricsRecorder, incr float64, labels ...string) {
105+
recorder.RecordFloat64Histo(h, incr, labels...)
106+
}
107+
108+
// Int64GaugeHandle is a typed handle for an int gauge metric. This handle is
109+
// passed at the recording point in order to know which metric to record on.
110+
type Int64GaugeHandle MetricDescriptor
111+
112+
// Record records the int64 histo value on the metrics recorder provided.
113+
func (h *Int64GaugeHandle) Record(recorder MetricsRecorder, incr int64, labels ...string) {
114+
recorder.RecordInt64Gauge(h, incr, labels...)
115+
}
116+
117+
// registeredMetrics are the registered metric descriptor names.
118+
var registeredMetrics = make(map[Metric]bool)
119+
120+
// metricsRegistry contains all of the registered metrics.
121+
//
122+
// This is written to only at init time, and read only after that.
123+
var metricsRegistry = make(map[Metric]*MetricDescriptor)
124+
125+
// DescriptorForMetric returns the MetricDescriptor from the global registry.
126+
//
127+
// Returns nil if MetricDescriptor not present.
128+
func DescriptorForMetric(metric Metric) *MetricDescriptor {
129+
return metricsRegistry[metric]
130+
}
131+
132+
func registerMetric(name Metric, def bool) {
133+
if registeredMetrics[name] {
134+
logger.Fatalf("metric %v already registered", name)
135+
}
136+
registeredMetrics[name] = true
137+
if def {
138+
DefaultMetrics = DefaultMetrics.Add(name)
139+
}
140+
}
141+
142+
// RegisterInt64Count registers the metric description onto the global registry.
143+
// It returns a typed handle to use to recording data.
144+
//
145+
// NOTE: this function must only be called during initialization time (i.e. in
146+
// an init() function), and is not thread-safe. If multiple metrics are
147+
// registered with the same name, this function will panic.
148+
func RegisterInt64Count(descriptor MetricDescriptor) *Int64CountHandle {
149+
registerMetric(descriptor.Name, descriptor.Default)
150+
descriptor.Type = MetricTypeIntCount
151+
descPtr := &descriptor
152+
metricsRegistry[descriptor.Name] = descPtr
153+
return (*Int64CountHandle)(descPtr)
154+
}
155+
156+
// RegisterFloat64Count registers the metric description onto the global
157+
// registry. It returns a typed handle to use to recording data.
158+
//
159+
// NOTE: this function must only be called during initialization time (i.e. in
160+
// an init() function), and is not thread-safe. If multiple metrics are
161+
// registered with the same name, this function will panic.
162+
func RegisterFloat64Count(descriptor MetricDescriptor) *Float64CountHandle {
163+
registerMetric(descriptor.Name, descriptor.Default)
164+
descriptor.Type = MetricTypeFloatCount
165+
descPtr := &descriptor
166+
metricsRegistry[descriptor.Name] = descPtr
167+
return (*Float64CountHandle)(descPtr)
168+
}
169+
170+
// RegisterInt64Histo registers the metric description onto the global registry.
171+
// It returns a typed handle to use to recording data.
172+
//
173+
// NOTE: this function must only be called during initialization time (i.e. in
174+
// an init() function), and is not thread-safe. If multiple metrics are
175+
// registered with the same name, this function will panic.
176+
func RegisterInt64Histo(descriptor MetricDescriptor) *Int64HistoHandle {
177+
registerMetric(descriptor.Name, descriptor.Default)
178+
descriptor.Type = MetricTypeIntHisto
179+
descPtr := &descriptor
180+
metricsRegistry[descriptor.Name] = descPtr
181+
return (*Int64HistoHandle)(descPtr)
182+
}
183+
184+
// RegisterFloat64Histo registers the metric description onto the global
185+
// registry. It returns a typed handle to use to recording data.
186+
//
187+
// NOTE: this function must only be called during initialization time (i.e. in
188+
// an init() function), and is not thread-safe. If multiple metrics are
189+
// registered with the same name, this function will panic.
190+
func RegisterFloat64Histo(descriptor MetricDescriptor) *Float64HistoHandle {
191+
registerMetric(descriptor.Name, descriptor.Default)
192+
descriptor.Type = MetricTypeFloatHisto
193+
descPtr := &descriptor
194+
metricsRegistry[descriptor.Name] = descPtr
195+
return (*Float64HistoHandle)(descPtr)
196+
}
197+
198+
// RegisterInt64Gauge registers the metric description onto the global registry.
199+
// It returns a typed handle to use to recording data.
200+
//
201+
// NOTE: this function must only be called during initialization time (i.e. in
202+
// an init() function), and is not thread-safe. If multiple metrics are
203+
// registered with the same name, this function will panic.
204+
func RegisterInt64Gauge(descriptor MetricDescriptor) *Int64GaugeHandle {
205+
registerMetric(descriptor.Name, descriptor.Default)
206+
descriptor.Type = MetricTypeIntGauge
207+
descPtr := &descriptor
208+
metricsRegistry[descriptor.Name] = descPtr
209+
return (*Int64GaugeHandle)(descPtr)
210+
}
211+
212+
// snapshotMetricsRegistryForTesting snapshots the global data of the metrics
213+
// registry. Registers a cleanup function on the provided testing.T that sets
214+
// the metrics registry to its original state. Only called in testing functions.
215+
func snapshotMetricsRegistryForTesting(t *testing.T) {
216+
oldDefaultMetrics := DefaultMetrics
217+
oldRegisteredMetrics := registeredMetrics
218+
oldMetricsRegistry := metricsRegistry
219+
220+
registeredMetrics = make(map[Metric]bool)
221+
metricsRegistry = make(map[Metric]*MetricDescriptor)
222+
maps.Copy(registeredMetrics, registeredMetrics)
223+
maps.Copy(metricsRegistry, metricsRegistry)
224+
225+
t.Cleanup(func() {
226+
DefaultMetrics = oldDefaultMetrics
227+
registeredMetrics = oldRegisteredMetrics
228+
metricsRegistry = oldMetricsRegistry
229+
})
230+
}

0 commit comments

Comments
 (0)