@@ -34,6 +34,10 @@ final class TcpMetrics {
3434 static final LongCounterMetricInstrument recurringRetransmits ;
3535 static final DoubleHistogramMetricInstrument minRtt ;
3636
37+ // Note: Metrics like delivery_rate, bytes_sent, packets_sent,
38+ // bytes_retransmitted, etc., are not
39+ // currently exposed by Netty's EpollTcpInfo.java wrapper around
40+ // getSockOpt(TCP_INFO)."
3741 static {
3842 MetricInstrumentRegistry registry = MetricInstrumentRegistry .getDefaultRegistry ();
3943 ImmutableList <String > requiredLabels = ImmutableList .of ("grpc.target" );
@@ -104,48 +108,105 @@ static final class Tracker {
104108 this .target = target ;
105109 }
106110
111+ private static final long RECORD_INTERVAL_MILLIS ;
112+
113+ static {
114+ long interval = 5 ;
115+ try {
116+ String flagValue = System .getProperty ("io.grpc.netty.tcpMetricsRecordIntervalMinutes" );
117+ if (flagValue != null ) {
118+ interval = Long .parseLong (flagValue );
119+ }
120+ } catch (NumberFormatException e ) {
121+ // Use default
122+ }
123+ RECORD_INTERVAL_MILLIS = java .util .concurrent .TimeUnit .MINUTES .toMillis (interval );
124+ }
125+
126+ private static final java .util .Random RANDOM = new java .util .Random ();
127+ private io .netty .util .concurrent .ScheduledFuture <?> reportTimer ;
128+
107129 void channelActive (Channel channel ) {
108130 if (metricRecorder != null && target != null ) {
109131 java .util .List <String > labelValues = getLabelValues (channel );
110132 metricRecorder .addLongCounter (TcpMetrics .connectionsCreated , 1 ,
111133 Collections .singletonList (target ), labelValues );
112134 metricRecorder .addLongUpDownCounter (TcpMetrics .connectionCount , 1 ,
113135 Collections .singletonList (target ), labelValues );
136+ scheduleNextReport (channel );
137+ }
138+ }
139+
140+ private void scheduleNextReport (final Channel channel ) {
141+ if (RECORD_INTERVAL_MILLIS <= 0 ) {
142+ return ;
143+ }
144+ if (!channel .isActive ()) {
145+ return ;
146+ }
147+
148+ double jitter = 0.1 + RANDOM .nextDouble (); // 10% to 110%
149+ long delayMillis = (long ) (RECORD_INTERVAL_MILLIS * jitter );
150+
151+ try {
152+ reportTimer = channel .eventLoop ().schedule (new Runnable () {
153+ @ Override
154+ public void run () {
155+ if (channel .isActive ()) {
156+ Tracker .this .recordTcpInfo (channel ); // Renamed from channelInactive to recordTcpInfo
157+ scheduleNextReport (channel ); // Re-arm
158+ }
159+ }
160+ }, delayMillis , java .util .concurrent .TimeUnit .MILLISECONDS );
161+ } catch (Throwable t ) {
162+ // Channel closed, event loop shut down, etc.
114163 }
115164 }
116165
117166 void channelInactive (Channel channel ) {
167+ if (reportTimer != null ) {
168+ reportTimer .cancel (false );
169+ }
118170 if (metricRecorder != null && target != null ) {
119171 java .util .List <String > labelValues = getLabelValues (channel );
120172 metricRecorder .addLongUpDownCounter (TcpMetrics .connectionCount , -1 ,
121173 Collections .singletonList (target ), labelValues );
122-
123- try {
124- if (channel .getClass ().getName ().equals ("io.netty.channel.epoll.EpollSocketChannel" )) {
125- Method tcpInfoMethod = channel .getClass ().getMethod ("tcpInfo" ,
126- Class .forName ("io.netty.channel.epoll.EpollTcpInfo" ));
127- Object info = Class .forName ("io.netty.channel.epoll.EpollTcpInfo" )
128- .getDeclaredConstructor ().newInstance ();
129- tcpInfoMethod .invoke (channel , info );
130-
131- Method totalRetransMethod = info .getClass ().getMethod ("totalRetrans" );
132- Method retransmitsMethod = info .getClass ().getMethod ("retransmits" );
133- Method rttMethod = info .getClass ().getMethod ("rtt" );
134-
135- long totalRetrans = (Long ) totalRetransMethod .invoke (info );
136- long retransmits = (Long ) retransmitsMethod .invoke (info );
137- long rtt = ((Number ) rttMethod .invoke (info )).longValue ();
138-
139- metricRecorder .addLongCounter (TcpMetrics .packetsRetransmitted , totalRetrans ,
140- Collections .singletonList (target ), labelValues );
141- metricRecorder .addLongCounter (TcpMetrics .recurringRetransmits , retransmits ,
142- Collections .singletonList (target ), labelValues );
143- metricRecorder .recordDoubleHistogram (TcpMetrics .minRtt , rtt / 1000000.0 ,
144- Collections .singletonList (target ), labelValues );
145- }
146- } catch (Throwable t ) {
147- // Epoll not available or error getting tcp_info, just ignore.
174+ // Final collection on close
175+ recordTcpInfo (channel );
176+ }
177+ }
178+
179+ private void recordTcpInfo (Channel channel ) {
180+ if (metricRecorder == null || target == null ) {
181+ return ;
182+ }
183+ java .util .List <String > labelValues = getLabelValues (channel );
184+ try {
185+ if (channel .getClass ().getName ().equals ("io.netty.channel.epoll.EpollSocketChannel" )) {
186+ Method tcpInfoMethod = channel .getClass ().getMethod ("tcpInfo" ,
187+ Class .forName ("io.netty.channel.epoll.EpollTcpInfo" ));
188+ Object info = Class .forName ("io.netty.channel.epoll.EpollTcpInfo" )
189+ .getDeclaredConstructor ().newInstance ();
190+ tcpInfoMethod .invoke (channel , info );
191+
192+ Method totalRetransMethod = info .getClass ().getMethod ("totalRetrans" );
193+ Method retransmitsMethod = info .getClass ().getMethod ("retransmits" );
194+ Method rttMethod = info .getClass ().getMethod ("rtt" );
195+
196+ long totalRetrans = (Long ) totalRetransMethod .invoke (info );
197+ int retransmits = (Integer ) retransmitsMethod .invoke (info );
198+ long rtt = (Long ) rttMethod .invoke (info );
199+
200+ metricRecorder .addLongCounter (TcpMetrics .packetsRetransmitted , totalRetrans ,
201+ Collections .singletonList (target ), labelValues );
202+ metricRecorder .addLongCounter (TcpMetrics .recurringRetransmits , retransmits ,
203+ Collections .singletonList (target ), labelValues );
204+ metricRecorder .recordDoubleHistogram (TcpMetrics .minRtt ,
205+ rtt / 1000000.0 , // Convert microseconds to seconds
206+ Collections .singletonList (target ), labelValues );
148207 }
208+ } catch (Throwable t ) {
209+ // Epoll not available or error getting tcp_info, just ignore.
149210 }
150211 }
151212 }
0 commit comments