Skip to content

Commit 0e39b29

Browse files
authored
Add custom label for per-RPC metrics
Implements gRFC A108. https://github.com/grpc/proposal/blob/master/A108-otel-custom-per-call-label.md
1 parent 65ae2ef commit 0e39b29

File tree

6 files changed

+202
-53
lines changed

6 files changed

+202
-53
lines changed

api/src/main/java/io/grpc/Grpc.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,13 @@ private Grpc() {
5656
public static final Attributes.Key<SSLSession> TRANSPORT_ATTR_SSL_SESSION =
5757
Attributes.Key.create("io.grpc.Grpc.TRANSPORT_ATTR_SSL_SESSION");
5858

59+
/**
60+
* The value for the custom label of per-RPC metrics. Defaults to empty string when unset. Must
61+
* not be set to {@code null}.
62+
*/
63+
public static final CallOptions.Key<String> CALL_OPTION_CUSTOM_LABEL =
64+
CallOptions.Key.createWithDefault("io.grpc.Grpc.CALL_OPTION_CUSTOM_LABEL", "");
65+
5966
/**
6067
* Annotation for transport attributes. It follows the annotation semantics defined
6168
* by {@link Attributes}.

opentelemetry/src/main/java/io/grpc/opentelemetry/OpenTelemetryMetricsModule.java

Lines changed: 41 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import static com.google.common.base.Preconditions.checkNotNull;
2020
import static io.grpc.opentelemetry.internal.OpenTelemetryConstants.BACKEND_SERVICE_KEY;
2121
import static io.grpc.opentelemetry.internal.OpenTelemetryConstants.BAGGAGE_KEY;
22+
import static io.grpc.opentelemetry.internal.OpenTelemetryConstants.CUSTOM_LABEL_KEY;
2223
import static io.grpc.opentelemetry.internal.OpenTelemetryConstants.LOCALITY_KEY;
2324
import static io.grpc.opentelemetry.internal.OpenTelemetryConstants.METHOD_KEY;
2425
import static io.grpc.opentelemetry.internal.OpenTelemetryConstants.STATUS_KEY;
@@ -39,6 +40,7 @@
3940
import io.grpc.Deadline;
4041
import io.grpc.ForwardingClientCall.SimpleForwardingClientCall;
4142
import io.grpc.ForwardingClientCallListener.SimpleForwardingClientCallListener;
43+
import io.grpc.Grpc;
4244
import io.grpc.Metadata;
4345
import io.grpc.MethodDescriptor;
4446
import io.grpc.ServerStreamTracer;
@@ -99,6 +101,7 @@ final class OpenTelemetryMetricsModule {
99101
private final Supplier<Stopwatch> stopwatchSupplier;
100102
private final boolean localityEnabled;
101103
private final boolean backendServiceEnabled;
104+
private final boolean customLabelEnabled;
102105
private final ImmutableList<OpenTelemetryPlugin> plugins;
103106
@Nullable
104107
private final TargetFilter targetAttributeFilter;
@@ -117,6 +120,7 @@ final class OpenTelemetryMetricsModule {
117120
this.stopwatchSupplier = checkNotNull(stopwatchSupplier, "stopwatchSupplier");
118121
this.localityEnabled = optionalLabels.contains(LOCALITY_KEY.getKey());
119122
this.backendServiceEnabled = optionalLabels.contains(BACKEND_SERVICE_KEY.getKey());
123+
this.customLabelEnabled = optionalLabels.contains(CUSTOM_LABEL_KEY.getKey());
120124
this.plugins = ImmutableList.copyOf(plugins);
121125
this.targetAttributeFilter = targetAttributeFilter;
122126
}
@@ -277,7 +281,7 @@ public void streamClosed(Status status) {
277281
statusCode = Code.DEADLINE_EXCEEDED;
278282
}
279283
}
280-
attemptsState.attemptEnded();
284+
attemptsState.attemptEnded(info.getCallOptions());
281285
recordFinishedAttempt();
282286
}
283287

@@ -301,6 +305,10 @@ void recordFinishedAttempt() {
301305
}
302306
builder.put(BACKEND_SERVICE_KEY, savedBackendService);
303307
}
308+
if (module.customLabelEnabled) {
309+
builder.put(
310+
CUSTOM_LABEL_KEY, info.getCallOptions().getOption(Grpc.CALL_OPTION_CUSTOM_LABEL));
311+
}
304312
for (OpenTelemetryPlugin.ClientStreamPlugin plugin : streamPlugins) {
305313
plugin.addLabels(builder);
306314
}
@@ -346,6 +354,7 @@ static final class CallAttemptsTracerFactory extends ClientStreamTracer.Factory
346354
CallAttemptsTracerFactory(
347355
OpenTelemetryMetricsModule module,
348356
String target,
357+
CallOptions callOptions,
349358
String fullMethodName,
350359
List<OpenTelemetryPlugin.ClientCallPlugin> callPlugins) {
351360
this.module = checkNotNull(module, "module");
@@ -355,9 +364,14 @@ static final class CallAttemptsTracerFactory extends ClientStreamTracer.Factory
355364
this.attemptDelayStopwatch = module.stopwatchSupplier.get();
356365
this.callStopWatch = module.stopwatchSupplier.get().start();
357366

358-
io.opentelemetry.api.common.Attributes attribute = io.opentelemetry.api.common.Attributes.of(
359-
METHOD_KEY, fullMethodName,
360-
TARGET_KEY, target);
367+
AttributesBuilder builder = io.opentelemetry.api.common.Attributes.builder()
368+
.put(METHOD_KEY, fullMethodName)
369+
.put(TARGET_KEY, target);
370+
if (module.customLabelEnabled) {
371+
builder.put(
372+
CUSTOM_LABEL_KEY, callOptions.getOption(Grpc.CALL_OPTION_CUSTOM_LABEL));
373+
}
374+
io.opentelemetry.api.common.Attributes attribute = builder.build();
361375

362376
// Record here in case mewClientStreamTracer() would never be called.
363377
if (module.resource.clientAttemptCountCounter() != null) {
@@ -381,9 +395,14 @@ public ClientStreamTracer newClientStreamTracer(StreamInfo info, Metadata metada
381395
// CallAttemptsTracerFactory constructor. attemptsPerCall will be non-zero after the first
382396
// attempt, as first attempt cannot be a transparent retry.
383397
if (attemptsPerCall.get() > 0) {
384-
io.opentelemetry.api.common.Attributes attribute =
385-
io.opentelemetry.api.common.Attributes.of(METHOD_KEY, fullMethodName,
386-
TARGET_KEY, target);
398+
AttributesBuilder builder = io.opentelemetry.api.common.Attributes.builder()
399+
.put(METHOD_KEY, fullMethodName)
400+
.put(TARGET_KEY, target);
401+
if (module.customLabelEnabled) {
402+
builder.put(
403+
CUSTOM_LABEL_KEY, info.getCallOptions().getOption(Grpc.CALL_OPTION_CUSTOM_LABEL));
404+
}
405+
io.opentelemetry.api.common.Attributes attribute = builder.build();
387406
if (module.resource.clientAttemptCountCounter() != null) {
388407
module.resource.clientAttemptCountCounter().add(1, attribute);
389408
}
@@ -411,7 +430,7 @@ private ClientTracer newClientTracer(StreamInfo info) {
411430
}
412431

413432
// Called whenever each attempt is ended.
414-
void attemptEnded() {
433+
void attemptEnded(CallOptions callOptions) {
415434
boolean shouldRecordFinishedCall = false;
416435
synchronized (lock) {
417436
if (--activeStreams == 0) {
@@ -423,11 +442,11 @@ void attemptEnded() {
423442
}
424443
}
425444
if (shouldRecordFinishedCall) {
426-
recordFinishedCall();
445+
recordFinishedCall(callOptions);
427446
}
428447
}
429448

430-
void callEnded(Status status) {
449+
void callEnded(Status status, CallOptions callOptions) {
431450
callStopWatch.stop();
432451
this.status = status;
433452
boolean shouldRecordFinishedCall = false;
@@ -443,11 +462,11 @@ void callEnded(Status status) {
443462
}
444463
}
445464
if (shouldRecordFinishedCall) {
446-
recordFinishedCall();
465+
recordFinishedCall(callOptions);
447466
}
448467
}
449468

450-
void recordFinishedCall() {
469+
void recordFinishedCall(CallOptions callOptions) {
451470
Context otelContext = otelContextWithBaggage();
452471
if (attemptsPerCall.get() == 0) {
453472
ClientTracer tracer = newClientTracer(null);
@@ -458,11 +477,13 @@ void recordFinishedCall() {
458477
callLatencyNanos = callStopWatch.elapsed(TimeUnit.NANOSECONDS);
459478

460479
// Base attributes
461-
io.opentelemetry.api.common.Attributes baseAttributes =
462-
io.opentelemetry.api.common.Attributes.of(
463-
METHOD_KEY, fullMethodName,
464-
TARGET_KEY, target
465-
);
480+
AttributesBuilder builder = io.opentelemetry.api.common.Attributes.builder()
481+
.put(METHOD_KEY, fullMethodName)
482+
.put(TARGET_KEY, target);
483+
if (module.customLabelEnabled) {
484+
builder.put(CUSTOM_LABEL_KEY, callOptions.getOption(Grpc.CALL_OPTION_CUSTOM_LABEL));
485+
}
486+
io.opentelemetry.api.common.Attributes baseAttributes = builder.build();
466487

467488
// Duration
468489
if (module.resource.clientCallDurationCounter() != null) {
@@ -688,11 +709,12 @@ public <ReqT, RespT> ClientCall<ReqT, RespT> interceptCall(
688709
callOptions = plugin.filterCallOptions(callOptions);
689710
}
690711
}
712+
final CallOptions finalCallOptions = callOptions;
691713
// Only record method name as an attribute if isSampledToLocalTracing is set to true,
692714
// which is true for all generated methods. Otherwise, programatically
693715
// created methods result in high cardinality metrics.
694716
final CallAttemptsTracerFactory tracerFactory = new CallAttemptsTracerFactory(
695-
OpenTelemetryMetricsModule.this, target,
717+
OpenTelemetryMetricsModule.this, target, callOptions,
696718
recordMethodName(method.getFullMethodName(), method.isSampledToLocalTracing()),
697719
callPlugins);
698720
ClientCall<ReqT, RespT> call =
@@ -707,7 +729,7 @@ public void start(Listener<RespT> responseListener, Metadata headers) {
707729
new SimpleForwardingClientCallListener<RespT>(responseListener) {
708730
@Override
709731
public void onClose(Status status, Metadata trailers) {
710-
tracerFactory.callEnded(status);
732+
tracerFactory.callEnded(status, finalCallOptions);
711733
super.onClose(status, trailers);
712734
}
713735
},

opentelemetry/src/main/java/io/grpc/opentelemetry/internal/OpenTelemetryConstants.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,9 @@ public final class OpenTelemetryConstants {
3838
public static final AttributeKey<String> BACKEND_SERVICE_KEY =
3939
AttributeKey.stringKey("grpc.lb.backend_service");
4040

41+
public static final AttributeKey<String> CUSTOM_LABEL_KEY =
42+
AttributeKey.stringKey("grpc.client.call.custom");
43+
4144
public static final AttributeKey<String> DISCONNECT_ERROR_KEY =
4245
AttributeKey.stringKey("grpc.disconnect_error");
4346

0 commit comments

Comments
 (0)