Skip to content

Commit a8f2271

Browse files
authored
openTelemetry: add tcp metrics (#12652)
Implements [A80](grpc/proposal#519)
1 parent b16da37 commit a8f2271

File tree

32 files changed

+1255
-158
lines changed

32 files changed

+1255
-158
lines changed

api/src/main/java/io/grpc/ForwardingServerBuilder.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,12 @@ public Server build() {
201201
return delegate().build();
202202
}
203203

204+
@Override
205+
public T addMetricSink(MetricSink metricSink) {
206+
delegate().addMetricSink(metricSink);
207+
return thisT();
208+
}
209+
204210
@Override
205211
public String toString() {
206212
return MoreObjects.toStringHelper(this).add("delegate", delegate()).toString();
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
/*
2+
* Copyright 2026 The gRPC Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package io.grpc;
18+
19+
import java.util.Arrays;
20+
import java.util.Collections;
21+
import java.util.List;
22+
23+
/**
24+
* TCP Metrics defined to be shared across transport implementations.
25+
* These metrics and their definitions are specified in
26+
* <a href=
27+
* "https://github.com/grpc/proposal/blob/master/A80-tcp-metrics.md">gRFC
28+
* A80</a>.
29+
*/
30+
@Internal
31+
public final class InternalTcpMetrics {
32+
33+
private InternalTcpMetrics() {
34+
}
35+
36+
private static final List<String> OPTIONAL_LABELS = Arrays.asList(
37+
"network.local.address",
38+
"network.local.port",
39+
"network.peer.address",
40+
"network.peer.port");
41+
42+
public static final DoubleHistogramMetricInstrument MIN_RTT_INSTRUMENT =
43+
MetricInstrumentRegistry.getDefaultRegistry()
44+
.registerDoubleHistogram(
45+
"grpc.tcp.min_rtt",
46+
"Minimum round-trip time of a TCP connection",
47+
"s",
48+
Collections.emptyList(),
49+
Collections.emptyList(),
50+
OPTIONAL_LABELS,
51+
false);
52+
53+
public static final LongCounterMetricInstrument CONNECTIONS_CREATED_INSTRUMENT =
54+
MetricInstrumentRegistry
55+
.getDefaultRegistry()
56+
.registerLongCounter(
57+
"grpc.tcp.connections_created",
58+
"The total number of TCP connections established.",
59+
"{connection}",
60+
Collections.emptyList(),
61+
OPTIONAL_LABELS,
62+
false);
63+
64+
public static final LongUpDownCounterMetricInstrument CONNECTION_COUNT_INSTRUMENT =
65+
MetricInstrumentRegistry
66+
.getDefaultRegistry()
67+
.registerLongUpDownCounter(
68+
"grpc.tcp.connection_count",
69+
"The current number of active TCP connections.",
70+
"{connection}",
71+
Collections.emptyList(),
72+
OPTIONAL_LABELS,
73+
false);
74+
75+
public static final LongCounterMetricInstrument PACKETS_RETRANSMITTED_INSTRUMENT =
76+
MetricInstrumentRegistry
77+
.getDefaultRegistry()
78+
.registerLongCounter(
79+
"grpc.tcp.packets_retransmitted",
80+
"The total number of packets retransmitted for all TCP connections.",
81+
"{packet}",
82+
Collections.emptyList(),
83+
OPTIONAL_LABELS,
84+
false);
85+
86+
public static final LongCounterMetricInstrument RECURRING_RETRANSMITS_INSTRUMENT =
87+
MetricInstrumentRegistry
88+
.getDefaultRegistry()
89+
.registerLongCounter(
90+
"grpc.tcp.recurring_retransmits",
91+
"The total number of times the retransmit timer "
92+
+ "popped for all TCP connections.",
93+
"{timeout}",
94+
Collections.emptyList(),
95+
OPTIONAL_LABELS,
96+
false);
97+
98+
}

api/src/main/java/io/grpc/NameResolver.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -355,7 +355,7 @@ public static final class Args {
355355
@Nullable private final ChannelLogger channelLogger;
356356
@Nullable private final Executor executor;
357357
@Nullable private final String overrideAuthority;
358-
@Nullable private final MetricRecorder metricRecorder;
358+
private final MetricRecorder metricRecorder;
359359
@Nullable private final NameResolverRegistry nameResolverRegistry;
360360
@Nullable private final IdentityHashMap<Key<?>, Object> customArgs;
361361

@@ -369,7 +369,8 @@ private Args(Builder builder) {
369369
this.channelLogger = builder.channelLogger;
370370
this.executor = builder.executor;
371371
this.overrideAuthority = builder.overrideAuthority;
372-
this.metricRecorder = builder.metricRecorder;
372+
this.metricRecorder = builder.metricRecorder != null ? builder.metricRecorder
373+
: new MetricRecorder() {};
373374
this.nameResolverRegistry = builder.nameResolverRegistry;
374375
this.customArgs = cloneCustomArgs(builder.customArgs);
375376
}
@@ -497,7 +498,6 @@ public String getOverrideAuthority() {
497498
/**
498499
* Returns the {@link MetricRecorder} that the channel uses to record metrics.
499500
*/
500-
@Nullable
501501
public MetricRecorder getMetricRecorder() {
502502
return metricRecorder;
503503
}
@@ -680,7 +680,7 @@ public <T> Builder setArg(Key<T> key, T value) {
680680
* See {@link Args#getMetricRecorder()}. This is an optional field.
681681
*/
682682
public Builder setMetricRecorder(MetricRecorder metricRecorder) {
683-
this.metricRecorder = metricRecorder;
683+
this.metricRecorder = checkNotNull(metricRecorder, "metricRecorder");
684684
return this;
685685
}
686686

api/src/main/java/io/grpc/ServerBuilder.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -435,6 +435,17 @@ public T setBinaryLog(BinaryLog binaryLog) {
435435
*/
436436
public abstract Server build();
437437

438+
/**
439+
* Adds a metric sink to the server.
440+
*
441+
* @param metricSink the metric sink to add.
442+
* @return this
443+
*/
444+
@ExperimentalApi("https://github.com/grpc/grpc-java/issues/12693")
445+
public T addMetricSink(MetricSink metricSink) {
446+
return thisT();
447+
}
448+
438449
/**
439450
* Returns the correctly typed version of the builder.
440451
*/

binder/src/main/java/io/grpc/binder/BinderServerBuilder.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ private BinderServerBuilder(
6868

6969
serverImplBuilder =
7070
new ServerImplBuilder(
71-
streamTracerFactories -> {
71+
(streamTracerFactories, metricRecorder) -> {
7272
internalBuilder.setStreamTracerFactories(streamTracerFactories);
7373
BinderServer server = internalBuilder.build();
7474
BinderInternal.setIBinder(binderReceiver, server.getHostBinder());

core/src/main/java/io/grpc/internal/ClientTransportFactory.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import io.grpc.ChannelCredentials;
2525
import io.grpc.ChannelLogger;
2626
import io.grpc.HttpConnectProxiedSocketAddress;
27+
import io.grpc.MetricRecorder;
2728
import java.io.Closeable;
2829
import java.net.SocketAddress;
2930
import java.util.Collection;
@@ -91,6 +92,8 @@ final class ClientTransportOptions {
9192
private Attributes eagAttributes = Attributes.EMPTY;
9293
@Nullable private String userAgent;
9394
@Nullable private HttpConnectProxiedSocketAddress connectProxiedSocketAddr;
95+
private MetricRecorder metricRecorder = new MetricRecorder() {
96+
};
9497

9598
public ChannelLogger getChannelLogger() {
9699
return channelLogger;
@@ -101,6 +104,15 @@ public ClientTransportOptions setChannelLogger(ChannelLogger channelLogger) {
101104
return this;
102105
}
103106

107+
public MetricRecorder getMetricRecorder() {
108+
return metricRecorder;
109+
}
110+
111+
public ClientTransportOptions setMetricRecorder(MetricRecorder metricRecorder) {
112+
this.metricRecorder = Preconditions.checkNotNull(metricRecorder, "metricRecorder");
113+
return this;
114+
}
115+
104116
public String getAuthority() {
105117
return authority;
106118
}

core/src/main/java/io/grpc/internal/InternalSubchannel.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ final class InternalSubchannel implements InternalInstrumented<ChannelStats>, Tr
8080
private final InternalChannelz channelz;
8181
private final CallTracer callsTracer;
8282
private final ChannelTracer channelTracer;
83+
private final MetricRecorder metricRecorder;
8384
private final ChannelLogger channelLogger;
8485
private final boolean reconnectDisabled;
8586

@@ -191,6 +192,7 @@ protected void handleNotInUse() {
191192
this.scheduledExecutor = scheduledExecutor;
192193
this.connectingTimer = stopwatchSupplier.get();
193194
this.syncContext = syncContext;
195+
this.metricRecorder = metricRecorder;
194196
this.callback = callback;
195197
this.channelz = channelz;
196198
this.callsTracer = callsTracer;
@@ -265,6 +267,7 @@ private void startNewTransport() {
265267
.setAuthority(eagChannelAuthority != null ? eagChannelAuthority : authority)
266268
.setEagAttributes(currentEagAttributes)
267269
.setUserAgent(userAgent)
270+
.setMetricRecorder(metricRecorder)
268271
.setHttpConnectProxiedSocketAddress(proxiedAddr);
269272
TransportLogger transportLogger = new TransportLogger();
270273
// In case the transport logs in the constructor, use the subchannel logId

core/src/main/java/io/grpc/internal/ServerImplBuilder.java

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,9 @@
3131
import io.grpc.HandlerRegistry;
3232
import io.grpc.InternalChannelz;
3333
import io.grpc.InternalConfiguratorRegistry;
34+
import io.grpc.MetricInstrumentRegistry;
35+
import io.grpc.MetricRecorder;
36+
import io.grpc.MetricSink;
3437
import io.grpc.Server;
3538
import io.grpc.ServerBuilder;
3639
import io.grpc.ServerCallExecutorSupplier;
@@ -80,6 +83,7 @@ public static ServerBuilder<?> forPort(int port) {
8083
final List<ServerTransportFilter> transportFilters = new ArrayList<>();
8184
final List<ServerInterceptor> interceptors = new ArrayList<>();
8285
private final List<ServerStreamTracer.Factory> streamTracerFactories = new ArrayList<>();
86+
final List<MetricSink> metricSinks = new ArrayList<>();
8387
private final ClientTransportServersBuilder clientTransportServersBuilder;
8488
HandlerRegistry fallbackRegistry = DEFAULT_FALLBACK_REGISTRY;
8589
ObjectPool<? extends Executor> executorPool = DEFAULT_EXECUTOR_POOL;
@@ -104,7 +108,8 @@ public static ServerBuilder<?> forPort(int port) {
104108
*/
105109
public interface ClientTransportServersBuilder {
106110
InternalServer buildClientTransportServers(
107-
List<? extends ServerStreamTracer.Factory> streamTracerFactories);
111+
List<? extends ServerStreamTracer.Factory> streamTracerFactories,
112+
MetricRecorder metricRecorder);
108113
}
109114

110115
/**
@@ -157,6 +162,15 @@ public ServerImplBuilder intercept(ServerInterceptor interceptor) {
157162
return this;
158163
}
159164

165+
/**
166+
* Adds a MetricSink to the server.
167+
*/
168+
@Override
169+
public ServerImplBuilder addMetricSink(MetricSink metricSink) {
170+
metricSinks.add(checkNotNull(metricSink, "metricSink"));
171+
return this;
172+
}
173+
160174
@Override
161175
public ServerImplBuilder addStreamTracerFactory(ServerStreamTracer.Factory factory) {
162176
streamTracerFactories.add(checkNotNull(factory, "factory"));
@@ -241,8 +255,11 @@ public void setDeadlineTicker(Deadline.Ticker ticker) {
241255

242256
@Override
243257
public Server build() {
258+
MetricRecorder metricRecorder = new MetricRecorderImpl(metricSinks,
259+
MetricInstrumentRegistry.getDefaultRegistry());
244260
return new ServerImpl(this,
245-
clientTransportServersBuilder.buildClientTransportServers(getTracerFactories()),
261+
clientTransportServersBuilder.buildClientTransportServers(
262+
getTracerFactories(), metricRecorder),
246263
Context.ROOT);
247264
}
248265

core/src/test/java/io/grpc/internal/ServerImplBuilderTest.java

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@
2222
import io.grpc.InternalConfigurator;
2323
import io.grpc.InternalConfiguratorRegistry;
2424
import io.grpc.Metadata;
25+
import io.grpc.MetricRecorder;
26+
import io.grpc.MetricSink;
27+
import io.grpc.NoopMetricSink;
2528
import io.grpc.ServerBuilder;
2629
import io.grpc.ServerCall;
2730
import io.grpc.ServerCallHandler;
@@ -73,7 +76,8 @@ public void setUp() throws Exception {
7376
new ClientTransportServersBuilder() {
7477
@Override
7578
public InternalServer buildClientTransportServers(
76-
List<? extends ServerStreamTracer.Factory> streamTracerFactories) {
79+
List<? extends ServerStreamTracer.Factory> streamTracerFactories,
80+
MetricRecorder metricRecorder) {
7781
throw new UnsupportedOperationException();
7882
}
7983
});
@@ -128,6 +132,13 @@ public void getTracerFactories_disableBoth() {
128132
assertThat(factories).containsExactly(DUMMY_USER_TRACER);
129133
}
130134

135+
@Test
136+
public void addMetricSink_addsToSinks() {
137+
MetricSink noopMetricSink = new NoopMetricSink();
138+
builder.addMetricSink(noopMetricSink);
139+
assertThat(builder.metricSinks).containsExactly(noopMetricSink);
140+
}
141+
131142
@Test
132143
public void getTracerFactories_callsGet() throws Exception {
133144
Class<?> runnable = classLoader.loadClass(StaticTestingClassLoaderCallsGet.class.getName());
@@ -139,7 +150,7 @@ public static final class StaticTestingClassLoaderCallsGet implements Runnable {
139150
public void run() {
140151
ServerImplBuilder builder =
141152
new ServerImplBuilder(
142-
streamTracerFactories -> {
153+
(streamTracerFactories, metricRecorder) -> {
143154
throw new UnsupportedOperationException();
144155
});
145156
assertThat(builder.getTracerFactories()).hasSize(2);
@@ -169,7 +180,7 @@ public void configureServerBuilder(ServerBuilder<?> builder) {
169180
}));
170181
ServerImplBuilder builder =
171182
new ServerImplBuilder(
172-
streamTracerFactories -> {
183+
(streamTracerFactories, metricRecorder) -> {
173184
throw new UnsupportedOperationException();
174185
});
175186
assertThat(builder.getTracerFactories()).containsExactly(DUMMY_USER_TRACER);
@@ -192,7 +203,7 @@ public void run() {
192203
InternalConfiguratorRegistry.setConfigurators(Collections.emptyList());
193204
ServerImplBuilder builder =
194205
new ServerImplBuilder(
195-
streamTracerFactories -> {
206+
(streamTracerFactories, metricRecorder) -> {
196207
throw new UnsupportedOperationException();
197208
});
198209
assertThat(builder.getTracerFactories()).isEmpty();

core/src/test/java/io/grpc/internal/ServerImplTest.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@
6565
import io.grpc.InternalServerInterceptors;
6666
import io.grpc.Metadata;
6767
import io.grpc.MethodDescriptor;
68+
import io.grpc.MetricRecorder;
6869
import io.grpc.ServerCall;
6970
import io.grpc.ServerCall.Listener;
7071
import io.grpc.ServerCallExecutorSupplier;
@@ -206,7 +207,8 @@ public void startUp() throws IOException {
206207
new ClientTransportServersBuilder() {
207208
@Override
208209
public InternalServer buildClientTransportServers(
209-
List<? extends ServerStreamTracer.Factory> streamTracerFactories) {
210+
List<? extends ServerStreamTracer.Factory> streamTracerFactories,
211+
MetricRecorder metricRecorder) {
210212
throw new UnsupportedOperationException();
211213
}
212214
});

0 commit comments

Comments
 (0)