snapshot of https://github.com/grpc/grpc-go.git (master, 2026-09-15; blobs > 131072B omitted)
Some checks failed
codecov / upload (push) Failing after 13m59s
CodeQL / Analyze (push) Failing after 1s
Testing / vet-proto (push) Failing after 1s
Testing / tests (arm, latest) (push) Waiting to run
Testing / extras (latest) (push) Failing after 1s
Testing / static checks (latest-1) (push) Failing after 1s
Testing / tests (-race, latest) (push) Failing after 1s
Testing / tests (i386, latest) (push) Failing after 1s
Testing / tests (latest) (push) Failing after 1s
Testing / tests (latest-1) (push) Failing after 1s
Lock Threads / lock (push) Failing after 1s
govulncheck / govulncheck (.) (push) Failing after 1s
govulncheck / govulncheck (./cmd/protoc-gen-go-grpc) (push) Failing after 1s
govulncheck / govulncheck (./gcp/observability) (push) Failing after 1s
govulncheck / govulncheck (./security/advancedtls) (push) Failing after 1s
govulncheck / govulncheck (./stats/opencensus) (push) Failing after 1s
Stale bot / stale (push) Failing after 1s
Some checks failed
codecov / upload (push) Failing after 13m59s
CodeQL / Analyze (push) Failing after 1s
Testing / vet-proto (push) Failing after 1s
Testing / tests (arm, latest) (push) Waiting to run
Testing / extras (latest) (push) Failing after 1s
Testing / static checks (latest-1) (push) Failing after 1s
Testing / tests (-race, latest) (push) Failing after 1s
Testing / tests (i386, latest) (push) Failing after 1s
Testing / tests (latest) (push) Failing after 1s
Testing / tests (latest-1) (push) Failing after 1s
Lock Threads / lock (push) Failing after 1s
govulncheck / govulncheck (.) (push) Failing after 1s
govulncheck / govulncheck (./cmd/protoc-gen-go-grpc) (push) Failing after 1s
govulncheck / govulncheck (./gcp/observability) (push) Failing after 1s
govulncheck / govulncheck (./security/advancedtls) (push) Failing after 1s
govulncheck / govulncheck (./stats/opencensus) (push) Failing after 1s
Stale bot / stale (push) Failing after 1s
This commit is contained in:
342
experimental/stats/metricregistry.go
Normal file
342
experimental/stats/metricregistry.go
Normal file
@@ -0,0 +1,342 @@
|
||||
/*
|
||||
*
|
||||
* Copyright 2024 gRPC authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
package stats
|
||||
|
||||
import (
|
||||
"maps"
|
||||
|
||||
"google.golang.org/grpc/grpclog"
|
||||
"google.golang.org/grpc/internal"
|
||||
"google.golang.org/grpc/stats"
|
||||
)
|
||||
|
||||
func init() {
|
||||
internal.SnapshotMetricRegistryForTesting = snapshotMetricsRegistryForTesting
|
||||
}
|
||||
|
||||
var logger = grpclog.Component("metrics-registry")
|
||||
|
||||
// DefaultMetrics are the default metrics registered through global metrics
|
||||
// registry. This is written to at initialization time only, and is read only
|
||||
// after initialization.
|
||||
var DefaultMetrics = stats.NewMetricSet()
|
||||
|
||||
// MetricDescriptor is the data for a registered metric.
|
||||
type MetricDescriptor struct {
|
||||
// The name of this metric. This name must be unique across the whole binary
|
||||
// (including any per call metrics). See
|
||||
// https://github.com/grpc/proposal/blob/master/A79-non-per-call-metrics-architecture.md#metric-instrument-naming-conventions
|
||||
// for metric naming conventions.
|
||||
Name string
|
||||
// The description of this metric.
|
||||
Description string
|
||||
// The unit (e.g. entries, seconds) of this metric.
|
||||
Unit string
|
||||
// The required label keys for this metric. These are intended to
|
||||
// metrics emitted from a stats handler.
|
||||
Labels []string
|
||||
// The optional label keys for this metric. These are intended to attached
|
||||
// to metrics emitted from a stats handler if configured.
|
||||
OptionalLabels []string
|
||||
// Whether this metric is on by default.
|
||||
Default bool
|
||||
// The type of metric. This is set by the metric registry, and not intended
|
||||
// to be set by a component registering a metric.
|
||||
Type MetricType
|
||||
// Bounds are the bounds of this metric. This only applies to histogram
|
||||
// metrics. If unset or set with length 0, stats handlers will fall back to
|
||||
// default bounds.
|
||||
Bounds []float64
|
||||
}
|
||||
|
||||
// MetricType is the type of metric.
|
||||
type MetricType int
|
||||
|
||||
// Type of metric supported by this instrument registry.
|
||||
const (
|
||||
MetricTypeIntCount MetricType = iota
|
||||
MetricTypeFloatCount
|
||||
MetricTypeIntHisto
|
||||
MetricTypeFloatHisto
|
||||
MetricTypeIntGauge
|
||||
MetricTypeIntUpDownCount
|
||||
MetricTypeIntAsyncGauge
|
||||
)
|
||||
|
||||
// Int64CountHandle is a typed handle for a int count metric. This handle
|
||||
// is passed at the recording point in order to know which metric to record
|
||||
// on.
|
||||
type Int64CountHandle MetricDescriptor
|
||||
|
||||
// Descriptor returns the int64 count handle typecast to a pointer to a
|
||||
// MetricDescriptor.
|
||||
func (h *Int64CountHandle) Descriptor() *MetricDescriptor {
|
||||
return (*MetricDescriptor)(h)
|
||||
}
|
||||
|
||||
// Record records the int64 count value on the metrics recorder provided.
|
||||
func (h *Int64CountHandle) Record(recorder MetricsRecorder, incr int64, labels ...string) {
|
||||
recorder.RecordInt64Count(h, incr, labels...)
|
||||
}
|
||||
|
||||
// Int64UpDownCountHandle is a typed handle for an int up-down counter metric.
|
||||
// This handle is passed at the recording point in order to know which metric
|
||||
// to record on.
|
||||
type Int64UpDownCountHandle MetricDescriptor
|
||||
|
||||
// Descriptor returns the int64 up-down counter handle typecast to a pointer to a
|
||||
// MetricDescriptor.
|
||||
func (h *Int64UpDownCountHandle) Descriptor() *MetricDescriptor {
|
||||
return (*MetricDescriptor)(h)
|
||||
}
|
||||
|
||||
// Record records the int64 up-down counter value on the metrics recorder provided.
|
||||
// The value 'v' can be positive to increment or negative to decrement.
|
||||
func (h *Int64UpDownCountHandle) Record(recorder MetricsRecorder, v int64, labels ...string) {
|
||||
recorder.RecordInt64UpDownCount(h, v, labels...)
|
||||
}
|
||||
|
||||
// Float64CountHandle is a typed handle for a float count metric. This handle is
|
||||
// passed at the recording point in order to know which metric to record on.
|
||||
type Float64CountHandle MetricDescriptor
|
||||
|
||||
// Descriptor returns the float64 count handle typecast to a pointer to a
|
||||
// MetricDescriptor.
|
||||
func (h *Float64CountHandle) Descriptor() *MetricDescriptor {
|
||||
return (*MetricDescriptor)(h)
|
||||
}
|
||||
|
||||
// Record records the float64 count value on the metrics recorder provided.
|
||||
func (h *Float64CountHandle) Record(recorder MetricsRecorder, incr float64, labels ...string) {
|
||||
recorder.RecordFloat64Count(h, incr, labels...)
|
||||
}
|
||||
|
||||
// Int64HistoHandle is a typed handle for an int histogram metric. This handle
|
||||
// is passed at the recording point in order to know which metric to record on.
|
||||
type Int64HistoHandle MetricDescriptor
|
||||
|
||||
// Descriptor returns the int64 histo handle typecast to a pointer to a
|
||||
// MetricDescriptor.
|
||||
func (h *Int64HistoHandle) Descriptor() *MetricDescriptor {
|
||||
return (*MetricDescriptor)(h)
|
||||
}
|
||||
|
||||
// Record records the int64 histo value on the metrics recorder provided.
|
||||
func (h *Int64HistoHandle) Record(recorder MetricsRecorder, incr int64, labels ...string) {
|
||||
recorder.RecordInt64Histo(h, incr, labels...)
|
||||
}
|
||||
|
||||
// Float64HistoHandle is a typed handle for a float histogram metric. This
|
||||
// handle is passed at the recording point in order to know which metric to
|
||||
// record on.
|
||||
type Float64HistoHandle MetricDescriptor
|
||||
|
||||
// Descriptor returns the float64 histo handle typecast to a pointer to a
|
||||
// MetricDescriptor.
|
||||
func (h *Float64HistoHandle) Descriptor() *MetricDescriptor {
|
||||
return (*MetricDescriptor)(h)
|
||||
}
|
||||
|
||||
// Record records the float64 histo value on the metrics recorder provided.
|
||||
func (h *Float64HistoHandle) Record(recorder MetricsRecorder, incr float64, labels ...string) {
|
||||
recorder.RecordFloat64Histo(h, incr, labels...)
|
||||
}
|
||||
|
||||
// Int64GaugeHandle is a typed handle for an int gauge metric. This handle is
|
||||
// passed at the recording point in order to know which metric to record on.
|
||||
type Int64GaugeHandle MetricDescriptor
|
||||
|
||||
// Descriptor returns the int64 gauge handle typecast to a pointer to a
|
||||
// MetricDescriptor.
|
||||
func (h *Int64GaugeHandle) Descriptor() *MetricDescriptor {
|
||||
return (*MetricDescriptor)(h)
|
||||
}
|
||||
|
||||
// Record records the int64 histo value on the metrics recorder provided.
|
||||
func (h *Int64GaugeHandle) Record(recorder MetricsRecorder, incr int64, labels ...string) {
|
||||
recorder.RecordInt64Gauge(h, incr, labels...)
|
||||
}
|
||||
|
||||
// AsyncMetric is a marker interface for asynchronous metric types.
|
||||
type AsyncMetric interface {
|
||||
isAsync()
|
||||
Descriptor() *MetricDescriptor
|
||||
}
|
||||
|
||||
// Int64AsyncGaugeHandle is a typed handle for an int gauge metric. This handle is
|
||||
// passed at the recording point in order to know which metric to record on.
|
||||
type Int64AsyncGaugeHandle MetricDescriptor
|
||||
|
||||
// isAsync implements the AsyncMetric interface.
|
||||
func (h *Int64AsyncGaugeHandle) isAsync() {}
|
||||
|
||||
// Descriptor returns the int64 gauge handle typecast to a pointer to a
|
||||
// MetricDescriptor.
|
||||
func (h *Int64AsyncGaugeHandle) Descriptor() *MetricDescriptor {
|
||||
return (*MetricDescriptor)(h)
|
||||
}
|
||||
|
||||
// Record records the int64 gauge value on the metrics recorder provided.
|
||||
func (h *Int64AsyncGaugeHandle) Record(recorder AsyncMetricsRecorder, value int64, labels ...string) {
|
||||
recorder.RecordInt64AsyncGauge(h, value, labels...)
|
||||
}
|
||||
|
||||
// registeredMetrics are the registered metric descriptor names.
|
||||
var registeredMetrics = make(map[string]bool)
|
||||
|
||||
// metricsRegistry contains all of the registered metrics.
|
||||
//
|
||||
// This is written to only at init time, and read only after that.
|
||||
var metricsRegistry = make(map[string]*MetricDescriptor)
|
||||
|
||||
// DescriptorForMetric returns the MetricDescriptor from the global registry.
|
||||
//
|
||||
// Returns nil if MetricDescriptor not present.
|
||||
func DescriptorForMetric(metricName string) *MetricDescriptor {
|
||||
return metricsRegistry[metricName]
|
||||
}
|
||||
|
||||
func registerMetric(metricName string, def bool) {
|
||||
if registeredMetrics[metricName] {
|
||||
logger.Fatalf("metric %v already registered", metricName)
|
||||
}
|
||||
registeredMetrics[metricName] = true
|
||||
if def {
|
||||
DefaultMetrics = DefaultMetrics.Add(metricName)
|
||||
}
|
||||
}
|
||||
|
||||
// RegisterInt64Count registers the metric description onto the global registry.
|
||||
// It returns a typed handle to use to recording data.
|
||||
//
|
||||
// NOTE: this function must only be called during initialization time (i.e. in
|
||||
// an init() function), and is not thread-safe. If multiple metrics are
|
||||
// registered with the same name, this function will panic.
|
||||
func RegisterInt64Count(descriptor MetricDescriptor) *Int64CountHandle {
|
||||
registerMetric(descriptor.Name, descriptor.Default)
|
||||
descriptor.Type = MetricTypeIntCount
|
||||
descPtr := &descriptor
|
||||
metricsRegistry[descriptor.Name] = descPtr
|
||||
return (*Int64CountHandle)(descPtr)
|
||||
}
|
||||
|
||||
// RegisterFloat64Count registers the metric description onto the global
|
||||
// registry. It returns a typed handle to use to recording data.
|
||||
//
|
||||
// NOTE: this function must only be called during initialization time (i.e. in
|
||||
// an init() function), and is not thread-safe. If multiple metrics are
|
||||
// registered with the same name, this function will panic.
|
||||
func RegisterFloat64Count(descriptor MetricDescriptor) *Float64CountHandle {
|
||||
registerMetric(descriptor.Name, descriptor.Default)
|
||||
descriptor.Type = MetricTypeFloatCount
|
||||
descPtr := &descriptor
|
||||
metricsRegistry[descriptor.Name] = descPtr
|
||||
return (*Float64CountHandle)(descPtr)
|
||||
}
|
||||
|
||||
// RegisterInt64Histo registers the metric description onto the global registry.
|
||||
// It returns a typed handle to use to recording data.
|
||||
//
|
||||
// NOTE: this function must only be called during initialization time (i.e. in
|
||||
// an init() function), and is not thread-safe. If multiple metrics are
|
||||
// registered with the same name, this function will panic.
|
||||
func RegisterInt64Histo(descriptor MetricDescriptor) *Int64HistoHandle {
|
||||
registerMetric(descriptor.Name, descriptor.Default)
|
||||
descriptor.Type = MetricTypeIntHisto
|
||||
descPtr := &descriptor
|
||||
metricsRegistry[descriptor.Name] = descPtr
|
||||
return (*Int64HistoHandle)(descPtr)
|
||||
}
|
||||
|
||||
// RegisterFloat64Histo registers the metric description onto the global
|
||||
// registry. It returns a typed handle to use to recording data.
|
||||
//
|
||||
// NOTE: this function must only be called during initialization time (i.e. in
|
||||
// an init() function), and is not thread-safe. If multiple metrics are
|
||||
// registered with the same name, this function will panic.
|
||||
func RegisterFloat64Histo(descriptor MetricDescriptor) *Float64HistoHandle {
|
||||
registerMetric(descriptor.Name, descriptor.Default)
|
||||
descriptor.Type = MetricTypeFloatHisto
|
||||
descPtr := &descriptor
|
||||
metricsRegistry[descriptor.Name] = descPtr
|
||||
return (*Float64HistoHandle)(descPtr)
|
||||
}
|
||||
|
||||
// RegisterInt64Gauge registers the metric description onto the global registry.
|
||||
// It returns a typed handle to use to recording data.
|
||||
//
|
||||
// NOTE: this function must only be called during initialization time (i.e. in
|
||||
// an init() function), and is not thread-safe. If multiple metrics are
|
||||
// registered with the same name, this function will panic.
|
||||
func RegisterInt64Gauge(descriptor MetricDescriptor) *Int64GaugeHandle {
|
||||
registerMetric(descriptor.Name, descriptor.Default)
|
||||
descriptor.Type = MetricTypeIntGauge
|
||||
descPtr := &descriptor
|
||||
metricsRegistry[descriptor.Name] = descPtr
|
||||
return (*Int64GaugeHandle)(descPtr)
|
||||
}
|
||||
|
||||
// RegisterInt64UpDownCount registers the metric description onto the global registry.
|
||||
// It returns a typed handle to use for recording data.
|
||||
//
|
||||
// NOTE: this function must only be called during initialization time (i.e. in
|
||||
// an init() function), and is not thread-safe. If multiple metrics are
|
||||
// registered with the same name, this function will panic.
|
||||
func RegisterInt64UpDownCount(descriptor MetricDescriptor) *Int64UpDownCountHandle {
|
||||
registerMetric(descriptor.Name, descriptor.Default)
|
||||
// Set the specific metric type for the up-down counter
|
||||
descriptor.Type = MetricTypeIntUpDownCount
|
||||
descPtr := &descriptor
|
||||
metricsRegistry[descriptor.Name] = descPtr
|
||||
return (*Int64UpDownCountHandle)(descPtr)
|
||||
}
|
||||
|
||||
// RegisterInt64AsyncGauge registers the metric description onto the global registry.
|
||||
// It returns a typed handle to use for recording data.
|
||||
//
|
||||
// NOTE: this function must only be called during initialization time (i.e. in
|
||||
// an init() function), and is not thread-safe. If multiple metrics are
|
||||
// registered with the same name, this function will panic.
|
||||
func RegisterInt64AsyncGauge(descriptor MetricDescriptor) *Int64AsyncGaugeHandle {
|
||||
registerMetric(descriptor.Name, descriptor.Default)
|
||||
descriptor.Type = MetricTypeIntAsyncGauge
|
||||
descPtr := &descriptor
|
||||
metricsRegistry[descriptor.Name] = descPtr
|
||||
return (*Int64AsyncGaugeHandle)(descPtr)
|
||||
}
|
||||
|
||||
// snapshotMetricsRegistryForTesting snapshots the global data of the metrics
|
||||
// registry. Returns a cleanup function that sets the metrics registry to its
|
||||
// original state.
|
||||
func snapshotMetricsRegistryForTesting() func() {
|
||||
oldDefaultMetrics := DefaultMetrics
|
||||
oldRegisteredMetrics := registeredMetrics
|
||||
oldMetricsRegistry := metricsRegistry
|
||||
|
||||
registeredMetrics = make(map[string]bool)
|
||||
metricsRegistry = make(map[string]*MetricDescriptor)
|
||||
maps.Copy(registeredMetrics, registeredMetrics)
|
||||
maps.Copy(metricsRegistry, metricsRegistry)
|
||||
|
||||
return func() {
|
||||
DefaultMetrics = oldDefaultMetrics
|
||||
registeredMetrics = oldRegisteredMetrics
|
||||
metricsRegistry = oldMetricsRegistry
|
||||
}
|
||||
}
|
||||
318
experimental/stats/metricregistry_test.go
Normal file
318
experimental/stats/metricregistry_test.go
Normal file
@@ -0,0 +1,318 @@
|
||||
/*
|
||||
*
|
||||
* Copyright 2024 gRPC authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
package stats
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"google.golang.org/grpc/internal/grpctest"
|
||||
)
|
||||
|
||||
type s struct {
|
||||
grpctest.Tester
|
||||
}
|
||||
|
||||
func Test(t *testing.T) {
|
||||
grpctest.RunSubTests(t, s{})
|
||||
}
|
||||
|
||||
// TestPanic tests that registering two metrics with the same name across any
|
||||
// type of metric triggers a panic.
|
||||
func (s) TestPanic(t *testing.T) {
|
||||
cleanup := snapshotMetricsRegistryForTesting()
|
||||
defer cleanup()
|
||||
|
||||
want := "metric simple counter already registered"
|
||||
defer func() {
|
||||
if r := recover(); !strings.Contains(fmt.Sprint(r), want) {
|
||||
t.Errorf("expected panic contains %q, got %q", want, r)
|
||||
}
|
||||
}()
|
||||
desc := MetricDescriptor{
|
||||
// Type is not expected to be set from the registerer, but meant to be
|
||||
// set by the metric registry.
|
||||
Name: "simple counter",
|
||||
Description: "number of times recorded on tests",
|
||||
Unit: "{call}",
|
||||
}
|
||||
RegisterInt64Count(desc)
|
||||
RegisterInt64Gauge(desc)
|
||||
}
|
||||
|
||||
// TestInstrumentRegistry tests the metric registry. It registers testing only
|
||||
// metrics using the metric registry, and creates a fake metrics recorder which
|
||||
// uses these metrics. Using the handles returned from the metric registry, this
|
||||
// test records stats using the fake metrics recorder. Then, the test verifies
|
||||
// the persisted metrics data in the metrics recorder is what is expected. Thus,
|
||||
// this tests the interactions between the metrics recorder and the metrics
|
||||
// registry.
|
||||
func (s) TestMetricRegistry(t *testing.T) {
|
||||
cleanup := snapshotMetricsRegistryForTesting()
|
||||
defer cleanup()
|
||||
|
||||
intCountHandle1 := RegisterInt64Count(MetricDescriptor{
|
||||
Name: "simple counter",
|
||||
Description: "sum of all emissions from tests",
|
||||
Unit: "int",
|
||||
Labels: []string{"int counter label"},
|
||||
OptionalLabels: []string{"int counter optional label"},
|
||||
Default: false,
|
||||
})
|
||||
floatCountHandle1 := RegisterFloat64Count(MetricDescriptor{
|
||||
Name: "float counter",
|
||||
Description: "sum of all emissions from tests",
|
||||
Unit: "float",
|
||||
Labels: []string{"float counter label"},
|
||||
OptionalLabels: []string{"float counter optional label"},
|
||||
Default: false,
|
||||
})
|
||||
intHistoHandle1 := RegisterInt64Histo(MetricDescriptor{
|
||||
Name: "int histo",
|
||||
Description: "sum of all emissions from tests",
|
||||
Unit: "int",
|
||||
Labels: []string{"int histo label"},
|
||||
OptionalLabels: []string{"int histo optional label"},
|
||||
Default: false,
|
||||
})
|
||||
floatHistoHandle1 := RegisterFloat64Histo(MetricDescriptor{
|
||||
Name: "float histo",
|
||||
Description: "sum of all emissions from tests",
|
||||
Unit: "float",
|
||||
Labels: []string{"float histo label"},
|
||||
OptionalLabels: []string{"float histo optional label"},
|
||||
Default: false,
|
||||
})
|
||||
intGaugeHandle1 := RegisterInt64Gauge(MetricDescriptor{
|
||||
Name: "simple gauge",
|
||||
Description: "the most recent int emitted by test",
|
||||
Unit: "int",
|
||||
Labels: []string{"int gauge label"},
|
||||
OptionalLabels: []string{"int gauge optional label"},
|
||||
Default: false,
|
||||
})
|
||||
intUpDownCountHandle1 := RegisterInt64UpDownCount(MetricDescriptor{
|
||||
Name: "simple up down counter",
|
||||
Description: "current number of emissions from tests",
|
||||
Unit: "int",
|
||||
Labels: []string{"int up down counter label"},
|
||||
OptionalLabels: []string{"int up down counter optional label"},
|
||||
Default: false,
|
||||
})
|
||||
intAsyncGaugeHandle1 := RegisterInt64AsyncGauge(MetricDescriptor{
|
||||
Name: "simple async gauge",
|
||||
Description: "the most recent int emitted by test",
|
||||
Unit: "int",
|
||||
Labels: []string{"int async gauge label"},
|
||||
OptionalLabels: []string{"int async gauge optional label"},
|
||||
Default: false,
|
||||
})
|
||||
|
||||
fmr := newFakeMetricsRecorder(t)
|
||||
|
||||
intCountHandle1.Record(fmr, 1, []string{"some label value", "some optional label value"}...)
|
||||
// The Metric Descriptor in the handle should be able to identify the metric
|
||||
// information. This is the key passed to metrics recorder to identify
|
||||
// metric.
|
||||
if got := fmr.intValues[intCountHandle1.Descriptor()]; got != 1 {
|
||||
t.Fatalf("fmr.intValues[intCountHandle1.MetricDescriptor] got %v, want: %v", got, 1)
|
||||
}
|
||||
|
||||
intUpDownCountHandle1.Record(fmr, 2, []string{"some label value", "some optional label value"}...)
|
||||
// The Metric Descriptor in the handle should be able to identify the metric
|
||||
// information. This is the key passed to metrics recorder to identify
|
||||
// metric.
|
||||
if got := fmr.intValues[intUpDownCountHandle1.Descriptor()]; got != 2 {
|
||||
t.Fatalf("fmr.intValues[intUpDownCountHandle1.MetricDescriptor] got %v, want: %v", got, 2)
|
||||
}
|
||||
|
||||
floatCountHandle1.Record(fmr, 1.2, []string{"some label value", "some optional label value"}...)
|
||||
if got := fmr.floatValues[floatCountHandle1.Descriptor()]; got != 1.2 {
|
||||
t.Fatalf("fmr.floatValues[floatCountHandle1.MetricDescriptor] got %v, want: %v", got, 1.2)
|
||||
}
|
||||
|
||||
intHistoHandle1.Record(fmr, 3, []string{"some label value", "some optional label value"}...)
|
||||
if got := fmr.intValues[intHistoHandle1.Descriptor()]; got != 3 {
|
||||
t.Fatalf("fmr.intValues[intHistoHandle1.MetricDescriptor] got %v, want: %v", got, 3)
|
||||
}
|
||||
|
||||
floatHistoHandle1.Record(fmr, 4.3, []string{"some label value", "some optional label value"}...)
|
||||
if got := fmr.floatValues[floatHistoHandle1.Descriptor()]; got != 4.3 {
|
||||
t.Fatalf("fmr.floatValues[floatHistoHandle1.MetricDescriptor] got %v, want: %v", got, 4.3)
|
||||
}
|
||||
|
||||
intGaugeHandle1.Record(fmr, 7, []string{"some label value", "some optional label value"}...)
|
||||
if got := fmr.intValues[intGaugeHandle1.Descriptor()]; got != 7 {
|
||||
t.Fatalf("fmr.intValues[intGaugeHandle1.MetricDescriptor] got %v, want: %v", got, 7)
|
||||
}
|
||||
intAsyncGaugeHandle1.Record(fmr, 9, []string{"some label value", "some optional label value"}...)
|
||||
if got := fmr.intValues[intAsyncGaugeHandle1.Descriptor()]; got != 9 {
|
||||
t.Fatalf("fmr.intValues[intAsyncGaugeHandle1.MetricDescriptor] got %v, want: %v", got, 9)
|
||||
}
|
||||
}
|
||||
|
||||
func (s) TestUpDownCounts(t *testing.T) {
|
||||
cleanup := snapshotMetricsRegistryForTesting()
|
||||
defer cleanup()
|
||||
|
||||
intUpDownCountHandle1 := RegisterInt64UpDownCount(MetricDescriptor{
|
||||
Name: "simple up down counter",
|
||||
Description: "current number of emissions from tests",
|
||||
Unit: "int",
|
||||
Labels: []string{"int up down counter label"},
|
||||
OptionalLabels: []string{"int up down counter optional label"},
|
||||
Default: false,
|
||||
})
|
||||
|
||||
fmr := newFakeMetricsRecorder(t)
|
||||
intUpDownCountHandle1.Record(fmr, 2, []string{"up down value", "some optional label value"}...)
|
||||
intUpDownCountHandle1.Record(fmr, -1, []string{"up down value", "some optional label value"}...)
|
||||
|
||||
if got := fmr.intValues[intUpDownCountHandle1.Descriptor()]; got != 1 {
|
||||
t.Fatalf("fmr.intValues[intUpDownCountHandle1.MetricDescriptor] got %v, want: %v", got, 1)
|
||||
}
|
||||
}
|
||||
|
||||
// TestNumerousIntCounts tests numerous int count metrics registered onto the
|
||||
// metric registry. A component (simulated by test) should be able to record on
|
||||
// the different registered int count metrics.
|
||||
func (s) TestNumerousIntCounts(t *testing.T) {
|
||||
cleanup := snapshotMetricsRegistryForTesting()
|
||||
defer cleanup()
|
||||
|
||||
intCountHandle1 := RegisterInt64Count(MetricDescriptor{
|
||||
Name: "int counter",
|
||||
Description: "sum of all emissions from tests",
|
||||
Unit: "int",
|
||||
Labels: []string{"int counter label"},
|
||||
OptionalLabels: []string{"int counter optional label"},
|
||||
Default: false,
|
||||
})
|
||||
intCountHandle2 := RegisterInt64Count(MetricDescriptor{
|
||||
Name: "int counter 2",
|
||||
Description: "sum of all emissions from tests",
|
||||
Unit: "int",
|
||||
Labels: []string{"int counter label"},
|
||||
OptionalLabels: []string{"int counter optional label"},
|
||||
Default: false,
|
||||
})
|
||||
intCountHandle3 := RegisterInt64Count(MetricDescriptor{
|
||||
Name: "int counter 3",
|
||||
Description: "sum of all emissions from tests",
|
||||
Unit: "int",
|
||||
Labels: []string{"int counter label"},
|
||||
OptionalLabels: []string{"int counter optional label"},
|
||||
Default: false,
|
||||
})
|
||||
|
||||
fmr := newFakeMetricsRecorder(t)
|
||||
|
||||
intCountHandle1.Record(fmr, 1, []string{"some label value", "some optional label value"}...)
|
||||
got := []int64{fmr.intValues[intCountHandle1.Descriptor()], fmr.intValues[intCountHandle2.Descriptor()], fmr.intValues[intCountHandle3.Descriptor()]}
|
||||
want := []int64{1, 0, 0}
|
||||
if diff := cmp.Diff(got, want); diff != "" {
|
||||
t.Fatalf("fmr.intValues (-got, +want): %v", diff)
|
||||
}
|
||||
|
||||
intCountHandle2.Record(fmr, 1, []string{"some label value", "some optional label value"}...)
|
||||
got = []int64{fmr.intValues[intCountHandle1.Descriptor()], fmr.intValues[intCountHandle2.Descriptor()], fmr.intValues[intCountHandle3.Descriptor()]}
|
||||
want = []int64{1, 1, 0}
|
||||
if diff := cmp.Diff(got, want); diff != "" {
|
||||
t.Fatalf("fmr.intValues (-got, +want): %v", diff)
|
||||
}
|
||||
|
||||
intCountHandle3.Record(fmr, 1, []string{"some label value", "some optional label value"}...)
|
||||
got = []int64{fmr.intValues[intCountHandle1.Descriptor()], fmr.intValues[intCountHandle2.Descriptor()], fmr.intValues[intCountHandle3.Descriptor()]}
|
||||
want = []int64{1, 1, 1}
|
||||
if diff := cmp.Diff(got, want); diff != "" {
|
||||
t.Fatalf("fmr.intValues (-got, +want): %v", diff)
|
||||
}
|
||||
|
||||
intCountHandle3.Record(fmr, 1, []string{"some label value", "some optional label value"}...)
|
||||
got = []int64{fmr.intValues[intCountHandle1.Descriptor()], fmr.intValues[intCountHandle2.Descriptor()], fmr.intValues[intCountHandle3.Descriptor()]}
|
||||
want = []int64{1, 1, 2}
|
||||
if diff := cmp.Diff(got, want); diff != "" {
|
||||
t.Fatalf("fmr.intValues (-got, +want): %v", diff)
|
||||
}
|
||||
}
|
||||
|
||||
type fakeMetricsRecorder struct {
|
||||
UnimplementedMetricsRecorder
|
||||
t *testing.T
|
||||
|
||||
intValues map[*MetricDescriptor]int64
|
||||
floatValues map[*MetricDescriptor]float64
|
||||
}
|
||||
|
||||
// newFakeMetricsRecorder returns a fake metrics recorder based off the current
|
||||
// state of global metric registry.
|
||||
func newFakeMetricsRecorder(t *testing.T) *fakeMetricsRecorder {
|
||||
fmr := &fakeMetricsRecorder{
|
||||
t: t,
|
||||
intValues: make(map[*MetricDescriptor]int64),
|
||||
floatValues: make(map[*MetricDescriptor]float64),
|
||||
}
|
||||
return fmr
|
||||
}
|
||||
|
||||
// verifyLabels verifies that the labels received are of the expected length.
|
||||
func verifyLabels(t *testing.T, labelsWant []string, optionalLabelsWant []string, labelsGot []string) {
|
||||
if len(labelsWant)+len(optionalLabelsWant) != len(labelsGot) {
|
||||
t.Fatalf("length of optional labels expected did not match got %v, want %v", len(labelsGot), len(labelsWant)+len(optionalLabelsWant))
|
||||
}
|
||||
}
|
||||
|
||||
func (r *fakeMetricsRecorder) RecordInt64Count(handle *Int64CountHandle, incr int64, labels ...string) {
|
||||
verifyLabels(r.t, handle.Descriptor().Labels, handle.Descriptor().OptionalLabels, labels)
|
||||
r.intValues[handle.Descriptor()] += incr
|
||||
}
|
||||
|
||||
func (r *fakeMetricsRecorder) RecordFloat64Count(handle *Float64CountHandle, incr float64, labels ...string) {
|
||||
verifyLabels(r.t, handle.Descriptor().Labels, handle.Descriptor().OptionalLabels, labels)
|
||||
r.floatValues[handle.Descriptor()] += incr
|
||||
}
|
||||
|
||||
func (r *fakeMetricsRecorder) RecordInt64Histo(handle *Int64HistoHandle, incr int64, labels ...string) {
|
||||
verifyLabels(r.t, handle.Descriptor().Labels, handle.Descriptor().OptionalLabels, labels)
|
||||
r.intValues[handle.Descriptor()] += incr
|
||||
}
|
||||
|
||||
func (r *fakeMetricsRecorder) RecordFloat64Histo(handle *Float64HistoHandle, incr float64, labels ...string) {
|
||||
verifyLabels(r.t, handle.Descriptor().Labels, handle.Descriptor().OptionalLabels, labels)
|
||||
r.floatValues[handle.Descriptor()] += incr
|
||||
}
|
||||
|
||||
func (r *fakeMetricsRecorder) RecordInt64Gauge(handle *Int64GaugeHandle, incr int64, labels ...string) {
|
||||
verifyLabels(r.t, handle.Descriptor().Labels, handle.Descriptor().OptionalLabels, labels)
|
||||
r.intValues[handle.Descriptor()] += incr
|
||||
}
|
||||
|
||||
func (r *fakeMetricsRecorder) RecordInt64UpDownCount(handle *Int64UpDownCountHandle, incr int64, labels ...string) {
|
||||
verifyLabels(r.t, handle.Descriptor().Labels, handle.Descriptor().OptionalLabels, labels)
|
||||
r.intValues[handle.Descriptor()] += incr
|
||||
}
|
||||
|
||||
func (r *fakeMetricsRecorder) RecordInt64AsyncGauge(handle *Int64AsyncGaugeHandle, val int64, labels ...string) {
|
||||
verifyLabels(r.t, handle.Descriptor().Labels, handle.Descriptor().OptionalLabels, labels)
|
||||
// Async gauges in OTel are "Observer" instruments; they report
|
||||
// the current state of the world every cycle, they do not accumulate deltas.
|
||||
r.intValues[handle.Descriptor()] = val
|
||||
}
|
||||
148
experimental/stats/metrics.go
Normal file
148
experimental/stats/metrics.go
Normal file
@@ -0,0 +1,148 @@
|
||||
/*
|
||||
*
|
||||
* Copyright 2024 gRPC authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
// Package stats contains experimental metrics/stats API's.
|
||||
package stats
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"google.golang.org/grpc/internal"
|
||||
"google.golang.org/grpc/stats"
|
||||
)
|
||||
|
||||
type customLabelKey struct{}
|
||||
|
||||
// NewContextWithCustomLabel returns a new context with the provided custom label
|
||||
// attached. The label will be propagated to all metric instruments specified in gRFC A108.
|
||||
func NewContextWithCustomLabel(ctx context.Context, label string) context.Context {
|
||||
return context.WithValue(ctx, customLabelKey{}, label)
|
||||
}
|
||||
|
||||
// CustomLabelFromContext returns the custom label from the context if it exists.
|
||||
// If the custom label is not present, it returns an empty string.
|
||||
func CustomLabelFromContext(ctx context.Context) string {
|
||||
label, _ := ctx.Value(customLabelKey{}).(string)
|
||||
return label
|
||||
}
|
||||
|
||||
// MetricsRecorder records on metrics derived from metric registry.
|
||||
// Implementors must embed UnimplementedMetricsRecorder.
|
||||
type MetricsRecorder interface {
|
||||
// RecordInt64Count records the measurement alongside labels on the int
|
||||
// count associated with the provided handle.
|
||||
RecordInt64Count(handle *Int64CountHandle, incr int64, labels ...string)
|
||||
// RecordFloat64Count records the measurement alongside labels on the float
|
||||
// count associated with the provided handle.
|
||||
RecordFloat64Count(handle *Float64CountHandle, incr float64, labels ...string)
|
||||
// RecordInt64Histo records the measurement alongside labels on the int
|
||||
// histo associated with the provided handle.
|
||||
RecordInt64Histo(handle *Int64HistoHandle, incr int64, labels ...string)
|
||||
// RecordFloat64Histo records the measurement alongside labels on the float
|
||||
// histo associated with the provided handle.
|
||||
RecordFloat64Histo(handle *Float64HistoHandle, incr float64, labels ...string)
|
||||
// RecordInt64Gauge records the measurement alongside labels on the int
|
||||
// gauge associated with the provided handle.
|
||||
RecordInt64Gauge(handle *Int64GaugeHandle, incr int64, labels ...string)
|
||||
// RecordInt64UpDownCounter records the measurement alongside labels on the int
|
||||
// count associated with the provided handle.
|
||||
RecordInt64UpDownCount(handle *Int64UpDownCountHandle, incr int64, labels ...string)
|
||||
// RegisterAsyncReporter registers a reporter to produce metric values for
|
||||
// only the listed descriptors. The returned function must be called when
|
||||
// the metrics are no longer needed, which will remove the reporter. The
|
||||
// returned method needs to be idempotent and concurrent safe.
|
||||
RegisterAsyncReporter(reporter AsyncMetricReporter, descriptors ...AsyncMetric) func()
|
||||
|
||||
// EnforceMetricsRecorderEmbedding is included to force implementers to embed
|
||||
// another implementation of this interface, allowing gRPC to add methods
|
||||
// without breaking users.
|
||||
internal.EnforceMetricsRecorderEmbedding
|
||||
}
|
||||
|
||||
// AsyncMetricReporter is an interface for types that record metrics asynchronously
|
||||
// for the set of descriptors they are registered with. The AsyncMetricsRecorder
|
||||
// parameter is used to record values for these metrics.
|
||||
//
|
||||
// Implementations must make unique recordings across all registered
|
||||
// AsyncMetricReporters. Meaning, they should not report values for a metric with
|
||||
// the same attributes as another AsyncMetricReporter will report.
|
||||
//
|
||||
// Implementations must be concurrent-safe.
|
||||
type AsyncMetricReporter interface {
|
||||
// Report records metric values using the provided recorder.
|
||||
Report(AsyncMetricsRecorder) error
|
||||
}
|
||||
|
||||
// AsyncMetricReporterFunc is an adapter to allow the use of ordinary functions as
|
||||
// AsyncMetricReporters.
|
||||
type AsyncMetricReporterFunc func(AsyncMetricsRecorder) error
|
||||
|
||||
// Report calls f(r).
|
||||
func (f AsyncMetricReporterFunc) Report(r AsyncMetricsRecorder) error {
|
||||
return f(r)
|
||||
}
|
||||
|
||||
// AsyncMetricsRecorder records on asynchronous metrics derived from metric registry.
|
||||
type AsyncMetricsRecorder interface {
|
||||
// RecordInt64AsyncGauge records the measurement alongside labels on the int
|
||||
// count associated with the provided handle asynchronously
|
||||
RecordInt64AsyncGauge(handle *Int64AsyncGaugeHandle, incr int64, labels ...string)
|
||||
}
|
||||
|
||||
// Metrics is an experimental legacy alias of the now-stable stats.MetricSet.
|
||||
// Metrics will be deleted in a future release.
|
||||
type Metrics = stats.MetricSet
|
||||
|
||||
// Metric was replaced by direct usage of strings.
|
||||
type Metric = string
|
||||
|
||||
// NewMetrics is an experimental legacy alias of the now-stable
|
||||
// stats.NewMetricSet. NewMetrics will be deleted in a future release.
|
||||
func NewMetrics(metrics ...Metric) *Metrics {
|
||||
return stats.NewMetricSet(metrics...)
|
||||
}
|
||||
|
||||
// UnimplementedMetricsRecorder must be embedded to have forward compatible implementations.
|
||||
type UnimplementedMetricsRecorder struct {
|
||||
internal.EnforceMetricsRecorderEmbedding
|
||||
}
|
||||
|
||||
// RecordInt64Count provides a no-op implementation.
|
||||
func (UnimplementedMetricsRecorder) RecordInt64Count(*Int64CountHandle, int64, ...string) {}
|
||||
|
||||
// RecordFloat64Count provides a no-op implementation.
|
||||
func (UnimplementedMetricsRecorder) RecordFloat64Count(*Float64CountHandle, float64, ...string) {}
|
||||
|
||||
// RecordInt64Histo provides a no-op implementation.
|
||||
func (UnimplementedMetricsRecorder) RecordInt64Histo(*Int64HistoHandle, int64, ...string) {}
|
||||
|
||||
// RecordFloat64Histo provides a no-op implementation.
|
||||
func (UnimplementedMetricsRecorder) RecordFloat64Histo(*Float64HistoHandle, float64, ...string) {}
|
||||
|
||||
// RecordInt64Gauge provides a no-op implementation.
|
||||
func (UnimplementedMetricsRecorder) RecordInt64Gauge(*Int64GaugeHandle, int64, ...string) {}
|
||||
|
||||
// RecordInt64UpDownCount provides a no-op implementation.
|
||||
func (UnimplementedMetricsRecorder) RecordInt64UpDownCount(*Int64UpDownCountHandle, int64, ...string) {
|
||||
}
|
||||
|
||||
// RegisterAsyncReporter provides a no-op implementation.
|
||||
func (UnimplementedMetricsRecorder) RegisterAsyncReporter(AsyncMetricReporter, ...AsyncMetric) func() {
|
||||
// No-op: Return an empty function to ensure caller doesn't panic on nil function call
|
||||
return func() {}
|
||||
}
|
||||
42
experimental/stats/telemetry/labels.go
Normal file
42
experimental/stats/telemetry/labels.go
Normal file
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
*
|
||||
* Copyright 2026 gRPC authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
// Package telemetry defines stats APIs for interacting with
|
||||
// telemetry labels.
|
||||
//
|
||||
// # Experimental
|
||||
//
|
||||
// Notice: All APIs in this package are experimental and may be changed or
|
||||
// removed in a later release.
|
||||
package telemetry
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
stats "google.golang.org/grpc/internal/stats"
|
||||
)
|
||||
|
||||
// NewContextWithLabelCallback registers a callback function that is executed
|
||||
// whenever telemetry labels are updated. This does _not_ require opentelemetry
|
||||
// instrumentation to be configured on the client or server.
|
||||
//
|
||||
// WARNING: Callbacks are executed on the RPC path so users should be mindful
|
||||
// of the potential performance impact when this is eventually executed.
|
||||
func NewContextWithLabelCallback(ctx context.Context, callback stats.LabelCallback) context.Context {
|
||||
return stats.RegisterTelemetryLabelCallback(ctx, callback)
|
||||
}
|
||||
Reference in New Issue
Block a user