22// SPDX-License-Identifier: Apache-2.0
33
44use crate :: time:: Timestamp ;
5- use core:: { cmp:: max, time:: Duration } ;
5+ use core:: {
6+ cmp:: { max, Ordering } ,
7+ time:: Duration ,
8+ } ;
69use num_rational:: Ratio ;
10+ use num_traits:: Inv ;
711
812#[ derive( Clone , Copy , Debug , PartialEq , Eq , Hash ) ]
913/// Bandwidth-related data tracked for each sent packet
@@ -26,45 +30,73 @@ pub struct PacketInfo {
2630 pub is_app_limited : bool ,
2731}
2832
29- const MICRO_BITS_PER_BYTE : u64 = 8 * 1000000 ;
30-
31- #[ derive( Copy , Clone , Debug , Default , Eq , Ord , PartialOrd , PartialEq ) ]
33+ /// Represents a rate at which data is transferred
34+ ///
35+ /// While bandwidth is typically thought of as an amount of data over a fixed
36+ /// amount of time (bytes per second, for example), in this case we internally
37+ /// represent bandwidth as the inverse: an amount of time to send a fixed amount
38+ /// of data (nanoseconds per byte, in this case). This allows for some of the
39+ /// math operations needed on `Bandwidth` to avoid division, while reducing the
40+ /// likelihood of panicking due to overflow.
41+ ///
42+ /// The maximum (non-infinite) value that can be represented is 1 GB/second.
43+ #[ derive( Copy , Clone , Debug , Eq , PartialEq ) ]
3244pub struct Bandwidth {
33- bits_per_second : u64 ,
45+ nanos_per_byte : u64 ,
3446}
3547
3648impl Bandwidth {
37- pub const ZERO : Bandwidth = Bandwidth { bits_per_second : 0 } ;
38-
39- pub const MAX : Bandwidth = Bandwidth {
40- bits_per_second : u64:: MAX ,
49+ pub const ZERO : Bandwidth = Bandwidth {
50+ nanos_per_byte : u64:: MAX ,
4151 } ;
4252
53+ pub const INFINITY : Bandwidth = Bandwidth { nanos_per_byte : 0 } ;
54+
4355 /// Constructs a new `Bandwidth` with the given bytes per interval
4456 pub const fn new ( bytes : u64 , interval : Duration ) -> Self {
45- if interval. is_zero ( ) {
57+ if interval. is_zero ( ) || bytes == 0 {
4658 Bandwidth :: ZERO
4759 } else {
4860 Self {
49- // Prefer multiplying by MICRO_BITS_PER_BYTE first to avoid losing resolution
50- bits_per_second : match bytes. checked_mul ( MICRO_BITS_PER_BYTE ) {
51- Some ( micro_bits) => micro_bits / interval. as_micros ( ) as u64 ,
52- None => {
53- // If that overflows, divide first by the interval
54- ( bytes / interval. as_micros ( ) as u64 ) . saturating_mul ( MICRO_BITS_PER_BYTE )
55- }
56- } ,
61+ nanos_per_byte : interval. as_nanos ( ) as u64 / bytes,
5762 }
5863 }
5964 }
6065}
6166
67+ impl Default for Bandwidth {
68+ fn default ( ) -> Self {
69+ Bandwidth :: ZERO
70+ }
71+ }
72+
73+ impl core:: cmp:: PartialOrd for Bandwidth {
74+ fn partial_cmp ( & self , other : & Self ) -> Option < Ordering > {
75+ Some ( self . cmp ( other) )
76+ }
77+ }
78+
79+ impl core:: cmp:: Ord for Bandwidth {
80+ fn cmp ( & self , other : & Self ) -> Ordering {
81+ // The higher the nano_per_byte, the lower the bandwidth,
82+ // so reverse the ordering when comparing
83+ self . nanos_per_byte . cmp ( & other. nanos_per_byte ) . reverse ( )
84+ }
85+ }
86+
6287impl core:: ops:: Mul < Ratio < u64 > > for Bandwidth {
6388 type Output = Bandwidth ;
6489
6590 fn mul ( self , rhs : Ratio < u64 > ) -> Self :: Output {
91+ if self == Bandwidth :: ZERO {
92+ return Bandwidth :: ZERO ;
93+ }
94+
6695 Bandwidth {
67- bits_per_second : ( rhs * self . bits_per_second ) . to_integer ( ) ,
96+ // Since `Bandwidth` is represented as time/byte and not bytes/time, we should divide
97+ // by the given ratio to result in a higher nanos_per_byte value (lower bandwidth).
98+ // To avoid division, we can multiply by the inverse of the ratio instead
99+ nanos_per_byte : ( rhs. inv ( ) * self . nanos_per_byte ) . to_integer ( ) ,
68100 }
69101 }
70102}
@@ -73,14 +105,10 @@ impl core::ops::Mul<Duration> for Bandwidth {
73105 type Output = u64 ;
74106
75107 fn mul ( self , rhs : Duration ) -> Self :: Output {
76- // Prefer multiplying by the duration first to avoid losing resolution
77- match self . bits_per_second . checked_mul ( rhs. as_micros ( ) as u64 ) {
78- Some ( micro_bits) => micro_bits / MICRO_BITS_PER_BYTE ,
79- None => {
80- // If that overflows, divide first by MICRO_BITS_PER_BYTE
81- ( self . bits_per_second / MICRO_BITS_PER_BYTE ) . saturating_mul ( rhs. as_micros ( ) as u64 )
82- }
108+ if self == Bandwidth :: INFINITY {
109+ return u64:: MAX ;
83110 }
111+ rhs. as_nanos ( ) as u64 / self . nanos_per_byte
84112 }
85113}
86114
@@ -94,7 +122,7 @@ impl core::ops::Div<Bandwidth> for u64 {
94122 type Output = Duration ;
95123
96124 fn div ( self , rhs : Bandwidth ) -> Self :: Output {
97- Duration :: from_micros ( self * MICRO_BITS_PER_BYTE / rhs. bits_per_second )
125+ Duration :: from_nanos ( rhs. nanos_per_byte . saturating_mul ( self ) )
98126 }
99127}
100128
0 commit comments