Skip to content

Commit e14b27c

Browse files
authored
stats/otel: a79 scaffolding to register an async gauge metric and api to record it- part 1 (#8731)
Addresses https://github.com/grpc/proposal/blob/master/A79-non-per-call-metrics-architecture.md This PR creates scaffolding to register an async gauge metric. It adds an AsyncMetricsRecorder interface that defines the api for recording an int64 async gauge metric. RELEASE NOTES: * stats/otel: Add scaffolding to register an async gauge metric. Add an AsyncMetricsRecorder interface that defines the api for recording an int64 async gauge metric.
1 parent 432bda3 commit e14b27c

File tree

3 files changed

+65
-14
lines changed

3 files changed

+65
-14
lines changed

experimental/stats/metricregistry.go

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ const (
7676
MetricTypeFloatHisto
7777
MetricTypeIntGauge
7878
MetricTypeIntUpDownCount
79+
MetricTypeIntAsyncGauge
7980
)
8081

8182
// Int64CountHandle is a typed handle for a int count metric. This handle
@@ -172,6 +173,30 @@ func (h *Int64GaugeHandle) Record(recorder MetricsRecorder, incr int64, labels .
172173
recorder.RecordInt64Gauge(h, incr, labels...)
173174
}
174175

176+
// AsyncMetric is a marker interface for asynchronous metric types.
177+
type AsyncMetric interface {
178+
isAsync()
179+
Descriptor() *MetricDescriptor
180+
}
181+
182+
// Int64AsyncGaugeHandle is a typed handle for an int gauge metric. This handle is
183+
// passed at the recording point in order to know which metric to record on.
184+
type Int64AsyncGaugeHandle MetricDescriptor
185+
186+
// isAsync implements the AsyncMetric interface.
187+
func (h *Int64AsyncGaugeHandle) isAsync() {}
188+
189+
// Descriptor returns the int64 gauge handle typecast to a pointer to a
190+
// MetricDescriptor.
191+
func (h *Int64AsyncGaugeHandle) Descriptor() *MetricDescriptor {
192+
return (*MetricDescriptor)(h)
193+
}
194+
195+
// Record records the int64 gauge value on the metrics recorder provided.
196+
func (h *Int64AsyncGaugeHandle) Record(recorder AsyncMetricsRecorder, value int64, labels ...string) {
197+
recorder.RecordInt64AsyncGauge(h, value, labels...)
198+
}
199+
175200
// registeredMetrics are the registered metric descriptor names.
176201
var registeredMetrics = make(map[string]bool)
177202

@@ -282,6 +307,20 @@ func RegisterInt64UpDownCount(descriptor MetricDescriptor) *Int64UpDownCountHand
282307
return (*Int64UpDownCountHandle)(descPtr)
283308
}
284309

310+
// RegisterInt64AsyncGauge registers the metric description onto the global registry.
311+
// It returns a typed handle to use for recording data.
312+
//
313+
// NOTE: this function must only be called during initialization time (i.e. in
314+
// an init() function), and is not thread-safe. If multiple metrics are
315+
// registered with the same name, this function will panic.
316+
func RegisterInt64AsyncGauge(descriptor MetricDescriptor) *Int64AsyncGaugeHandle {
317+
registerMetric(descriptor.Name, descriptor.Default)
318+
descriptor.Type = MetricTypeIntAsyncGauge
319+
descPtr := &descriptor
320+
metricsRegistry[descriptor.Name] = descPtr
321+
return (*Int64AsyncGaugeHandle)(descPtr)
322+
}
323+
285324
// snapshotMetricsRegistryForTesting snapshots the global data of the metrics
286325
// registry. Returns a cleanup function that sets the metrics registry to its
287326
// original state.

experimental/stats/metricregistry_test.go

Lines changed: 19 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,14 @@ func (s) TestMetricRegistry(t *testing.T) {
117117
OptionalLabels: []string{"int up down counter optional label"},
118118
Default: false,
119119
})
120+
intAsyncGaugeHandle1 := RegisterInt64AsyncGauge(MetricDescriptor{
121+
Name: "simple async gauge",
122+
Description: "the most recent int emitted by test",
123+
Unit: "int",
124+
Labels: []string{"int async gauge label"},
125+
OptionalLabels: []string{"int async gauge optional label"},
126+
Default: false,
127+
})
120128

