Skip to content

Commit e9f2745

Browse files
committed
Add RPC semantic convention stable opt-in support
This adds support for the stable RPC semantic conventions via the `otel.semconv-stability.opt-in=rpc` configuration option. Key changes: - Add emitOldRpcSemconv()/emitStableRpcSemconv() to SemconvStability - Add stableRpcSystemName() mapping (apache_dubbo→dubbo, connect_rpc→connectrpc) - Add RpcAttributesGetter.getFullMethod() for "service/method" format - Update RpcCommonAttributesExtractor for dual semconv emission - Update RpcClientMetrics/RpcServerMetrics for dual histogram (ms→s unit change)
1 parent 9ebb336 commit e9f2745

File tree

10 files changed

+826
-320
lines changed

10 files changed

+826
-320
lines changed

instrumentation-api-incubator/build.gradle.kts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -86,13 +86,13 @@ tasks {
8686
val testStableSemconv by registering(Test::class) {
8787
testClassesDirs = sourceSets.test.get().output.classesDirs
8888
classpath = sourceSets.test.get().runtimeClasspath
89-
jvmArgs("-Dotel.semconv-stability.opt-in=database,code")
89+
jvmArgs("-Dotel.semconv-stability.opt-in=database,code,rpc")
9090
}
9191

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

9898
check {

instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcAttributesGetter.java

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,4 +34,24 @@ default Long getRequestSize(REQUEST request) {
3434
default Long getResponseSize(REQUEST request) {
3535
return null;
3636
}
37+
38+
/**
39+
* Returns the fully-qualified RPC method name for stable semconv.
40+
*
41+
* <p>The default implementation concatenates service + "/" + method. Framework implementations
42+
* can override for efficiency if they already have the fully-qualified name available.
43+
*
44+
* @param request the request object
45+
* @return the fully-qualified RPC method name (e.g., "my.Service/Method"), or null if service or
46+
* method is unavailable
47+
*/
48+
@Nullable
49+
default String getFullMethod(REQUEST request) {
50+
String service = getService(request);
51+
String method = getMethod(request);
52+
if (service == null || method == null) {
53+
return null;
54+
}
55+
return service + "/" + method;
56+
}
3757
}

instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcClientMetrics.java

Lines changed: 64 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,10 @@
1919
import io.opentelemetry.instrumentation.api.instrumenter.OperationListener;
2020
import io.opentelemetry.instrumentation.api.instrumenter.OperationMetrics;
2121
import io.opentelemetry.instrumentation.api.internal.OperationMetricsUtil;
22+
import io.opentelemetry.instrumentation.api.internal.SemconvStability;
2223
import java.util.concurrent.TimeUnit;
2324
import java.util.logging.Logger;
25+
import javax.annotation.Nullable;
2426

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

3234
private static final double NANOS_PER_MS = TimeUnit.MILLISECONDS.toNanos(1);
35+
private static final double NANOS_PER_S = TimeUnit.SECONDS.toNanos(1);
3336

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

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

39-
private final DoubleHistogram clientDurationHistogram;
40-
private final LongHistogram clientRequestSize;
41-
private final LongHistogram clientResponseSize;
42+
@Nullable private final DoubleHistogram oldClientDurationHistogram;
43+
@Nullable private final DoubleHistogram stableClientDurationHistogram;
44+
private final LongHistogram oldClientRequestSize;
45+
private final LongHistogram oldClientResponseSize;
4246

4347
private RpcClientMetrics(Meter meter) {
44-
DoubleHistogramBuilder durationBuilder =
45-
meter
46-
.histogramBuilder("rpc.client.duration")
47-
.setDescription("The duration of an outbound RPC invocation.")
48-
.setUnit("ms");
49-
RpcMetricsAdvice.applyClientDurationAdvice(durationBuilder);
50-
clientDurationHistogram = durationBuilder.build();
48+
// Old metric (milliseconds)
49+
if (SemconvStability.emitOldRpcSemconv()) {
50+
DoubleHistogramBuilder oldDurationBuilder =
51+
meter
52+
.histogramBuilder("rpc.client.duration")
53+
.setDescription("The duration of an outbound RPC invocation.")
54+
.setUnit("ms");
55+
RpcMetricsAdvice.applyClientDurationAdvice(oldDurationBuilder, false);
56+
oldClientDurationHistogram = oldDurationBuilder.build();
57+
} else {
58+
oldClientDurationHistogram = null;
59+
}
60+
61+
// Stable metric (seconds)
62+
if (SemconvStability.emitStableRpcSemconv()) {
63+
DoubleHistogramBuilder stableDurationBuilder =
64+
meter
65+
.histogramBuilder("rpc.client.call.duration")
66+
.setDescription("The duration of an outbound RPC invocation.")
67+
.setUnit("s");
68+
RpcMetricsAdvice.applyClientDurationAdvice(stableDurationBuilder, true);
69+
stableClientDurationHistogram = stableDurationBuilder.build();
70+
} else {
71+
stableClientDurationHistogram = null;
72+
}
5173

5274
LongHistogramBuilder requestSizeBuilder =
5375
meter
5476
.histogramBuilder("rpc.client.request.size")
5577
.setUnit("By")
5678
.setDescription("Measures the size of RPC request messages (uncompressed).")
5779
.ofLongs();
58-
RpcMetricsAdvice.applyClientRequestSizeAdvice(requestSizeBuilder);
59-
clientRequestSize = requestSizeBuilder.build();
80+
RpcMetricsAdvice.applyDeprecatedClientRequestSizeAdvice(requestSizeBuilder);
81+
oldClientRequestSize = requestSizeBuilder.build();
6082

6183
LongHistogramBuilder responseSizeBuilder =
6284
meter
6385
.histogramBuilder("rpc.client.response.size")
6486
.setUnit("By")
6587
.setDescription("Measures the size of RPC response messages (uncompressed).")
6688
.ofLongs();
67-
RpcMetricsAdvice.applyClientRequestSizeAdvice(responseSizeBuilder);
68-
clientResponseSize = responseSizeBuilder.build();
89+
RpcMetricsAdvice.applyDeprecatedClientRequestSizeAdvice(responseSizeBuilder);
90+
oldClientResponseSize = responseSizeBuilder.build();
6991
}
7092

7193
/**
@@ -95,17 +117,37 @@ public void onEnd(Context context, Attributes endAttributes, long endNanos) {
95117
return;
96118
}
97119
Attributes attributes = state.startAttributes().toBuilder().putAll(endAttributes).build();
98-
clientDurationHistogram.record(
99-
(endNanos - state.startTimeNanos()) / NANOS_PER_MS, attributes, context);
120+
double durationNanos = (endNanos - state.startTimeNanos());
121+
122+
// Record to old histogram (milliseconds)
123+
if (oldClientDurationHistogram != null) {
124+
oldClientDurationHistogram.record(
125+
durationNanos / NANOS_PER_MS,
126+
SemconvStability.getOldRpcMetricAttributes(attributes),
127+
context);
128+
}
100129

101-
Long rpcClientRequestBodySize = attributes.get(RpcSizeAttributesExtractor.RPC_REQUEST_SIZE);
102-
if (rpcClientRequestBodySize != null) {
103-
clientRequestSize.record(rpcClientRequestBodySize, attributes, context);
130+
// Record to stable histogram (seconds)
131+
if (stableClientDurationHistogram != null) {
132+
stableClientDurationHistogram.record(durationNanos / NANOS_PER_S, attributes, context);
104133
}
105134

106-
Long rpcClientResponseBodySize = attributes.get(RpcSizeAttributesExtractor.RPC_RESPONSE_SIZE);
107-
if (rpcClientResponseBodySize != null) {
108-
clientResponseSize.record(rpcClientResponseBodySize, attributes, context);
135+
if (SemconvStability.emitOldRpcSemconv()) {
136+
Long rpcClientRequestBodySize = attributes.get(RpcSizeAttributesExtractor.RPC_REQUEST_SIZE);
137+
if (rpcClientRequestBodySize != null) {
138+
oldClientRequestSize.record(
139+
rpcClientRequestBodySize,
140+
SemconvStability.getOldRpcMetricAttributes(attributes),
141+
context);
142+
}
143+
144+
Long rpcClientResponseBodySize = attributes.get(RpcSizeAttributesExtractor.RPC_RESPONSE_SIZE);
145+
if (rpcClientResponseBodySize != null) {
146+
oldClientResponseSize.record(
147+
rpcClientResponseBodySize,
148+
SemconvStability.getOldRpcMetricAttributes(attributes),
149+
context);
150+
}
109151
}
110152
}
111153

instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcCommonAttributesExtractor.java

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,19 +6,27 @@
66
package io.opentelemetry.instrumentation.api.incubator.semconv.rpc;
77

88
import static io.opentelemetry.instrumentation.api.internal.AttributesExtractorUtil.internalSet;
9+
import static io.opentelemetry.semconv.ErrorAttributes.ERROR_TYPE;
910

1011
import io.opentelemetry.api.common.AttributeKey;
1112
import io.opentelemetry.api.common.AttributesBuilder;
1213
import io.opentelemetry.context.Context;
1314
import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor;
15+
import io.opentelemetry.instrumentation.api.internal.SemconvStability;
1416
import javax.annotation.Nullable;
1517

1618
abstract class RpcCommonAttributesExtractor<REQUEST, RESPONSE>
1719
implements AttributesExtractor<REQUEST, RESPONSE> {
1820

19-
// copied from RpcIncubatingAttributes
2021
static final AttributeKey<String> RPC_METHOD = AttributeKey.stringKey("rpc.method");
22+
23+
// Stable semconv keys
24+
static final AttributeKey<String> RPC_SYSTEM_NAME = AttributeKey.stringKey("rpc.system.name");
25+
26+
// removed in stable semconv (merged into rpc.method)
2127
static final AttributeKey<String> RPC_SERVICE = AttributeKey.stringKey("rpc.service");
28+
29+
// use RPC_SYSTEM_NAME for stable semconv
2230
static final AttributeKey<String> RPC_SYSTEM = AttributeKey.stringKey("rpc.system");
2331

2432
private final RpcAttributesGetter<REQUEST> getter;
@@ -29,9 +37,22 @@ abstract class RpcCommonAttributesExtractor<REQUEST, RESPONSE>
2937

3038
@Override
3139
public final void onStart(AttributesBuilder attributes, Context parentContext, REQUEST request) {
32-
internalSet(attributes, RPC_SYSTEM, getter.getSystem(request));
33-
internalSet(attributes, RPC_SERVICE, getter.getService(request));
34-
internalSet(attributes, RPC_METHOD, getter.getMethod(request));
40+
String system = getter.getSystem(request);
41+
42+
if (SemconvStability.emitStableRpcSemconv()) {
43+
internalSet(
44+
attributes,
45+
RPC_SYSTEM_NAME,
46+
system == null ? null : SemconvStability.stableRpcSystemName(system));
47+
internalSet(attributes, RPC_METHOD, getter.getFullMethod(request));
48+
}
49+
50+
if (SemconvStability.emitOldRpcSemconv()) {
51+
internalSet(attributes, RPC_SYSTEM, system);
52+
internalSet(attributes, RPC_SERVICE, getter.getService(request));
53+
internalSet(
54+
attributes, SemconvStability.getOldRpcMethodAttributeKey(), getter.getMethod(request));
55+
}
3556
}
3657

3758
@Override
@@ -41,6 +62,10 @@ public final void onEnd(
4162
REQUEST request,
4263
@Nullable RESPONSE response,
4364
@Nullable Throwable error) {
44-
// No response attributes
65+
if (SemconvStability.emitStableRpcSemconv()) {
66+
if (error != null) {
67+
internalSet(attributes, ERROR_TYPE, error.getClass().getName());
68+
}
69+
}
4570
}
4671
}

instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/rpc/RpcMetricsAdvice.java

Lines changed: 65 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -5,67 +5,111 @@
55

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

8-
import static java.util.Arrays.asList;
9-
108
import io.opentelemetry.api.common.AttributeKey;
119
import io.opentelemetry.api.incubator.metrics.ExtendedDoubleHistogramBuilder;
1210
import io.opentelemetry.api.incubator.metrics.ExtendedLongHistogramBuilder;
1311
import io.opentelemetry.api.metrics.DoubleHistogramBuilder;
1412
import io.opentelemetry.api.metrics.LongHistogramBuilder;
13+
import io.opentelemetry.instrumentation.api.internal.SemconvStability;
1514
import io.opentelemetry.semconv.NetworkAttributes;
1615
import io.opentelemetry.semconv.ServerAttributes;
16+
import java.util.ArrayList;
1717
import java.util.List;
1818

1919
final class RpcMetricsAdvice {
2020

21+
// Stable semconv key
22+
private static final AttributeKey<String> RPC_RESPONSE_STATUS_CODE =
23+
AttributeKey.stringKey("rpc.response.status_code");
24+
2125
// copied from RpcIncubatingAttributes
26+
@Deprecated // use RPC_RESPONSE_STATUS_CODE for stable semconv
2227
private static final AttributeKey<Long> RPC_GRPC_STATUS_CODE =
2328
AttributeKey.longKey("rpc.grpc.status_code");
24-
private static final List<AttributeKey<?>> RPC_METRICS_ATTRIBUTE_KEYS =
25-
asList(
26-
RpcCommonAttributesExtractor.RPC_SYSTEM,
27-
RpcCommonAttributesExtractor.RPC_SERVICE,
28-
RpcCommonAttributesExtractor.RPC_METHOD,
29-
RPC_GRPC_STATUS_CODE,
30-
NetworkAttributes.NETWORK_TYPE,
31-
NetworkAttributes.NETWORK_TRANSPORT,
32-
ServerAttributes.SERVER_ADDRESS,
33-
ServerAttributes.SERVER_PORT);
34-
35-
static void applyClientDurationAdvice(DoubleHistogramBuilder builder) {
29+
30+
private static final List<AttributeKey<?>> RPC_METRICS_DEPRECATED_ATTRIBUTE_KEYS =
31+
buildAttributeKeysList(false);
32+
private static final List<AttributeKey<?>> RPC_METRICS_STABLE_ATTRIBUTE_KEYS =
33+
buildAttributeKeysList(true);
34+
35+
@SuppressWarnings("deprecation") // until old rpc semconv are dropped
36+
private static List<AttributeKey<?>> buildAttributeKeysList(boolean stable) {
37+
List<AttributeKey<?>> keys = new ArrayList<>();
38+
39+
// Add stable or old RPC system key
40+
if (stable) {
41+
keys.add(RpcCommonAttributesExtractor.RPC_SYSTEM_NAME);
42+
} else {
43+
keys.add(RpcCommonAttributesExtractor.RPC_SYSTEM);
44+
}
45+
46+
// Add RPC service (old only)
47+
if (!stable) {
48+
keys.add(RpcCommonAttributesExtractor.RPC_SERVICE);
49+
}
50+
51+
keys.add(RpcCommonAttributesExtractor.RPC_METHOD);
52+
53+
// Add status code key
54+
if (SemconvStability.emitStableRpcSemconv()) {
55+
keys.add(RPC_RESPONSE_STATUS_CODE);
56+
} else {
57+
keys.add(RPC_GRPC_STATUS_CODE);
58+
}
59+
60+
// Network type only for old semconv
61+
if (!stable) {
62+
keys.add(NetworkAttributes.NETWORK_TYPE);
63+
}
64+
65+
// Common attributes
66+
keys.add(NetworkAttributes.NETWORK_TRANSPORT);
67+
keys.add(ServerAttributes.SERVER_ADDRESS);
68+
keys.add(ServerAttributes.SERVER_PORT);
69+
70+
return keys;
71+
}
72+
73+
private static List<AttributeKey<?>> getAttributeKeys(boolean stable) {
74+
return stable ? RPC_METRICS_STABLE_ATTRIBUTE_KEYS : RPC_METRICS_DEPRECATED_ATTRIBUTE_KEYS;
75+
}
76+
77+
static void applyClientDurationAdvice(DoubleHistogramBuilder builder, boolean stable) {
3678
if (!(builder instanceof ExtendedDoubleHistogramBuilder)) {
3779
return;
3880
}
3981
// the list of recommended metrics attributes is from
4082
// https://github.com/open-telemetry/semantic-conventions/blob/main/docs/rpc/rpc-metrics.md
41-
((ExtendedDoubleHistogramBuilder) builder).setAttributesAdvice(RPC_METRICS_ATTRIBUTE_KEYS);
83+
((ExtendedDoubleHistogramBuilder) builder).setAttributesAdvice(getAttributeKeys(stable));
4284
}
4385

44-
static void applyServerDurationAdvice(DoubleHistogramBuilder builder) {
86+
static void applyServerDurationAdvice(DoubleHistogramBuilder builder, boolean stable) {
4587
if (!(builder instanceof ExtendedDoubleHistogramBuilder)) {
4688
return;
4789
}
4890
// the list of recommended metrics attributes is from
4991
// https://github.com/open-telemetry/semantic-conventions/blob/main/docs/rpc/rpc-metrics.md
50-
((ExtendedDoubleHistogramBuilder) builder).setAttributesAdvice(RPC_METRICS_ATTRIBUTE_KEYS);
92+
((ExtendedDoubleHistogramBuilder) builder).setAttributesAdvice(getAttributeKeys(stable));
5193
}
5294

53-
static void applyClientRequestSizeAdvice(LongHistogramBuilder builder) {
95+
static void applyDeprecatedClientRequestSizeAdvice(LongHistogramBuilder builder) {
5496
if (!(builder instanceof ExtendedLongHistogramBuilder)) {
5597
return;
5698
}
5799
// the list of recommended metrics attributes is from
58100
// https://github.com/open-telemetry/semantic-conventions/blob/main/docs/rpc/rpc-metrics.md
59-
((ExtendedLongHistogramBuilder) builder).setAttributesAdvice(RPC_METRICS_ATTRIBUTE_KEYS);
101+
((ExtendedLongHistogramBuilder) builder)
102+
.setAttributesAdvice(RPC_METRICS_DEPRECATED_ATTRIBUTE_KEYS);
60103
}
61104

62-
static void applyServerRequestSizeAdvice(LongHistogramBuilder builder) {
105+
static void applyDeprecatedServerRequestSizeAdvice(LongHistogramBuilder builder) {
63106
if (!(builder instanceof ExtendedLongHistogramBuilder)) {
64107
return;
65108
}
66109
// the list of recommended metrics attributes is from
67110
// https://github.com/open-telemetry/semantic-conventions/blob/main/docs/rpc/rpc-metrics.md
68-
((ExtendedLongHistogramBuilder) builder).setAttributesAdvice(RPC_METRICS_ATTRIBUTE_KEYS);
111+
((ExtendedLongHistogramBuilder) builder)
112+
.setAttributesAdvice(RPC_METRICS_DEPRECATED_ATTRIBUTE_KEYS);
69113
}
70114

71115
private RpcMetricsAdvice() {}

0 commit comments

Comments
 (0)