1use std::fmt;
8use std::marker::PhantomData;
9use std::ops::{Add, AddAssign};
10
11#[derive(
17 Debug,
18 Clone,
19 Copy,
20 PartialEq,
21 Eq,
22 PartialOrd,
23 Ord,
24 Default,
25 serde::Serialize,
26 serde::Deserialize,
27)]
28pub struct ClientToBackend;
29
30#[derive(
32 Debug,
33 Clone,
34 Copy,
35 PartialEq,
36 Eq,
37 PartialOrd,
38 Ord,
39 Default,
40 serde::Serialize,
41 serde::Deserialize,
42)]
43pub struct BackendToClient;
44
45#[derive(
47 Debug,
48 Clone,
49 Copy,
50 PartialEq,
51 Eq,
52 PartialOrd,
53 Ord,
54 Default,
55 serde::Serialize,
56 serde::Deserialize,
57)]
58pub struct Client;
59
60#[derive(
62 Debug,
63 Clone,
64 Copy,
65 PartialEq,
66 Eq,
67 PartialOrd,
68 Ord,
69 Default,
70 serde::Serialize,
71 serde::Deserialize,
72)]
73pub struct Sent;
74
75#[derive(
77 Debug,
78 Clone,
79 Copy,
80 PartialEq,
81 Eq,
82 PartialOrd,
83 Ord,
84 Default,
85 serde::Serialize,
86 serde::Deserialize,
87)]
88pub struct Received;
89
90#[repr(transparent)]
99#[derive(
100 Debug,
101 Clone,
102 Copy,
103 PartialEq,
104 Eq,
105 PartialOrd,
106 Ord,
107 Default,
108 serde::Serialize,
109 serde::Deserialize,
110)]
111pub struct ByteCounter<D> {
112 bytes: u64,
113 _direction: PhantomData<D>,
114}
115
116impl<D> ByteCounter<D> {
117 pub const ZERO: Self = Self {
118 bytes: 0,
119 _direction: PhantomData,
120 };
121
122 #[must_use]
123 pub const fn new(bytes: u64) -> Self {
124 Self {
125 bytes,
126 _direction: PhantomData,
127 }
128 }
129
130 #[must_use]
131 pub const fn zero() -> Self {
132 Self::ZERO
133 }
134
135 #[must_use]
136 pub const fn as_u64(&self) -> u64 {
137 self.bytes
138 }
139
140 #[must_use]
141 #[inline]
142 pub const fn add(self, bytes: usize) -> Self {
143 Self {
144 bytes: self.bytes + bytes as u64,
145 _direction: PhantomData,
146 }
147 }
148
149 #[must_use]
150 #[inline]
151 pub const fn add_u64(self, bytes: u64) -> Self {
152 Self {
153 bytes: self.bytes + bytes,
154 _direction: PhantomData,
155 }
156 }
157
158 #[must_use]
159 #[inline]
160 #[allow(clippy::needless_pass_by_value)] pub const fn saturating_sub(self, other: Self) -> Self {
162 Self {
165 bytes: self.bytes.saturating_sub(other.bytes),
166 _direction: PhantomData,
167 }
168 }
169}
170
171impl<D> From<u64> for ByteCounter<D> {
172 #[inline]
173 fn from(bytes: u64) -> Self {
174 Self::new(bytes)
175 }
176}
177
178impl<D> From<ByteCounter<D>> for u64 {
179 #[inline]
180 fn from(counter: ByteCounter<D>) -> Self {
181 counter.bytes
182 }
183}
184
185impl<D> Add for ByteCounter<D> {
186 type Output = Self;
187 #[inline]
188 fn add(self, other: Self) -> Self {
189 Self {
190 bytes: self.bytes + other.bytes,
191 _direction: PhantomData,
192 }
193 }
194}
195
196impl<D> AddAssign for ByteCounter<D> {
197 #[inline]
198 fn add_assign(&mut self, other: Self) {
199 self.bytes += other.bytes;
200 }
201}
202
203impl<D> fmt::Display for ByteCounter<D> {
204 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
205 write!(f, "{} bytes", self.bytes)
206 }
207}
208
209pub type ClientBytes = ByteCounter<Client>;
215
216pub type ClientToBackendBytes = ByteCounter<ClientToBackend>;
218
219pub type BackendToClientBytes = ByteCounter<BackendToClient>;
221
222pub type BytesSent = ByteCounter<Sent>;
224
225pub type BytesReceived = ByteCounter<Received>;
227
228#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
230pub struct TransferMetrics {
231 pub client_to_backend: ClientToBackendBytes,
232 pub backend_to_client: BackendToClientBytes,
233}
234
235impl TransferMetrics {
236 #[must_use]
237 pub const fn zero() -> Self {
238 Self {
239 client_to_backend: ClientToBackendBytes::ZERO,
240 backend_to_client: BackendToClientBytes::ZERO,
241 }
242 }
243
244 #[must_use]
245 pub const fn new(client_to_backend: u64, backend_to_client: u64) -> Self {
246 Self {
247 client_to_backend: ClientToBackendBytes::new(client_to_backend),
248 backend_to_client: BackendToClientBytes::new(backend_to_client),
249 }
250 }
251
252 #[must_use]
253 #[inline]
254 pub const fn total(&self) -> u64 {
255 self.client_to_backend.as_u64() + self.backend_to_client.as_u64()
256 }
257
258 #[must_use]
259 #[inline]
260 pub const fn as_tuple(&self) -> (u64, u64) {
261 (
262 self.client_to_backend.as_u64(),
263 self.backend_to_client.as_u64(),
264 )
265 }
266
267 #[must_use]
269 pub const fn saturating_sub(self, other: Self) -> Self {
270 Self {
271 client_to_backend: self
272 .client_to_backend
273 .saturating_sub(other.client_to_backend),
274 backend_to_client: self
275 .backend_to_client
276 .saturating_sub(other.backend_to_client),
277 }
278 }
279}
280
281impl From<(u64, u64)> for TransferMetrics {
282 fn from((c2b, b2c): (u64, u64)) -> Self {
283 Self::new(c2b, b2c)
284 }
285}
286
287impl fmt::Display for TransferMetrics {
288 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
289 write!(
290 f,
291 "sent: {}, received: {}",
292 self.client_to_backend, self.backend_to_client
293 )
294 }
295}
296
297#[cfg(test)]
298mod tests {
299 use super::*;
300 use proptest::prelude::*;
301
302 proptest! {
307 #[test]
309 fn prop_byte_counter_arithmetic(initial in 0u64..1_000_000, increment in 0usize..1_000_000, b in 0u64..1_000_000) {
310 let c2b = ClientToBackendBytes::new(initial).add(increment);
312 let b2c = BackendToClientBytes::new(initial).add(increment);
313 prop_assert_eq!(c2b.as_u64(), initial + increment as u64);
314 prop_assert_eq!(b2c.as_u64(), initial + increment as u64);
315
316 let sum1 = ClientToBackendBytes::new(initial) + ClientToBackendBytes::new(b);
318 let sum2 = BackendToClientBytes::new(initial) + BackendToClientBytes::new(b);
319 prop_assert_eq!(sum1.as_u64(), initial + b);
320 prop_assert_eq!(sum2.as_u64(), initial + b);
321
322 prop_assert_eq!(
324 ClientToBackendBytes::new(initial).saturating_sub(ClientToBackendBytes::new(b)).as_u64(),
325 initial.saturating_sub(b)
326 );
327 prop_assert_eq!(
328 BackendToClientBytes::new(initial).saturating_sub(BackendToClientBytes::new(b)).as_u64(),
329 initial.saturating_sub(b)
330 );
331 }
332 }
333
334 #[test]
335 fn test_phantom_type_zero_size() {
336 use std::mem::size_of;
337 assert_eq!(size_of::<ClientToBackendBytes>(), size_of::<u64>());
339 assert_eq!(size_of::<BackendToClientBytes>(), size_of::<u64>());
340 assert_eq!(size_of::<BytesSent>(), size_of::<u64>());
341 }
342
343 #[test]
348 fn test_transfer_metrics_total() {
349 let metrics = TransferMetrics::new(100, 200);
350 assert_eq!(metrics.total(), 300);
351 assert_eq!(metrics.as_tuple(), (100, 200));
352 }
353
354 proptest! {
355 #[test]
357 fn prop_transfer_metrics_total(c2b in 0u64..1_000_000, b2c in 0u64..1_000_000) {
358 let metrics = TransferMetrics::new(c2b, b2c);
359 prop_assert_eq!(metrics.total(), c2b + b2c);
360 }
361
362 #[test]
364 fn prop_transfer_metrics_saturating_sub(a1 in 0u64..1_000_000, a2 in 0u64..1_000_000, b1 in 0u64..1_000_000, b2 in 0u64..1_000_000) {
365 let metrics1 = TransferMetrics::new(a1, a2);
366 let metrics2 = TransferMetrics::new(b1, b2);
367 let diff = metrics1.saturating_sub(metrics2);
368
369 prop_assert_eq!(diff.client_to_backend.as_u64(), a1.saturating_sub(b1));
370 prop_assert_eq!(diff.backend_to_client.as_u64(), a2.saturating_sub(b2));
371 }
372 }
373
374 #[test]
379 fn test_counter_display_with_unit() {
380 let c = TotalConnections::new(42);
381 assert_eq!(format!("{c}"), "42 connections");
382 }
383
384 #[test]
385 fn test_counter_display_with_unit_zero() {
386 let c = TotalConnections::new(0);
387 assert_eq!(format!("{c}"), "0 connections");
388 }
389
390 #[test]
391 fn test_counter_display_bytes_per_second() {
392 let c = BytesPerSecondRate::new(1500);
393 assert_eq!(format!("{c}"), "1500 B/s");
394 }
395
396 #[test]
397 fn test_counter_display_article_bytes() {
398 let c = ArticleBytesTotal::new(999_999);
399 assert_eq!(format!("{c}"), "999999 bytes");
400 }
401
402 #[test]
403 fn test_counter_display_empty_unit() {
404 let c = TimingMeasurementCount::new(7);
406 assert_eq!(format!("{c}"), "7");
407 }
408
409 #[test]
410 fn test_counter_display_empty_unit_zero() {
411 let c = TimingMeasurementCount::new(0);
412 assert_eq!(format!("{c}"), "0");
413 }
414
415 #[test]
416 fn test_counter_display_empty_unit_large() {
417 let c = TimingMeasurementCount::new(u64::MAX);
418 assert_eq!(format!("{c}"), format!("{}", u64::MAX));
419 }
420
421 #[test]
422 fn test_timing_measurement_count_new_and_get() {
423 let c = TimingMeasurementCount::new(123);
424 assert_eq!(c.get(), 123);
425 }
426
427 #[test]
428 fn test_timing_measurement_count_zero_constant() {
429 assert_eq!(TimingMeasurementCount::ZERO.get(), 0);
430 }
431
432 #[test]
433 fn test_timing_measurement_count_from_u64() {
434 let c = TimingMeasurementCount::from(55u64);
435 assert_eq!(c.get(), 55);
436 }
437
438 #[test]
439 fn test_timing_measurement_count_default() {
440 let c = TimingMeasurementCount::default();
441 assert_eq!(c.get(), 0);
442 }
443
444 #[test]
445 fn test_timing_measurement_count_eq() {
446 assert_eq!(
447 TimingMeasurementCount::new(10),
448 TimingMeasurementCount::new(10)
449 );
450 assert_ne!(
451 TimingMeasurementCount::new(10),
452 TimingMeasurementCount::new(11)
453 );
454 }
455
456 #[test]
457 fn test_timing_measurement_count_ord() {
458 assert!(TimingMeasurementCount::new(5) < TimingMeasurementCount::new(10));
459 assert!(TimingMeasurementCount::new(10) > TimingMeasurementCount::new(5));
460 }
461
462 #[test]
463 fn test_timing_measurement_count_clone_copy() {
464 let a = TimingMeasurementCount::new(42);
465 let b = a; assert_eq!(a, b);
467 }
468
469 #[test]
470 fn test_timing_measurement_count_debug() {
471 let c = TimingMeasurementCount::new(99);
472 let dbg = format!("{c:?}");
473 assert!(dbg.contains("99"));
474 }
475
476 #[test]
477 fn test_counter_repr_transparent() {
478 use std::mem::size_of;
479 assert_eq!(size_of::<TotalConnections>(), size_of::<u64>());
481 assert_eq!(size_of::<BytesPerSecondRate>(), size_of::<u64>());
482 assert_eq!(size_of::<ArticleBytesTotal>(), size_of::<u64>());
483 assert_eq!(size_of::<TimingMeasurementCount>(), size_of::<u64>());
484 }
485}
486
487macro_rules! define_counter {
497 ($name:ident, $unit:expr) => {
498 #[repr(transparent)]
499 #[derive(
500 Debug,
501 Clone,
502 Copy,
503 PartialEq,
504 Eq,
505 PartialOrd,
506 Ord,
507 Default,
508 serde::Serialize,
509 serde::Deserialize,
510 )]
511 pub struct $name(u64);
512
513 impl $name {
514 pub const ZERO: Self = Self(0);
515
516 #[must_use]
517 pub const fn new(value: u64) -> Self {
518 Self(value)
519 }
520
521 #[must_use]
522 pub const fn get(&self) -> u64 {
523 self.0
524 }
525 }
526
527 impl From<u64> for $name {
528 #[inline]
529 fn from(value: u64) -> Self {
530 Self(value)
531 }
532 }
533
534 impl fmt::Display for $name {
535 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
536 let unit: &str = $unit;
537 if unit.is_empty() {
538 write!(f, "{}", self.0)
539 } else {
540 write!(f, "{} {}", self.0, unit)
541 }
542 }
543 }
544 };
545}
546
547define_counter!(TotalConnections, "connections");
552define_counter!(BytesPerSecondRate, "B/s");
553define_counter!(ArticleBytesTotal, "bytes");
554
555define_counter!(TimingMeasurementCount, "");