opentelemetry: Add grpc.target label to per-call metrics

As defined by gRFC A66, the target is on all client-side per-call
metrics (both call and attempt).
This commit is contained in:
Eric Anderson 2024-05-05 11:10:44 -07:00
parent ca35577327
commit 8516cfef9c
3 changed files with 45 additions and 19 deletions

View File

@ -19,6 +19,7 @@ package io.grpc.opentelemetry;
import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkNotNull;
import static io.grpc.opentelemetry.internal.OpenTelemetryConstants.METHOD_KEY; import static io.grpc.opentelemetry.internal.OpenTelemetryConstants.METHOD_KEY;
import static io.grpc.opentelemetry.internal.OpenTelemetryConstants.STATUS_KEY; import static io.grpc.opentelemetry.internal.OpenTelemetryConstants.STATUS_KEY;
import static io.grpc.opentelemetry.internal.OpenTelemetryConstants.TARGET_KEY;
import com.google.common.annotations.VisibleForTesting; import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Stopwatch; import com.google.common.base.Stopwatch;
@ -97,8 +98,8 @@ final class OpenTelemetryMetricsModule {
/** /**
* Returns the client interceptor that facilitates OpenTelemetry metrics reporting. * Returns the client interceptor that facilitates OpenTelemetry metrics reporting.
*/ */
ClientInterceptor getClientInterceptor() { ClientInterceptor getClientInterceptor(String target) {
return new MetricsClientInterceptor(); return new MetricsClientInterceptor(target);
} }
static String recordMethodName(String fullMethodName, boolean isGeneratedMethod) { static String recordMethodName(String fullMethodName, boolean isGeneratedMethod) {
@ -135,6 +136,7 @@ final class OpenTelemetryMetricsModule {
final CallAttemptsTracerFactory attemptsState; final CallAttemptsTracerFactory attemptsState;
final OpenTelemetryMetricsModule module; final OpenTelemetryMetricsModule module;
final StreamInfo info; final StreamInfo info;
final String target;
final String fullMethodName; final String fullMethodName;
volatile long outboundWireSize; volatile long outboundWireSize;
volatile long inboundWireSize; volatile long inboundWireSize;
@ -142,10 +144,11 @@ final class OpenTelemetryMetricsModule {
Code statusCode; Code statusCode;
ClientTracer(CallAttemptsTracerFactory attemptsState, OpenTelemetryMetricsModule module, ClientTracer(CallAttemptsTracerFactory attemptsState, OpenTelemetryMetricsModule module,
StreamInfo info, String fullMethodName) { StreamInfo info, String target, String fullMethodName) {
this.attemptsState = attemptsState; this.attemptsState = attemptsState;
this.module = module; this.module = module;
this.info = info; this.info = info;
this.target = target;
this.fullMethodName = fullMethodName; this.fullMethodName = fullMethodName;
this.stopwatch = module.stopwatchSupplier.get().start(); this.stopwatch = module.stopwatchSupplier.get().start();
} }
@ -189,9 +192,9 @@ final class OpenTelemetryMetricsModule {
} }
void recordFinishedAttempt() { void recordFinishedAttempt() {
// TODO(dnvindhya) : add target as an attribute
io.opentelemetry.api.common.Attributes attribute = io.opentelemetry.api.common.Attributes attribute =
io.opentelemetry.api.common.Attributes.of(METHOD_KEY, fullMethodName, io.opentelemetry.api.common.Attributes.of(METHOD_KEY, fullMethodName,
TARGET_KEY, target,
STATUS_KEY, statusCode.toString()); STATUS_KEY, statusCode.toString());
if (module.resource.clientAttemptDurationCounter() != null ) { if (module.resource.clientAttemptDurationCounter() != null ) {
@ -212,6 +215,7 @@ final class OpenTelemetryMetricsModule {
@VisibleForTesting @VisibleForTesting
static final class CallAttemptsTracerFactory extends ClientStreamTracer.Factory { static final class CallAttemptsTracerFactory extends ClientStreamTracer.Factory {
private final OpenTelemetryMetricsModule module; private final OpenTelemetryMetricsModule module;
private final String target;
private final Stopwatch attemptStopwatch; private final Stopwatch attemptStopwatch;
private final Stopwatch callStopWatch; private final Stopwatch callStopWatch;
@GuardedBy("lock") @GuardedBy("lock")
@ -226,15 +230,17 @@ final class OpenTelemetryMetricsModule {
@GuardedBy("lock") @GuardedBy("lock")
private boolean finishedCallToBeRecorded; private boolean finishedCallToBeRecorded;
CallAttemptsTracerFactory(OpenTelemetryMetricsModule module, String fullMethodName) { CallAttemptsTracerFactory(
OpenTelemetryMetricsModule module, String target, String fullMethodName) {
this.module = checkNotNull(module, "module"); this.module = checkNotNull(module, "module");
this.target = checkNotNull(target, "target");
this.fullMethodName = checkNotNull(fullMethodName, "fullMethodName"); this.fullMethodName = checkNotNull(fullMethodName, "fullMethodName");
this.attemptStopwatch = module.stopwatchSupplier.get(); this.attemptStopwatch = module.stopwatchSupplier.get();
this.callStopWatch = module.stopwatchSupplier.get().start(); this.callStopWatch = module.stopwatchSupplier.get().start();
// TODO(dnvindhya) : add target as an attribute io.opentelemetry.api.common.Attributes attribute = io.opentelemetry.api.common.Attributes.of(
io.opentelemetry.api.common.Attributes attribute = METHOD_KEY, fullMethodName,
io.opentelemetry.api.common.Attributes.of(METHOD_KEY, fullMethodName); TARGET_KEY, target);
// Record here in case mewClientStreamTracer() would never be called. // Record here in case mewClientStreamTracer() would never be called.
if (module.resource.clientAttemptCountCounter() != null) { if (module.resource.clientAttemptCountCounter() != null) {
@ -257,9 +263,9 @@ final class OpenTelemetryMetricsModule {
// CallAttemptsTracerFactory constructor. attemptsPerCall will be non-zero after the first // CallAttemptsTracerFactory constructor. attemptsPerCall will be non-zero after the first
// attempt, as first attempt cannot be a transparent retry. // attempt, as first attempt cannot be a transparent retry.
if (attemptsPerCall.get() > 0) { if (attemptsPerCall.get() > 0) {
// TODO(dnvindhya): Add target as an attribute
io.opentelemetry.api.common.Attributes attribute = io.opentelemetry.api.common.Attributes attribute =
io.opentelemetry.api.common.Attributes.of(METHOD_KEY, fullMethodName); io.opentelemetry.api.common.Attributes.of(METHOD_KEY, fullMethodName,
TARGET_KEY, target);
if (module.resource.clientAttemptCountCounter() != null) { if (module.resource.clientAttemptCountCounter() != null) {
module.resource.clientAttemptCountCounter().add(1, attribute); module.resource.clientAttemptCountCounter().add(1, attribute);
} }
@ -267,7 +273,7 @@ final class OpenTelemetryMetricsModule {
if (!info.isTransparentRetry()) { if (!info.isTransparentRetry()) {
attemptsPerCall.incrementAndGet(); attemptsPerCall.incrementAndGet();
} }
return new ClientTracer(this, module, info, fullMethodName); return new ClientTracer(this, module, info, target, fullMethodName);
} }
// Called whenever each attempt is ended. // Called whenever each attempt is ended.
@ -309,15 +315,15 @@ final class OpenTelemetryMetricsModule {
void recordFinishedCall() { void recordFinishedCall() {
if (attemptsPerCall.get() == 0) { if (attemptsPerCall.get() == 0) {
ClientTracer tracer = new ClientTracer(this, module, null, fullMethodName); ClientTracer tracer = new ClientTracer(this, module, null, target, fullMethodName);
tracer.attemptNanos = attemptStopwatch.elapsed(TimeUnit.NANOSECONDS); tracer.attemptNanos = attemptStopwatch.elapsed(TimeUnit.NANOSECONDS);
tracer.statusCode = status.getCode(); tracer.statusCode = status.getCode();
tracer.recordFinishedAttempt(); tracer.recordFinishedAttempt();
} }
callLatencyNanos = callStopWatch.elapsed(TimeUnit.NANOSECONDS); callLatencyNanos = callStopWatch.elapsed(TimeUnit.NANOSECONDS);
// TODO(dnvindhya): record target as an attribute
io.opentelemetry.api.common.Attributes attribute = io.opentelemetry.api.common.Attributes attribute =
io.opentelemetry.api.common.Attributes.of(METHOD_KEY, fullMethodName, io.opentelemetry.api.common.Attributes.of(METHOD_KEY, fullMethodName,
TARGET_KEY, target,
STATUS_KEY, status.getCode().toString()); STATUS_KEY, status.getCode().toString());
if (module.resource.clientCallDurationCounter() != null) { if (module.resource.clientCallDurationCounter() != null) {
@ -459,6 +465,12 @@ final class OpenTelemetryMetricsModule {
@VisibleForTesting @VisibleForTesting
final class MetricsClientInterceptor implements ClientInterceptor { final class MetricsClientInterceptor implements ClientInterceptor {
private final String target;
MetricsClientInterceptor(String target) {
this.target = checkNotNull(target, "target");
}
@Override @Override
public <ReqT, RespT> ClientCall<ReqT, RespT> interceptCall( public <ReqT, RespT> ClientCall<ReqT, RespT> interceptCall(
MethodDescriptor<ReqT, RespT> method, CallOptions callOptions, Channel next) { MethodDescriptor<ReqT, RespT> method, CallOptions callOptions, Channel next) {
@ -466,7 +478,7 @@ final class OpenTelemetryMetricsModule {
// which is true for all generated methods. Otherwise, programatically // which is true for all generated methods. Otherwise, programatically
// created methods result in high cardinality metrics. // created methods result in high cardinality metrics.
final CallAttemptsTracerFactory tracerFactory = new CallAttemptsTracerFactory( final CallAttemptsTracerFactory tracerFactory = new CallAttemptsTracerFactory(
OpenTelemetryMetricsModule.this, recordMethodName(method.getFullMethodName(), OpenTelemetryMetricsModule.this, target, recordMethodName(method.getFullMethodName(),
method.isSampledToLocalTracing())); method.isSampledToLocalTracing()));
ClientCall<ReqT, RespT> call = ClientCall<ReqT, RespT> call =
next.newCall(method, callOptions.withStreamTracerFactory(tracerFactory)); next.newCall(method, callOptions.withStreamTracerFactory(tracerFactory));

View File

@ -27,6 +27,7 @@ import com.google.common.collect.ImmutableMap;
import io.grpc.ExperimentalApi; import io.grpc.ExperimentalApi;
import io.grpc.InternalConfigurator; import io.grpc.InternalConfigurator;
import io.grpc.InternalConfiguratorRegistry; import io.grpc.InternalConfiguratorRegistry;
import io.grpc.InternalManagedChannelBuilder;
import io.grpc.ManagedChannelBuilder; import io.grpc.ManagedChannelBuilder;
import io.grpc.MetricSink; import io.grpc.MetricSink;
import io.grpc.ServerBuilder; import io.grpc.ServerBuilder;
@ -146,7 +147,8 @@ public final class OpenTelemetryModule {
*/ */
public void configureChannelBuilder(ManagedChannelBuilder<?> builder) { public void configureChannelBuilder(ManagedChannelBuilder<?> builder) {
builder.addMetricSink(sink); builder.addMetricSink(sink);
builder.intercept(openTelemetryMetricsModule.getClientInterceptor()); InternalManagedChannelBuilder.interceptWithTarget(
builder, openTelemetryMetricsModule::getClientInterceptor);
} }
/** /**

View File

@ -19,6 +19,7 @@ package io.grpc.opentelemetry;
import static io.grpc.ClientStreamTracer.NAME_RESOLUTION_DELAYED; import static io.grpc.ClientStreamTracer.NAME_RESOLUTION_DELAYED;
import static io.grpc.opentelemetry.internal.OpenTelemetryConstants.METHOD_KEY; import static io.grpc.opentelemetry.internal.OpenTelemetryConstants.METHOD_KEY;
import static io.grpc.opentelemetry.internal.OpenTelemetryConstants.STATUS_KEY; import static io.grpc.opentelemetry.internal.OpenTelemetryConstants.STATUS_KEY;
import static io.grpc.opentelemetry.internal.OpenTelemetryConstants.TARGET_KEY;
import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat; import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat;
import static java.util.concurrent.TimeUnit.MILLISECONDS; import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
@ -185,7 +186,7 @@ public class OpenTelemetryMetricsModuleTest {
Channel interceptedChannel = Channel interceptedChannel =
ClientInterceptors.intercept( ClientInterceptors.intercept(
grpcServerRule.getChannel(), callOptionsCatureInterceptor, grpcServerRule.getChannel(), callOptionsCatureInterceptor,
module.getClientInterceptor()); module.getClientInterceptor("target:///"));
ClientCall<String, String> call; ClientCall<String, String> call;
call = interceptedChannel.newCall(method, CALL_OPTIONS); call = interceptedChannel.newCall(method, CALL_OPTIONS);
@ -211,16 +212,18 @@ public class OpenTelemetryMetricsModuleTest {
@Test @Test
public void clientBasicMetrics() { public void clientBasicMetrics() {
String target = "target:///";
OpenTelemetryMetricsResource resource = OpenTelemetryModule.createMetricInstruments(testMeter, OpenTelemetryMetricsResource resource = OpenTelemetryModule.createMetricInstruments(testMeter,
enabledMetricsMap, disableDefaultMetrics); enabledMetricsMap, disableDefaultMetrics);
OpenTelemetryMetricsModule module = OpenTelemetryMetricsModule module =
new OpenTelemetryMetricsModule(fakeClock.getStopwatchSupplier(), resource); new OpenTelemetryMetricsModule(fakeClock.getStopwatchSupplier(), resource);
OpenTelemetryMetricsModule.CallAttemptsTracerFactory callAttemptsTracerFactory = OpenTelemetryMetricsModule.CallAttemptsTracerFactory callAttemptsTracerFactory =
new CallAttemptsTracerFactory(module, method.getFullMethodName()); new CallAttemptsTracerFactory(module, target, method.getFullMethodName());
Metadata headers = new Metadata(); Metadata headers = new Metadata();
ClientStreamTracer tracer = ClientStreamTracer tracer =
callAttemptsTracerFactory.newClientStreamTracer(STREAM_INFO, headers); callAttemptsTracerFactory.newClientStreamTracer(STREAM_INFO, headers);
io.opentelemetry.api.common.Attributes attributes = io.opentelemetry.api.common.Attributes.of( io.opentelemetry.api.common.Attributes attributes = io.opentelemetry.api.common.Attributes.of(
TARGET_KEY, target,
METHOD_KEY, method.getFullMethodName()); METHOD_KEY, method.getFullMethodName());
assertThat(openTelemetryTesting.getMetrics()) assertThat(openTelemetryTesting.getMetrics())
@ -262,6 +265,7 @@ public class OpenTelemetryMetricsModuleTest {
io.opentelemetry.api.common.Attributes clientAttributes io.opentelemetry.api.common.Attributes clientAttributes
= io.opentelemetry.api.common.Attributes.of( = io.opentelemetry.api.common.Attributes.of(
TARGET_KEY, target,
METHOD_KEY, method.getFullMethodName(), METHOD_KEY, method.getFullMethodName(),
STATUS_KEY, Status.Code.OK.toString()); STATUS_KEY, Status.Code.OK.toString());
@ -346,17 +350,19 @@ public class OpenTelemetryMetricsModuleTest {
// This test is only unit-testing the metrics recording logic. The retry behavior is faked. // This test is only unit-testing the metrics recording logic. The retry behavior is faked.
@Test @Test
public void recordAttemptMetrics() { public void recordAttemptMetrics() {
String target = "dns:///example.com";
OpenTelemetryMetricsResource resource = OpenTelemetryModule.createMetricInstruments(testMeter, OpenTelemetryMetricsResource resource = OpenTelemetryModule.createMetricInstruments(testMeter,
enabledMetricsMap, disableDefaultMetrics); enabledMetricsMap, disableDefaultMetrics);
OpenTelemetryMetricsModule module = OpenTelemetryMetricsModule module =
new OpenTelemetryMetricsModule(fakeClock.getStopwatchSupplier(), resource); new OpenTelemetryMetricsModule(fakeClock.getStopwatchSupplier(), resource);
OpenTelemetryMetricsModule.CallAttemptsTracerFactory callAttemptsTracerFactory = OpenTelemetryMetricsModule.CallAttemptsTracerFactory callAttemptsTracerFactory =
new OpenTelemetryMetricsModule.CallAttemptsTracerFactory(module, new OpenTelemetryMetricsModule.CallAttemptsTracerFactory(module, target,
method.getFullMethodName()); method.getFullMethodName());
ClientStreamTracer tracer = ClientStreamTracer tracer =
callAttemptsTracerFactory.newClientStreamTracer(STREAM_INFO, new Metadata()); callAttemptsTracerFactory.newClientStreamTracer(STREAM_INFO, new Metadata());
io.opentelemetry.api.common.Attributes attributes = io.opentelemetry.api.common.Attributes.of( io.opentelemetry.api.common.Attributes attributes = io.opentelemetry.api.common.Attributes.of(
TARGET_KEY, target,
METHOD_KEY, method.getFullMethodName()); METHOD_KEY, method.getFullMethodName());
assertThat(openTelemetryTesting.getMetrics()) assertThat(openTelemetryTesting.getMetrics())
@ -387,6 +393,7 @@ public class OpenTelemetryMetricsModuleTest {
io.opentelemetry.api.common.Attributes clientAttributes io.opentelemetry.api.common.Attributes clientAttributes
= io.opentelemetry.api.common.Attributes.of( = io.opentelemetry.api.common.Attributes.of(
TARGET_KEY, target,
METHOD_KEY, method.getFullMethodName(), METHOD_KEY, method.getFullMethodName(),
STATUS_KEY, Code.UNAVAILABLE.toString()); STATUS_KEY, Code.UNAVAILABLE.toString());
@ -467,6 +474,7 @@ public class OpenTelemetryMetricsModuleTest {
io.opentelemetry.api.common.Attributes clientAttributes1 io.opentelemetry.api.common.Attributes clientAttributes1
= io.opentelemetry.api.common.Attributes.of( = io.opentelemetry.api.common.Attributes.of(
TARGET_KEY, target,
METHOD_KEY, method.getFullMethodName(), METHOD_KEY, method.getFullMethodName(),
STATUS_KEY, Code.NOT_FOUND.toString()); STATUS_KEY, Code.NOT_FOUND.toString());
@ -653,6 +661,7 @@ public class OpenTelemetryMetricsModuleTest {
io.opentelemetry.api.common.Attributes clientAttributes2 io.opentelemetry.api.common.Attributes clientAttributes2
= io.opentelemetry.api.common.Attributes.of( = io.opentelemetry.api.common.Attributes.of(
TARGET_KEY, target,
METHOD_KEY, method.getFullMethodName(), METHOD_KEY, method.getFullMethodName(),
STATUS_KEY, Code.OK.toString()); STATUS_KEY, Code.OK.toString());
@ -767,12 +776,13 @@ public class OpenTelemetryMetricsModuleTest {
@Test @Test
public void clientStreamNeverCreatedStillRecordMetrics() { public void clientStreamNeverCreatedStillRecordMetrics() {
String target = "dns:///foo.example.com";
OpenTelemetryMetricsResource resource = OpenTelemetryModule.createMetricInstruments(testMeter, OpenTelemetryMetricsResource resource = OpenTelemetryModule.createMetricInstruments(testMeter,
enabledMetricsMap, disableDefaultMetrics); enabledMetricsMap, disableDefaultMetrics);
OpenTelemetryMetricsModule module = OpenTelemetryMetricsModule module =
new OpenTelemetryMetricsModule(fakeClock.getStopwatchSupplier(), resource); new OpenTelemetryMetricsModule(fakeClock.getStopwatchSupplier(), resource);
OpenTelemetryMetricsModule.CallAttemptsTracerFactory callAttemptsTracerFactory = OpenTelemetryMetricsModule.CallAttemptsTracerFactory callAttemptsTracerFactory =
new OpenTelemetryMetricsModule.CallAttemptsTracerFactory(module, new OpenTelemetryMetricsModule.CallAttemptsTracerFactory(module, target,
method.getFullMethodName()); method.getFullMethodName());
fakeClock.forwardTime(3000, MILLISECONDS); fakeClock.forwardTime(3000, MILLISECONDS);
Status status = Status.DEADLINE_EXCEEDED.withDescription("5 seconds"); Status status = Status.DEADLINE_EXCEEDED.withDescription("5 seconds");
@ -780,10 +790,12 @@ public class OpenTelemetryMetricsModuleTest {
io.opentelemetry.api.common.Attributes attemptStartedAttributes io.opentelemetry.api.common.Attributes attemptStartedAttributes
= io.opentelemetry.api.common.Attributes.of( = io.opentelemetry.api.common.Attributes.of(
TARGET_KEY, target,
METHOD_KEY, method.getFullMethodName()); METHOD_KEY, method.getFullMethodName());
io.opentelemetry.api.common.Attributes clientAttributes io.opentelemetry.api.common.Attributes clientAttributes
= io.opentelemetry.api.common.Attributes.of( = io.opentelemetry.api.common.Attributes.of(
TARGET_KEY, target,
METHOD_KEY, method.getFullMethodName(), METHOD_KEY, method.getFullMethodName(),
STATUS_KEY, STATUS_KEY,
Code.DEADLINE_EXCEEDED.toString()); Code.DEADLINE_EXCEEDED.toString());