@@ -21,27 +21,23 @@ import (
21
21
"sync/atomic"
22
22
"time"
23
23
24
- otelcodes "go.opentelemetry.io/otel/codes "
25
- "go.opentelemetry.io/otel/trace "
24
+ otelattribute "go.opentelemetry.io/otel/attribute "
25
+ otelmetric "go.opentelemetry.io/otel/metric "
26
26
"google.golang.org/grpc"
27
- grpccodes "google.golang.org/grpc/codes"
28
27
estats "google.golang.org/grpc/experimental/stats"
29
28
istats "google.golang.org/grpc/internal/stats"
30
29
"google.golang.org/grpc/metadata"
31
30
"google.golang.org/grpc/stats"
32
31
"google.golang.org/grpc/status"
33
-
34
- otelattribute "go.opentelemetry.io/otel/attribute"
35
- otelmetric "go.opentelemetry.io/otel/metric"
36
32
)
37
33
38
- type clientStatsHandler struct {
34
+ type clientMetricsHandler struct {
39
35
estats.MetricsRecorder
40
36
options Options
41
37
clientMetrics clientMetrics
42
38
}
43
39
44
- func (h * clientStatsHandler ) initializeMetrics () {
40
+ func (h * clientMetricsHandler ) initializeMetrics () {
45
41
// Will set no metrics to record, logically making this stats handler a
46
42
// no-op.
47
43
if h .options .MetricsOptions .MeterProvider == nil {
@@ -71,12 +67,25 @@ func (h *clientStatsHandler) initializeMetrics() {
71
67
rm .registerMetrics (metrics , meter )
72
68
}
73
69
74
- func (h * clientStatsHandler ) unaryInterceptor (ctx context.Context , method string , req , reply any , cc * grpc.ClientConn , invoker grpc.UnaryInvoker , opts ... grpc.CallOption ) error {
75
- ci := & callInfo {
76
- target : cc .CanonicalTarget (),
77
- method : h .determineMethod (method , opts ... ),
70
+ // getOrCreateCallInfo returns the existing callInfo from context if present,
71
+ // or creates and attaches a new one.
72
+ func getOrCreateCallInfo (ctx context.Context , cc * grpc.ClientConn , method string , opts ... grpc.CallOption ) (context.Context , * callInfo ) {
73
+ ci := getCallInfo (ctx )
74
+ if ci == nil {
75
+ if logger .V (2 ) {
76
+ logger .Info ("Creating new CallInfo since its not present in context" )
77
+ }
78
+ ci = & callInfo {
79
+ target : cc .CanonicalTarget (),
80
+ method : determineMethod (method , opts ... ),
81
+ }
82
+ ctx = setCallInfo (ctx , ci )
78
83
}
79
- ctx = setCallInfo (ctx , ci )
84
+ return ctx , ci
85
+ }
86
+
87
+ func (h * clientMetricsHandler ) unaryInterceptor (ctx context.Context , method string , req , reply any , cc * grpc.ClientConn , invoker grpc.UnaryInvoker , opts ... grpc.CallOption ) error {
88
+ ctx , ci := getOrCreateCallInfo (ctx , cc , method , opts ... )
80
89
81
90
if h .options .MetricsOptions .pluginOption != nil {
82
91
md := h .options .MetricsOptions .pluginOption .GetMetadata ()
@@ -88,19 +97,15 @@ func (h *clientStatsHandler) unaryInterceptor(ctx context.Context, method string
88
97
}
89
98
90
99
startTime := time .Now ()
91
- var span trace.Span
92
- if h .options .isTracingEnabled () {
93
- ctx , span = h .createCallTraceSpan (ctx , method )
94
- }
95
100
err := invoker (ctx , method , req , reply , cc , opts ... )
96
- h .perCallTracesAndMetrics (ctx , err , startTime , ci , span )
101
+ h .perCallMetrics (ctx , err , startTime , ci )
97
102
return err
98
103
}
99
104
100
105
// determineMethod determines the method to record attributes with. This will be
101
106
// "other" if StaticMethod isn't specified or if method filter is set and
102
107
// specifies, the method name as is otherwise.
103
- func ( h * clientStatsHandler ) determineMethod (method string , opts ... grpc.CallOption ) string {
108
+ func determineMethod (method string , opts ... grpc.CallOption ) string {
104
109
for _ , opt := range opts {
105
110
if _ , ok := opt .(grpc.StaticMethodCallOption ); ok {
106
111
return removeLeadingSlash (method )
@@ -109,12 +114,8 @@ func (h *clientStatsHandler) determineMethod(method string, opts ...grpc.CallOpt
109
114
return "other"
110
115
}
111
116
112
- func (h * clientStatsHandler ) streamInterceptor (ctx context.Context , desc * grpc.StreamDesc , cc * grpc.ClientConn , method string , streamer grpc.Streamer , opts ... grpc.CallOption ) (grpc.ClientStream , error ) {
113
- ci := & callInfo {
114
- target : cc .CanonicalTarget (),
115
- method : h .determineMethod (method , opts ... ),
116
- }
117
- ctx = setCallInfo (ctx , ci )
117
+ func (h * clientMetricsHandler ) streamInterceptor (ctx context.Context , desc * grpc.StreamDesc , cc * grpc.ClientConn , method string , streamer grpc.Streamer , opts ... grpc.CallOption ) (grpc.ClientStream , error ) {
118
+ ctx , ci := getOrCreateCallInfo (ctx , cc , method , opts ... )
118
119
119
120
if h .options .MetricsOptions .pluginOption != nil {
120
121
md := h .options .MetricsOptions .pluginOption .GetMetadata ()
@@ -126,49 +127,45 @@ func (h *clientStatsHandler) streamInterceptor(ctx context.Context, desc *grpc.S
126
127
}
127
128
128
129
startTime := time .Now ()
129
- var span trace.Span
130
- if h .options .isTracingEnabled () {
131
- ctx , span = h .createCallTraceSpan (ctx , method )
132
- }
133
130
callback := func (err error ) {
134
- h .perCallTracesAndMetrics (ctx , err , startTime , ci , span )
131
+ h .perCallMetrics (ctx , err , startTime , ci )
135
132
}
136
133
opts = append ([]grpc.CallOption {grpc .OnFinish (callback )}, opts ... )
137
134
return streamer (ctx , desc , cc , method , opts ... )
138
135
}
139
136
140
- // perCallTracesAndMetrics records per call trace spans and metrics.
141
- func (h * clientStatsHandler ) perCallTracesAndMetrics (ctx context.Context , err error , startTime time.Time , ci * callInfo , ts trace.Span ) {
142
- if h .options .isTracingEnabled () {
143
- s := status .Convert (err )
144
- if s .Code () == grpccodes .OK {
145
- ts .SetStatus (otelcodes .Ok , s .Message ())
146
- } else {
147
- ts .SetStatus (otelcodes .Error , s .Message ())
148
- }
149
- ts .End ()
150
- }
151
- if h .options .isMetricsEnabled () {
152
- callLatency := float64 (time .Since (startTime )) / float64 (time .Second )
153
- attrs := otelmetric .WithAttributeSet (otelattribute .NewSet (
154
- otelattribute .String ("grpc.method" , ci .method ),
155
- otelattribute .String ("grpc.target" , ci .target ),
156
- otelattribute .String ("grpc.status" , canonicalString (status .Code (err ))),
157
- ))
158
- h .clientMetrics .callDuration .Record (ctx , callLatency , attrs )
159
- }
137
+ // perCallMetrics records per call metrics for both unary and stream calls.
138
+ func (h * clientMetricsHandler ) perCallMetrics (ctx context.Context , err error , startTime time.Time , ci * callInfo ) {
139
+ callLatency := float64 (time .Since (startTime )) / float64 (time .Second )
140
+ attrs := otelmetric .WithAttributeSet (otelattribute .NewSet (
141
+ otelattribute .String ("grpc.method" , ci .method ),
142
+ otelattribute .String ("grpc.target" , ci .target ),
143
+ otelattribute .String ("grpc.status" , canonicalString (status .Code (err ))),
144
+ ))
145
+ h .clientMetrics .callDuration .Record (ctx , callLatency , attrs )
160
146
}
161
147
162
148
// TagConn exists to satisfy stats.Handler.
163
- func (h * clientStatsHandler ) TagConn (ctx context.Context , _ * stats.ConnTagInfo ) context.Context {
149
+ func (h * clientMetricsHandler ) TagConn (ctx context.Context , _ * stats.ConnTagInfo ) context.Context {
164
150
return ctx
165
151
}
166
152
167
153
// HandleConn exists to satisfy stats.Handler.
168
- func (h * clientStatsHandler ) HandleConn (context.Context , stats.ConnStats ) {}
154
+ func (h * clientMetricsHandler ) HandleConn (context.Context , stats.ConnStats ) {}
155
+
156
+ // getOrCreateRPCAttemptInfo retrieves or creates an rpc attemptInfo object
157
+ // and ensures it is set in the context along with the rpcInfo.
158
+ func getOrCreateRPCAttemptInfo (ctx context.Context ) (context.Context , * attemptInfo ) {
159
+ ri := getRPCInfo (ctx )
160
+ if ri != nil {
161
+ return ctx , ri .ai
162
+ }
163
+ ri = & rpcInfo {ai : & attemptInfo {}}
164
+ return setRPCInfo (ctx , ri ), ri .ai
165
+ }
169
166
170
- // TagRPC implements per RPC attempt context management.
171
- func (h * clientStatsHandler ) TagRPC (ctx context.Context , info * stats.RPCTagInfo ) context.Context {
167
+ // TagRPC implements per RPC attempt context management for metrics .
168
+ func (h * clientMetricsHandler ) TagRPC (ctx context.Context , info * stats.RPCTagInfo ) context.Context {
172
169
// Numerous stats handlers can be used for the same channel. The cluster
173
170
// impl balancer which writes to this will only write once, thus have this
174
171
// stats handler's per attempt scoped context point to the same optional
@@ -185,34 +182,25 @@ func (h *clientStatsHandler) TagRPC(ctx context.Context, info *stats.RPCTagInfo)
185
182
}
186
183
ctx = istats .SetLabels (ctx , labels )
187
184
}
188
- ai := & attemptInfo {
189
- startTime : time .Now (),
190
- xdsLabels : labels .TelemetryLabels ,
191
- method : removeLeadingSlash (info .FullMethodName ),
192
- }
193
- if h .options .isTracingEnabled () {
194
- ctx , ai = h .traceTagRPC (ctx , ai , info .NameResolutionDelay )
195
- }
196
- return setRPCInfo (ctx , & rpcInfo {
197
- ai : ai ,
198
- })
185
+ ctx , ai := getOrCreateRPCAttemptInfo (ctx )
186
+ ai .startTime = time .Now ()
187
+ ai .xdsLabels = labels .TelemetryLabels
188
+ ai .method = removeLeadingSlash (info .FullMethodName )
189
+
190
+ return setRPCInfo (ctx , & rpcInfo {ai : ai })
199
191
}
200
192
201
- func (h * clientStatsHandler ) HandleRPC (ctx context.Context , rs stats.RPCStats ) {
193
+ // HandleRPC handles per RPC stats implementation.
194
+ func (h * clientMetricsHandler ) HandleRPC (ctx context.Context , rs stats.RPCStats ) {
202
195
ri := getRPCInfo (ctx )
203
196
if ri == nil {
204
197
logger .Error ("ctx passed into client side stats handler metrics event handling has no client attempt data present" )
205
198
return
206
199
}
207
- if h .options .isMetricsEnabled () {
208
- h .processRPCEvent (ctx , rs , ri .ai )
209
- }
210
- if h .options .isTracingEnabled () {
211
- populateSpan (rs , ri .ai )
212
- }
200
+ h .processRPCEvent (ctx , rs , ri .ai )
213
201
}
214
202
215
- func (h * clientStatsHandler ) processRPCEvent (ctx context.Context , s stats.RPCStats , ai * attemptInfo ) {
203
+ func (h * clientMetricsHandler ) processRPCEvent (ctx context.Context , s stats.RPCStats , ai * attemptInfo ) {
216
204
switch st := s .(type ) {
217
205
case * stats.Begin :
218
206
ci := getCallInfo (ctx )
@@ -240,7 +228,7 @@ func (h *clientStatsHandler) processRPCEvent(ctx context.Context, s stats.RPCSta
240
228
}
241
229
}
242
230
243
- func (h * clientStatsHandler ) setLabelsFromPluginOption (ai * attemptInfo , incomingMetadata metadata.MD ) {
231
+ func (h * clientMetricsHandler ) setLabelsFromPluginOption (ai * attemptInfo , incomingMetadata metadata.MD ) {
244
232
if ai .pluginOptionLabels == nil && h .options .MetricsOptions .pluginOption != nil {
245
233
labels := h .options .MetricsOptions .pluginOption .GetLabels (incomingMetadata )
246
234
if labels == nil {
@@ -250,7 +238,7 @@ func (h *clientStatsHandler) setLabelsFromPluginOption(ai *attemptInfo, incoming
250
238
}
251
239
}
252
240
253
- func (h * clientStatsHandler ) processRPCEnd (ctx context.Context , ai * attemptInfo , e * stats.End ) {
241
+ func (h * clientMetricsHandler ) processRPCEnd (ctx context.Context , ai * attemptInfo , e * stats.End ) {
254
242
ci := getCallInfo (ctx )
255
243
if ci == nil {
256
244
logger .Error ("ctx passed into client side stats handler metrics event handling has no metrics data present" )
0 commit comments