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
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
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
Stale bot / stale (push) Failing after 1s
Lock Threads / lock (push) Failing after 1s
Some checks failed
codecov / upload (push) Failing after 13m59s
CodeQL / Analyze (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
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
Stale bot / stale (push) Failing after 1s
Lock Threads / lock (push) Failing after 1s
This commit is contained in:
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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user