"""Counts what aiortc's SCTP sender does not, applied on import (aiortc 1.15). aiortc halves its congestion window on every loss and drops it to one packet when the T3 timer expires, and keeps no record of either. Those two counts are what say whether a slow transfer was held back by the path: a window that never grows past a few hundred KB is losses, and the node could not tell. A loss is counted when the sender enters fast recovery, so a burst of packets dropped together is one loss, which is also one halving of the window. """ from aiortc.rtcsctptransport import RTCSctpTransport _receive_sack_chunk = RTCSctpTransport._receive_sack_chunk _t3_expired = RTCSctpTransport._t3_expired async def _counting_receive_sack_chunk(self, chunk) -> None: recovering = self._fast_recovery_exit is not None await _receive_sack_chunk(self, chunk) if not recovering and self._fast_recovery_exit is not None: self._mb_losses = getattr(self, "_mb_losses", 0) + 1 def _counting_t3_expired(self) -> None: self._mb_t3 = getattr(self, "_mb_t3", 0) + 1 _t3_expired(self) RTCSctpTransport._receive_sack_chunk = _counting_receive_sack_chunk RTCSctpTransport._t3_expired = _counting_t3_expired def describe(pc) -> str: """The association of `pc` as a log suffix, or nothing when it has none.""" reported = getattr(pc, "sctp_line", None) if reported is not None: # held by a connection process (workers.py) return reported sctp = getattr(pc, "sctp", None) if sctp is None: return "" srtt = getattr(sctp, "_srtt", None) rtt = f"{srtt * 1000:.0f} ms" if srtt else "?" return (f" sctp cwnd={sctp._cwnd // 1024} KB rtt={rtt}" f" loss={getattr(sctp, '_mb_losses', 0)}" f" t3={getattr(sctp, '_mb_t3', 0)}")