1use coven_foundation::clock::ClockRef;
7use coven_foundation::locks::lock_unpoisoned;
8use std::{fmt, sync::Mutex, time::Duration};
9
10#[derive(Clone, Copy, Debug, PartialEq, Eq)]
12pub enum RequestKind {
13 Status,
15 Read,
17 Create,
19 Replace,
21 List,
23 Delete,
25 Upload,
27 Account,
29}
30
31impl RequestKind {
32 const ALL: [Self; 8] = [
33 Self::Status,
34 Self::Read,
35 Self::Create,
36 Self::Replace,
37 Self::List,
38 Self::Delete,
39 Self::Upload,
40 Self::Account,
41 ];
42
43 fn index(self) -> usize {
44 self as usize
45 }
46
47 fn name(self) -> &'static str {
48 match self {
49 Self::Status => "status",
50 Self::Read => "read",
51 Self::Create => "create",
52 Self::Replace => "replace",
53 Self::List => "list",
54 Self::Delete => "delete",
55 Self::Upload => "upload",
56 Self::Account => "account",
57 }
58 }
59}
60
61#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
65pub struct RequestTotal {
66 pub count: u64,
68 pub bytes: u64,
70 pub time: Duration,
72}
73
74#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
76pub struct RequestCounts([RequestTotal; RequestKind::ALL.len()]);
77
78impl RequestCounts {
79 pub fn kind(&self, kind: RequestKind) -> RequestTotal {
81 self.0[kind.index()]
82 }
83
84 pub fn total(&self) -> RequestTotal {
86 self.0.iter().fold(RequestTotal::default(), |sum, kind| RequestTotal {
87 count: sum.count + kind.count,
88 bytes: sum.bytes + kind.bytes,
89 time: sum.time + kind.time,
90 })
91 }
92
93 pub fn since(&self, earlier: &Self) -> Self {
95 let mut since = Self::default();
96 for kind in RequestKind::ALL {
97 let (now, then) = (self.kind(kind), earlier.kind(kind));
98 since.0[kind.index()] = RequestTotal {
99 count: now.count.saturating_sub(then.count),
100 bytes: now.bytes.saturating_sub(then.bytes),
101 time: now.time.saturating_sub(then.time),
102 };
103 }
104 since
105 }
106}
107
108impl fmt::Display for RequestCounts {
110 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
111 let mut sent = RequestKind::ALL
112 .into_iter()
113 .filter(|kind| self.kind(*kind).count > 0 || self.kind(*kind).bytes > 0)
114 .peekable();
115 if sent.peek().is_none() {
116 return f.write_str("none");
117 }
118 for (index, kind) in sent.enumerate() {
119 let total = self.kind(kind);
120 if index > 0 {
121 f.write_str(", ")?;
122 }
123 write!(f, "{} {} ({} B, {:?})", kind.name(), total.count, total.bytes, total.time)?;
124 }
125 Ok(())
126 }
127}
128
129pub(crate) struct RequestTally {
131 clock: ClockRef,
132 totals: Mutex<RequestCounts>,
133}
134
135impl RequestTally {
136 pub(crate) fn new(clock: ClockRef) -> Self {
137 Self { clock, totals: Mutex::new(RequestCounts::default()) }
138 }
139
140 pub(crate) fn counts(&self) -> RequestCounts {
141 *lock_unpoisoned(&self.totals)
142 }
143
144 pub(crate) async fn send<T>(
147 &self,
148 kind: RequestKind,
149 bytes: u64,
150 request: impl std::future::Future<Output = T>,
151 ) -> T {
152 let started = self.clock.elapsed();
153 let result = request.await;
154 self.add(kind, 1, bytes, self.clock.elapsed().saturating_sub(started));
155 result
156 }
157
158 pub(crate) async fn receive<T>(
160 &self,
161 read: impl std::future::Future<Output = Result<usize, T>>,
162 ) -> Result<usize, T> {
163 let started = self.clock.elapsed();
164 let result = read.await;
165 let received = result.as_ref().map_or(0, |length| *length as u64);
166 self.add(RequestKind::Read, 0, received, self.clock.elapsed().saturating_sub(started));
167 result
168 }
169
170 pub(crate) fn received(&self, bytes: u64) {
172 self.add(RequestKind::Read, 0, bytes, Duration::ZERO);
173 }
174
175 fn add(&self, kind: RequestKind, count: u64, bytes: u64, time: Duration) {
176 let mut totals = lock_unpoisoned(&self.totals);
177 let total = &mut totals.0[kind.index()];
178 total.count += count;
179 total.bytes += bytes;
180 total.time += time;
181 }
182}
183
184#[cfg(test)]
185#[path = "request_tally_tests.rs"]
186mod tests;