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:
150
stats/opencensus/client_metrics.go
Normal file
150
stats/opencensus/client_metrics.go
Normal file
@@ -0,0 +1,150 @@
|
||||
/*
|
||||
* Copyright 2022 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 opencensus
|
||||
|
||||
import (
|
||||
"go.opencensus.io/stats"
|
||||
"go.opencensus.io/stats/view"
|
||||
"go.opencensus.io/tag"
|
||||
)
|
||||
|
||||
var (
|
||||
keyClientMethod = tag.MustNewKey("grpc_client_method")
|
||||
keyClientStatus = tag.MustNewKey("grpc_client_status")
|
||||
)
|
||||
|
||||
// Measures, which are recorded by client stats handler: Note that due to the
|
||||
// nature of how stats handlers are called on gRPC's client side, the per rpc
|
||||
// unit is actually per attempt throughout this definition file.
|
||||
var (
|
||||
clientSentMessagesPerRPC = stats.Int64("grpc.io/client/sent_messages_per_rpc", "Number of messages sent in the RPC (always 1 for non-streaming RPCs).", stats.UnitDimensionless)
|
||||
clientSentBytesPerRPC = stats.Int64("grpc.io/client/sent_bytes_per_rpc", "Total bytes sent across all request messages per RPC.", stats.UnitBytes)
|
||||
clientSentCompressedBytesPerRPC = stats.Int64("grpc.io/client/sent_compressed_message_bytes_per_rpc", "Total compressed bytes sent across all request messages per RPC.", stats.UnitBytes)
|
||||
clientReceivedMessagesPerRPC = stats.Int64("grpc.io/client/received_messages_per_rpc", "Number of response messages received per RPC (always 1 for non-streaming RPCs).", stats.UnitDimensionless)
|
||||
clientReceivedBytesPerRPC = stats.Int64("grpc.io/client/received_bytes_per_rpc", "Total bytes received across all response messages per RPC.", stats.UnitBytes)
|
||||
clientReceivedCompressedBytesPerRPC = stats.Int64("grpc.io/client/received_compressed_message_bytes_per_rpc", "Total compressed bytes received across all response messages per RPC.", stats.UnitBytes)
|
||||
clientRoundtripLatency = stats.Float64("grpc.io/client/roundtrip_latency", "Time between first byte of request sent to last byte of response received, or terminal error.", stats.UnitMilliseconds)
|
||||
clientStartedRPCs = stats.Int64("grpc.io/client/started_rpcs", "The total number of client RPCs ever opened, including those that have not completed.", stats.UnitDimensionless)
|
||||
clientServerLatency = stats.Float64("grpc.io/client/server_latency", `Propagated from the server and should have the same value as "grpc.io/server/latency".`, stats.UnitMilliseconds)
|
||||
// Per call measure:
|
||||
clientAPILatency = stats.Float64("grpc.io/client/api_latency", "The end-to-end time the gRPC library takes to complete an RPC from the application’s perspective", stats.UnitMilliseconds)
|
||||
)
|
||||
|
||||
var (
|
||||
// ClientSentMessagesPerRPCView is the distribution of sent messages per
|
||||
// RPC, keyed on method.
|
||||
ClientSentMessagesPerRPCView = &view.View{
|
||||
Measure: clientSentMessagesPerRPC,
|
||||
Name: "grpc.io/client/sent_messages_per_rpc",
|
||||
Description: "Distribution of sent messages per RPC, by method.",
|
||||
TagKeys: []tag.Key{keyClientMethod},
|
||||
Aggregation: countDistribution,
|
||||
}
|
||||
// ClientReceivedMessagesPerRPCView is the distribution of received messages
|
||||
// per RPC, keyed on method.
|
||||
ClientReceivedMessagesPerRPCView = &view.View{
|
||||
Measure: clientReceivedMessagesPerRPC,
|
||||
Name: "grpc.io/client/received_messages_per_rpc",
|
||||
Description: "Distribution of received messages per RPC, by method.",
|
||||
TagKeys: []tag.Key{keyClientMethod},
|
||||
Aggregation: countDistribution,
|
||||
}
|
||||
// ClientSentBytesPerRPCView is the distribution of sent bytes per RPC,
|
||||
// keyed on method.
|
||||
ClientSentBytesPerRPCView = &view.View{
|
||||
Measure: clientSentBytesPerRPC,
|
||||
Name: "grpc.io/client/sent_bytes_per_rpc",
|
||||
Description: "Distribution of sent bytes per RPC, by method.",
|
||||
TagKeys: []tag.Key{keyClientMethod},
|
||||
Aggregation: bytesDistribution,
|
||||
}
|
||||
// ClientSentCompressedMessageBytesPerRPCView is the distribution of
|
||||
// compressed sent message bytes per RPC, keyed on method.
|
||||
ClientSentCompressedMessageBytesPerRPCView = &view.View{
|
||||
Measure: clientSentCompressedBytesPerRPC,
|
||||
Name: "grpc.io/client/sent_compressed_message_bytes_per_rpc",
|
||||
Description: "Distribution of sent compressed message bytes per RPC, by method.",
|
||||
TagKeys: []tag.Key{keyClientMethod},
|
||||
Aggregation: bytesDistribution,
|
||||
}
|
||||
// ClientReceivedBytesPerRPCView is the distribution of received bytes per
|
||||
// RPC, keyed on method.
|
||||
ClientReceivedBytesPerRPCView = &view.View{
|
||||
Measure: clientReceivedBytesPerRPC,
|
||||
Name: "grpc.io/client/received_bytes_per_rpc",
|
||||
Description: "Distribution of received bytes per RPC, by method.",
|
||||
TagKeys: []tag.Key{keyClientMethod},
|
||||
Aggregation: bytesDistribution,
|
||||
}
|
||||
// ClientReceivedCompressedMessageBytesPerRPCView is the distribution of
|
||||
// compressed received message bytes per RPC, keyed on method.
|
||||
ClientReceivedCompressedMessageBytesPerRPCView = &view.View{
|
||||
Measure: clientReceivedCompressedBytesPerRPC,
|
||||
Name: "grpc.io/client/received_compressed_message_bytes_per_rpc",
|
||||
Description: "Distribution of received compressed message bytes per RPC, by method.",
|
||||
TagKeys: []tag.Key{keyClientMethod},
|
||||
Aggregation: bytesDistribution,
|
||||
}
|
||||
// ClientStartedRPCsView is the count of opened RPCs, keyed on method.
|
||||
ClientStartedRPCsView = &view.View{
|
||||
Measure: clientStartedRPCs,
|
||||
Name: "grpc.io/client/started_rpcs",
|
||||
Description: "Number of opened client RPCs, by method.",
|
||||
TagKeys: []tag.Key{keyClientMethod},
|
||||
Aggregation: view.Count(),
|
||||
}
|
||||
// ClientCompletedRPCsView is the count of completed RPCs, keyed on method
|
||||
// and status.
|
||||
ClientCompletedRPCsView = &view.View{
|
||||
Measure: clientRoundtripLatency,
|
||||
Name: "grpc.io/client/completed_rpcs",
|
||||
Description: "Number of completed RPCs by method and status.",
|
||||
TagKeys: []tag.Key{keyClientMethod, keyClientStatus},
|
||||
Aggregation: view.Count(),
|
||||
}
|
||||
// ClientRoundtripLatencyView is the distribution of round-trip latency in
|
||||
// milliseconds per RPC, keyed on method.
|
||||
ClientRoundtripLatencyView = &view.View{
|
||||
Measure: clientRoundtripLatency,
|
||||
Name: "grpc.io/client/roundtrip_latency",
|
||||
Description: "Distribution of round-trip latency, by method.",
|
||||
TagKeys: []tag.Key{keyClientMethod},
|
||||
Aggregation: millisecondsDistribution,
|
||||
}
|
||||
|
||||
// The following metric is per call:
|
||||
|
||||
// ClientAPILatencyView is the distribution of client api latency for the
|
||||
// full RPC call, keyed on method and status.
|
||||
ClientAPILatencyView = &view.View{
|
||||
Measure: clientAPILatency,
|
||||
Name: "grpc.io/client/api_latency",
|
||||
Description: "Distribution of client api latency, by method and status",
|
||||
TagKeys: []tag.Key{keyClientMethod, keyClientStatus},
|
||||
Aggregation: millisecondsDistribution,
|
||||
}
|
||||
)
|
||||
|
||||
// DefaultClientViews is the set of client views which are considered the
|
||||
// minimum required to monitor client side performance.
|
||||
var DefaultClientViews = []*view.View{
|
||||
ClientSentBytesPerRPCView,
|
||||
ClientReceivedBytesPerRPCView,
|
||||
ClientRoundtripLatencyView,
|
||||
ClientCompletedRPCsView,
|
||||
ClientStartedRPCsView,
|
||||
}
|
||||
1619
stats/opencensus/e2e_test.go
Normal file
1619
stats/opencensus/e2e_test.go
Normal file
File diff suppressed because it is too large
Load Diff
20
stats/opencensus/go.mod
Normal file
20
stats/opencensus/go.mod
Normal file
@@ -0,0 +1,20 @@
|
||||
module google.golang.org/grpc/stats/opencensus
|
||||
|
||||
go 1.25.0
|
||||
|
||||
require (
|
||||
github.com/google/go-cmp v0.7.0
|
||||
go.opencensus.io v0.24.0
|
||||
google.golang.org/grpc v1.83.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
|
||||
golang.org/x/net v0.58.0 // indirect
|
||||
golang.org/x/sys v0.47.0 // indirect
|
||||
golang.org/x/text v0.41.0 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260817212433-ac3dfec99bb1 // indirect
|
||||
google.golang.org/protobuf v1.36.12 // indirect
|
||||
)
|
||||
|
||||
replace google.golang.org/grpc => ../..
|
||||
251
stats/opencensus/opencensus.go
Normal file
251
stats/opencensus/opencensus.go
Normal file
@@ -0,0 +1,251 @@
|
||||
/*
|
||||
* Copyright 2022 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 opencensus implements opencensus instrumentation code for gRPC-Go
|
||||
// clients and servers.
|
||||
package opencensus
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
ocstats "go.opencensus.io/stats"
|
||||
"go.opencensus.io/tag"
|
||||
"go.opencensus.io/trace"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/internal"
|
||||
"google.golang.org/grpc/stats"
|
||||
"google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
var (
|
||||
joinDialOptions = internal.JoinDialOptions.(func(...grpc.DialOption) grpc.DialOption)
|
||||
)
|
||||
|
||||
// TraceOptions are the tracing options for opencensus instrumentation.
|
||||
type TraceOptions struct {
|
||||
// TS is the Sampler used for tracing.
|
||||
TS trace.Sampler
|
||||
// DisableTrace determines whether traces are disabled for an OpenCensus
|
||||
// Dial or Server option. will overwrite any global option setting.
|
||||
DisableTrace bool
|
||||
}
|
||||
|
||||
// DialOption returns a dial option which enables OpenCensus instrumentation
|
||||
// code for a grpc.ClientConn.
|
||||
//
|
||||
// Client applications interested in instrumenting their grpc.ClientConn should
|
||||
// pass the dial option returned from this function as the first dial option to
|
||||
// grpc.Dial().
|
||||
//
|
||||
// Using this option will always lead to instrumentation, however in order to
|
||||
// use the data an exporter must be registered with the OpenCensus trace package
|
||||
// for traces and the OpenCensus view package for metrics. Client side has
|
||||
// retries, so a Unary and Streaming Interceptor are registered to handle per
|
||||
// RPC traces/metrics, and a Stats Handler is registered to handle per RPC
|
||||
// attempt trace/metrics. These three components registered work together in
|
||||
// conjunction, and do not work standalone. It is not supported to use this
|
||||
// alongside another stats handler dial option.
|
||||
func DialOption(to TraceOptions) grpc.DialOption {
|
||||
csh := &clientStatsHandler{to: to}
|
||||
return joinDialOptions(grpc.WithChainUnaryInterceptor(csh.unaryInterceptor), grpc.WithChainStreamInterceptor(csh.streamInterceptor), grpc.WithStatsHandler(csh))
|
||||
}
|
||||
|
||||
// ServerOption returns a server option which enables OpenCensus instrumentation
|
||||
// code for a grpc.Server.
|
||||
//
|
||||
// Server applications interested in instrumenting their grpc.Server should
|
||||
// pass the server option returned from this function as the first argument to
|
||||
// grpc.NewServer().
|
||||
//
|
||||
// Using this option will always lead to instrumentation, however in order to
|
||||
// use the data an exporter must be registered with the OpenCensus trace package
|
||||
// for traces and the OpenCensus view package for metrics. Server side does not
|
||||
// have retries, so a registered Stats Handler is the only option that is
|
||||
// returned. It is not supported to use this alongside another stats handler
|
||||
// server option.
|
||||
func ServerOption(to TraceOptions) grpc.ServerOption {
|
||||
return grpc.StatsHandler(&serverStatsHandler{to: to})
|
||||
}
|
||||
|
||||
// createCallSpan creates a call span if tracing is enabled, which will be put
|
||||
// in the context provided if created.
|
||||
func (csh *clientStatsHandler) createCallSpan(ctx context.Context, method string) (context.Context, *trace.Span) {
|
||||
var span *trace.Span
|
||||
if !csh.to.DisableTrace {
|
||||
mn := strings.ReplaceAll(removeLeadingSlash(method), "/", ".")
|
||||
ctx, span = trace.StartSpan(ctx, mn, trace.WithSampler(csh.to.TS), trace.WithSpanKind(trace.SpanKindClient))
|
||||
}
|
||||
return ctx, span
|
||||
}
|
||||
|
||||
// perCallTracesAndMetrics records per call spans and metrics.
|
||||
func perCallTracesAndMetrics(err error, span *trace.Span, startTime time.Time, method string) {
|
||||
s := status.Convert(err)
|
||||
if span != nil {
|
||||
span.SetStatus(trace.Status{Code: int32(s.Code()), Message: s.Message()})
|
||||
span.End()
|
||||
}
|
||||
callLatency := float64(time.Since(startTime)) / float64(time.Millisecond)
|
||||
ocstats.RecordWithOptions(context.Background(),
|
||||
ocstats.WithTags(
|
||||
tag.Upsert(keyClientMethod, removeLeadingSlash(method)),
|
||||
tag.Upsert(keyClientStatus, canonicalString(s.Code())),
|
||||
),
|
||||
ocstats.WithMeasurements(
|
||||
clientAPILatency.M(callLatency),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
// unaryInterceptor handles per RPC context management. It also handles per RPC
|
||||
// tracing and stats by creating a top level call span and recording the latency
|
||||
// for the full RPC call.
|
||||
func (csh *clientStatsHandler) unaryInterceptor(ctx context.Context, method string, req, reply any, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
|
||||
startTime := time.Now()
|
||||
ctx, span := csh.createCallSpan(ctx, method)
|
||||
err := invoker(ctx, method, req, reply, cc, opts...)
|
||||
perCallTracesAndMetrics(err, span, startTime, method)
|
||||
return err
|
||||
}
|
||||
|
||||
// streamInterceptor handles per RPC context management. It also handles per RPC
|
||||
// tracing and stats by creating a top level call span and recording the latency
|
||||
// for the full RPC call.
|
||||
func (csh *clientStatsHandler) streamInterceptor(ctx context.Context, desc *grpc.StreamDesc, cc *grpc.ClientConn, method string, streamer grpc.Streamer, opts ...grpc.CallOption) (grpc.ClientStream, error) {
|
||||
startTime := time.Now()
|
||||
ctx, span := csh.createCallSpan(ctx, method)
|
||||
callback := func(err error) {
|
||||
perCallTracesAndMetrics(err, span, startTime, method)
|
||||
}
|
||||
opts = append([]grpc.CallOption{grpc.OnFinish(callback)}, opts...)
|
||||
s, err := streamer(ctx, desc, cc, method, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return s, nil
|
||||
}
|
||||
|
||||
type rpcInfo struct {
|
||||
mi *metricsInfo
|
||||
ti *traceInfo
|
||||
}
|
||||
|
||||
type rpcInfoKey struct{}
|
||||
|
||||
func setRPCInfo(ctx context.Context, ri *rpcInfo) context.Context {
|
||||
return context.WithValue(ctx, rpcInfoKey{}, ri)
|
||||
}
|
||||
|
||||
// getRPCInfo returns the rpcInfo stored in the context, or nil
|
||||
// if there isn't one.
|
||||
func getRPCInfo(ctx context.Context) *rpcInfo {
|
||||
ri, _ := ctx.Value(rpcInfoKey{}).(*rpcInfo)
|
||||
return ri
|
||||
}
|
||||
|
||||
// SpanContextFromContext returns the Span Context about the Span in the
|
||||
// context. Returns false if no Span in the context.
|
||||
func SpanContextFromContext(ctx context.Context) (trace.SpanContext, bool) {
|
||||
ri, ok := ctx.Value(rpcInfoKey{}).(*rpcInfo)
|
||||
if !ok {
|
||||
return trace.SpanContext{}, false
|
||||
}
|
||||
if ri.ti == nil || ri.ti.span == nil {
|
||||
return trace.SpanContext{}, false
|
||||
}
|
||||
sc := ri.ti.span.SpanContext()
|
||||
return sc, true
|
||||
}
|
||||
|
||||
type clientStatsHandler struct {
|
||||
to TraceOptions
|
||||
}
|
||||
|
||||
// TagConn exists to satisfy stats.Handler.
|
||||
func (csh *clientStatsHandler) TagConn(ctx context.Context, _ *stats.ConnTagInfo) context.Context {
|
||||
return ctx
|
||||
}
|
||||
|
||||
// HandleConn exists to satisfy stats.Handler.
|
||||
func (csh *clientStatsHandler) HandleConn(context.Context, stats.ConnStats) {}
|
||||
|
||||
// TagRPC implements per RPC attempt context management.
|
||||
func (csh *clientStatsHandler) TagRPC(ctx context.Context, rti *stats.RPCTagInfo) context.Context {
|
||||
ctx, mi := csh.statsTagRPC(ctx, rti)
|
||||
var ti *traceInfo
|
||||
if !csh.to.DisableTrace {
|
||||
ctx, ti = csh.traceTagRPC(ctx, rti)
|
||||
}
|
||||
ri := &rpcInfo{
|
||||
mi: mi,
|
||||
ti: ti,
|
||||
}
|
||||
return setRPCInfo(ctx, ri)
|
||||
}
|
||||
|
||||
func (csh *clientStatsHandler) HandleRPC(ctx context.Context, rs stats.RPCStats) {
|
||||
ri := getRPCInfo(ctx)
|
||||
if ri == nil {
|
||||
// Shouldn't happen because TagRPC populates this information.
|
||||
return
|
||||
}
|
||||
recordRPCData(ctx, rs, ri.mi)
|
||||
if !csh.to.DisableTrace {
|
||||
populateSpan(ctx, rs, ri.ti)
|
||||
}
|
||||
}
|
||||
|
||||
type serverStatsHandler struct {
|
||||
to TraceOptions
|
||||
}
|
||||
|
||||
// TagConn exists to satisfy stats.Handler.
|
||||
func (ssh *serverStatsHandler) TagConn(ctx context.Context, _ *stats.ConnTagInfo) context.Context {
|
||||
return ctx
|
||||
}
|
||||
|
||||
// HandleConn exists to satisfy stats.Handler.
|
||||
func (ssh *serverStatsHandler) HandleConn(context.Context, stats.ConnStats) {}
|
||||
|
||||
// TagRPC implements per RPC context management.
|
||||
func (ssh *serverStatsHandler) TagRPC(ctx context.Context, rti *stats.RPCTagInfo) context.Context {
|
||||
ctx, mi := ssh.statsTagRPC(ctx, rti)
|
||||
var ti *traceInfo
|
||||
if !ssh.to.DisableTrace {
|
||||
ctx, ti = ssh.traceTagRPC(ctx, rti)
|
||||
}
|
||||
ri := &rpcInfo{
|
||||
mi: mi,
|
||||
ti: ti,
|
||||
}
|
||||
return setRPCInfo(ctx, ri)
|
||||
}
|
||||
|
||||
// HandleRPC implements per RPC tracing and stats implementation.
|
||||
func (ssh *serverStatsHandler) HandleRPC(ctx context.Context, rs stats.RPCStats) {
|
||||
ri := getRPCInfo(ctx)
|
||||
if ri == nil {
|
||||
// Shouldn't happen because TagRPC populates this information.
|
||||
return
|
||||
}
|
||||
recordRPCData(ctx, rs, ri.mi)
|
||||
if !ssh.to.DisableTrace {
|
||||
populateSpan(ctx, rs, ri.ti)
|
||||
}
|
||||
}
|
||||
135
stats/opencensus/server_metrics.go
Normal file
135
stats/opencensus/server_metrics.go
Normal file
@@ -0,0 +1,135 @@
|
||||
/*
|
||||
* Copyright 2022 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 opencensus
|
||||
|
||||
import (
|
||||
"go.opencensus.io/stats"
|
||||
"go.opencensus.io/stats/view"
|
||||
"go.opencensus.io/tag"
|
||||
)
|
||||
|
||||
var (
|
||||
keyServerMethod = tag.MustNewKey("grpc_server_method")
|
||||
keyServerStatus = tag.MustNewKey("grpc_server_status")
|
||||
)
|
||||
|
||||
// Measures, which are recorded by server stats handler: Note that on gRPC's
|
||||
// server side, the per rpc unit is truly per rpc, as there is no concept of a
|
||||
// rpc attempt server side.
|
||||
var (
|
||||
serverReceivedMessagesPerRPC = stats.Int64("grpc.io/server/received_messages_per_rpc", "Number of messages received in each RPC. Has value 1 for non-streaming RPCs.", stats.UnitDimensionless) // the collection/measurement point of this measure handles the /rpc aspect of it
|
||||
serverReceivedBytesPerRPC = stats.Int64("grpc.io/server/received_bytes_per_rpc", "Total bytes received across all messages per RPC.", stats.UnitBytes)
|
||||
serverReceivedCompressedBytesPerRPC = stats.Int64("grpc.io/server/received_compressed_bytes_per_rpc", "Total compressed bytes received across all messages per RPC.", stats.UnitBytes)
|
||||
serverSentMessagesPerRPC = stats.Int64("grpc.io/server/sent_messages_per_rpc", "Number of messages sent in each RPC. Has value 1 for non-streaming RPCs.", stats.UnitDimensionless)
|
||||
serverSentBytesPerRPC = stats.Int64("grpc.io/server/sent_bytes_per_rpc", "Total bytes sent in across all response messages per RPC.", stats.UnitBytes)
|
||||
serverSentCompressedBytesPerRPC = stats.Int64("grpc.io/server/sent_compressed_bytes_per_rpc", "Total compressed bytes sent in across all response messages per RPC.", stats.UnitBytes)
|
||||
serverStartedRPCs = stats.Int64("grpc.io/server/started_rpcs", "The total number of server RPCs ever opened, including those that have not completed.", stats.UnitDimensionless)
|
||||
serverLatency = stats.Float64("grpc.io/server/server_latency", "Time between first byte of request received to last byte of response sent, or terminal error.", stats.UnitMilliseconds)
|
||||
)
|
||||
|
||||
var (
|
||||
// ServerSentMessagesPerRPCView is the distribution of sent messages per
|
||||
// RPC, keyed on method.
|
||||
ServerSentMessagesPerRPCView = &view.View{
|
||||
Name: "grpc.io/server/sent_messages_per_rpc",
|
||||
Description: "Distribution of sent messages per RPC, by method.",
|
||||
TagKeys: []tag.Key{keyServerMethod},
|
||||
Measure: serverSentMessagesPerRPC,
|
||||
Aggregation: countDistribution,
|
||||
}
|
||||
// ServerReceivedMessagesPerRPCView is the distribution of received messages
|
||||
// per RPC, keyed on method.
|
||||
ServerReceivedMessagesPerRPCView = &view.View{
|
||||
Name: "grpc.io/server/received_messages_per_rpc",
|
||||
Description: "Distribution of received messages per RPC, by method.",
|
||||
TagKeys: []tag.Key{keyServerMethod},
|
||||
Measure: serverReceivedMessagesPerRPC,
|
||||
Aggregation: countDistribution,
|
||||
}
|
||||
// ServerSentBytesPerRPCView is the distribution of received bytes per RPC,
|
||||
// keyed on method.
|
||||
ServerSentBytesPerRPCView = &view.View{
|
||||
Name: "grpc.io/server/sent_bytes_per_rpc",
|
||||
Description: "Distribution of sent bytes per RPC, by method.",
|
||||
Measure: serverSentBytesPerRPC,
|
||||
TagKeys: []tag.Key{keyServerMethod},
|
||||
Aggregation: bytesDistribution,
|
||||
}
|
||||
// ServerSentCompressedMessageBytesPerRPCView is the distribution of
|
||||
// received compressed message bytes per RPC, keyed on method.
|
||||
ServerSentCompressedMessageBytesPerRPCView = &view.View{
|
||||
Name: "grpc.io/server/sent_compressed_message_bytes_per_rpc",
|
||||
Description: "Distribution of sent compressed message bytes per RPC, by method.",
|
||||
Measure: serverSentCompressedBytesPerRPC,
|
||||
TagKeys: []tag.Key{keyServerMethod},
|
||||
Aggregation: bytesDistribution,
|
||||
}
|
||||
// ServerReceivedBytesPerRPCView is the distribution of sent bytes per RPC,
|
||||
// keyed on method.
|
||||
ServerReceivedBytesPerRPCView = &view.View{
|
||||
Name: "grpc.io/server/received_bytes_per_rpc",
|
||||
Description: "Distribution of received bytes per RPC, by method.",
|
||||
Measure: serverReceivedBytesPerRPC,
|
||||
TagKeys: []tag.Key{keyServerMethod},
|
||||
Aggregation: bytesDistribution,
|
||||
}
|
||||
// ServerReceivedCompressedMessageBytesPerRPCView is the distribution of
|
||||
// sent compressed message bytes per RPC, keyed on method.
|
||||
ServerReceivedCompressedMessageBytesPerRPCView = &view.View{
|
||||
Name: "grpc.io/server/received_compressed_message_bytes_per_rpc",
|
||||
Description: "Distribution of received compressed message bytes per RPC, by method.",
|
||||
Measure: serverReceivedCompressedBytesPerRPC,
|
||||
TagKeys: []tag.Key{keyServerMethod},
|
||||
Aggregation: bytesDistribution,
|
||||
}
|
||||
// ServerStartedRPCsView is the count of opened RPCs, keyed on method.
|
||||
ServerStartedRPCsView = &view.View{
|
||||
Measure: serverStartedRPCs,
|
||||
Name: "grpc.io/server/started_rpcs",
|
||||
Description: "Number of opened server RPCs, by method.",
|
||||
TagKeys: []tag.Key{keyServerMethod},
|
||||
Aggregation: view.Count(),
|
||||
}
|
||||
// ServerCompletedRPCsView is the count of completed RPCs, keyed on
|
||||
// method and status.
|
||||
ServerCompletedRPCsView = &view.View{
|
||||
Name: "grpc.io/server/completed_rpcs",
|
||||
Description: "Number of completed RPCs by method and status.",
|
||||
TagKeys: []tag.Key{keyServerMethod, keyServerStatus},
|
||||
Measure: serverLatency,
|
||||
Aggregation: view.Count(),
|
||||
}
|
||||
// ServerLatencyView is the distribution of server latency in milliseconds
|
||||
// per RPC, keyed on method.
|
||||
ServerLatencyView = &view.View{
|
||||
Name: "grpc.io/server/server_latency",
|
||||
Description: "Distribution of server latency in milliseconds, by method.",
|
||||
TagKeys: []tag.Key{keyServerMethod},
|
||||
Measure: serverLatency,
|
||||
Aggregation: millisecondsDistribution,
|
||||
}
|
||||
)
|
||||
|
||||
// DefaultServerViews is the set of server views which are considered the
|
||||
// minimum required to monitor server side performance.
|
||||
var DefaultServerViews = []*view.View{
|
||||
ServerReceivedBytesPerRPCView,
|
||||
ServerSentBytesPerRPCView,
|
||||
ServerLatencyView,
|
||||
ServerCompletedRPCsView,
|
||||
ServerStartedRPCsView,
|
||||
}
|
||||
226
stats/opencensus/stats.go
Normal file
226
stats/opencensus/stats.go
Normal file
@@ -0,0 +1,226 @@
|
||||
/*
|
||||
* Copyright 2022 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 opencensus
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
ocstats "go.opencensus.io/stats"
|
||||
"go.opencensus.io/stats/view"
|
||||
"go.opencensus.io/tag"
|
||||
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/grpclog"
|
||||
"google.golang.org/grpc/internal"
|
||||
"google.golang.org/grpc/metadata"
|
||||
"google.golang.org/grpc/stats"
|
||||
"google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
var logger = grpclog.Component("opencensus-instrumentation")
|
||||
|
||||
var canonicalString = internal.CanonicalString.(func(codes.Code) string)
|
||||
|
||||
var (
|
||||
// bounds separate variable for testing purposes.
|
||||
bytesDistributionBounds = []float64{1024, 2048, 4096, 16384, 65536, 262144, 1048576, 4194304, 16777216, 67108864, 268435456, 1073741824, 4294967296}
|
||||
bytesDistribution = view.Distribution(bytesDistributionBounds...)
|
||||
millisecondsDistribution = view.Distribution(0.01, 0.05, 0.1, 0.3, 0.6, 0.8, 1, 2, 3, 4, 5, 6, 8, 10, 13, 16, 20, 25, 30, 40, 50, 65, 80, 100, 130, 160, 200, 250, 300, 400, 500, 650, 800, 1000, 2000, 5000, 10000, 20000, 50000, 100000)
|
||||
countDistributionBounds = []float64{1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384, 32768, 65536}
|
||||
countDistribution = view.Distribution(countDistributionBounds...)
|
||||
)
|
||||
|
||||
func removeLeadingSlash(mn string) string {
|
||||
return strings.TrimLeft(mn, "/")
|
||||
}
|
||||
|
||||
// metricsInfo is data used for recording metrics about the rpc attempt client
|
||||
// side, and the overall rpc server side.
|
||||
type metricsInfo struct {
|
||||
// access these counts atomically for hedging in the future
|
||||
// number of messages sent from side (client || server)
|
||||
sentMsgs int64
|
||||
// number of bytes sent (within each message) from side (client || server)
|
||||
sentBytes int64
|
||||
// number of bytes after compression (within each message) from side (client || server)
|
||||
sentCompressedBytes int64
|
||||
// number of messages received on side (client || server)
|
||||
recvMsgs int64
|
||||
// number of bytes received (within each message) received on side (client
|
||||
// || server)
|
||||
recvBytes int64
|
||||
// number of compressed bytes received (within each message) received on
|
||||
// side (client || server)
|
||||
recvCompressedBytes int64
|
||||
|
||||
startTime time.Time
|
||||
method string
|
||||
}
|
||||
|
||||
// statsTagRPC creates a recording object to derive measurements from in the
|
||||
// context, scoping the recordings to per RPC Attempt client side (scope of the
|
||||
// context). It also populates the gRPC Metadata within the context with any
|
||||
// opencensus specific tags set by the application in the context, binary
|
||||
// encoded to send across the wire.
|
||||
func (csh *clientStatsHandler) statsTagRPC(ctx context.Context, info *stats.RPCTagInfo) (context.Context, *metricsInfo) {
|
||||
mi := &metricsInfo{
|
||||
startTime: time.Now(),
|
||||
method: info.FullMethodName,
|
||||
}
|
||||
|
||||
// Populate gRPC Metadata with OpenCensus tag map if set by application.
|
||||
if tm := tag.FromContext(ctx); tm != nil {
|
||||
ctx = metadata.AppendToOutgoingContext(ctx, "grpc-tags-bin", string(tag.Encode(tm)))
|
||||
}
|
||||
return ctx, mi
|
||||
}
|
||||
|
||||
// statsTagRPC creates a recording object to derive measurements from in the
|
||||
// context, scoping the recordings to per RPC server side (scope of the
|
||||
// context). It also deserializes the opencensus tags set in the context's gRPC
|
||||
// Metadata, and adds a server method tag to the opencensus tags. If multiple
|
||||
// tags exist, it adds the last one.
|
||||
func (ssh *serverStatsHandler) statsTagRPC(ctx context.Context, info *stats.RPCTagInfo) (context.Context, *metricsInfo) {
|
||||
mi := &metricsInfo{
|
||||
startTime: time.Now(),
|
||||
method: info.FullMethodName,
|
||||
}
|
||||
|
||||
if tgValues := metadata.ValueFromIncomingContext(ctx, "grpc-tags-bin"); len(tgValues) > 0 {
|
||||
tagsBin := []byte(tgValues[len(tgValues)-1])
|
||||
if tags, err := tag.Decode(tagsBin); err == nil {
|
||||
ctx = tag.NewContext(ctx, tags)
|
||||
}
|
||||
}
|
||||
|
||||
// We can ignore the error here because in the error case, the context
|
||||
// passed in is returned. If the call errors, the server side application
|
||||
// layer won't get this key server method information in the tag map, but
|
||||
// this instrumentation code will function as normal.
|
||||
ctx, _ = tag.New(ctx, tag.Upsert(keyServerMethod, removeLeadingSlash(info.FullMethodName)))
|
||||
return ctx, mi
|
||||
}
|
||||
|
||||
func recordRPCData(ctx context.Context, s stats.RPCStats, mi *metricsInfo) {
|
||||
if mi == nil {
|
||||
// Shouldn't happen, as gRPC calls TagRPC which populates the metricsInfo in
|
||||
// context.
|
||||
logger.Error("ctx passed into stats handler metrics event handling has no metrics data present")
|
||||
return
|
||||
}
|
||||
switch st := s.(type) {
|
||||
case *stats.InHeader, *stats.OutHeader, *stats.InTrailer, *stats.OutTrailer, *stats.PickerUpdated:
|
||||
// Headers, Trailers, and picker updates are not relevant to the measures,
|
||||
// as the measures concern number of messages and bytes for messages. This
|
||||
// aligns with flow control.
|
||||
case *stats.Begin:
|
||||
recordDataBegin(ctx, mi, st)
|
||||
case *stats.OutPayload:
|
||||
recordDataOutPayload(mi, st)
|
||||
case *stats.InPayload:
|
||||
recordDataInPayload(mi, st)
|
||||
case *stats.End:
|
||||
recordDataEnd(ctx, mi, st)
|
||||
default:
|
||||
// Shouldn't happen. gRPC calls into stats handler, and will never not
|
||||
// be one of the types above.
|
||||
logger.Errorf("Received unexpected stats type (%T) with data: %v", s, s)
|
||||
}
|
||||
}
|
||||
|
||||
// recordDataBegin takes a measurement related to the RPC beginning,
|
||||
// client/server started RPCs dependent on the caller.
|
||||
func recordDataBegin(ctx context.Context, mi *metricsInfo, b *stats.Begin) {
|
||||
if b.Client {
|
||||
ocstats.RecordWithOptions(ctx,
|
||||
ocstats.WithTags(tag.Upsert(keyClientMethod, removeLeadingSlash(mi.method))),
|
||||
ocstats.WithMeasurements(clientStartedRPCs.M(1)))
|
||||
return
|
||||
}
|
||||
ocstats.RecordWithOptions(ctx,
|
||||
ocstats.WithTags(tag.Upsert(keyServerMethod, removeLeadingSlash(mi.method))),
|
||||
ocstats.WithMeasurements(serverStartedRPCs.M(1)))
|
||||
}
|
||||
|
||||
// recordDataOutPayload records the length in bytes of outgoing messages and
|
||||
// increases total count of sent messages both stored in the RPCs (attempt on
|
||||
// client side) context for use in taking measurements at RPC end.
|
||||
func recordDataOutPayload(mi *metricsInfo, op *stats.OutPayload) {
|
||||
atomic.AddInt64(&mi.sentMsgs, 1)
|
||||
atomic.AddInt64(&mi.sentBytes, int64(op.Length))
|
||||
atomic.AddInt64(&mi.sentCompressedBytes, int64(op.CompressedLength))
|
||||
}
|
||||
|
||||
// recordDataInPayload records the length in bytes of incoming messages and
|
||||
// increases total count of sent messages both stored in the RPCs (attempt on
|
||||
// client side) context for use in taking measurements at RPC end.
|
||||
func recordDataInPayload(mi *metricsInfo, ip *stats.InPayload) {
|
||||
atomic.AddInt64(&mi.recvMsgs, 1)
|
||||
atomic.AddInt64(&mi.recvBytes, int64(ip.Length))
|
||||
atomic.AddInt64(&mi.recvCompressedBytes, int64(ip.CompressedLength))
|
||||
}
|
||||
|
||||
// recordDataEnd takes per RPC measurements derived from information derived
|
||||
// from the lifetime of the RPC (RPC attempt client side).
|
||||
func recordDataEnd(ctx context.Context, mi *metricsInfo, e *stats.End) {
|
||||
// latency bounds for distribution data (speced millisecond bounds) have
|
||||
// fractions, thus need a float.
|
||||
latency := float64(time.Since(mi.startTime)) / float64(time.Millisecond)
|
||||
var st string
|
||||
if e.Error != nil {
|
||||
s, _ := status.FromError(e.Error)
|
||||
st = canonicalString(s.Code())
|
||||
} else {
|
||||
st = "OK"
|
||||
}
|
||||
|
||||
// TODO: Attach trace data through attachments?!?!
|
||||
|
||||
if e.Client {
|
||||
ocstats.RecordWithOptions(ctx,
|
||||
ocstats.WithTags(
|
||||
tag.Upsert(keyClientMethod, removeLeadingSlash(mi.method)),
|
||||
tag.Upsert(keyClientStatus, st)),
|
||||
ocstats.WithMeasurements(
|
||||
clientSentBytesPerRPC.M(atomic.LoadInt64(&mi.sentBytes)),
|
||||
clientSentCompressedBytesPerRPC.M(atomic.LoadInt64(&mi.sentCompressedBytes)),
|
||||
clientSentMessagesPerRPC.M(atomic.LoadInt64(&mi.sentMsgs)),
|
||||
clientReceivedMessagesPerRPC.M(atomic.LoadInt64(&mi.recvMsgs)),
|
||||
clientReceivedBytesPerRPC.M(atomic.LoadInt64(&mi.recvBytes)),
|
||||
clientReceivedCompressedBytesPerRPC.M(atomic.LoadInt64(&mi.recvCompressedBytes)),
|
||||
clientRoundtripLatency.M(latency),
|
||||
clientServerLatency.M(latency),
|
||||
))
|
||||
return
|
||||
}
|
||||
ocstats.RecordWithOptions(ctx,
|
||||
ocstats.WithTags(
|
||||
tag.Upsert(keyServerMethod, removeLeadingSlash(mi.method)),
|
||||
tag.Upsert(keyServerStatus, st),
|
||||
),
|
||||
ocstats.WithMeasurements(
|
||||
serverSentBytesPerRPC.M(atomic.LoadInt64(&mi.sentBytes)),
|
||||
serverSentCompressedBytesPerRPC.M(atomic.LoadInt64(&mi.sentCompressedBytes)),
|
||||
serverSentMessagesPerRPC.M(atomic.LoadInt64(&mi.sentMsgs)),
|
||||
serverReceivedMessagesPerRPC.M(atomic.LoadInt64(&mi.recvMsgs)),
|
||||
serverReceivedBytesPerRPC.M(atomic.LoadInt64(&mi.recvBytes)),
|
||||
serverReceivedCompressedBytesPerRPC.M(atomic.LoadInt64(&mi.recvCompressedBytes)),
|
||||
serverLatency.M(latency)))
|
||||
}
|
||||
130
stats/opencensus/trace.go
Normal file
130
stats/opencensus/trace.go
Normal file
@@ -0,0 +1,130 @@
|
||||
/*
|
||||
* Copyright 2022 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 opencensus
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
|
||||
"go.opencensus.io/trace"
|
||||
"go.opencensus.io/trace/propagation"
|
||||
|
||||
"google.golang.org/grpc/metadata"
|
||||
"google.golang.org/grpc/stats"
|
||||
"google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
// traceInfo is data used for recording traces.
|
||||
type traceInfo struct {
|
||||
span *trace.Span
|
||||
countSentMsg uint32
|
||||
countRecvMsg uint32
|
||||
}
|
||||
|
||||
// traceTagRPC populates context with a new span, and serializes information
|
||||
// about this span into gRPC Metadata.
|
||||
func (csh *clientStatsHandler) traceTagRPC(ctx context.Context, rti *stats.RPCTagInfo) (context.Context, *traceInfo) {
|
||||
// TODO: get consensus on whether this method name of "s.m" is correct.
|
||||
mn := "Attempt." + strings.ReplaceAll(removeLeadingSlash(rti.FullMethodName), "/", ".")
|
||||
// Returned context is ignored because will populate context with data that
|
||||
// wraps the span instead. Don't set span kind client on this attempt span
|
||||
// to prevent backend from prepending span name with "Sent.".
|
||||
_, span := trace.StartSpan(ctx, mn, trace.WithSampler(csh.to.TS))
|
||||
|
||||
tcBin := propagation.Binary(span.SpanContext())
|
||||
return metadata.AppendToOutgoingContext(ctx, "grpc-trace-bin", string(tcBin)), &traceInfo{
|
||||
span: span,
|
||||
countSentMsg: 0, // msg events scoped to scope of context, per attempt client side
|
||||
countRecvMsg: 0,
|
||||
}
|
||||
}
|
||||
|
||||
// traceTagRPC populates context with new span data, with a parent based on the
|
||||
// spanContext deserialized from context passed in (wire data in gRPC metadata)
|
||||
// if present. If multiple spanContexts exist, it takes the last one.
|
||||
func (ssh *serverStatsHandler) traceTagRPC(ctx context.Context, rti *stats.RPCTagInfo) (context.Context, *traceInfo) {
|
||||
mn := strings.ReplaceAll(removeLeadingSlash(rti.FullMethodName), "/", ".")
|
||||
|
||||
var tcBin []byte
|
||||
if tcValues := metadata.ValueFromIncomingContext(ctx, "grpc-trace-bin"); len(tcValues) > 0 {
|
||||
tcBin = []byte(tcValues[len(tcValues)-1])
|
||||
}
|
||||
|
||||
var span *trace.Span
|
||||
if sc, ok := propagation.FromBinary(tcBin); ok {
|
||||
// Returned context is ignored because will populate context with data
|
||||
// that wraps the span instead.
|
||||
_, span = trace.StartSpanWithRemoteParent(ctx, mn, sc, trace.WithSpanKind(trace.SpanKindServer), trace.WithSampler(ssh.to.TS))
|
||||
span.AddLink(trace.Link{TraceID: sc.TraceID, SpanID: sc.SpanID, Type: trace.LinkTypeChild})
|
||||
} else {
|
||||
// Returned context is ignored because will populate context with data
|
||||
// that wraps the span instead.
|
||||
_, span = trace.StartSpan(ctx, mn, trace.WithSpanKind(trace.SpanKindServer), trace.WithSampler(ssh.to.TS))
|
||||
}
|
||||
|
||||
return ctx, &traceInfo{
|
||||
span: span,
|
||||
countSentMsg: 0,
|
||||
countRecvMsg: 0,
|
||||
}
|
||||
}
|
||||
|
||||
// populateSpan populates span information based on stats passed in (invariants
|
||||
// of the RPC lifecycle), and also ends span which triggers the span to be
|
||||
// exported.
|
||||
func populateSpan(_ context.Context, rs stats.RPCStats, ti *traceInfo) {
|
||||
if ti == nil || ti.span == nil {
|
||||
// Shouldn't happen, tagRPC call comes before this function gets called
|
||||
// which populates this information.
|
||||
logger.Error("ctx passed into stats handler tracing event handling has no span present")
|
||||
return
|
||||
}
|
||||
span := ti.span
|
||||
|
||||
switch rs := rs.(type) {
|
||||
case *stats.Begin:
|
||||
// Note: Go always added these attributes even though they are not
|
||||
// defined by the OpenCensus gRPC spec. Thus, they are unimportant for
|
||||
// correctness.
|
||||
span.AddAttributes(
|
||||
trace.BoolAttribute("Client", rs.Client),
|
||||
trace.BoolAttribute("FailFast", rs.FailFast),
|
||||
)
|
||||
case *stats.PickerUpdated:
|
||||
span.Annotate(nil, "Delayed LB pick complete")
|
||||
case *stats.InPayload:
|
||||
// message id - "must be calculated as two different counters starting
|
||||
// from one for sent messages and one for received messages."
|
||||
mi := atomic.AddUint32(&ti.countRecvMsg, 1)
|
||||
span.AddMessageReceiveEvent(int64(mi), int64(rs.Length), int64(rs.CompressedLength))
|
||||
case *stats.OutPayload:
|
||||
mi := atomic.AddUint32(&ti.countSentMsg, 1)
|
||||
span.AddMessageSendEvent(int64(mi), int64(rs.Length), int64(rs.CompressedLength))
|
||||
case *stats.End:
|
||||
if rs.Error != nil {
|
||||
// "The mapping between gRPC canonical codes and OpenCensus codes
|
||||
// can be found here", which implies 1:1 mapping to gRPC statuses
|
||||
// (OpenCensus statuses are based off gRPC statuses and a subset).
|
||||
s := status.Convert(rs.Error)
|
||||
span.SetStatus(trace.Status{Code: int32(s.Code()), Message: s.Message()})
|
||||
} else {
|
||||
span.SetStatus(trace.Status{Code: trace.StatusCodeOK}) // could get rid of this else conditional and just leave as 0 value, but this makes it explicit
|
||||
}
|
||||
span.End()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user