-
Notifications
You must be signed in to change notification settings - Fork 4.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
experimental/stats: Add metrics registry #7349
Merged
Merged
Changes from all commits
Commits
Show all changes
13 commits
Select commit
Hold shift + click to select a range
a845ca6
Add instrument registry
zasweq 6920f1d
Move symbols around
zasweq e5d3d68
Move Metrics Set to stats and export
zasweq c5cb641
Snapshot for 1:1
zasweq 7c051c0
Implemented offline discussion
zasweq 97b48c1
Implemented offline discussion
zasweq 94caf39
Responded to Doug's comments
zasweq 94bc27b
Responded to some of Doug's comments
zasweq e413a80
Move metrics and metrics recorder to experimental/stats/metrics.go
zasweq e47eccc
Fix tests and switch handles to MetricDescriptor aliases
zasweq 83d5a1d
Responded to most of Doug's comments
zasweq 6f81040
Added nil check for panic
zasweq 1ec4e54
switch to fmt.Sprint
zasweq File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,230 @@ | ||
/* | ||
* | ||
* 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" | ||
"testing" | ||
|
||
"google.golang.org/grpc/grpclog" | ||
) | ||
|
||
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 = NewMetrics() | ||
|
||
// 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 Metric | ||
// 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 | ||
} | ||
|
||
// MetricType is the type of metric. | ||
type MetricType int | ||
|
||
const ( | ||
MetricTypeIntCount MetricType = iota | ||
MetricTypeFloatCount | ||
MetricTypeIntHisto | ||
MetricTypeFloatHisto | ||
MetricTypeIntGauge | ||
) | ||
|
||
// 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 | ||
|
||
// 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...) | ||
} | ||
|
||
// 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 | ||
|
||
// 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 | ||
|
||
// 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 | ||
|
||
// 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 | ||
|
||
// 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...) | ||
} | ||
|
||
// registeredMetrics are the registered metric descriptor names. | ||
var registeredMetrics = make(map[Metric]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[Metric]*MetricDescriptor) | ||
|
||
// DescriptorForMetric returns the MetricDescriptor from the global registry. | ||
// | ||
// Returns nil if MetricDescriptor not present. | ||
func DescriptorForMetric(metric Metric) *MetricDescriptor { | ||
return metricsRegistry[metric] | ||
} | ||
|
||
func registerMetric(name Metric, def bool) { | ||
if registeredMetrics[name] { | ||
logger.Fatalf("metric %v already registered", name) | ||
} | ||
registeredMetrics[name] = true | ||
if def { | ||
DefaultMetrics = DefaultMetrics.Add(name) | ||
} | ||
} | ||
|
||
// 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 { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's make sure the "numbers vs. no numbers" matches here too. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. See above comment; chose numbers route. |
||
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) | ||
} | ||
|
||
// snapshotMetricsRegistryForTesting snapshots the global data of the metrics | ||
// registry. Registers a cleanup function on the provided testing.T that sets | ||
// the metrics registry to its original state. Only called in testing functions. | ||
func snapshotMetricsRegistryForTesting(t *testing.T) { | ||
oldDefaultMetrics := DefaultMetrics | ||
oldRegisteredMetrics := registeredMetrics | ||
oldMetricsRegistry := metricsRegistry | ||
|
||
registeredMetrics = make(map[Metric]bool) | ||
metricsRegistry = make(map[Metric]*MetricDescriptor) | ||
maps.Copy(registeredMetrics, registeredMetrics) | ||
maps.Copy(metricsRegistry, metricsRegistry) | ||
|
||
t.Cleanup(func() { | ||
DefaultMetrics = oldDefaultMetrics | ||
registeredMetrics = oldRegisteredMetrics | ||
metricsRegistry = oldMetricsRegistry | ||
}) | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Something to think about for later when you're implementing the things that actually use all of this: should we remove this field from the descriptor since it (I think?) only affects the initial registration and whether to put it into the
DefaultMetrics
set?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah I've thought about this persistence before, I wanted to delete this since this information is encoded in DefaultSet, but this is how the user registers through API now (since we persist a copy of what user sets). Do you see a way to delete the field but keep the API?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Two obvious other options:
RegisterFooMetric()
andRegisterDefaultFooMetric()
RegisterFooMetric(md MetricDescriptor, default bool)
I think this is okay, too, but we should consider the above since this data is effectively discarded immediately after registration.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd rather do 2. Let's keep this in mind for the future since I'm sure this metrics registry will iterate organically as we merge more layers.