121129
fmr := newFakeMetricsRecorder(t)
122130

@@ -155,9 +163,13 @@ func (s) TestMetricRegistry(t *testing.T) {
155163
if got := fmr.intValues[intGaugeHandle1.Descriptor()]; got != 7 {
156164
t.Fatalf("fmr.intValues[intGaugeHandle1.MetricDescriptor] got %v, want: %v", got, 7)
157165
}
166+
intAsyncGaugeHandle1.Record(fmr, 9, []string{"some label value", "some optional label value"}...)
167+
if got := fmr.intValues[intAsyncGaugeHandle1.Descriptor()]; got != 9 {
168+
t.Fatalf("fmr.intValues[intAsyncGaugeHandle1.MetricDescriptor] got %v, want: %v", got, 9)
169+
}
158170
}
159171

160-
func TestUpDownCounts(t *testing.T) {
172+
func (s) TestUpDownCounts(t *testing.T) {
161173
cleanup := snapshotMetricsRegistryForTesting()
162174
defer cleanup()
163175

@@ -182,7 +194,7 @@ func TestUpDownCounts(t *testing.T) {
182194
// TestNumerousIntCounts tests numerous int count metrics registered onto the
183195
// metric registry. A component (simulated by test) should be able to record on
184196
// the different registered int count metrics.
185-
func TestNumerousIntCounts(t *testing.T) {
197+
func (s) TestNumerousIntCounts(t *testing.T) {
186198
cleanup := snapshotMetricsRegistryForTesting()
187199
defer cleanup()
188200

@@ -257,18 +269,6 @@ func newFakeMetricsRecorder(t *testing.T) *fakeMetricsRecorder {
257269
intValues: make(map[*MetricDescriptor]int64),
258270
floatValues: make(map[*MetricDescriptor]float64),
259271
}
260-
261-
for _, desc := range metricsRegistry {
262-
switch desc.Type {
263-
case MetricTypeIntCount:
264-
case MetricTypeIntHisto:
265-
case MetricTypeIntGauge:
266-
fmr.intValues[desc] = 0
267-
case MetricTypeFloatCount:
268-
case MetricTypeFloatHisto:
269-
fmr.floatValues[desc] = 0
270-
}
271-
}
272272
return fmr
273273
}
274274

@@ -308,3 +308,8 @@ func (r *fakeMetricsRecorder) RecordInt64UpDownCount(handle *Int64UpDownCountHan
308308
verifyLabels(r.t, handle.Descriptor().Labels, handle.Descriptor().OptionalLabels, labels)
309309
r.intValues[handle.Descriptor()] += incr
310310
}
311+
312+
func (r *fakeMetricsRecorder) RecordInt64AsyncGauge(handle *Int64AsyncGaugeHandle, incr int64, labels ...string) {
313+
verifyLabels(r.t, handle.Descriptor().Labels, handle.Descriptor().OptionalLabels, labels)
314+
r.intValues[handle.Descriptor()] += incr
315+
}

experimental/stats/metrics.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,13 @@ type MetricsRecorder interface {
4343
RecordInt64UpDownCount(handle *Int64UpDownCountHandle, incr int64, labels ...string)
4444
}
4545

46+
// AsyncMetricsRecorder records on asynchronous metrics derived from metric registry.
47+
type AsyncMetricsRecorder interface {
48+
// RecordInt64AsyncGauge records the measurement alongside labels on the int
49+
// count associated with the provided handle asynchronously
50+
RecordInt64AsyncGauge(handle *Int64AsyncGaugeHandle, incr int64, labels ...string)
51+
}
52+
4653
// Metrics is an experimental legacy alias of the now-stable stats.MetricSet.
4754
// Metrics will be deleted in a future release.
4855
type Metrics = stats.MetricSet

0 commit comments

Comments
 (0)