Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,12 @@

package io.opentelemetry.instrumentation.api.incubator.semconv.rpc;

import static io.opentelemetry.instrumentation.api.incubator.semconv.rpc.RpcCommonAttributesExtractor.OLD_RPC_METHOD_CONTEXT_KEY;
import static io.opentelemetry.instrumentation.api.incubator.semconv.rpc.RpcCommonAttributesExtractor.RPC_METHOD;
import static io.opentelemetry.instrumentation.api.internal.SemconvStability.emitOldRpcSemconv;
import static io.opentelemetry.instrumentation.api.internal.SemconvStability.emitStableRpcSemconv;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static java.util.concurrent.TimeUnit.SECONDS;
import static java.util.logging.Level.FINE;

import com.google.auto.value.AutoValue;
Expand All @@ -21,6 +26,7 @@
import io.opentelemetry.instrumentation.api.instrumenter.OperationMetrics;
import io.opentelemetry.instrumentation.api.internal.OperationMetricsUtil;
import java.util.logging.Logger;
import javax.annotation.Nullable;

/**
* {@link OperationListener} which keeps track of <a
Expand All @@ -30,42 +36,62 @@
public final class RpcClientMetrics implements OperationListener {

private static final double NANOS_PER_MS = MILLISECONDS.toNanos(1);
private static final double NANOS_PER_S = SECONDS.toNanos(1);

private static final ContextKey<RpcClientMetrics.State> RPC_CLIENT_REQUEST_METRICS_STATE =
ContextKey.named("rpc-client-request-metrics-state");

private static final Logger logger = Logger.getLogger(RpcClientMetrics.class.getName());

private final DoubleHistogram clientDurationHistogram;
private final LongHistogram clientRequestSize;
private final LongHistogram clientResponseSize;
@Nullable private final DoubleHistogram oldClientDurationHistogram;
@Nullable private final DoubleHistogram stableClientDurationHistogram;
private final LongHistogram oldClientRequestSize;
private final LongHistogram oldClientResponseSize;

private RpcClientMetrics(Meter meter) {
DoubleHistogramBuilder durationBuilder =
meter
.histogramBuilder("rpc.client.duration")
.setDescription("The duration of an outbound RPC invocation.")
.setUnit("ms");
RpcMetricsAdvice.applyClientDurationAdvice(durationBuilder);
clientDurationHistogram = durationBuilder.build();
// Old metric (milliseconds)
if (emitOldRpcSemconv()) {
DoubleHistogramBuilder oldDurationBuilder =
meter
.histogramBuilder("rpc.client.duration")
.setDescription("The duration of an outbound RPC invocation.")
.setUnit("ms");
RpcMetricsAdvice.applyClientDurationAdvice(oldDurationBuilder, false);
oldClientDurationHistogram = oldDurationBuilder.build();
} else {
oldClientDurationHistogram = null;
}

// Stable metric (seconds)
if (emitStableRpcSemconv()) {
DoubleHistogramBuilder stableDurationBuilder =
meter
.histogramBuilder("rpc.client.call.duration")
.setDescription("Measures the duration of outbound remote procedure calls (RPC).")
.setUnit("s");
RpcMetricsAdvice.applyClientDurationAdvice(stableDurationBuilder, true);
stableClientDurationHistogram = stableDurationBuilder.build();
} else {
stableClientDurationHistogram = null;
}

LongHistogramBuilder requestSizeBuilder =
meter
.histogramBuilder("rpc.client.request.size")
.setUnit("By")
.setDescription("Measures the size of RPC request messages (uncompressed).")
.ofLongs();
RpcMetricsAdvice.applyClientRequestSizeAdvice(requestSizeBuilder);
clientRequestSize = requestSizeBuilder.build();
RpcMetricsAdvice.applyOldClientRequestSizeAdvice(requestSizeBuilder);
oldClientRequestSize = requestSizeBuilder.build();

LongHistogramBuilder responseSizeBuilder =
meter
.histogramBuilder("rpc.client.response.size")
.setUnit("By")
.setDescription("Measures the size of RPC response messages (uncompressed).")
.ofLongs();
RpcMetricsAdvice.applyClientRequestSizeAdvice(responseSizeBuilder);
clientResponseSize = responseSizeBuilder.build();
RpcMetricsAdvice.applyOldClientRequestSizeAdvice(responseSizeBuilder);
oldClientResponseSize = responseSizeBuilder.build();
}

/**
Expand All @@ -81,7 +107,8 @@ public static OperationMetrics get() {
public Context onStart(Context context, Attributes startAttributes, long startNanos) {
return context.with(
RPC_CLIENT_REQUEST_METRICS_STATE,
new AutoValue_RpcClientMetrics_State(startAttributes, startNanos));
new AutoValue_RpcClientMetrics_State(
startAttributes, startNanos, context.get(OLD_RPC_METHOD_CONTEXT_KEY)));
}

@Override
Expand All @@ -95,18 +122,41 @@ public void onEnd(Context context, Attributes endAttributes, long endNanos) {
return;
}
Attributes attributes = state.startAttributes().toBuilder().putAll(endAttributes).build();
clientDurationHistogram.record(
(endNanos - state.startTimeNanos()) / NANOS_PER_MS, attributes, context);
double durationNanos = endNanos - state.startTimeNanos();

// Record to old histogram (milliseconds)
if (oldClientDurationHistogram != null) {
Attributes oldAttributes = getOldAttributes(attributes, state);
oldClientDurationHistogram.record(durationNanos / NANOS_PER_MS, oldAttributes, context);
}

Long rpcClientRequestBodySize = attributes.get(RpcSizeAttributesExtractor.RPC_REQUEST_SIZE);
if (rpcClientRequestBodySize != null) {
clientRequestSize.record(rpcClientRequestBodySize, attributes, context);
// Record to stable histogram (seconds)
if (stableClientDurationHistogram != null) {
stableClientDurationHistogram.record(durationNanos / NANOS_PER_S, attributes, context);
}

Long rpcClientResponseBodySize = attributes.get(RpcSizeAttributesExtractor.RPC_RESPONSE_SIZE);
if (rpcClientResponseBodySize != null) {
clientResponseSize.record(rpcClientResponseBodySize, attributes, context);
if (emitOldRpcSemconv()) {
Attributes oldAttributes = getOldAttributes(attributes, state);

Long rpcClientRequestBodySize = attributes.get(RpcSizeAttributesExtractor.RPC_REQUEST_SIZE);
if (rpcClientRequestBodySize != null) {
oldClientRequestSize.record(rpcClientRequestBodySize, oldAttributes, context);
}

Long rpcClientResponseBodySize = attributes.get(RpcSizeAttributesExtractor.RPC_RESPONSE_SIZE);
if (rpcClientResponseBodySize != null) {
oldClientResponseSize.record(rpcClientResponseBodySize, oldAttributes, context);
}
}
}

private static Attributes getOldAttributes(Attributes attributes, State state) {
String oldRpcMethod = state.oldRpcMethod();
if (oldRpcMethod != null) {
// dup mode: replace stable rpc.method with old value
return attributes.toBuilder().put(RPC_METHOD, oldRpcMethod).build();
}
return attributes;
}

@AutoValue
Expand All @@ -115,5 +165,8 @@ abstract static class State {
abstract Attributes startAttributes();

abstract long startTimeNanos();

@Nullable
abstract String oldRpcMethod();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,33 @@
import io.opentelemetry.api.common.AttributeKey;
import io.opentelemetry.api.common.AttributesBuilder;
import io.opentelemetry.context.Context;
import io.opentelemetry.context.ContextKey;
import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor;
import io.opentelemetry.instrumentation.api.instrumenter.ContextCustomizer;
import javax.annotation.Nullable;

abstract class RpcCommonAttributesExtractor<REQUEST, RESPONSE>
public abstract class RpcCommonAttributesExtractor<REQUEST, RESPONSE>
Comment thread
trask marked this conversation as resolved.
Outdated
implements AttributesExtractor<REQUEST, RESPONSE> {

static final AttributeKey<String> RPC_METHOD = AttributeKey.stringKey("rpc.method");

static final ContextKey<String> OLD_RPC_METHOD_CONTEXT_KEY =

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@trask In #15932 you commented that under rpc/dup, stable semconv should take priority and there's no need for this extra context key.

The reason it exists: in dup mode, both old and stable metrics use the same rpc.method attribute key but with different values — old uses just the method name ("exampleMethod"), stable uses "service/method" format ("myservice.EchoService/exampleMethod"). Since both values live in the same Attributes object, stable wins and overwrites old.

Without this context key, the old rpc.server.duration / rpc.client.duration histograms would report rpc.method = "myservice.EchoService/exampleMethod" instead of "exampleMethod" — a breaking change for anyone querying old metrics during migration (which is the whole point of dup mode).

The context key is never emitted — it just passes the old method value through Context so the metrics classes can swap it back when recording to old histograms.

Happy to drop this if you think letting the values clash in dup mode is acceptable. Just wanted to make sure the trade-off is clear.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks, it makes sense to me

ContextKey.named("otel-rpc-old-method");

@SuppressWarnings("deprecation") // for getMethod()
public static <REQUEST> ContextCustomizer<REQUEST> oldMethodContextCustomizer(
RpcAttributesGetter<REQUEST, ?> getter) {
return (context, request, startAttributes) -> {
if (emitOldRpcSemconv() && emitStableRpcSemconv()) {
String oldMethod = getter.getMethod(request);
if (oldMethod != null) {
return context.with(OLD_RPC_METHOD_CONTEXT_KEY, oldMethod);
}
}
return context;
};
}

// Stable semconv keys
static final AttributeKey<String> RPC_SYSTEM_NAME = AttributeKey.stringKey("rpc.system.name");

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,69 +5,111 @@

package io.opentelemetry.instrumentation.api.incubator.semconv.rpc;

import static io.opentelemetry.instrumentation.api.internal.SemconvStability.emitStableRpcSemconv;
import static io.opentelemetry.semconv.NetworkAttributes.NETWORK_TRANSPORT;
import static io.opentelemetry.semconv.NetworkAttributes.NETWORK_TYPE;
import static io.opentelemetry.semconv.ServerAttributes.SERVER_ADDRESS;
import static io.opentelemetry.semconv.ServerAttributes.SERVER_PORT;
import static java.util.Arrays.asList;

import io.opentelemetry.api.common.AttributeKey;
import io.opentelemetry.api.incubator.metrics.ExtendedDoubleHistogramBuilder;
import io.opentelemetry.api.incubator.metrics.ExtendedLongHistogramBuilder;
import io.opentelemetry.api.metrics.DoubleHistogramBuilder;
import io.opentelemetry.api.metrics.LongHistogramBuilder;
import java.util.ArrayList;
import java.util.List;

final class RpcMetricsAdvice {

// Stable semconv key
private static final AttributeKey<String> RPC_RESPONSE_STATUS_CODE =
AttributeKey.stringKey("rpc.response.status_code");

// copied from RpcIncubatingAttributes
@Deprecated // use RPC_RESPONSE_STATUS_CODE for stable semconv
private static final AttributeKey<Long> RPC_GRPC_STATUS_CODE =
AttributeKey.longKey("rpc.grpc.status_code");
private static final List<AttributeKey<?>> RPC_METRICS_ATTRIBUTE_KEYS =
asList(
RpcCommonAttributesExtractor.RPC_SYSTEM,
RpcCommonAttributesExtractor.RPC_SERVICE,
RpcCommonAttributesExtractor.RPC_METHOD,
RPC_GRPC_STATUS_CODE,
NETWORK_TYPE,
NETWORK_TRANSPORT,
SERVER_ADDRESS,
SERVER_PORT);

static void applyClientDurationAdvice(DoubleHistogramBuilder builder) {

private static final List<AttributeKey<?>> RPC_METRICS_OLD_ATTRIBUTE_KEYS =
buildAttributeKeysList(false);
private static final List<AttributeKey<?>> RPC_METRICS_STABLE_ATTRIBUTE_KEYS =
buildAttributeKeysList(true);

@SuppressWarnings("deprecation") // until old rpc semconv are dropped
private static List<AttributeKey<?>> buildAttributeKeysList(boolean stable) {
List<AttributeKey<?>> keys = new ArrayList<>();

// Add stable or old RPC system key
keys.add(
stable
? RpcCommonAttributesExtractor.RPC_SYSTEM_NAME
: RpcCommonAttributesExtractor.RPC_SYSTEM);

// Add RPC service (old only)
if (!stable) {
keys.add(RpcCommonAttributesExtractor.RPC_SERVICE);
}

keys.add(RpcCommonAttributesExtractor.RPC_METHOD);

// Add status code key
if (emitStableRpcSemconv()) {
keys.add(RPC_RESPONSE_STATUS_CODE);
} else {
keys.add(RPC_GRPC_STATUS_CODE);
}

// Network type only for old semconv
if (!stable) {
keys.add(NETWORK_TYPE);
}

// Common attributes
keys.add(NETWORK_TRANSPORT);
keys.add(SERVER_ADDRESS);
keys.add(SERVER_PORT);

return keys;
}

private static List<AttributeKey<?>> getAttributeKeys(boolean stable) {
return stable ? RPC_METRICS_STABLE_ATTRIBUTE_KEYS : RPC_METRICS_OLD_ATTRIBUTE_KEYS;
}

static void applyClientDurationAdvice(DoubleHistogramBuilder builder, boolean stable) {
if (!(builder instanceof ExtendedDoubleHistogramBuilder)) {
return;
}
// the list of recommended metrics attributes is from
// https://github.com/open-telemetry/semantic-conventions/blob/main/docs/rpc/rpc-metrics.md
((ExtendedDoubleHistogramBuilder) builder).setAttributesAdvice(RPC_METRICS_ATTRIBUTE_KEYS);
((ExtendedDoubleHistogramBuilder) builder).setAttributesAdvice(getAttributeKeys(stable));
}

static void applyServerDurationAdvice(DoubleHistogramBuilder builder) {
static void applyServerDurationAdvice(DoubleHistogramBuilder builder, boolean stable) {
if (!(builder instanceof ExtendedDoubleHistogramBuilder)) {
return;
}
// the list of recommended metrics attributes is from
// https://github.com/open-telemetry/semantic-conventions/blob/main/docs/rpc/rpc-metrics.md
((ExtendedDoubleHistogramBuilder) builder).setAttributesAdvice(RPC_METRICS_ATTRIBUTE_KEYS);
((ExtendedDoubleHistogramBuilder) builder).setAttributesAdvice(getAttributeKeys(stable));
}

static void applyClientRequestSizeAdvice(LongHistogramBuilder builder) {
static void applyOldClientRequestSizeAdvice(LongHistogramBuilder builder) {
if (!(builder instanceof ExtendedLongHistogramBuilder)) {
return;
}
// the list of recommended metrics attributes is from
// https://github.com/open-telemetry/semantic-conventions/blob/main/docs/rpc/rpc-metrics.md
((ExtendedLongHistogramBuilder) builder).setAttributesAdvice(RPC_METRICS_ATTRIBUTE_KEYS);
((ExtendedLongHistogramBuilder) builder).setAttributesAdvice(RPC_METRICS_OLD_ATTRIBUTE_KEYS);
}

static void applyServerRequestSizeAdvice(LongHistogramBuilder builder) {
static void applyOldServerRequestSizeAdvice(LongHistogramBuilder builder) {
if (!(builder instanceof ExtendedLongHistogramBuilder)) {
return;
}
// the list of recommended metrics attributes is from
// https://github.com/open-telemetry/semantic-conventions/blob/main/docs/rpc/rpc-metrics.md
((ExtendedLongHistogramBuilder) builder).setAttributesAdvice(RPC_METRICS_ATTRIBUTE_KEYS);
((ExtendedLongHistogramBuilder) builder).setAttributesAdvice(RPC_METRICS_OLD_ATTRIBUTE_KEYS);
}

private RpcMetricsAdvice() {}
Expand Down
Loading
Loading