twilight_gateway/
latency.rs1use std::time::{Duration, Instant};
4
5#[derive(Clone, Debug)]
16pub struct Latency {
17 #[allow(clippy::struct_field_names)]
19 latency_sum: Duration,
20 periods: u32,
22 received: Option<Instant>,
24 recent: [Duration; Self::RECENT_LEN],
26 sent: Option<Instant>,
28}
29
30impl Latency {
31 const RECENT_LEN: usize = 5;
33
34 pub(crate) const fn new() -> Self {
36 Self {
37 latency_sum: Duration::ZERO,
38 periods: 0,
39 received: None,
40 recent: [Duration::MAX; Self::RECENT_LEN],
41 sent: None,
42 }
43 }
44
45 pub const fn average(&self) -> Option<Duration> {
52 self.latency_sum.checked_div(self.periods)
53 }
54
55 pub const fn periods(&self) -> u32 {
57 self.periods
58 }
59
60 pub fn recent(&self) -> &[Duration] {
62 let maybe_zero_idx = self
66 .recent
67 .iter()
68 .position(|duration| *duration == Duration::MAX);
69
70 &self.recent[0..maybe_zero_idx.unwrap_or(Self::RECENT_LEN)]
71 }
72
73 pub const fn received(&self) -> Option<Instant> {
75 self.received
76 }
77
78 pub const fn sent(&self) -> Option<Instant> {
80 self.sent
81 }
82
83 #[track_caller]
95 pub(crate) fn record_received(&mut self) {
96 debug_assert!(self.received.is_none(), "period completed multiple times");
97
98 let now = Instant::now();
99 let period_latency = now - self.sent.expect("period has not begun");
100 self.received = Some(now);
101 self.periods += 1;
102
103 self.latency_sum += period_latency;
104 self.recent.copy_within(..Self::RECENT_LEN - 1, 1);
105 self.recent[0] = period_latency;
106 }
107
108 pub(crate) fn record_sent(&mut self) {
114 self.received = None;
115 self.sent = Some(Instant::now());
116 }
117}
118
119#[cfg(test)]
120mod tests {
121 use super::Latency;
122 use static_assertions::assert_impl_all;
123 use std::{fmt::Debug, time::Duration};
124
125 assert_impl_all!(Latency: Clone, Debug, Send, Sync);
126
127 const fn default_latency() -> Latency {
128 Latency {
129 latency_sum: Duration::from_millis(510),
130 periods: 17,
131 received: None,
132 recent: [
133 Duration::from_millis(20),
134 Duration::from_millis(25),
135 Duration::from_millis(30),
136 Duration::from_millis(35),
137 Duration::from_millis(40),
138 ],
139 sent: None,
140 }
141 }
142
143 #[test]
144 fn public_api() {
145 let latency = default_latency();
146 assert_eq!(latency.average(), Some(Duration::from_millis(30)));
147 assert_eq!(latency.periods(), 17);
148 assert!(latency.received().is_none());
149 assert!(latency.sent().is_none());
150
151 assert_eq!(latency.recent.len(), Latency::RECENT_LEN);
152 let mut iter = latency.recent().iter();
153 assert_eq!(iter.next(), Some(&Duration::from_millis(20)));
154 assert_eq!(iter.next_back(), Some(&Duration::from_millis(40)));
155 assert_eq!(iter.next(), Some(&Duration::from_millis(25)));
156 assert_eq!(iter.next(), Some(&Duration::from_millis(30)));
157 assert_eq!(iter.next_back(), Some(&Duration::from_millis(35)));
158 assert!(iter.next().is_none());
159 assert!(iter.next_back().is_none());
160 }
161
162 #[test]
165 fn recent() {
166 let no_recents = Latency {
169 latency_sum: Duration::ZERO,
170 periods: 0,
171 received: None,
172 recent: [Duration::MAX; Latency::RECENT_LEN],
173 sent: None,
174 };
175 assert!(no_recents.recent().is_empty());
176
177 let partial = Latency {
180 recent: [
181 Duration::from_millis(40),
182 Duration::from_millis(50),
183 Duration::MAX,
184 Duration::MAX,
185 Duration::MAX,
186 ],
187 ..no_recents
188 };
189 assert_eq!(
190 [Duration::from_millis(40), Duration::from_millis(50)],
191 partial.recent()
192 );
193
194 let full = Latency {
197 recent: [
198 Duration::from_millis(40),
199 Duration::from_millis(50),
200 Duration::from_millis(60),
201 Duration::from_millis(70),
202 Duration::from_millis(60),
203 ],
204 ..no_recents
205 };
206 assert_eq!(
207 [
208 Duration::from_millis(40),
209 Duration::from_millis(50),
210 Duration::from_millis(60),
211 Duration::from_millis(70),
212 Duration::from_millis(60),
213 ],
214 full.recent()
215 );
216 }
217
218 #[test]
219 fn record_period() {
220 let mut latency = Latency::new();
221 assert_eq!(latency.periods(), 0);
222 assert!(latency.received().is_none());
223 assert!(latency.sent().is_none());
224 assert!(latency.recent().is_empty());
225
226 latency.record_sent();
227 assert_eq!(latency.periods(), 0);
228 assert!(latency.received().is_none());
229 assert!(latency.sent().is_some());
230
231 latency.record_received();
232 assert_eq!(latency.periods(), 1);
233 assert!(latency.received().is_some());
234 assert!(latency.sent().is_some());
235 assert_eq!(latency.recent().len(), 1);
236
237 latency.record_sent();
238 assert_eq!(latency.periods(), 1);
239 assert!(latency.received().is_none());
240 assert!(latency.sent().is_some());
241 assert_eq!(latency.recent().len(), 1);
242 }
243
244 #[test]
245 #[should_panic(expected = "period completed multiple times")]
246 fn record_completed_period() {
247 let mut latency = Latency::new();
248 latency.record_sent();
249 latency.record_received();
250 latency.record_received();
251 }
252
253 #[test]
254 #[should_panic(expected = "period has not begun")]
255 fn record_not_begun_period() {
256 let mut latency = Latency::new();
257 latency.record_received();
258 }
259}