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}
This commit is contained in:
Victor Boivie 2023-08-31 19:51:21 +02:00 committed by WebRTC LUCI CQ
parent 905197174f
commit a7c6de9068
6 changed files with 83 additions and 11 deletions

View File

@ -211,6 +211,16 @@ struct Metrics {
// 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 @@ class DcSctpSocketInterface {
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

View File

@ -244,6 +244,7 @@ if (rtc_include_tests) {
"../../../test:test_support",
"../common:handover_testing",
"../common:internal_types",
"../common:math",
"../packet:chunk",
"../packet:error_cause",
"../packet:parameter",

View File

@ -598,6 +598,8 @@ absl::optional<Metrics> DcSctpSocket::GetMetrics() const {
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;
}

View File

@ -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 @@ TEST(DcSctpSocketTest, RxAndTxPacketMetricsIncrease) {
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");

View File

@ -426,18 +426,21 @@ RetransmissionQueue::GetChunksForFastRetransmit(size_t bytes_in_packet) {
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 @@ std::vector<std::pair<TSN, Data>> RetransmissionQueue::GetChunksToSend(
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) {
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));

View File

@ -113,6 +113,9 @@ class RetransmissionQueue {
// 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 @@ class RetransmissionQueue {
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;