dcsctp: Add retransmission counters to metrics
Bug: webrtc:15458
Change-Id: Ib90cb0b9a94e1f358685ed319538654b0c8ed5c4
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/318581
Reviewed-by: Florent Castelli <orphis@webrtc.org>
Commit-Queue: Victor Boivie <boivie@webrtc.org>
Cr-Commit-Position: refs/heads/main@{#40683}
diff --git a/net/dcsctp/public/dcsctp_socket.h b/net/dcsctp/public/dcsctp_socket.h
index 2df6a2c..9fda56a 100644
--- a/net/dcsctp/public/dcsctp_socket.h
+++ b/net/dcsctp/public/dcsctp_socket.h
@@ -211,6 +211,16 @@
// Number of messages requested to be sent.
size_t tx_messages_count = 0;
+ // Number of packets retransmitted. Since SCTP packets can contain both
+ // retransmitted DATA chunks and DATA chunks that are transmitted for the
+ // first time, this represents an upper bound as it's incremented every time a
+ // packet contains a retransmitted DATA chunk.
+ size_t rtx_packets_count = 0;
+
+ // Total number of bytes retransmitted. This includes the payload and
+ // DATA/I-DATA headers, but not SCTP packet headers.
+ uint64_t rtx_bytes_count = 0;
+
// The current congestion window (cwnd) in bytes, corresponding to spinfo_cwnd
// defined in RFC6458.
size_t cwnd_bytes = 0;
@@ -583,7 +593,9 @@
size_t bytes) = 0;
// Retrieves the latest metrics. If the socket is not fully connected,
- // `absl::nullopt` will be returned.
+ // `absl::nullopt` will be returned. Note that metrics are not guaranteed to
+ // be carried over if this socket is handed over by calling
+ // `GetHandoverStateAndClose`.
virtual absl::optional<Metrics> GetMetrics() const = 0;
// Returns empty bitmask if the socket is in the state in which a snapshot of
diff --git a/net/dcsctp/socket/BUILD.gn b/net/dcsctp/socket/BUILD.gn
index aeb157b..681ddd4 100644
--- a/net/dcsctp/socket/BUILD.gn
+++ b/net/dcsctp/socket/BUILD.gn
@@ -244,6 +244,7 @@
"../../../test:test_support",
"../common:handover_testing",
"../common:internal_types",
+ "../common:math",
"../packet:chunk",
"../packet:error_cause",
"../packet:parameter",
diff --git a/net/dcsctp/socket/dcsctp_socket.cc b/net/dcsctp/socket/dcsctp_socket.cc
index 139ec280..712fcea 100644
--- a/net/dcsctp/socket/dcsctp_socket.cc
+++ b/net/dcsctp/socket/dcsctp_socket.cc
@@ -598,6 +598,8 @@
tcb_->capabilities().negotiated_maximum_incoming_streams;
metrics.negotiated_maximum_incoming_streams =
tcb_->capabilities().negotiated_maximum_incoming_streams;
+ metrics.rtx_packets_count = tcb_->retransmission_queue().rtx_packets_count();
+ metrics.rtx_bytes_count = tcb_->retransmission_queue().rtx_bytes_count();
return metrics;
}
diff --git a/net/dcsctp/socket/dcsctp_socket_test.cc b/net/dcsctp/socket/dcsctp_socket_test.cc
index 0da893d..c31c048 100644
--- a/net/dcsctp/socket/dcsctp_socket_test.cc
+++ b/net/dcsctp/socket/dcsctp_socket_test.cc
@@ -22,6 +22,7 @@
#include "absl/types/optional.h"
#include "api/array_view.h"
#include "net/dcsctp/common/handover_testing.h"
+#include "net/dcsctp/common/math.h"
#include "net/dcsctp/packet/chunk/chunk.h"
#include "net/dcsctp/packet/chunk/cookie_echo_chunk.h"
#include "net/dcsctp/packet/chunk/data_chunk.h"
@@ -2032,6 +2033,44 @@
EXPECT_EQ(a.socket.GetMetrics()->peer_rwnd_bytes, initial_a_rwnd);
}
+TEST(DcSctpSocketTest, RetransmissionMetricsAreSetForFastRetransmit) {
+ SocketUnderTest a("A");
+ SocketUnderTest z("Z");
+ ConnectSockets(a, z);
+
+ // Enough to trigger fast retransmit of the missing second packet.
+ std::vector<uint8_t> payload(DcSctpOptions::kMaxSafeMTUSize * 5);
+ a.socket.Send(DcSctpMessage(StreamID(1), PPID(53), payload), kSendOptions);
+
+ // Receive first packet, drop second, receive and retransmit the remaining.
+ z.socket.ReceivePacket(a.cb.ConsumeSentPacket());
+ a.cb.ConsumeSentPacket();
+ ExchangeMessages(a, z);
+
+ EXPECT_EQ(a.socket.GetMetrics()->rtx_packets_count, 1u);
+ size_t expected_data_size =
+ RoundDownTo4(DcSctpOptions::kMaxSafeMTUSize - SctpPacket::kHeaderSize);
+ EXPECT_EQ(a.socket.GetMetrics()->rtx_bytes_count, expected_data_size);
+}
+
+TEST(DcSctpSocketTest, RetransmissionMetricsAreSetForNormalRetransmit) {
+ SocketUnderTest a("A");
+ SocketUnderTest z("Z");
+ ConnectSockets(a, z);
+
+ std::vector<uint8_t> payload(kSmallMessageSize);
+ a.socket.Send(DcSctpMessage(StreamID(1), PPID(53), payload), kSendOptions);
+
+ a.cb.ConsumeSentPacket();
+ AdvanceTime(a, z, a.options.rto_initial);
+ ExchangeMessages(a, z);
+
+ EXPECT_EQ(a.socket.GetMetrics()->rtx_packets_count, 1u);
+ size_t expected_data_size =
+ RoundUpTo4(kSmallMessageSize + DataChunk::kHeaderSize);
+ EXPECT_EQ(a.socket.GetMetrics()->rtx_bytes_count, expected_data_size);
+}
+
TEST_P(DcSctpSocketParametrizedTest, UnackDataAlsoIncludesSendQueue) {
SocketUnderTest a("A");
auto z = std::make_unique<SocketUnderTest>("Z");
diff --git a/net/dcsctp/tx/retransmission_queue.cc b/net/dcsctp/tx/retransmission_queue.cc
index 05bf6da..93084cc 100644
--- a/net/dcsctp/tx/retransmission_queue.cc
+++ b/net/dcsctp/tx/retransmission_queue.cc
@@ -426,18 +426,21 @@
if (!t3_rtx_.is_running()) {
t3_rtx_.Start();
}
+
+ size_t bytes_retransmitted = absl::c_accumulate(
+ to_be_sent, 0, [&](size_t r, const std::pair<TSN, Data>& d) {
+ return r + GetSerializedChunkSize(d.second);
+ });
+ ++rtx_packets_count_;
+ rtx_bytes_count_ += bytes_retransmitted;
+
RTC_DLOG(LS_VERBOSE) << log_prefix_ << "Fast-retransmitting TSN "
<< StrJoin(to_be_sent, ",",
[&](rtc::StringBuilder& sb,
const std::pair<TSN, Data>& c) {
sb << *c.first;
})
- << " - "
- << absl::c_accumulate(
- to_be_sent, 0,
- [&](size_t r, const std::pair<TSN, Data>& d) {
- return r + GetSerializedChunkSize(d.second);
- })
+ << " - " << bytes_retransmitted
<< " bytes. outstanding_bytes=" << outstanding_bytes()
<< " (" << old_outstanding_bytes << ")";
@@ -463,10 +466,17 @@
RoundDownTo4(std::min(max_bytes_to_send(), bytes_remaining_in_packet));
to_be_sent = outstanding_data_.GetChunksToBeRetransmitted(max_bytes);
- max_bytes -= absl::c_accumulate(to_be_sent, 0,
- [&](size_t r, const std::pair<TSN, Data>& d) {
- return r + GetSerializedChunkSize(d.second);
- });
+
+ size_t bytes_retransmitted = absl::c_accumulate(
+ to_be_sent, 0, [&](size_t r, const std::pair<TSN, Data>& d) {
+ return r + GetSerializedChunkSize(d.second);
+ });
+ max_bytes -= bytes_retransmitted;
+
+ if (!to_be_sent.empty()) {
+ ++rtx_packets_count_;
+ rtx_bytes_count_ += bytes_retransmitted;
+ }
while (max_bytes > data_chunk_header_size_) {
RTC_DCHECK(IsDivisibleBy4(max_bytes));
diff --git a/net/dcsctp/tx/retransmission_queue.h b/net/dcsctp/tx/retransmission_queue.h
index b4350e9..51e9c5b 100644
--- a/net/dcsctp/tx/retransmission_queue.h
+++ b/net/dcsctp/tx/retransmission_queue.h
@@ -113,6 +113,9 @@
// Returns the current receiver window size.
size_t rwnd() const { return rwnd_; }
+ size_t rtx_packets_count() const { return rtx_packets_count_; }
+ uint64_t rtx_bytes_count() const { return rtx_bytes_count_; }
+
// Returns the number of bytes of packets that are in-flight.
size_t outstanding_bytes() const {
return outstanding_data_.outstanding_bytes();
@@ -241,6 +244,11 @@
size_t ssthresh_;
// Partial Bytes Acked. See RFC4960.
size_t partial_bytes_acked_;
+
+ // See `dcsctp::Metrics`.
+ size_t rtx_packets_count_ = 0;
+ uint64_t rtx_bytes_count_ = 0;
+
// If set, fast recovery is enabled until this TSN has been cumulative
// acked.
absl::optional<UnwrappedTSN> fast_recovery_exit_tsn_ = absl::nullopt;