Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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.RPC_METHOD;
import static io.opentelemetry.instrumentation.api.incubator.semconv.rpc.RpcMetricsContextCustomizers.OLD_RPC_METHOD_CONTEXT_KEY;
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,67 @@
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;
@Nullable private final LongHistogram oldClientRequestSize;
@Nullable 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();

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();

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();
// 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;
}

if (emitOldRpcSemconv()) {
LongHistogramBuilder requestSizeBuilder =
meter
.histogramBuilder("rpc.client.request.size")
.setUnit("By")
.setDescription("Measures the size of RPC request messages (uncompressed).")
.ofLongs();
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.applyOldClientRequestSizeAdvice(responseSizeBuilder);
oldClientResponseSize = responseSizeBuilder.build();
} else {
oldClientRequestSize = null;
oldClientResponseSize = null;
}
}

/**
Expand All @@ -81,7 +112,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 +127,38 @@ 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();

if (emitOldRpcSemconv()) {
Attributes oldAttributes = getOldAttributes(attributes, state);

Long rpcClientRequestBodySize = attributes.get(RpcSizeAttributesExtractor.RPC_REQUEST_SIZE);
if (rpcClientRequestBodySize != null) {
clientRequestSize.record(rpcClientRequestBodySize, attributes, context);
if (oldClientDurationHistogram != null) {
oldClientDurationHistogram.record(durationNanos / NANOS_PER_MS, oldAttributes, context);
}

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

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

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);
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 +167,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 @@ -9,65 +9,105 @@
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);

if (stable) {
keys.add(RPC_RESPONSE_STATUS_CODE);
} else {
keys.add(RPC_GRPC_STATUS_CODE);
}

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

// Common attributes
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

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

import static io.opentelemetry.instrumentation.api.internal.SemconvStability.emitOldRpcSemconv;
import static io.opentelemetry.instrumentation.api.internal.SemconvStability.emitStableRpcSemconv;

import io.opentelemetry.context.ContextKey;
import io.opentelemetry.instrumentation.api.instrumenter.ContextCustomizer;

/**
* Provides {@link ContextCustomizer} instances for RPC metrics dual-semconv support.
*
* @deprecated This class is only needed during the transition period when both old and stable RPC
* semantic conventions are emitted simultaneously.
*/
@Deprecated // to be removed in 3.0
public final class RpcMetricsContextCustomizers {

static final ContextKey<String> OLD_RPC_METHOD_CONTEXT_KEY =
ContextKey.named("otel-rpc-old-method");

/**
* Returns a {@link ContextCustomizer} that captures the old {@code rpc.method} value in context
* so that RPC metrics can use it when both old and stable semantic conventions are active.
*/
public static <REQUEST> ContextCustomizer<REQUEST> dualEmitContextCustomizer(
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;
};
}

private RpcMetricsContextCustomizers() {}
}
Loading
Loading