2323import io .grpc .MetricInstrumentRegistry ;
2424import io .grpc .MetricRecorder ;
2525import io .netty .channel .Channel ;
26+ import java .lang .reflect .Constructor ;
2627import java .lang .reflect .Method ;
2728import java .util .Arrays ;
2829import java .util .Collections ;
30+ import java .util .HashMap ;
2931import java .util .List ;
32+ import java .util .Map ;
33+ import javax .annotation .Nullable ;
3034
35+ /**
36+ * Utility for collecting TCP metrics from Netty channels.
37+ */
3138final class TcpMetrics {
3239
3340 private static final Metrics DEFAULT_METRICS ;
@@ -51,9 +58,9 @@ static Metrics getDefaultMetrics() {
5158 static final class Metrics {
5259 final LongCounterMetricInstrument connectionsCreated ;
5360 final LongUpDownCounterMetricInstrument connectionCount ;
54- final LongCounterMetricInstrument packetsRetransmitted ;
55- final LongCounterMetricInstrument recurringRetransmits ;
56- final DoubleHistogramMetricInstrument minRtt ;
61+ @ Nullable final LongCounterMetricInstrument packetsRetransmitted ;
62+ @ Nullable final LongCounterMetricInstrument recurringRetransmits ;
63+ @ Nullable final DoubleHistogramMetricInstrument minRtt ;
5764
5865 Metrics (MetricInstrumentRegistry registry , boolean epollAvailable ) {
5966 List <String > requiredLabels = Collections .singletonList ("grpc.target" );
@@ -171,12 +178,32 @@ private static DoubleHistogramMetricInstrument safelyRegisterDoubleHistogram(
171178 }
172179 }
173180
181+ private static final class ChannelReflectionAccessor {
182+ final Method tcpInfoMethod ;
183+ final Constructor <?> tcpInfoConstructor ;
184+ final Method totalRetransMethod ;
185+ final Method retransmitsMethod ;
186+ final Method rttMethod ;
187+
188+ ChannelReflectionAccessor (
189+ Method tcpInfoMethod , Constructor <?> tcpInfoConstructor , Method totalRetransMethod ,
190+ Method retransmitsMethod , Method rttMethod ) {
191+ this .tcpInfoMethod = tcpInfoMethod ;
192+ this .tcpInfoConstructor = tcpInfoConstructor ;
193+ this .totalRetransMethod = totalRetransMethod ;
194+ this .retransmitsMethod = retransmitsMethod ;
195+ this .rttMethod = rttMethod ;
196+ }
197+ }
198+
174199 static final class Tracker {
175200 private final MetricRecorder metricRecorder ;
176201 private final String target ;
177202 private final Metrics metrics ;
178203 private final String epollSocketChannelClassName ;
179204 private final String epollTcpInfoClassName ;
205+ private volatile ChannelReflectionAccessor channelReflectionAccessor ;
206+ private long lastTotalRetrans ;
180207
181208 Tracker (MetricRecorder metricRecorder , String target ) {
182209 this (metricRecorder , target , DEFAULT_METRICS );
@@ -197,6 +224,9 @@ static final class Tracker {
197224 this .epollTcpInfoClassName = epollTcpInfoClassName ;
198225 }
199226
227+ private static final Map <String , ChannelReflectionAccessor > accessorCache =
228+ new HashMap <>();
229+
200230 private static final long RECORD_INTERVAL_MILLIS ;
201231
202232 static {
@@ -261,43 +291,65 @@ void channelInactive(Channel channel) {
261291 metricRecorder .addLongUpDownCounter (metrics .connectionCount , -1 ,
262292 Collections .singletonList (target ), labelValues );
263293 // Final collection on close
264- recordTcpInfo (channel );
294+ recordTcpInfo (channel , true );
265295 }
266296 }
267297
268- private void recordTcpInfo (Channel channel ) {
298+ void recordTcpInfo (Channel channel ) {
299+ recordTcpInfo (channel , false );
300+ }
301+
302+ void recordTcpInfo (Channel channel , boolean isClosed ) {
269303 if (metricRecorder == null || target == null ) {
270304 return ;
271305 }
272306 java .util .List <String > labelValues = getLabelValues (channel );
273307 try {
274- if (channel .getClass ().getName ().equals (epollSocketChannelClassName )) {
275- Class <?> tcpInfoClass = Class .forName (epollTcpInfoClassName );
276- Method tcpInfoMethod = channel .getClass ().getMethod ("tcpInfo" , tcpInfoClass );
277- Object info = tcpInfoClass .getDeclaredConstructor ().newInstance ();
278- tcpInfoMethod .invoke (channel , info );
279-
280- Method totalRetransMethod = tcpInfoClass .getMethod ("totalRetrans" );
281- Method retransmitsMethod = tcpInfoClass .getMethod ("retransmits" );
282- Method rttMethod = tcpInfoClass .getMethod ("rtt" );
283-
284- long totalRetrans = (Long ) totalRetransMethod .invoke (info );
285- int retransmits = (Integer ) retransmitsMethod .invoke (info );
286- long rtt = (Long ) rttMethod .invoke (info );
287-
288- if (metrics .packetsRetransmitted != null ) {
289- metricRecorder .addLongCounter (metrics .packetsRetransmitted , totalRetrans ,
290- Collections .singletonList (target ), labelValues );
308+ if (channelReflectionAccessor == null ) {
309+ if (!channel .getClass ().getName ().equals (epollSocketChannelClassName )) {
310+ return ;
291311 }
292- if (metrics .recurringRetransmits != null ) {
293- metricRecorder .addLongCounter (metrics .recurringRetransmits , retransmits ,
294- Collections .singletonList (target ), labelValues );
312+ synchronized (accessorCache ) {
313+ channelReflectionAccessor = accessorCache .get (epollTcpInfoClassName );
314+ if (channelReflectionAccessor == null ) {
315+ Class <?> tcpInfoClass = Class .forName (epollTcpInfoClassName );
316+ Method tcpInfoMethod = channel .getClass ().getMethod ("tcpInfo" , tcpInfoClass );
317+ Constructor <?> tcpInfoConstructor = tcpInfoClass .getDeclaredConstructor ();
318+ Method totalRetransMethod = tcpInfoClass .getMethod ("totalRetrans" );
319+ Method retransmitsMethod = tcpInfoClass .getMethod ("retransmits" );
320+ Method rttMethod = tcpInfoClass .getMethod ("rtt" );
321+
322+ channelReflectionAccessor = new ChannelReflectionAccessor (
323+ tcpInfoMethod , tcpInfoConstructor , totalRetransMethod , retransmitsMethod ,
324+ rttMethod );
325+ accessorCache .put (epollTcpInfoClassName , channelReflectionAccessor );
326+ }
295327 }
296- if (metrics .minRtt != null ) {
297- metricRecorder .recordDoubleHistogram (metrics .minRtt ,
298- rtt / 1000000.0 , // Convert microseconds to seconds
328+ }
329+
330+ Object info = channelReflectionAccessor .tcpInfoConstructor .newInstance ();
331+ channelReflectionAccessor .tcpInfoMethod .invoke (channel , info );
332+
333+ long totalRetrans = (Long ) channelReflectionAccessor .totalRetransMethod .invoke (info );
334+ int retransmits = (Integer ) channelReflectionAccessor .retransmitsMethod .invoke (info );
335+ long rtt = (Long ) channelReflectionAccessor .rttMethod .invoke (info );
336+
337+ if (metrics .packetsRetransmitted != null ) {
338+ long delta = totalRetrans - lastTotalRetrans ;
339+ if (delta > 0 ) {
340+ metricRecorder .addLongCounter (metrics .packetsRetransmitted , delta ,
299341 Collections .singletonList (target ), labelValues );
300342 }
343+ lastTotalRetrans = totalRetrans ;
344+ }
345+ if (isClosed && metrics .recurringRetransmits != null ) {
346+ metricRecorder .addLongCounter (metrics .recurringRetransmits , retransmits ,
347+ Collections .singletonList (target ), labelValues );
348+ }
349+ if (metrics .minRtt != null ) {
350+ metricRecorder .recordDoubleHistogram (metrics .minRtt ,
351+ rtt / 1000000.0 , // Convert microseconds to seconds
352+ Collections .singletonList (target ), labelValues );
301353 }
302354 } catch (Throwable t ) {
303355 // Epoll not available or error getting tcp_info, just ignore.
0 commit comments