Line data Source code
1 : #if defined(__linux__) 2 : 3 : // `struct tcp_info` is defined in two places: /usr/include/netinet/tcp.h (included from 4 : // envoy/common/platform.h) and /usr/include/linux/tcp.h. The former version is older and doesn't 5 : // contain all the fields needed. Including both headers results in a compilation error due to the 6 : // duplicate (and different) definitions of `struct tcp_info`. To work around this, define 7 : // `DO_NOT_INCLUDE_NETINET_TCP_H` to prevent inclusion of the wrong version. 8 : #define DO_NOT_INCLUDE_NETINET_TCP_H 1 9 : 10 : #include "source/extensions/transport_sockets/tcp_stats/tcp_stats.h" 11 : 12 : #include <linux/tcp.h> 13 : 14 : #include "envoy/buffer/buffer.h" 15 : #include "envoy/network/connection.h" 16 : 17 : #include "source/common/common/assert.h" 18 : #include "source/common/protobuf/utility.h" 19 : 20 : namespace Envoy { 21 : namespace Extensions { 22 : namespace TransportSockets { 23 : namespace TcpStats { 24 : 25 : Config::Config(const envoy::extensions::transport_sockets::tcp_stats::v3::Config& config_proto, 26 : Stats::Scope& scope) 27 : : stats_(generateStats(scope)), 28 0 : update_period_(PROTOBUF_GET_OPTIONAL_MS(config_proto, update_period)) {} 29 : 30 0 : TcpStats Config::generateStats(Stats::Scope& scope) { 31 0 : const std::string prefix("tcp_stats"); 32 0 : return TcpStats{ALL_TCP_STATS(POOL_COUNTER_PREFIX(scope, prefix), 33 0 : POOL_GAUGE_PREFIX(scope, prefix), 34 0 : POOL_HISTOGRAM_PREFIX(scope, prefix))}; 35 0 : } 36 : 37 : TcpStatsSocket::TcpStatsSocket(ConfigConstSharedPtr config, 38 : Network::TransportSocketPtr inner_socket) 39 0 : : PassthroughSocket(std::move(inner_socket)), config_(std::move(config)) {} 40 : 41 0 : void TcpStatsSocket::setTransportSocketCallbacks(Network::TransportSocketCallbacks& callbacks) { 42 0 : callbacks_ = &callbacks; 43 0 : transport_socket_->setTransportSocketCallbacks(callbacks); 44 0 : } 45 : 46 0 : void TcpStatsSocket::onConnected() { 47 0 : if (config_->update_period_.has_value()) { 48 0 : timer_ = callbacks_->connection().dispatcher().createTimer([this]() { 49 0 : recordStats(); 50 0 : timer_->enableTimer(config_->update_period_.value()); 51 0 : }); 52 0 : timer_->enableTimer(config_->update_period_.value()); 53 0 : } 54 : 55 0 : transport_socket_->onConnected(); 56 0 : } 57 : 58 0 : void TcpStatsSocket::closeSocket(Network::ConnectionEvent event) { 59 : // Record final values. 60 0 : recordStats(); 61 : 62 : // Ensure gauges are zero'd out at the end of a connection no matter what the OS told us. 63 0 : if (last_cx_tx_unsent_bytes_ > 0) { 64 0 : config_->stats_.cx_tx_unsent_bytes_.sub(last_cx_tx_unsent_bytes_); 65 0 : } 66 0 : if (last_cx_tx_unacked_segments_ > 0) { 67 0 : config_->stats_.cx_tx_unacked_segments_.sub(last_cx_tx_unacked_segments_); 68 0 : } 69 : 70 0 : if (timer_ != nullptr) { 71 0 : timer_->disableTimer(); 72 0 : } 73 : 74 0 : transport_socket_->closeSocket(event); 75 0 : } 76 : 77 0 : absl::optional<struct tcp_info> TcpStatsSocket::querySocketInfo() { 78 0 : struct tcp_info info; 79 0 : memset(&info, 0, sizeof(info)); 80 0 : socklen_t optlen = sizeof(info); 81 0 : const auto result = callbacks_->ioHandle().getOption(IPPROTO_TCP, TCP_INFO, &info, &optlen); 82 0 : if (result.return_value_ != 0) { 83 0 : ENVOY_LOG(debug, "Failed getsockopt(IPPROTO_TCP, TCP_INFO): rc {} errno {} optlen {}", 84 0 : result.return_value_, result.errno_, optlen); 85 0 : return absl::nullopt; 86 0 : } else { 87 0 : return info; 88 0 : } 89 0 : } 90 : 91 0 : void TcpStatsSocket::recordStats() { 92 0 : absl::optional<struct tcp_info> tcp_info = querySocketInfo(); 93 0 : if (!tcp_info.has_value()) { 94 0 : return; 95 0 : } 96 : 97 0 : auto update_counter = [](Stats::Counter& counter, auto& last_value, auto current_value) { 98 0 : int64_t diff = static_cast<int64_t>(current_value) - static_cast<int64_t>(last_value); 99 0 : ASSERT(diff >= 0); 100 0 : if (diff > 0) { 101 0 : counter.add(diff); 102 0 : } 103 0 : last_value = current_value; 104 0 : }; 105 : 106 0 : auto update_gauge = [](Stats::Gauge& gauge, auto& last_value, auto current_value) { 107 0 : static_assert(sizeof(last_value) == sizeof(current_value)); 108 0 : int64_t diff = static_cast<int64_t>(current_value) - static_cast<int64_t>(last_value); 109 0 : gauge.add(diff); 110 0 : last_value = current_value; 111 0 : }; 112 : 113 : // This is before the update to `cx_tx_data_segments_` and `cx_tx_retransmitted_segments_` because 114 : // they use the same metrics, and `update_counter` will update `last_...`, so this needs to use 115 : // those `last_...` values (and not update them) first. 116 : // 117 : // Don't record a value if the numerator is negative, or the denominator is zero or negative 118 : // (prevent divide-by-zero). 119 0 : if ((tcp_info->tcpi_data_segs_out > last_cx_tx_data_segments_) && 120 0 : (tcp_info->tcpi_total_retrans >= last_cx_tx_retransmitted_segments_)) { 121 : // uint32 * uint32 cannot overflow a uint64, so this can safely be done as integer math 122 : // instead of floating point. 123 0 : static_assert((sizeof(tcp_info->tcpi_total_retrans) == sizeof(uint32_t)) && 124 0 : (Stats::Histogram::PercentScale < UINT32_MAX)); 125 : 126 0 : const uint32_t data_segs_out_diff = tcp_info->tcpi_data_segs_out - last_cx_tx_data_segments_; 127 0 : const uint32_t retransmitted_segs_diff = 128 0 : tcp_info->tcpi_total_retrans - last_cx_tx_retransmitted_segments_; 129 0 : const uint64_t percent_retransmissions = 130 0 : (static_cast<uint64_t>(retransmitted_segs_diff) * 131 0 : static_cast<uint64_t>(Stats::Histogram::PercentScale)) / 132 0 : static_cast<uint64_t>(data_segs_out_diff); 133 0 : config_->stats_.cx_tx_percent_retransmitted_segments_.recordValue(percent_retransmissions); 134 0 : } 135 : 136 0 : update_counter(config_->stats_.cx_tx_segments_, last_cx_tx_segments_, tcp_info->tcpi_segs_out); 137 0 : update_counter(config_->stats_.cx_rx_segments_, last_cx_rx_segments_, tcp_info->tcpi_segs_in); 138 0 : update_counter(config_->stats_.cx_tx_data_segments_, last_cx_tx_data_segments_, 139 0 : tcp_info->tcpi_data_segs_out); 140 0 : update_counter(config_->stats_.cx_rx_data_segments_, last_cx_rx_data_segments_, 141 0 : tcp_info->tcpi_data_segs_in); 142 0 : update_counter(config_->stats_.cx_tx_retransmitted_segments_, last_cx_tx_retransmitted_segments_, 143 0 : tcp_info->tcpi_total_retrans); 144 0 : update_counter(config_->stats_.cx_rx_bytes_received_, last_cx_rx_bytes_received_, 145 0 : tcp_info->tcpi_bytes_received); 146 0 : update_counter(config_->stats_.cx_tx_bytes_sent_, last_cx_tx_bytes_sent_, 147 0 : tcp_info->tcpi_bytes_sent); 148 : 149 0 : update_gauge(config_->stats_.cx_tx_unsent_bytes_, last_cx_tx_unsent_bytes_, 150 0 : tcp_info->tcpi_notsent_bytes); 151 0 : update_gauge(config_->stats_.cx_tx_unacked_segments_, last_cx_tx_unacked_segments_, 152 0 : tcp_info->tcpi_unacked); 153 : 154 0 : config_->stats_.cx_rtt_us_.recordValue(tcp_info->tcpi_rtt); 155 0 : config_->stats_.cx_rtt_variance_us_.recordValue(tcp_info->tcpi_rttvar); 156 0 : } 157 : 158 : } // namespace TcpStats 159 : } // namespace TransportSockets 160 : } // namespace Extensions 161 : } // namespace Envoy 162 : #endif // defined(__linux__)