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
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
Testing / tests (arm, latest) (push) Waiting to run
Testing / vet-proto (push) Failing after 1s
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
Some checks failed
codecov / upload (push) Failing after 13m59s
CodeQL / Analyze (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
Testing / tests (arm, latest) (push) Waiting to run
Testing / vet-proto (push) Failing after 1s
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
This commit is contained in:
41
stats/opentelemetry/internal/pluginoption.go
Normal file
41
stats/opentelemetry/internal/pluginoption.go
Normal file
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* 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 internal defines the PluginOption interface.
|
||||
package internal
|
||||
|
||||
import (
|
||||
"google.golang.org/grpc/metadata"
|
||||
)
|
||||
|
||||
// SetPluginOption sets the plugin option on Options.
|
||||
var SetPluginOption any // func(*Options, PluginOption)
|
||||
|
||||
// PluginOption is the interface which represents a plugin option for the
|
||||
// OpenTelemetry instrumentation component. This plugin option emits labels from
|
||||
// metadata and also creates metadata containing labels. These labels are
|
||||
// intended to be added to applicable OpenTelemetry metrics recorded in the
|
||||
// OpenTelemetry instrumentation component.
|
||||
//
|
||||
// In the future, we hope to stabilize and expose this API to allow plugins to
|
||||
// inject labels of their choosing into metrics recorded.
|
||||
type PluginOption interface {
|
||||
// GetMetadata creates a MD with metadata exchange labels.
|
||||
GetMetadata() metadata.MD
|
||||
// GetLabels emits labels to be attached to metrics for the RPC that
|
||||
// contains the provided incomingMetadata.
|
||||
GetLabels(incomingMetadata metadata.MD) map[string]string
|
||||
}
|
||||
853
stats/opentelemetry/internal/testutils/testutils.go
Normal file
853
stats/opentelemetry/internal/testutils/testutils.go
Normal file
@@ -0,0 +1,853 @@
|
||||
/*
|
||||
* 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 testutils contains helpers for OpenTelemetry tests.
|
||||
package testutils
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"context"
|
||||
"fmt"
|
||||
"slices"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
"go.opentelemetry.io/otel/sdk/metric"
|
||||
"go.opentelemetry.io/otel/sdk/metric/metricdata"
|
||||
"go.opentelemetry.io/otel/sdk/metric/metricdata/metricdatatest"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
// Redefine default bounds here to avoid a cyclic dependency with top level
|
||||
// opentelemetry package. Could define once through internal, but would make
|
||||
// external opentelemetry godoc less readable.
|
||||
var (
|
||||
// DefaultLatencyBounds are the default bounds for latency metrics.
|
||||
DefaultLatencyBounds = []float64{0, 0.00001, 0.00005, 0.0001, 0.0003, 0.0006, 0.0008, 0.001, 0.002, 0.003, 0.004, 0.005, 0.006, 0.008, 0.01, 0.013, 0.016, 0.02, 0.025, 0.03, 0.04, 0.05, 0.065, 0.08, 0.1, 0.13, 0.16, 0.2, 0.25, 0.3, 0.4, 0.5, 0.65, 0.8, 1, 2, 5, 10, 20, 50, 100}
|
||||
// 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}
|
||||
)
|
||||
|
||||
// waitForServerCompletedRPCs waits until the unary and streaming stats.End
|
||||
// calls are finished processing. It does this by waiting for the expected
|
||||
// metric triggered by stats.End to appear through the passed in metrics reader.
|
||||
//
|
||||
// Returns a new gotMetrics map containing the metric data being polled for, or
|
||||
// an error if failed to wait for metric.
|
||||
func waitForServerCompletedRPCs(ctx context.Context, reader metric.Reader, wantMetric metricdata.Metrics) (map[string]metricdata.Metrics, error) {
|
||||
for ; ctx.Err() == nil; <-time.After(time.Millisecond) {
|
||||
rm := &metricdata.ResourceMetrics{}
|
||||
reader.Collect(ctx, rm)
|
||||
gotMetrics := map[string]metricdata.Metrics{}
|
||||
for _, sm := range rm.ScopeMetrics {
|
||||
for _, m := range sm.Metrics {
|
||||
gotMetrics[m.Name] = m
|
||||
}
|
||||
}
|
||||
val, ok := gotMetrics[wantMetric.Name]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
switch data := val.Data.(type) {
|
||||
case metricdata.Histogram[int64]:
|
||||
if len(wantMetric.Data.(metricdata.Histogram[int64]).DataPoints) > len(data.DataPoints) {
|
||||
continue
|
||||
}
|
||||
case metricdata.Histogram[float64]:
|
||||
if len(wantMetric.Data.(metricdata.Histogram[float64]).DataPoints) > len(data.DataPoints) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
return gotMetrics, nil
|
||||
}
|
||||
return nil, fmt.Errorf("error waiting for metric %v: %v", wantMetric, ctx.Err())
|
||||
}
|
||||
|
||||
// checkDataPointWithinFiveSeconds checks if the metric passed in contains a
|
||||
// histogram with dataPoints that fall within buckets that are <=5. Returns an
|
||||
// error if check fails.
|
||||
func checkDataPointWithinFiveSeconds(metric metricdata.Metrics) error {
|
||||
histo, ok := metric.Data.(metricdata.Histogram[float64])
|
||||
if !ok {
|
||||
return fmt.Errorf("metric data is not histogram")
|
||||
}
|
||||
for _, dataPoint := range histo.DataPoints {
|
||||
var boundWithFive int
|
||||
for i, bound := range dataPoint.Bounds {
|
||||
if bound >= 5 {
|
||||
boundWithFive = i
|
||||
}
|
||||
}
|
||||
foundPoint := false
|
||||
for i, count := range dataPoint.BucketCounts {
|
||||
if i >= boundWithFive {
|
||||
return fmt.Errorf("data point not found in bucket <=5 seconds")
|
||||
}
|
||||
if count == 1 {
|
||||
foundPoint = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !foundPoint {
|
||||
return fmt.Errorf("no data point found for metric")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// MetricDataOptions are the options used to configure the metricData emissions
|
||||
// of expected metrics data from NewMetricData.
|
||||
type MetricDataOptions struct {
|
||||
// CSMLabels are the csm labels to attach to metrics which receive csm
|
||||
// labels (all A66 expect client call and started RPC's client and server
|
||||
// side).
|
||||
CSMLabels []attribute.KeyValue
|
||||
// Target is the target of the client and server.
|
||||
Target string
|
||||
// UnaryCallFailed is whether the Unary Call failed, which would trigger
|
||||
// trailers only.
|
||||
UnaryCallFailed bool
|
||||
// UnaryCompressedMessageSize is the compressed message size of the Unary
|
||||
// RPC. This assumes both client and server sent the same message size.
|
||||
UnaryCompressedMessageSize float64
|
||||
// StreamingCompressedMessageSize is the compressed message size of the
|
||||
// Streaming RPC. This assumes both client and server sent the same message
|
||||
// size.
|
||||
StreamingCompressedMessageSize float64
|
||||
}
|
||||
|
||||
// createBucketCounts creates a list of bucket counts based off the
|
||||
// recordingPoints and bounds. Both recordingPoints and bounds are assumed to be
|
||||
// in order.
|
||||
func createBucketCounts(recordingPoints []float64, bounds []float64) []uint64 {
|
||||
var bucketCounts []uint64
|
||||
var recordingPointIndex int
|
||||
for _, bound := range bounds {
|
||||
var bucketCount uint64
|
||||
if recordingPointIndex >= len(recordingPoints) {
|
||||
bucketCounts = append(bucketCounts, bucketCount)
|
||||
continue
|
||||
}
|
||||
for recordingPoints[recordingPointIndex] <= bound {
|
||||
bucketCount++
|
||||
recordingPointIndex++
|
||||
if recordingPointIndex >= len(recordingPoints) {
|
||||
break
|
||||
}
|
||||
}
|
||||
bucketCounts = append(bucketCounts, bucketCount)
|
||||
}
|
||||
// The rest of the recording points are last bound -> infinity.
|
||||
bucketCounts = append(bucketCounts, uint64(len(recordingPoints)-recordingPointIndex))
|
||||
return bucketCounts
|
||||
}
|
||||
|
||||
// MetricDataUnary returns a list of expected metrics defined in A66 for a
|
||||
// client and server for one unary RPC.
|
||||
func MetricDataUnary(options MetricDataOptions) []metricdata.Metrics {
|
||||
methodAttr := attribute.String("grpc.method", "grpc.testing.TestService/UnaryCall")
|
||||
targetAttr := attribute.String("grpc.target", options.Target)
|
||||
statusAttr := attribute.String("grpc.status", "OK")
|
||||
if options.UnaryCallFailed {
|
||||
statusAttr = attribute.String("grpc.status", "UNKNOWN")
|
||||
}
|
||||
clientSideEnd := []attribute.KeyValue{
|
||||
methodAttr,
|
||||
targetAttr,
|
||||
statusAttr,
|
||||
}
|
||||
serverSideEnd := []attribute.KeyValue{
|
||||
methodAttr,
|
||||
statusAttr,
|
||||
}
|
||||
clientSideEnd = append(clientSideEnd, options.CSMLabels...)
|
||||
serverSideEnd = append(serverSideEnd, options.CSMLabels...)
|
||||
compressedBytesSentRecv := int64(options.UnaryCompressedMessageSize)
|
||||
bucketCounts := createBucketCounts([]float64{options.UnaryCompressedMessageSize}, DefaultSizeBounds)
|
||||
extrema := metricdata.NewExtrema(int64(options.UnaryCompressedMessageSize))
|
||||
return []metricdata.Metrics{
|
||||
{
|
||||
Name: "grpc.client.attempt.started",
|
||||
Description: "Number of client call attempts started.",
|
||||
Unit: "{attempt}",
|
||||
Data: metricdata.Sum[int64]{
|
||||
DataPoints: []metricdata.DataPoint[int64]{
|
||||
{
|
||||
Attributes: attribute.NewSet(methodAttr, targetAttr),
|
||||
Value: 1,
|
||||
},
|
||||
},
|
||||
Temporality: metricdata.CumulativeTemporality,
|
||||
IsMonotonic: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "grpc.client.attempt.duration",
|
||||
Description: "End-to-end time taken to complete a client call attempt.",
|
||||
Unit: "s",
|
||||
Data: metricdata.Histogram[float64]{
|
||||
DataPoints: []metricdata.HistogramDataPoint[float64]{
|
||||
{
|
||||
Attributes: attribute.NewSet(clientSideEnd...),
|
||||
Count: 1,
|
||||
Bounds: DefaultLatencyBounds,
|
||||
},
|
||||
},
|
||||
Temporality: metricdata.CumulativeTemporality,
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "grpc.client.attempt.sent_total_compressed_message_size",
|
||||
Description: "Compressed message bytes sent per client call attempt.",
|
||||
Unit: "By",
|
||||
Data: metricdata.Histogram[int64]{
|
||||
DataPoints: []metricdata.HistogramDataPoint[int64]{
|
||||
{
|
||||
Attributes: attribute.NewSet(clientSideEnd...),
|
||||
Count: 1,
|
||||
Bounds: DefaultSizeBounds,
|
||||
BucketCounts: bucketCounts,
|
||||
Min: extrema,
|
||||
Max: extrema,
|
||||
Sum: compressedBytesSentRecv,
|
||||
},
|
||||
},
|
||||
Temporality: metricdata.CumulativeTemporality,
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "grpc.client.attempt.rcvd_total_compressed_message_size",
|
||||
Description: "Compressed message bytes received per call attempt.",
|
||||
Unit: "By",
|
||||
Data: metricdata.Histogram[int64]{
|
||||
DataPoints: []metricdata.HistogramDataPoint[int64]{
|
||||
{
|
||||
Attributes: attribute.NewSet(clientSideEnd...),
|
||||
Count: 1,
|
||||
Bounds: DefaultSizeBounds,
|
||||
BucketCounts: bucketCounts,
|
||||
Min: extrema,
|
||||
Max: extrema,
|
||||
Sum: compressedBytesSentRecv,
|
||||
},
|
||||
},
|
||||
Temporality: metricdata.CumulativeTemporality,
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "grpc.client.call.duration",
|
||||
Description: "Time taken by gRPC to complete an RPC from application's perspective.",
|
||||
Unit: "s",
|
||||
Data: metricdata.Histogram[float64]{
|
||||
DataPoints: []metricdata.HistogramDataPoint[float64]{
|
||||
{
|
||||
Attributes: attribute.NewSet(methodAttr, targetAttr, statusAttr),
|
||||
Count: 1,
|
||||
Bounds: DefaultLatencyBounds,
|
||||
},
|
||||
},
|
||||
Temporality: metricdata.CumulativeTemporality,
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "grpc.server.call.started",
|
||||
Description: "Number of server calls started.",
|
||||
Unit: "{call}",
|
||||
Data: metricdata.Sum[int64]{
|
||||
DataPoints: []metricdata.DataPoint[int64]{
|
||||
{
|
||||
Attributes: attribute.NewSet(methodAttr),
|
||||
Value: 1,
|
||||
},
|
||||
},
|
||||
Temporality: metricdata.CumulativeTemporality,
|
||||
IsMonotonic: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "grpc.server.call.sent_total_compressed_message_size",
|
||||
Unit: "By",
|
||||
Description: "Compressed message bytes sent per server call.",
|
||||
Data: metricdata.Histogram[int64]{
|
||||
DataPoints: []metricdata.HistogramDataPoint[int64]{
|
||||
{
|
||||
Attributes: attribute.NewSet(serverSideEnd...),
|
||||
Count: 1,
|
||||
Bounds: DefaultSizeBounds,
|
||||
BucketCounts: bucketCounts,
|
||||
Min: extrema,
|
||||
Max: extrema,
|
||||
Sum: compressedBytesSentRecv,
|
||||
},
|
||||
},
|
||||
Temporality: metricdata.CumulativeTemporality,
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "grpc.server.call.rcvd_total_compressed_message_size",
|
||||
Unit: "By",
|
||||
Description: "Compressed message bytes received per server call.",
|
||||
Data: metricdata.Histogram[int64]{
|
||||
DataPoints: []metricdata.HistogramDataPoint[int64]{
|
||||
{
|
||||
Attributes: attribute.NewSet(serverSideEnd...),
|
||||
Count: 1,
|
||||
Bounds: DefaultSizeBounds,
|
||||
BucketCounts: bucketCounts,
|
||||
Min: extrema,
|
||||
Max: extrema,
|
||||
Sum: compressedBytesSentRecv,
|
||||
},
|
||||
},
|
||||
Temporality: metricdata.CumulativeTemporality,
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "grpc.server.call.duration",
|
||||
Description: "End-to-end time taken to complete a call from server transport's perspective.",
|
||||
Unit: "s",
|
||||
Data: metricdata.Histogram[float64]{
|
||||
DataPoints: []metricdata.HistogramDataPoint[float64]{
|
||||
{
|
||||
Attributes: attribute.NewSet(serverSideEnd...),
|
||||
Count: 1,
|
||||
Bounds: DefaultLatencyBounds,
|
||||
},
|
||||
},
|
||||
Temporality: metricdata.CumulativeTemporality,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// MetricDataStreaming returns a list of expected metrics defined in A66 for a
|
||||
// client and server for one streaming RPC.
|
||||
func MetricDataStreaming(options MetricDataOptions) []metricdata.Metrics {
|
||||
methodAttr := attribute.String("grpc.method", "grpc.testing.TestService/FullDuplexCall")
|
||||
targetAttr := attribute.String("grpc.target", options.Target)
|
||||
statusAttr := attribute.String("grpc.status", "OK")
|
||||
clientSideEnd := []attribute.KeyValue{
|
||||
methodAttr,
|
||||
targetAttr,
|
||||
statusAttr,
|
||||
}
|
||||
serverSideEnd := []attribute.KeyValue{
|
||||
methodAttr,
|
||||
statusAttr,
|
||||
}
|
||||
clientSideEnd = append(clientSideEnd, options.CSMLabels...)
|
||||
serverSideEnd = append(serverSideEnd, options.CSMLabels...)
|
||||
compressedBytesSentRecv := int64(options.StreamingCompressedMessageSize)
|
||||
bucketCounts := createBucketCounts([]float64{options.StreamingCompressedMessageSize}, DefaultSizeBounds)
|
||||
extrema := metricdata.NewExtrema(int64(options.StreamingCompressedMessageSize))
|
||||
return []metricdata.Metrics{
|
||||
{
|
||||
Name: "grpc.client.attempt.started",
|
||||
Description: "Number of client call attempts started.",
|
||||
Unit: "{attempt}",
|
||||
Data: metricdata.Sum[int64]{
|
||||
DataPoints: []metricdata.DataPoint[int64]{
|
||||
{
|
||||
Attributes: attribute.NewSet(methodAttr, targetAttr),
|
||||
Value: 1,
|
||||
},
|
||||
},
|
||||
Temporality: metricdata.CumulativeTemporality,
|
||||
IsMonotonic: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "grpc.client.attempt.duration",
|
||||
Description: "End-to-end time taken to complete a client call attempt.",
|
||||
Unit: "s",
|
||||
Data: metricdata.Histogram[float64]{
|
||||
DataPoints: []metricdata.HistogramDataPoint[float64]{
|
||||
{
|
||||
Attributes: attribute.NewSet(clientSideEnd...),
|
||||
Count: 1,
|
||||
Bounds: DefaultLatencyBounds,
|
||||
},
|
||||
},
|
||||
Temporality: metricdata.CumulativeTemporality,
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "grpc.client.attempt.sent_total_compressed_message_size",
|
||||
Description: "Compressed message bytes sent per client call attempt.",
|
||||
Unit: "By",
|
||||
Data: metricdata.Histogram[int64]{
|
||||
DataPoints: []metricdata.HistogramDataPoint[int64]{
|
||||
{
|
||||
Attributes: attribute.NewSet(clientSideEnd...),
|
||||
Count: 1,
|
||||
Bounds: DefaultSizeBounds,
|
||||
BucketCounts: bucketCounts,
|
||||
Min: extrema,
|
||||
Max: extrema,
|
||||
Sum: compressedBytesSentRecv,
|
||||
},
|
||||
},
|
||||
Temporality: metricdata.CumulativeTemporality,
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "grpc.client.attempt.rcvd_total_compressed_message_size",
|
||||
Description: "Compressed message bytes received per call attempt.",
|
||||
Unit: "By",
|
||||
Data: metricdata.Histogram[int64]{
|
||||
DataPoints: []metricdata.HistogramDataPoint[int64]{
|
||||
{
|
||||
Attributes: attribute.NewSet(clientSideEnd...),
|
||||
Count: 1,
|
||||
Bounds: DefaultSizeBounds,
|
||||
BucketCounts: bucketCounts,
|
||||
Min: extrema,
|
||||
Max: extrema,
|
||||
Sum: compressedBytesSentRecv,
|
||||
},
|
||||
},
|
||||
Temporality: metricdata.CumulativeTemporality,
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "grpc.client.call.duration",
|
||||
Description: "Time taken by gRPC to complete an RPC from application's perspective.",
|
||||
Unit: "s",
|
||||
Data: metricdata.Histogram[float64]{
|
||||
DataPoints: []metricdata.HistogramDataPoint[float64]{
|
||||
{
|
||||
Attributes: attribute.NewSet(methodAttr, targetAttr, statusAttr),
|
||||
Count: 1,
|
||||
Bounds: DefaultLatencyBounds,
|
||||
},
|
||||
},
|
||||
Temporality: metricdata.CumulativeTemporality,
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "grpc.server.call.started",
|
||||
Description: "Number of server calls started.",
|
||||
Unit: "{call}",
|
||||
Data: metricdata.Sum[int64]{
|
||||
DataPoints: []metricdata.DataPoint[int64]{
|
||||
{
|
||||
Attributes: attribute.NewSet(methodAttr),
|
||||
Value: 1,
|
||||
},
|
||||
},
|
||||
Temporality: metricdata.CumulativeTemporality,
|
||||
IsMonotonic: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "grpc.server.call.sent_total_compressed_message_size",
|
||||
Unit: "By",
|
||||
Description: "Compressed message bytes sent per server call.",
|
||||
Data: metricdata.Histogram[int64]{
|
||||
DataPoints: []metricdata.HistogramDataPoint[int64]{
|
||||
{
|
||||
Attributes: attribute.NewSet(serverSideEnd...),
|
||||
Count: 1,
|
||||
Bounds: DefaultSizeBounds,
|
||||
BucketCounts: bucketCounts,
|
||||
Min: extrema,
|
||||
Max: extrema,
|
||||
Sum: compressedBytesSentRecv,
|
||||
},
|
||||
},
|
||||
Temporality: metricdata.CumulativeTemporality,
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "grpc.server.call.rcvd_total_compressed_message_size",
|
||||
Unit: "By",
|
||||
Description: "Compressed message bytes received per server call.",
|
||||
Data: metricdata.Histogram[int64]{
|
||||
DataPoints: []metricdata.HistogramDataPoint[int64]{
|
||||
{
|
||||
Attributes: attribute.NewSet(serverSideEnd...),
|
||||
Count: 1,
|
||||
Bounds: DefaultSizeBounds,
|
||||
BucketCounts: bucketCounts,
|
||||
Min: extrema,
|
||||
Max: extrema,
|
||||
Sum: compressedBytesSentRecv,
|
||||
},
|
||||
},
|
||||
Temporality: metricdata.CumulativeTemporality,
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "grpc.server.call.duration",
|
||||
Description: "End-to-end time taken to complete a call from server transport's perspective.",
|
||||
Unit: "s",
|
||||
Data: metricdata.Histogram[float64]{
|
||||
DataPoints: []metricdata.HistogramDataPoint[float64]{
|
||||
{
|
||||
Attributes: attribute.NewSet(serverSideEnd...),
|
||||
Count: 1,
|
||||
Bounds: DefaultLatencyBounds,
|
||||
},
|
||||
},
|
||||
Temporality: metricdata.CumulativeTemporality,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// MetricData returns a metricsDataSlice for A66 metrics for client and server
|
||||
// with a unary RPC and streaming RPC with certain compression and message flow
|
||||
// sent. If csmAttributes is set to true, the corresponding CSM Metrics (not
|
||||
// client side call metrics, or started on client and server side).
|
||||
func MetricData(options MetricDataOptions) []metricdata.Metrics {
|
||||
unaryMethodAttr := attribute.String("grpc.method", "grpc.testing.TestService/UnaryCall")
|
||||
duplexMethodAttr := attribute.String("grpc.method", "grpc.testing.TestService/FullDuplexCall")
|
||||
targetAttr := attribute.String("grpc.target", options.Target)
|
||||
unaryStatusAttr := attribute.String("grpc.status", "OK")
|
||||
streamingStatusAttr := attribute.String("grpc.status", "OK")
|
||||
if options.UnaryCallFailed {
|
||||
unaryStatusAttr = attribute.String("grpc.status", "UNKNOWN")
|
||||
}
|
||||
unaryMethodClientSideEnd := []attribute.KeyValue{
|
||||
unaryMethodAttr,
|
||||
targetAttr,
|
||||
unaryStatusAttr,
|
||||
}
|
||||
streamingMethodClientSideEnd := []attribute.KeyValue{
|
||||
duplexMethodAttr,
|
||||
targetAttr,
|
||||
streamingStatusAttr,
|
||||
}
|
||||
unaryMethodServerSideEnd := []attribute.KeyValue{
|
||||
unaryMethodAttr,
|
||||
unaryStatusAttr,
|
||||
}
|
||||
streamingMethodServerSideEnd := []attribute.KeyValue{
|
||||
duplexMethodAttr,
|
||||
streamingStatusAttr,
|
||||
}
|
||||
|
||||
unaryMethodClientSideEnd = append(unaryMethodClientSideEnd, options.CSMLabels...)
|
||||
streamingMethodClientSideEnd = append(streamingMethodClientSideEnd, options.CSMLabels...)
|
||||
unaryMethodServerSideEnd = append(unaryMethodServerSideEnd, options.CSMLabels...)
|
||||
streamingMethodServerSideEnd = append(streamingMethodServerSideEnd, options.CSMLabels...)
|
||||
unaryCompressedBytesSentRecv := int64(options.UnaryCompressedMessageSize)
|
||||
unaryBucketCounts := createBucketCounts([]float64{options.UnaryCompressedMessageSize}, DefaultSizeBounds)
|
||||
unaryExtrema := metricdata.NewExtrema(int64(options.UnaryCompressedMessageSize))
|
||||
|
||||
streamingCompressedBytesSentRecv := int64(options.StreamingCompressedMessageSize)
|
||||
streamingBucketCounts := createBucketCounts([]float64{options.StreamingCompressedMessageSize}, DefaultSizeBounds)
|
||||
streamingExtrema := metricdata.NewExtrema(int64(options.StreamingCompressedMessageSize))
|
||||
|
||||
return []metricdata.Metrics{
|
||||
{
|
||||
Name: "grpc.client.attempt.started",
|
||||
Description: "Number of client call attempts started.",
|
||||
Unit: "{attempt}",
|
||||
Data: metricdata.Sum[int64]{
|
||||
DataPoints: []metricdata.DataPoint[int64]{
|
||||
{
|
||||
Attributes: attribute.NewSet(unaryMethodAttr, targetAttr),
|
||||
Value: 1,
|
||||
},
|
||||
{
|
||||
Attributes: attribute.NewSet(duplexMethodAttr, targetAttr),
|
||||
Value: 1,
|
||||
},
|
||||
},
|
||||
Temporality: metricdata.CumulativeTemporality,
|
||||
IsMonotonic: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "grpc.client.attempt.duration",
|
||||
Description: "End-to-end time taken to complete a client call attempt.",
|
||||
Unit: "s",
|
||||
Data: metricdata.Histogram[float64]{
|
||||
DataPoints: []metricdata.HistogramDataPoint[float64]{
|
||||
{
|
||||
Attributes: attribute.NewSet(unaryMethodClientSideEnd...),
|
||||
Count: 1,
|
||||
Bounds: DefaultLatencyBounds,
|
||||
},
|
||||
{
|
||||
Attributes: attribute.NewSet(streamingMethodClientSideEnd...),
|
||||
Count: 1,
|
||||
Bounds: DefaultLatencyBounds,
|
||||
},
|
||||
},
|
||||
Temporality: metricdata.CumulativeTemporality,
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "grpc.client.attempt.sent_total_compressed_message_size",
|
||||
Description: "Compressed message bytes sent per client call attempt.",
|
||||
Unit: "By",
|
||||
Data: metricdata.Histogram[int64]{
|
||||
DataPoints: []metricdata.HistogramDataPoint[int64]{
|
||||
{
|
||||
Attributes: attribute.NewSet(unaryMethodClientSideEnd...),
|
||||
Count: 1,
|
||||
Bounds: DefaultSizeBounds,
|
||||
BucketCounts: unaryBucketCounts,
|
||||
Min: unaryExtrema,
|
||||
Max: unaryExtrema,
|
||||
Sum: unaryCompressedBytesSentRecv,
|
||||
},
|
||||
{
|
||||
Attributes: attribute.NewSet(streamingMethodClientSideEnd...),
|
||||
Count: 1,
|
||||
Bounds: DefaultSizeBounds,
|
||||
BucketCounts: streamingBucketCounts,
|
||||
Min: streamingExtrema,
|
||||
Max: streamingExtrema,
|
||||
Sum: streamingCompressedBytesSentRecv,
|
||||
},
|
||||
},
|
||||
Temporality: metricdata.CumulativeTemporality,
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "grpc.client.attempt.rcvd_total_compressed_message_size",
|
||||
Description: "Compressed message bytes received per call attempt.",
|
||||
Unit: "By",
|
||||
Data: metricdata.Histogram[int64]{
|
||||
DataPoints: []metricdata.HistogramDataPoint[int64]{
|
||||
{
|
||||
Attributes: attribute.NewSet(unaryMethodClientSideEnd...),
|
||||
Count: 1,
|
||||
Bounds: DefaultSizeBounds,
|
||||
BucketCounts: unaryBucketCounts,
|
||||
Min: unaryExtrema,
|
||||
Max: unaryExtrema,
|
||||
Sum: unaryCompressedBytesSentRecv,
|
||||
},
|
||||
{
|
||||
Attributes: attribute.NewSet(streamingMethodClientSideEnd...),
|
||||
Count: 1,
|
||||
Bounds: DefaultSizeBounds,
|
||||
BucketCounts: streamingBucketCounts,
|
||||
Min: streamingExtrema,
|
||||
Max: streamingExtrema,
|
||||
Sum: streamingCompressedBytesSentRecv,
|
||||
},
|
||||
},
|
||||
Temporality: metricdata.CumulativeTemporality,
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "grpc.client.call.duration",
|
||||
Description: "Time taken by gRPC to complete an RPC from application's perspective.",
|
||||
Unit: "s",
|
||||
Data: metricdata.Histogram[float64]{
|
||||
DataPoints: []metricdata.HistogramDataPoint[float64]{
|
||||
{
|
||||
Attributes: attribute.NewSet(unaryMethodAttr, targetAttr, unaryStatusAttr),
|
||||
Count: 1,
|
||||
Bounds: DefaultLatencyBounds,
|
||||
},
|
||||
{
|
||||
Attributes: attribute.NewSet(duplexMethodAttr, targetAttr, streamingStatusAttr),
|
||||
Count: 1,
|
||||
Bounds: DefaultLatencyBounds,
|
||||
},
|
||||
},
|
||||
Temporality: metricdata.CumulativeTemporality,
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "grpc.server.call.started",
|
||||
Description: "Number of server calls started.",
|
||||
Unit: "{call}",
|
||||
Data: metricdata.Sum[int64]{
|
||||
DataPoints: []metricdata.DataPoint[int64]{
|
||||
{
|
||||
Attributes: attribute.NewSet(unaryMethodAttr),
|
||||
Value: 1,
|
||||
},
|
||||
{
|
||||
Attributes: attribute.NewSet(duplexMethodAttr),
|
||||
Value: 1,
|
||||
},
|
||||
},
|
||||
Temporality: metricdata.CumulativeTemporality,
|
||||
IsMonotonic: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "grpc.server.call.sent_total_compressed_message_size",
|
||||
Unit: "By",
|
||||
Description: "Compressed message bytes sent per server call.",
|
||||
Data: metricdata.Histogram[int64]{
|
||||
DataPoints: []metricdata.HistogramDataPoint[int64]{
|
||||
{
|
||||
Attributes: attribute.NewSet(unaryMethodServerSideEnd...),
|
||||
Count: 1,
|
||||
Bounds: DefaultSizeBounds,
|
||||
BucketCounts: unaryBucketCounts,
|
||||
Min: unaryExtrema,
|
||||
Max: unaryExtrema,
|
||||
Sum: unaryCompressedBytesSentRecv,
|
||||
},
|
||||
{
|
||||
Attributes: attribute.NewSet(streamingMethodServerSideEnd...),
|
||||
Count: 1,
|
||||
Bounds: DefaultSizeBounds,
|
||||
BucketCounts: streamingBucketCounts,
|
||||
Min: streamingExtrema,
|
||||
Max: streamingExtrema,
|
||||
Sum: streamingCompressedBytesSentRecv,
|
||||
},
|
||||
},
|
||||
Temporality: metricdata.CumulativeTemporality,
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "grpc.server.call.rcvd_total_compressed_message_size",
|
||||
Unit: "By",
|
||||
Description: "Compressed message bytes received per server call.",
|
||||
Data: metricdata.Histogram[int64]{
|
||||
DataPoints: []metricdata.HistogramDataPoint[int64]{
|
||||
{
|
||||
Attributes: attribute.NewSet(unaryMethodServerSideEnd...),
|
||||
Count: 1,
|
||||
Bounds: DefaultSizeBounds,
|
||||
BucketCounts: unaryBucketCounts,
|
||||
Min: unaryExtrema,
|
||||
Max: unaryExtrema,
|
||||
Sum: unaryCompressedBytesSentRecv,
|
||||
},
|
||||
{
|
||||
Attributes: attribute.NewSet(streamingMethodServerSideEnd...),
|
||||
Count: 1,
|
||||
Bounds: DefaultSizeBounds,
|
||||
BucketCounts: streamingBucketCounts,
|
||||
Min: streamingExtrema,
|
||||
Max: streamingExtrema,
|
||||
Sum: streamingCompressedBytesSentRecv,
|
||||
},
|
||||
},
|
||||
Temporality: metricdata.CumulativeTemporality,
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "grpc.server.call.duration",
|
||||
Description: "End-to-end time taken to complete a call from server transport's perspective.",
|
||||
Unit: "s",
|
||||
Data: metricdata.Histogram[float64]{
|
||||
DataPoints: []metricdata.HistogramDataPoint[float64]{
|
||||
{
|
||||
Attributes: attribute.NewSet(unaryMethodServerSideEnd...),
|
||||
Count: 1,
|
||||
Bounds: DefaultLatencyBounds,
|
||||
},
|
||||
{
|
||||
Attributes: attribute.NewSet(streamingMethodServerSideEnd...),
|
||||
Count: 1,
|
||||
Bounds: DefaultLatencyBounds,
|
||||
},
|
||||
},
|
||||
Temporality: metricdata.CumulativeTemporality,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// GzipCompressedMessageSize returns the byte length of the proto message after
|
||||
// gzip compression.
|
||||
func GzipCompressedMessageSize(t *testing.T, m proto.Message) int {
|
||||
t.Helper()
|
||||
data, err := proto.Marshal(m)
|
||||
if err != nil {
|
||||
t.Fatalf("proto.Marshal failed: %v", err)
|
||||
}
|
||||
var buf bytes.Buffer
|
||||
zw := gzip.NewWriter(&buf)
|
||||
if _, err := zw.Write(data); err != nil {
|
||||
t.Fatalf("gzip.Write failed: %v", err)
|
||||
}
|
||||
if err := zw.Close(); err != nil {
|
||||
t.Fatalf("gzip.Close failed: %v", err)
|
||||
}
|
||||
return buf.Len()
|
||||
}
|
||||
|
||||
// CompareMetrics asserts wantMetrics are what we expect. For duration metrics
|
||||
// makes sure the data point is within possible testing time (five seconds from
|
||||
// context timeout).
|
||||
func CompareMetrics(t *testing.T, gotMetrics map[string]metricdata.Metrics, wantMetrics []metricdata.Metrics) {
|
||||
for _, metric := range wantMetrics {
|
||||
val, ok := gotMetrics[metric.Name]
|
||||
if !ok {
|
||||
t.Errorf("Metric %v not present in recorded metrics", metric.Name)
|
||||
continue
|
||||
}
|
||||
|
||||
if metric.Name == "grpc.server.call.sent_total_compressed_message_size" || metric.Name == "grpc.server.call.rcvd_total_compressed_message_size" {
|
||||
val := gotMetrics[metric.Name]
|
||||
if !metricdatatest.AssertEqual(t, metric, val, metricdatatest.IgnoreTimestamp(), metricdatatest.IgnoreExemplars()) {
|
||||
t.Errorf("Metrics data type not equal for metric: %v", metric.Name)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// If one of the duration metrics, ignore the bucket counts, and make
|
||||
// sure it count falls within a bucket <= 5 seconds (maximum duration of
|
||||
// test due to context).
|
||||
if metric.Name == "grpc.client.attempt.duration" || metric.Name == "grpc.client.call.duration" || metric.Name == "grpc.server.call.duration" {
|
||||
val := gotMetrics[metric.Name]
|
||||
if !metricdatatest.AssertEqual(t, metric, val, metricdatatest.IgnoreTimestamp(), metricdatatest.IgnoreExemplars(), metricdatatest.IgnoreValue()) {
|
||||
t.Errorf("Metrics data type not equal for metric: %v", metric.Name)
|
||||
}
|
||||
if err := checkDataPointWithinFiveSeconds(val); err != nil {
|
||||
t.Errorf("Data point not within five seconds for metric %v: %v", metric.Name, err)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if !metricdatatest.AssertEqual(t, metric, val, metricdatatest.IgnoreTimestamp(), metricdatatest.IgnoreExemplars()) {
|
||||
t.Errorf("Metrics data type not equal for metric: %v", metric.Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// WaitForServerMetrics waits for eventual server metrics (not emitted
|
||||
// synchronously with client side rpc returning).
|
||||
func WaitForServerMetrics(ctx context.Context, t *testing.T, mr *metric.ManualReader, gotMetrics map[string]metricdata.Metrics, wantMetrics []metricdata.Metrics) map[string]metricdata.Metrics {
|
||||
terminalMetrics := []string{
|
||||
"grpc.server.call.sent_total_compressed_message_size",
|
||||
"grpc.server.call.rcvd_total_compressed_message_size",
|
||||
"grpc.client.attempt.duration",
|
||||
"grpc.client.call.duration",
|
||||
"grpc.server.call.duration",
|
||||
}
|
||||
for _, metric := range wantMetrics {
|
||||
if !slices.Contains(terminalMetrics, metric.Name) {
|
||||
continue
|
||||
}
|
||||
// Sync the metric reader to see the event because stats.End is
|
||||
// handled async server side. Thus, poll until metrics created from
|
||||
// stats.End show up.
|
||||
var err error
|
||||
if gotMetrics, err = waitForServerCompletedRPCs(ctx, mr, metric); err != nil { // move to shared helper
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
return gotMetrics
|
||||
}
|
||||
131
stats/opentelemetry/internal/tracing/carrier.go
Normal file
131
stats/opentelemetry/internal/tracing/carrier.go
Normal file
@@ -0,0 +1,131 @@
|
||||
/*
|
||||
*
|
||||
* 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 tracing implements the OpenTelemetry carrier for context propagation
|
||||
// in gRPC tracing.
|
||||
package tracing
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"google.golang.org/grpc/grpclog"
|
||||
"google.golang.org/grpc/metadata"
|
||||
)
|
||||
|
||||
var logger = grpclog.Component("otel-plugin")
|
||||
|
||||
// IncomingCarrier is a TextMapCarrier that uses incoming `context.Context` to
|
||||
// retrieve any propagated key-value pairs in text format.
|
||||
type IncomingCarrier struct {
|
||||
ctx context.Context
|
||||
}
|
||||
|
||||
// NewIncomingCarrier creates a new `IncomingCarrier` with the given context.
|
||||
// The incoming carrier should be used with propagator's `Extract()` method in
|
||||
// the incoming rpc path.
|
||||
func NewIncomingCarrier(ctx context.Context) *IncomingCarrier {
|
||||
return &IncomingCarrier{ctx: ctx}
|
||||
}
|
||||
|
||||
// Get returns the string value associated with the passed key from the
|
||||
// carrier's incoming context metadata.
|
||||
//
|
||||
// It returns an empty string if the key is not present in the carrier's
|
||||
// context or if the value associated with the key is empty.
|
||||
//
|
||||
// If multiple values are present for a key, it returns the last one.
|
||||
func (c *IncomingCarrier) Get(key string) string {
|
||||
values := metadata.ValueFromIncomingContext(c.ctx, key)
|
||||
if len(values) == 0 {
|
||||
return ""
|
||||
}
|
||||
return values[len(values)-1]
|
||||
}
|
||||
|
||||
// Set just logs an error. It implements the `TextMapCarrier` interface but
|
||||
// should not be used with `IncomingCarrier`.
|
||||
func (c *IncomingCarrier) Set(string, string) {
|
||||
logger.Error("Set() should not be used with IncomingCarrier.")
|
||||
}
|
||||
|
||||
// Keys returns the keys stored in the carrier's context metadata. It returns
|
||||
// keys from incoming context metadata.
|
||||
func (c *IncomingCarrier) Keys() []string {
|
||||
md, ok := metadata.FromIncomingContext(c.ctx)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
keys := make([]string, 0, len(md))
|
||||
for key := range md {
|
||||
keys = append(keys, key)
|
||||
}
|
||||
return keys
|
||||
}
|
||||
|
||||
// Context returns the underlying context associated with the
|
||||
// `IncomingCarrier“.
|
||||
func (c *IncomingCarrier) Context() context.Context {
|
||||
return c.ctx
|
||||
}
|
||||
|
||||
// OutgoingCarrier is a TextMapCarrier that uses outgoing `context.Context` to
|
||||
// store any propagated key-value pairs in text format.
|
||||
type OutgoingCarrier struct {
|
||||
ctx context.Context
|
||||
}
|
||||
|
||||
// NewOutgoingCarrier creates a new Carrier with the given context. The
|
||||
// outgoing carrier should be used with propagator's `Inject()` method in the
|
||||
// outgoing rpc path.
|
||||
func NewOutgoingCarrier(ctx context.Context) *OutgoingCarrier {
|
||||
return &OutgoingCarrier{ctx: ctx}
|
||||
}
|
||||
|
||||
// Get just logs an error and returns an empty string. It implements the
|
||||
// `TextMapCarrier` interface but should not be used with `OutgoingCarrier`.
|
||||
func (c *OutgoingCarrier) Get(string) string {
|
||||
logger.Error("Get() should not be used with `OutgoingCarrier`")
|
||||
return ""
|
||||
}
|
||||
|
||||
// Set stores the key-value pair in the carrier's outgoing context metadata.
|
||||
//
|
||||
// If the key already exists, given value is appended to the last.
|
||||
func (c *OutgoingCarrier) Set(key, value string) {
|
||||
c.ctx = metadata.AppendToOutgoingContext(c.ctx, key, value)
|
||||
}
|
||||
|
||||
// Keys returns the keys stored in the carrier's context metadata. It returns
|
||||
// keys from outgoing context metadata.
|
||||
func (c *OutgoingCarrier) Keys() []string {
|
||||
md, ok := metadata.FromOutgoingContext(c.ctx)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
keys := make([]string, 0, len(md))
|
||||
for key := range md {
|
||||
keys = append(keys, key)
|
||||
}
|
||||
return keys
|
||||
}
|
||||
|
||||
// Context returns the underlying context associated with the
|
||||
// `OutgoingCarrier“.
|
||||
func (c *OutgoingCarrier) Context() context.Context {
|
||||
return c.ctx
|
||||
}
|
||||
190
stats/opentelemetry/internal/tracing/carrier_test.go
Normal file
190
stats/opentelemetry/internal/tracing/carrier_test.go
Normal file
@@ -0,0 +1,190 @@
|
||||
/*
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* htestp://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 tracing
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/google/go-cmp/cmp/cmpopts"
|
||||
"google.golang.org/grpc/internal/grpctest"
|
||||
"google.golang.org/grpc/metadata"
|
||||
)
|
||||
|
||||
type s struct {
|
||||
grpctest.Tester
|
||||
}
|
||||
|
||||
func Test(t *testing.T) {
|
||||
grpctest.RunSubTests(t, s{})
|
||||
}
|
||||
|
||||
// TestIncomingCarrier verifies that `IncomingCarrier.Get()` returns correct
|
||||
// value for the corresponding key in the carrier's context metadata, if key is
|
||||
// present. If key is not present, it verifies that empty string is returned.
|
||||
//
|
||||
// If multiple values are present for a key, it verifies that last value is
|
||||
// returned.
|
||||
//
|
||||
// If key ends with `-bin`, it verifies that a correct binary value is returned
|
||||
// in the string format for the binary header.
|
||||
func (s) TestIncomingCarrier(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
md metadata.MD
|
||||
key string
|
||||
want string
|
||||
wantKeys []string
|
||||
}{
|
||||
{
|
||||
name: "existing key",
|
||||
md: metadata.Pairs("key1", "value1"),
|
||||
key: "key1",
|
||||
want: "value1",
|
||||
wantKeys: []string{"key1"},
|
||||
},
|
||||
{
|
||||
name: "non-existing key",
|
||||
md: metadata.Pairs("key1", "value1"),
|
||||
key: "key2",
|
||||
want: "",
|
||||
wantKeys: []string{"key1"},
|
||||
},
|
||||
{
|
||||
name: "empty key",
|
||||
md: metadata.MD{},
|
||||
key: "key1",
|
||||
want: "",
|
||||
wantKeys: []string{},
|
||||
},
|
||||
{
|
||||
name: "more than one key/value pair",
|
||||
md: metadata.MD{"key1": []string{"value1"}, "key2": []string{"value2"}},
|
||||
key: "key2",
|
||||
want: "value2",
|
||||
wantKeys: []string{"key1", "key2"},
|
||||
},
|
||||
{
|
||||
name: "more than one value for a key",
|
||||
md: metadata.MD{"key1": []string{"value1", "value2"}},
|
||||
key: "key1",
|
||||
want: "value2",
|
||||
wantKeys: []string{"key1"},
|
||||
},
|
||||
{
|
||||
name: "grpc-trace-bin key",
|
||||
md: metadata.Pairs("grpc-trace-bin", string([]byte{0x01, 0x02, 0x03})),
|
||||
key: "grpc-trace-bin",
|
||||
want: string([]byte{0x01, 0x02, 0x03}),
|
||||
wantKeys: []string{"grpc-trace-bin"},
|
||||
},
|
||||
{
|
||||
name: "grpc-trace-bin key with another string key",
|
||||
md: metadata.MD{"key1": []string{"value1"}, "grpc-trace-bin": []string{string([]byte{0x01, 0x02, 0x03})}},
|
||||
key: "grpc-trace-bin",
|
||||
want: string([]byte{0x01, 0x02, 0x03}),
|
||||
wantKeys: []string{"key1", "grpc-trace-bin"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
c := NewIncomingCarrier(metadata.NewIncomingContext(ctx, test.md))
|
||||
got := c.Get(test.key)
|
||||
if got != test.want {
|
||||
t.Fatalf("c.Get() = %s, want %s", got, test.want)
|
||||
}
|
||||
if gotKeys := c.Keys(); !cmp.Equal(test.wantKeys, gotKeys, cmpopts.SortSlices(func(a, b string) bool { return a < b })) {
|
||||
t.Fatalf("c.Keys() = keys %v, want %v", gotKeys, test.wantKeys)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestOutgoingCarrier verifies that a key-value pair is set in carrier's
|
||||
// context metadata using `OutgoingCarrier.Set()`. If key is not present, it
|
||||
// verifies that key-value pair is insterted. If key is already present, it
|
||||
// verifies that new value is appended at the end of list for the existing key.
|
||||
//
|
||||
// If key ends with `-bin`, it verifies that a binary value is set for
|
||||
// `-bin` header in string format.
|
||||
//
|
||||
// It also verifies that both existing and newly inserted keys are present in
|
||||
// the carrier's context using `Carrier.Keys()`.
|
||||
func (s) TestOutgoingCarrier(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
initialMD metadata.MD
|
||||
setKey string
|
||||
setValue string
|
||||
wantValue string // expected value of the set key
|
||||
wantKeys []string
|
||||
}{
|
||||
{
|
||||
name: "new key",
|
||||
initialMD: metadata.MD{},
|
||||
setKey: "key1",
|
||||
setValue: "value1",
|
||||
wantValue: "value1",
|
||||
wantKeys: []string{"key1"},
|
||||
},
|
||||
{
|
||||
name: "add to existing key",
|
||||
initialMD: metadata.MD{"key1": []string{"oldvalue"}},
|
||||
setKey: "key1",
|
||||
setValue: "newvalue",
|
||||
wantValue: "newvalue",
|
||||
wantKeys: []string{"key1"},
|
||||
},
|
||||
{
|
||||
name: "new key with different existing key",
|
||||
initialMD: metadata.MD{"key2": []string{"value2"}},
|
||||
setKey: "key1",
|
||||
setValue: "value1",
|
||||
wantValue: "value1",
|
||||
wantKeys: []string{"key2", "key1"},
|
||||
},
|
||||
{
|
||||
name: "grpc-trace-bin binary key",
|
||||
initialMD: metadata.MD{"key1": []string{"value1"}},
|
||||
setKey: "grpc-trace-bin",
|
||||
setValue: string([]byte{0x01, 0x02, 0x03}),
|
||||
wantValue: string([]byte{0x01, 0x02, 0x03}),
|
||||
wantKeys: []string{"key1", "grpc-trace-bin"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
c := NewOutgoingCarrier(metadata.NewOutgoingContext(ctx, test.initialMD))
|
||||
c.Set(test.setKey, test.setValue)
|
||||
if gotKeys := c.Keys(); !cmp.Equal(test.wantKeys, gotKeys, cmpopts.SortSlices(func(a, b string) bool { return a < b })) {
|
||||
t.Fatalf("c.Keys() = keys %v, want %v", gotKeys, test.wantKeys)
|
||||
}
|
||||
if md, ok := metadata.FromOutgoingContext(c.Context()); ok && md.Get(test.setKey)[len(md.Get(test.setKey))-1] != test.wantValue {
|
||||
t.Fatalf("got value %s, want %s, for key %s", md.Get(test.setKey)[len(md.Get(test.setKey))-1], test.wantValue, test.setKey)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user