1
#include "source/extensions/quic/connection_debug_visitor/quic_stats/quic_stats.h"
2

            
3
#include <memory>
4

            
5
#include "envoy/stream_info/stream_info.h"
6

            
7
#include "source/common/common/logger.h"
8
#include "source/common/quic/envoy_quic_connection_debug_visitor_factory_interface.h"
9

            
10
#include "quiche/quic/core/frames/quic_connection_close_frame.h"
11
#include "quiche/quic/core/quic_session.h"
12
#include "quiche/quic/core/quic_types.h"
13

            
14
namespace Envoy {
15
namespace Extensions {
16
namespace Quic {
17
namespace ConnectionDebugVisitors {
18
namespace QuicStats {
19

            
20
QuicStatsVisitor::QuicStatsVisitor(Config& config, Event::Dispatcher& dispatcher)
21
6
    : config_(config) {
22
6
  if (config_.update_period_.has_value()) {
23
5
    timer_ = dispatcher.createTimer([this]() {
24
5
      recordStats();
25
5
      timer_->enableTimer(config_.update_period_.value());
26
5
    });
27
3
    timer_->enableTimer(config_.update_period_.value());
28
3
  }
29
6
}
30

            
31
void QuicStatsVisitor::OnConnectionClosed(const quic::QuicConnectionCloseFrame&,
32
4
                                          quic::ConnectionCloseSource) {
33
4
  if (timer_ != nullptr) {
34
1
    timer_->disableTimer();
35
1
  }
36
4
  recordStats();
37
4
}
38

            
39
2
const quic::QuicConnectionStats& QuicStatsVisitorProd::getQuicStats() {
40
2
  return session_.connection()->GetStats();
41
2
}
42

            
43
9
void QuicStatsVisitor::recordStats() {
44
9
  const quic::QuicConnectionStats& quic_stats = getQuicStats();
45

            
46
54
  auto update_counter = [](Stats::Counter& counter, auto& last_value, auto current_value) {
47
54
    int64_t diff = static_cast<int64_t>(current_value) - static_cast<int64_t>(last_value);
48
54
    ASSERT(diff >= 0);
49
54
    if (diff > 0) {
50
25
      counter.add(diff);
51
25
    }
52
54
    last_value = current_value;
53
54
  };
54

            
55
  // This is before the update to `last_packets_sent_` and `last_packets_retransmitted_` because
56
  // they use the same metrics, and `update_counter` will update `last_...`, so this needs to use
57
  // those `last_...` values (and not update them) first.
58
  //
59
  // Don't record a value if the numerator is negative, or the denominator is zero or negative
60
  // (prevent divide-by-zero).
61
9
  if ((quic_stats.packets_sent > last_packets_sent_) &&
62
9
      (quic_stats.packets_retransmitted >= last_packets_retransmitted_)) {
63
7
    const uint64_t pkts_diff = quic_stats.packets_sent - last_packets_sent_;
64
7
    const uint64_t retrans_diff = quic_stats.packets_retransmitted - last_packets_retransmitted_;
65

            
66
    // The following math will not overflow unless the number of packets seen is well over 10
67
    // trillion. In that case, the value recorded in the histogram may be incorrect. This case is
68
    // left unhandled for performance, and it is extremely unlikely.
69
7
    constexpr uint64_t max_supported = UINT64_MAX / Stats::Histogram::PercentScale;
70
7
    constexpr uint64_t ten_trillion = (10ULL * 1000ULL * 1000ULL * 1000ULL * 1000ULL);
71
7
    static_assert(max_supported > ten_trillion);
72
7
    ASSERT(retrans_diff < max_supported);
73

            
74
7
    const uint64_t percent_retransmissions =
75
7
        (retrans_diff * static_cast<uint64_t>(Stats::Histogram::PercentScale)) / pkts_diff;
76
7
    config_.stats_.cx_tx_percent_retransmitted_packets_.recordValue(percent_retransmissions);
77
7
  }
78

            
79
9
  update_counter(config_.stats_.cx_tx_packets_total_, last_packets_sent_, quic_stats.packets_sent);
80
9
  update_counter(config_.stats_.cx_tx_packets_retransmitted_total_, last_packets_retransmitted_,
81
9
                 quic_stats.packets_retransmitted);
82
9
  update_counter(config_.stats_.cx_tx_amplification_throttling_total_,
83
9
                 last_num_amplification_throttling_, quic_stats.num_amplification_throttling);
84
9
  update_counter(config_.stats_.cx_rx_packets_total_, last_packets_received_,
85
9
                 quic_stats.packets_received);
86
9
  update_counter(config_.stats_.cx_path_degrading_total_, last_num_path_degrading_,
87
9
                 quic_stats.num_path_degrading);
88
9
  update_counter(config_.stats_.cx_forward_progress_after_path_degrading_total_,
89
9
                 last_num_forward_progress_after_path_degrading_,
90
9
                 quic_stats.num_forward_progress_after_path_degrading);
91

            
92
9
  if (quic_stats.srtt_us > 0) {
93
6
    config_.stats_.cx_rtt_us_.recordValue(quic_stats.srtt_us);
94
6
  }
95
9
  if (!quic_stats.estimated_bandwidth.IsZero() && !quic_stats.estimated_bandwidth.IsInfinite()) {
96
6
    config_.stats_.cx_tx_estimated_bandwidth_.recordValue(
97
6
        quic_stats.estimated_bandwidth.ToBytesPerPeriod(quic::QuicTime::Delta::FromSeconds(1)));
98
6
  }
99
9
  config_.stats_.cx_tx_mtu_.recordValue(quic_stats.egress_mtu);
100
9
  config_.stats_.cx_rx_mtu_.recordValue(quic_stats.ingress_mtu);
101
9
}
102

            
103
Config::Config(
104
    const envoy::extensions::quic::connection_debug_visitor::quic_stats::v3::Config& config,
105
    Stats::Scope& scope)
106
5
    : update_period_(PROTOBUF_GET_OPTIONAL_MS(config, update_period)),
107
5
      stats_(generateStats(scope)) {}
108

            
109
5
QuicStats Config::generateStats(Stats::Scope& scope) {
110
5
  constexpr absl::string_view prefix("quic_stats");
111
5
  return QuicStats{ALL_QUIC_STATS(POOL_COUNTER_PREFIX(scope, prefix),
112
5
                                  POOL_GAUGE_PREFIX(scope, prefix),
113
5
                                  POOL_HISTOGRAM_PREFIX(scope, prefix))};
114
5
}
115

            
116
std::unique_ptr<quic::QuicConnectionDebugVisitor>
117
Config::createQuicConnectionDebugVisitor(Event::Dispatcher& dispatcher, quic::QuicSession& session,
118
2
                                         const StreamInfo::StreamInfo&) {
119
2
  return std::make_unique<QuicStatsVisitorProd>(*this, dispatcher, session);
120
2
}
121

            
122
Envoy::Quic::EnvoyQuicConnectionDebugVisitorFactoryInterfacePtr
123
QuicStatsFactoryFactory::createFactory(
124
    const Protobuf::Message& config,
125
2
    Server::Configuration::ListenerFactoryContext& server_context) {
126
2
  return std::make_unique<Config>(
127
2
      dynamic_cast<
128
2
          const envoy::extensions::quic::connection_debug_visitor::quic_stats::v3::Config&>(config),
129
2
      server_context.listenerScope());
130
2
}
131

            
132
REGISTER_FACTORY(QuicStatsFactoryFactory,
133
                 Envoy::Quic::EnvoyQuicConnectionDebugVisitorFactoryFactoryInterface);
134

            
135
} // namespace QuicStats
136
} // namespace ConnectionDebugVisitors
137
} // namespace Quic
138
} // namespace Extensions
139
} // namespace Envoy