Skip to content
Open
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
4 changes: 2 additions & 2 deletions instrumentation-api-incubator/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -86,13 +86,13 @@ tasks {
val testStableSemconv by registering(Test::class) {
testClassesDirs = sourceSets.test.get().output.classesDirs
classpath = sourceSets.test.get().runtimeClasspath
jvmArgs("-Dotel.semconv-stability.opt-in=database,code")
jvmArgs("-Dotel.semconv-stability.opt-in=database,code,rpc")
}

val testBothSemconv by registering(Test::class) {
testClassesDirs = sourceSets.test.get().output.classesDirs
classpath = sourceSets.test.get().runtimeClasspath
jvmArgs("-Dotel.semconv-stability.opt-in=database/dup,code/dup")
jvmArgs("-Dotel.semconv-stability.opt-in=database/dup,code/dup,rpc/dup")
}

check {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,24 @@ default Long getRequestSize(REQUEST request) {
default Long getResponseSize(REQUEST request) {
return null;
}

/**
* Returns the fully-qualified RPC method name for stable semconv.
*
* <p>The default implementation concatenates service + "/" + method. Framework implementations
* can override for efficiency if they already have the fully-qualified name available.
*
* @param request the request object
* @return the fully-qualified RPC method name (e.g., "my.Service/Method"), or null if service or
* method is unavailable
*/
@Nullable
default String getFullMethod(REQUEST request) {
String service = getService(request);
String method = getMethod(request);
if (service == null || method == null) {
return null;
}
return service + "/" + method;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,10 @@
import io.opentelemetry.instrumentation.api.instrumenter.OperationListener;
import io.opentelemetry.instrumentation.api.instrumenter.OperationMetrics;
import io.opentelemetry.instrumentation.api.internal.OperationMetricsUtil;
import io.opentelemetry.instrumentation.api.internal.SemconvStability;
import java.util.concurrent.TimeUnit;
import java.util.logging.Logger;
import javax.annotation.Nullable;

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

private static final double NANOS_PER_MS = TimeUnit.MILLISECONDS.toNanos(1);
private static final double NANOS_PER_S = TimeUnit.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 (SemconvStability.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 (SemconvStability.emitStableRpcSemconv()) {
DoubleHistogramBuilder stableDurationBuilder =
meter
.histogramBuilder("rpc.client.call.duration")
.setDescription("The duration of an outbound RPC invocation.")
.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.applyDeprecatedClientRequestSizeAdvice(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.applyDeprecatedClientRequestSizeAdvice(responseSizeBuilder);
oldClientResponseSize = responseSizeBuilder.build();
}

/**
Expand Down Expand Up @@ -95,17 +117,29 @@ 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());

Long rpcClientRequestBodySize = attributes.get(RpcSizeAttributesExtractor.RPC_REQUEST_SIZE);
if (rpcClientRequestBodySize != null) {
clientRequestSize.record(rpcClientRequestBodySize, attributes, context);
// Record to old histogram (milliseconds)
if (oldClientDurationHistogram != null) {
oldClientDurationHistogram.record(
durationNanos / NANOS_PER_MS, SemconvStability.getOldRpcAttributes(attributes), context);
}

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

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

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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,27 @@
package io.opentelemetry.instrumentation.api.incubator.semconv.rpc;

import static io.opentelemetry.instrumentation.api.internal.AttributesExtractorUtil.internalSet;
import static io.opentelemetry.semconv.ErrorAttributes.ERROR_TYPE;

import io.opentelemetry.api.common.AttributeKey;
import io.opentelemetry.api.common.AttributesBuilder;
import io.opentelemetry.context.Context;
import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor;
import io.opentelemetry.instrumentation.api.internal.SemconvStability;
import javax.annotation.Nullable;

abstract class RpcCommonAttributesExtractor<REQUEST, RESPONSE>
implements AttributesExtractor<REQUEST, RESPONSE> {

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

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

// removed in stable semconv (merged into rpc.method)
static final AttributeKey<String> RPC_SERVICE = AttributeKey.stringKey("rpc.service");

// use RPC_SYSTEM_NAME for stable semconv
static final AttributeKey<String> RPC_SYSTEM = AttributeKey.stringKey("rpc.system");

private final RpcAttributesGetter<REQUEST> getter;
Expand All @@ -29,9 +37,26 @@ abstract class RpcCommonAttributesExtractor<REQUEST, RESPONSE>

@Override
public final void onStart(AttributesBuilder attributes, Context parentContext, REQUEST request) {
internalSet(attributes, RPC_SYSTEM, getter.getSystem(request));
internalSet(attributes, RPC_SERVICE, getter.getService(request));
internalSet(attributes, RPC_METHOD, getter.getMethod(request));
String system = getter.getSystem(request);

if (SemconvStability.emitStableRpcSemconv()) {
internalSet(
attributes,
RPC_SYSTEM_NAME,
system == null ? null : SemconvStability.stableRpcSystemName(system));
internalSet(attributes, RPC_METHOD, getter.getFullMethod(request));
}

if (SemconvStability.emitOldRpcSemconv()) {
internalSet(attributes, RPC_SYSTEM, system);
internalSet(attributes, RPC_SERVICE, getter.getService(request));
internalSet(
attributes, SemconvStability.getOldRpcMethodAttributeKey(), getter.getMethod(request));
if (!SemconvStability.emitStableRpcSemconv()) {
// only set old rpc.method if stable not emitted to avoid duplication
internalSet(attributes, RPC_METHOD, getter.getMethod(request));
}
}
}

@Override
Expand All @@ -41,6 +66,10 @@ public final void onEnd(
REQUEST request,
@Nullable RESPONSE response,
@Nullable Throwable error) {
// No response attributes
if (SemconvStability.emitStableRpcSemconv()) {
if (error != null) {
internalSet(attributes, ERROR_TYPE, error.getClass().getName());
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,67 +5,118 @@

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

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 io.opentelemetry.instrumentation.api.internal.SemconvStability;
import io.opentelemetry.semconv.NetworkAttributes;
import io.opentelemetry.semconv.ServerAttributes;
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,
NetworkAttributes.NETWORK_TYPE,
NetworkAttributes.NETWORK_TRANSPORT,
ServerAttributes.SERVER_ADDRESS,
ServerAttributes.SERVER_PORT);

static void applyClientDurationAdvice(DoubleHistogramBuilder builder) {

private static final List<AttributeKey<?>> RPC_METRICS_DEPRECATED_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
if (stable) {
keys.add(RpcCommonAttributesExtractor.RPC_SYSTEM_NAME);
} else {
keys.add(RpcCommonAttributesExtractor.RPC_SYSTEM);
}

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

// Add RPC method key
if (stable) {
// Stable uses rpc.method with full format
keys.add(RpcCommonAttributesExtractor.RPC_METHOD);
} else {
// Old uses rpc.method.deprecated in dup mode, or rpc.method in old-only mode
keys.add(SemconvStability.getOldRpcMethodAttributeKey());
}

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

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

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

return keys;
}

private static List<AttributeKey<?>> getAttributeKeys(boolean stable) {
return stable ? RPC_METRICS_STABLE_ATTRIBUTE_KEYS : RPC_METRICS_DEPRECATED_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 applyDeprecatedClientRequestSizeAdvice(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_DEPRECATED_ATTRIBUTE_KEYS);
}

static void applyServerRequestSizeAdvice(LongHistogramBuilder builder) {
static void applyDeprecatedServerRequestSizeAdvice(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_DEPRECATED_ATTRIBUTE_KEYS);
}

private RpcMetricsAdvice() {}
Expand Down
Loading
Loading