From 3c0586a427dd1fb8ba66f8c4dbd038273a406bbe Mon Sep 17 00:00:00 2001 From: Zach Reyes <39203661+zasweq@users.noreply.github.com> Date: Mon, 2 Dec 2024 12:19:40 -0500 Subject: [PATCH] stats/opentelemetry: Cleanup OpenTelemetry API's before stabilization (#7874) Co-authored-by: Doug Fawley --- experimental/stats/metricregistry.go | 27 ++++--- experimental/stats/metrics.go | 75 ----------------- .../testutils/stats/test_metrics_recorder.go | 8 +- stats/metrics.go | 81 +++++++++++++++++++ stats/opentelemetry/client_metrics.go | 29 +++---- stats/opentelemetry/example_test.go | 8 +- stats/opentelemetry/opentelemetry.go | 48 ++++++----- stats/opentelemetry/server_metrics.go | 22 ++--- 8 files changed, 156 insertions(+), 142 deletions(-) create mode 100644 stats/metrics.go diff --git a/experimental/stats/metricregistry.go b/experimental/stats/metricregistry.go index 1d827dd5d9d4..ad75313a18e1 100644 --- a/experimental/stats/metricregistry.go +++ b/experimental/stats/metricregistry.go @@ -23,6 +23,7 @@ import ( "google.golang.org/grpc/grpclog" "google.golang.org/grpc/internal" + "google.golang.org/grpc/stats" ) func init() { @@ -34,7 +35,7 @@ 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() +var DefaultMetrics = stats.NewMetricSet() // MetricDescriptor is the data for a registered metric. type MetricDescriptor struct { @@ -42,7 +43,7 @@ type MetricDescriptor struct { // (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 + Name string // The description of this metric. Description string // The unit (e.g. entries, seconds) of this metric. @@ -154,27 +155,27 @@ func (h *Int64GaugeHandle) Record(recorder MetricsRecorder, incr int64, labels . } // registeredMetrics are the registered metric descriptor names. -var registeredMetrics = make(map[Metric]bool) +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[Metric]*MetricDescriptor) +var metricsRegistry = make(map[string]*MetricDescriptor) // DescriptorForMetric returns the MetricDescriptor from the global registry. // // Returns nil if MetricDescriptor not present. -func DescriptorForMetric(metric Metric) *MetricDescriptor { - return metricsRegistry[metric] +func DescriptorForMetric(metricName string) *MetricDescriptor { + return metricsRegistry[metricName] } -func registerMetric(name Metric, def bool) { - if registeredMetrics[name] { - logger.Fatalf("metric %v already registered", name) +func registerMetric(metricName string, def bool) { + if registeredMetrics[metricName] { + logger.Fatalf("metric %v already registered", metricName) } - registeredMetrics[name] = true + registeredMetrics[metricName] = true if def { - DefaultMetrics = DefaultMetrics.Add(name) + DefaultMetrics = DefaultMetrics.Add(metricName) } } @@ -256,8 +257,8 @@ func snapshotMetricsRegistryForTesting() func() { oldRegisteredMetrics := registeredMetrics oldMetricsRegistry := metricsRegistry - registeredMetrics = make(map[Metric]bool) - metricsRegistry = make(map[Metric]*MetricDescriptor) + registeredMetrics = make(map[string]bool) + metricsRegistry = make(map[string]*MetricDescriptor) maps.Copy(registeredMetrics, registeredMetrics) maps.Copy(metricsRegistry, metricsRegistry) diff --git a/experimental/stats/metrics.go b/experimental/stats/metrics.go index 3221f7a633a3..bf9e7f987b04 100644 --- a/experimental/stats/metrics.go +++ b/experimental/stats/metrics.go @@ -19,8 +19,6 @@ // Package stats contains experimental metrics/stats API's. package stats -import "maps" - // MetricsRecorder records on metrics derived from metric registry. type MetricsRecorder interface { // RecordInt64Count records the measurement alongside labels on the int @@ -39,76 +37,3 @@ type MetricsRecorder interface { // gauge associated with the provided handle. RecordInt64Gauge(handle *Int64GaugeHandle, incr int64, labels ...string) } - -// Metric is an identifier for a metric. -type Metric string - -// Metrics is a set of metrics to record. Once created, Metrics is immutable, -// however Add and Remove can make copies with specific metrics added or -// removed, respectively. -// -// Do not construct directly; use NewMetrics instead. -type Metrics struct { - // metrics are the set of metrics to initialize. - metrics map[Metric]bool -} - -// NewMetrics returns a Metrics containing Metrics. -func NewMetrics(metrics ...Metric) *Metrics { - newMetrics := make(map[Metric]bool) - for _, metric := range metrics { - newMetrics[metric] = true - } - return &Metrics{ - metrics: newMetrics, - } -} - -// Metrics returns the metrics set. The returned map is read-only and must not -// be modified. -func (m *Metrics) Metrics() map[Metric]bool { - return m.metrics -} - -// Add adds the metrics to the metrics set and returns a new copy with the -// additional metrics. -func (m *Metrics) Add(metrics ...Metric) *Metrics { - newMetrics := make(map[Metric]bool) - for metric := range m.metrics { - newMetrics[metric] = true - } - - for _, metric := range metrics { - newMetrics[metric] = true - } - return &Metrics{ - metrics: newMetrics, - } -} - -// Join joins the metrics passed in with the metrics set, and returns a new copy -// with the merged metrics. -func (m *Metrics) Join(metrics *Metrics) *Metrics { - newMetrics := make(map[Metric]bool) - maps.Copy(newMetrics, m.metrics) - maps.Copy(newMetrics, metrics.metrics) - return &Metrics{ - metrics: newMetrics, - } -} - -// Remove removes the metrics from the metrics set and returns a new copy with -// the metrics removed. -func (m *Metrics) Remove(metrics ...Metric) *Metrics { - newMetrics := make(map[Metric]bool) - for metric := range m.metrics { - newMetrics[metric] = true - } - - for _, metric := range metrics { - delete(newMetrics, metric) - } - return &Metrics{ - metrics: newMetrics, - } -} diff --git a/internal/testutils/stats/test_metrics_recorder.go b/internal/testutils/stats/test_metrics_recorder.go index e13013e38d53..e1a03b8d8008 100644 --- a/internal/testutils/stats/test_metrics_recorder.go +++ b/internal/testutils/stats/test_metrics_recorder.go @@ -44,7 +44,7 @@ type TestMetricsRecorder struct { // mu protects data. mu sync.Mutex // data is the most recent update for each metric name. - data map[estats.Metric]float64 + data map[string]float64 } // NewTestMetricsRecorder returns a new TestMetricsRecorder. @@ -56,7 +56,7 @@ func NewTestMetricsRecorder() *TestMetricsRecorder { floatHistoCh: testutils.NewChannelWithSize(10), intGaugeCh: testutils.NewChannelWithSize(10), - data: make(map[estats.Metric]float64), + data: make(map[string]float64), } } @@ -65,7 +65,7 @@ func NewTestMetricsRecorder() *TestMetricsRecorder { func (r *TestMetricsRecorder) Metric(name string) (float64, bool) { r.mu.Lock() defer r.mu.Unlock() - data, ok := r.data[estats.Metric(name)] + data, ok := r.data[name] return data, ok } @@ -73,7 +73,7 @@ func (r *TestMetricsRecorder) Metric(name string) (float64, bool) { func (r *TestMetricsRecorder) ClearMetrics() { r.mu.Lock() defer r.mu.Unlock() - r.data = make(map[estats.Metric]float64) + r.data = make(map[string]float64) } // MetricsData represents data associated with a metric. diff --git a/stats/metrics.go b/stats/metrics.go new file mode 100644 index 000000000000..641c8e9794a5 --- /dev/null +++ b/stats/metrics.go @@ -0,0 +1,81 @@ +/* + * 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" + +// MetricSet is a set of metrics to record. Once created, MetricSet is immutable, +// however Add and Remove can make copies with specific metrics added or +// removed, respectively. +// +// Do not construct directly; use NewMetricSet instead. +type MetricSet struct { + // metrics are the set of metrics to initialize. + metrics map[string]bool +} + +// NewMetricSet returns a MetricSet containing metricNames. +func NewMetricSet(metricNames ...string) *MetricSet { + newMetrics := make(map[string]bool) + for _, metric := range metricNames { + newMetrics[metric] = true + } + return &MetricSet{metrics: newMetrics} +} + +// Metrics returns the metrics set. The returned map is read-only and must not +// be modified. +func (m *MetricSet) Metrics() map[string]bool { + return m.metrics +} + +// Add adds the metricNames to the metrics set and returns a new copy with the +// additional metrics. +func (m *MetricSet) Add(metricNames ...string) *MetricSet { + newMetrics := make(map[string]bool) + for metric := range m.metrics { + newMetrics[metric] = true + } + + for _, metric := range metricNames { + newMetrics[metric] = true + } + return &MetricSet{metrics: newMetrics} +} + +// Join joins the metrics passed in with the metrics set, and returns a new copy +// with the merged metrics. +func (m *MetricSet) Join(metrics *MetricSet) *MetricSet { + newMetrics := make(map[string]bool) + maps.Copy(newMetrics, m.metrics) + maps.Copy(newMetrics, metrics.metrics) + return &MetricSet{metrics: newMetrics} +} + +// Remove removes the metricNames from the metrics set and returns a new copy +// with the metrics removed. +func (m *MetricSet) Remove(metricNames ...string) *MetricSet { + newMetrics := make(map[string]bool) + for metric := range m.metrics { + newMetrics[metric] = true + } + + for _, metric := range metricNames { + delete(newMetrics, metric) + } + return &MetricSet{metrics: newMetrics} +} diff --git a/stats/opentelemetry/client_metrics.go b/stats/opentelemetry/client_metrics.go index 4af7f933c8ba..265791e5a261 100644 --- a/stats/opentelemetry/client_metrics.go +++ b/stats/opentelemetry/client_metrics.go @@ -260,18 +260,19 @@ func (h *clientStatsHandler) processRPCEnd(ctx context.Context, ai *attemptInfo, } const ( - // ClientAttemptStarted is the number of client call attempts started. - ClientAttemptStarted estats.Metric = "grpc.client.attempt.started" - // ClientAttemptDuration is the end-to-end time taken to complete a client - // call attempt. - ClientAttemptDuration estats.Metric = "grpc.client.attempt.duration" - // ClientAttemptSentCompressedTotalMessageSize is the compressed message - // bytes sent per client call attempt. - ClientAttemptSentCompressedTotalMessageSize estats.Metric = "grpc.client.attempt.sent_total_compressed_message_size" - // ClientAttemptRcvdCompressedTotalMessageSize is the compressed message - // bytes received per call attempt. - ClientAttemptRcvdCompressedTotalMessageSize estats.Metric = "grpc.client.attempt.rcvd_total_compressed_message_size" - // ClientCallDuration is the time taken by gRPC to complete an RPC from - // application's perspective. - ClientCallDuration estats.Metric = "grpc.client.call.duration" + // ClientAttemptStartedMetricName is the number of client call attempts + // started. + ClientAttemptStartedMetricName string = "grpc.client.attempt.started" + // ClientAttemptDurationMetricName is the end-to-end time taken to complete + // a client call attempt. + ClientAttemptDurationMetricName string = "grpc.client.attempt.duration" + // ClientAttemptSentCompressedTotalMessageSizeMetricName is the compressed + // message bytes sent per client call attempt. + ClientAttemptSentCompressedTotalMessageSizeMetricName string = "grpc.client.attempt.sent_total_compressed_message_size" + // ClientAttemptRcvdCompressedTotalMessageSizeMetricName is the compressed + // message bytes received per call attempt. + ClientAttemptRcvdCompressedTotalMessageSizeMetricName string = "grpc.client.attempt.rcvd_total_compressed_message_size" + // ClientCallDurationMetricName is the time taken by gRPC to complete an RPC + // from application's perspective. + ClientCallDurationMetricName string = "grpc.client.call.duration" ) diff --git a/stats/opentelemetry/example_test.go b/stats/opentelemetry/example_test.go index 66676f3c035b..e87e4ebb65b9 100644 --- a/stats/opentelemetry/example_test.go +++ b/stats/opentelemetry/example_test.go @@ -19,7 +19,7 @@ package opentelemetry_test import ( "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" - estats "google.golang.org/grpc/experimental/stats" + "google.golang.org/grpc/stats" "google.golang.org/grpc/stats/opentelemetry" "go.opentelemetry.io/otel/sdk/metric" @@ -88,7 +88,7 @@ func ExampleMetrics_excludeSome() { // To exclude specific metrics, initialize Options as follows: opts := opentelemetry.Options{ MetricsOptions: opentelemetry.MetricsOptions{ - Metrics: opentelemetry.DefaultMetrics().Remove(opentelemetry.ClientAttemptDuration, opentelemetry.ClientAttemptRcvdCompressedTotalMessageSize), + Metrics: opentelemetry.DefaultMetrics().Remove(opentelemetry.ClientAttemptDurationMetricName, opentelemetry.ClientAttemptRcvdCompressedTotalMessageSizeMetricName), }, } do := opentelemetry.DialOption(opts) @@ -103,7 +103,7 @@ func ExampleMetrics_disableAll() { // To disable all metrics, initialize Options as follows: opts := opentelemetry.Options{ MetricsOptions: opentelemetry.MetricsOptions{ - Metrics: estats.NewMetrics(), // Distinct to nil, which creates default metrics. This empty set creates no metrics. + Metrics: stats.NewMetricSet(), // Distinct to nil, which creates default metrics. This empty set creates no metrics. }, } do := opentelemetry.DialOption(opts) @@ -118,7 +118,7 @@ func ExampleMetrics_enableSome() { // To only create specific metrics, initialize Options as follows: opts := opentelemetry.Options{ MetricsOptions: opentelemetry.MetricsOptions{ - Metrics: estats.NewMetrics(opentelemetry.ClientAttemptDuration, opentelemetry.ClientAttemptRcvdCompressedTotalMessageSize), // only create these metrics + Metrics: stats.NewMetricSet(opentelemetry.ClientAttemptDurationMetricName, opentelemetry.ClientAttemptRcvdCompressedTotalMessageSizeMetricName), // only create these metrics }, } do := opentelemetry.DialOption(opts) diff --git a/stats/opentelemetry/opentelemetry.go b/stats/opentelemetry/opentelemetry.go index cc5ad387fb4c..dcc424775f14 100644 --- a/stats/opentelemetry/opentelemetry.go +++ b/stats/opentelemetry/opentelemetry.go @@ -16,6 +16,10 @@ // Package opentelemetry implements opentelemetry instrumentation code for // gRPC-Go clients and servers. +// +// For details on configuring opentelemetry and various instruments that this +// package creates, see +// [gRPC OpenTelemetry Metrics](https://grpc.io/docs/guides/opentelemetry-metrics/). package opentelemetry import ( @@ -28,6 +32,7 @@ import ( estats "google.golang.org/grpc/experimental/stats" "google.golang.org/grpc/grpclog" "google.golang.org/grpc/internal" + "google.golang.org/grpc/stats" otelinternal "google.golang.org/grpc/stats/opentelemetry/internal" otelattribute "go.opentelemetry.io/otel/attribute" @@ -57,27 +62,28 @@ type Options struct { type MetricsOptions struct { // MeterProvider is the MeterProvider instance that will be used to create // instruments. To enable metrics collection, set a meter provider. If - // unset, no metrics will be recorded. Any implementation knobs (i.e. views, - // bounds) set in the MeterProvider take precedence over the API calls from - // this interface. (i.e. it will create default views for unset views). + // unset, no metrics will be recorded. MeterProvider otelmetric.MeterProvider // Metrics are the metrics to instrument. Will create instrument and record telemetry // for corresponding metric supported by the client and server // instrumentation components if applicable. If not set, the default metrics // will be recorded. - Metrics *estats.Metrics - - // MethodAttributeFilter is to record the method name of RPCs handled by - // grpc.UnknownServiceHandler, but take care to limit the values allowed, as - // allowing too many will increase cardinality and could cause severe memory - // or performance problems. On Client Side, pass a - // grpc.StaticMethodCallOption as a call option into Invoke or NewStream. - // This only applies for server side metrics. + Metrics *stats.MetricSet + + // MethodAttributeFilter is a function that determines whether to record the + // method name of RPCs as an attribute, or to bucket into "other". Take care + // to limit the values allowed, as allowing too many will increase + // cardinality and could cause severe memory or performance problems. + // + // This only applies for server-side metrics. For clients, to record the + // method name in the attributes, pass grpc.StaticMethodCallOption to Invoke + // or NewStream. Note that when using protobuf generated clients, this + // CallOption is included automatically. MethodAttributeFilter func(string) bool - // OptionalLabels are labels received from LB Policies that this component - // should add to metrics that record after receiving incoming metadata. + // OptionalLabels specifies a list of optional labels to enable on any + // metrics that support them. OptionalLabels []string // pluginOption is used to get labels to attach to certain metrics, if set. @@ -207,7 +213,7 @@ type serverMetrics struct { callDuration otelmetric.Float64Histogram } -func createInt64Counter(setOfMetrics map[estats.Metric]bool, metricName estats.Metric, meter otelmetric.Meter, options ...otelmetric.Int64CounterOption) otelmetric.Int64Counter { +func createInt64Counter(setOfMetrics map[string]bool, metricName string, meter otelmetric.Meter, options ...otelmetric.Int64CounterOption) otelmetric.Int64Counter { if _, ok := setOfMetrics[metricName]; !ok { return noop.Int64Counter{} } @@ -219,7 +225,7 @@ func createInt64Counter(setOfMetrics map[estats.Metric]bool, metricName estats.M return ret } -func createFloat64Counter(setOfMetrics map[estats.Metric]bool, metricName estats.Metric, meter otelmetric.Meter, options ...otelmetric.Float64CounterOption) otelmetric.Float64Counter { +func createFloat64Counter(setOfMetrics map[string]bool, metricName string, meter otelmetric.Meter, options ...otelmetric.Float64CounterOption) otelmetric.Float64Counter { if _, ok := setOfMetrics[metricName]; !ok { return noop.Float64Counter{} } @@ -231,7 +237,7 @@ func createFloat64Counter(setOfMetrics map[estats.Metric]bool, metricName estats return ret } -func createInt64Histogram(setOfMetrics map[estats.Metric]bool, metricName estats.Metric, meter otelmetric.Meter, options ...otelmetric.Int64HistogramOption) otelmetric.Int64Histogram { +func createInt64Histogram(setOfMetrics map[string]bool, metricName string, meter otelmetric.Meter, options ...otelmetric.Int64HistogramOption) otelmetric.Int64Histogram { if _, ok := setOfMetrics[metricName]; !ok { return noop.Int64Histogram{} } @@ -243,7 +249,7 @@ func createInt64Histogram(setOfMetrics map[estats.Metric]bool, metricName estats return ret } -func createFloat64Histogram(setOfMetrics map[estats.Metric]bool, metricName estats.Metric, meter otelmetric.Meter, options ...otelmetric.Float64HistogramOption) otelmetric.Float64Histogram { +func createFloat64Histogram(setOfMetrics map[string]bool, metricName string, meter otelmetric.Meter, options ...otelmetric.Float64HistogramOption) otelmetric.Float64Histogram { if _, ok := setOfMetrics[metricName]; !ok { return noop.Float64Histogram{} } @@ -255,7 +261,7 @@ func createFloat64Histogram(setOfMetrics map[estats.Metric]bool, metricName esta return ret } -func createInt64Gauge(setOfMetrics map[estats.Metric]bool, metricName estats.Metric, meter otelmetric.Meter, options ...otelmetric.Int64GaugeOption) otelmetric.Int64Gauge { +func createInt64Gauge(setOfMetrics map[string]bool, metricName string, meter otelmetric.Meter, options ...otelmetric.Int64GaugeOption) otelmetric.Int64Gauge { if _, ok := setOfMetrics[metricName]; !ok { return noop.Int64Gauge{} } @@ -298,7 +304,7 @@ type registryMetrics struct { optionalLabels []string } -func (rm *registryMetrics) registerMetrics(metrics *estats.Metrics, meter otelmetric.Meter) { +func (rm *registryMetrics) registerMetrics(metrics *stats.MetricSet, meter otelmetric.Meter) { rm.intCounts = make(map[*estats.MetricDescriptor]otelmetric.Int64Counter) rm.floatCounts = make(map[*estats.MetricDescriptor]otelmetric.Float64Counter) rm.intHistos = make(map[*estats.MetricDescriptor]otelmetric.Int64Histogram) @@ -379,12 +385,12 @@ var ( // DefaultSizeBounds are the default bounds for metrics which record size. DefaultSizeBounds = []float64{0, 1024, 2048, 4096, 16384, 65536, 262144, 1048576, 4194304, 16777216, 67108864, 268435456, 1073741824, 4294967296} // defaultPerCallMetrics are the default metrics provided by this module. - defaultPerCallMetrics = estats.NewMetrics(ClientAttemptStarted, ClientAttemptDuration, ClientAttemptSentCompressedTotalMessageSize, ClientAttemptRcvdCompressedTotalMessageSize, ClientCallDuration, ServerCallStarted, ServerCallSentCompressedTotalMessageSize, ServerCallRcvdCompressedTotalMessageSize, ServerCallDuration) + defaultPerCallMetrics = stats.NewMetricSet(ClientAttemptStartedMetricName, ClientAttemptDurationMetricName, ClientAttemptSentCompressedTotalMessageSizeMetricName, ClientAttemptRcvdCompressedTotalMessageSizeMetricName, ClientCallDurationMetricName, ServerCallStartedMetricName, ServerCallSentCompressedTotalMessageSizeMetricName, ServerCallRcvdCompressedTotalMessageSizeMetricName, ServerCallDurationMetricName) ) // DefaultMetrics returns a set of default OpenTelemetry metrics. // // This should only be invoked after init time. -func DefaultMetrics() *estats.Metrics { +func DefaultMetrics() *stats.MetricSet { return defaultPerCallMetrics.Join(estats.DefaultMetrics) } diff --git a/stats/opentelemetry/server_metrics.go b/stats/opentelemetry/server_metrics.go index eaea559b2c10..4765afa8ed53 100644 --- a/stats/opentelemetry/server_metrics.go +++ b/stats/opentelemetry/server_metrics.go @@ -264,15 +264,15 @@ func (h *serverStatsHandler) processRPCEnd(ctx context.Context, ai *attemptInfo, } const ( - // ServerCallStarted is the number of server calls started. - ServerCallStarted estats.Metric = "grpc.server.call.started" - // ServerCallSentCompressedTotalMessageSize is the compressed message bytes - // sent per server call. - ServerCallSentCompressedTotalMessageSize estats.Metric = "grpc.server.call.sent_total_compressed_message_size" - // ServerCallRcvdCompressedTotalMessageSize is the compressed message bytes - // received per server call. - ServerCallRcvdCompressedTotalMessageSize estats.Metric = "grpc.server.call.rcvd_total_compressed_message_size" - // ServerCallDuration is the end-to-end time taken to complete a call from - // server transport's perspective. - ServerCallDuration estats.Metric = "grpc.server.call.duration" + // ServerCallStartedMetricName is the number of server calls started. + ServerCallStartedMetricName string = "grpc.server.call.started" + // ServerCallSentCompressedTotalMessageSizeMetricName is the compressed + // message bytes sent per server call. + ServerCallSentCompressedTotalMessageSizeMetricName string = "grpc.server.call.sent_total_compressed_message_size" + // ServerCallRcvdCompressedTotalMessageSizeMetricName is the compressed + // message bytes received per server call. + ServerCallRcvdCompressedTotalMessageSizeMetricName string = "grpc.server.call.rcvd_total_compressed_message_size" + // ServerCallDurationMetricName is the end-to-end time taken to complete a + // call from server transport's perspective. + ServerCallDurationMetricName string = "grpc.server.call.duration" )