1use std::num::NonZeroU64;
12
13#[allow(clippy::cast_precision_loss)] const fn count_as_f64_for_rate(value: u64) -> f64 {
15 value as f64
18}
19
20#[allow(
22 clippy::cast_possible_truncation,
23 clippy::cast_precision_loss,
24 clippy::cast_sign_loss
25)]
26fn bytes_per_second_to_u64(bytes_delta: u64, seconds: f64) -> u64 {
27 (count_as_f64_for_rate(bytes_delta) / seconds) as u64
31}
32
33macro_rules! counter_type {
42 ($name:ident) => {
43 #[derive(
44 Debug,
45 Clone,
46 Copy,
47 PartialEq,
48 Eq,
49 PartialOrd,
50 Ord,
51 Default,
52 serde::Serialize,
53 serde::Deserialize,
54 )]
55 pub struct $name(u64);
56
57 impl $name {
58 #[inline]
59 pub const fn new(value: u64) -> Self {
60 Self(value)
61 }
62
63 #[inline]
64 pub const fn get(self) -> u64 {
65 self.0
66 }
67
68 #[inline]
69 pub const fn increment(&mut self) {
70 self.0 += 1;
71 }
72
73 #[must_use]
74 #[inline]
75 pub const fn saturating_sub(self, other: Self) -> Self {
76 Self(self.0.saturating_sub(other.0))
77 }
78 }
79
80 impl std::fmt::Display for $name {
81 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
82 write!(f, "{}", self.0)
83 }
84 }
85 };
86}
87
88macro_rules! timing_type {
90 ($name:ident) => {
91 #[derive(
92 Debug,
93 Clone,
94 Copy,
95 PartialEq,
96 Eq,
97 PartialOrd,
98 Ord,
99 Default,
100 serde::Serialize,
101 serde::Deserialize,
102 )]
103 pub struct $name(u64);
104
105 impl $name {
106 #[inline]
107 pub const fn new(micros: u64) -> Self {
108 Self(micros)
109 }
110
111 #[inline]
112 pub const fn get(self) -> u64 {
113 self.0
114 }
115
116 #[inline]
117 pub const fn add(&mut self, other: Self) {
118 self.0 += other.0;
119 }
120
121 #[must_use]
122 pub fn average(total: Self, count: NonZeroU64) -> Milliseconds {
123 let avg_micros =
124 count_as_f64_for_rate(total.0) / count_as_f64_for_rate(count.get());
125 Milliseconds::from_micros(avg_micros)
126 }
127 }
128 };
129}
130
131macro_rules! f64_type {
133 ($name:ident) => {
134 #[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Default, serde::Serialize, serde::Deserialize)]
135 pub struct $name(f64);
136
137 impl $name {
138 #[inline]
139 pub const fn new(value: f64) -> Self {
140 Self(value)
141 }
142
143 #[inline]
144 pub const fn get(self) -> f64 {
145 self.0
146 }
147 }
148 };
149}
150
151#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, serde::Serialize, serde::Deserialize)]
160pub enum BackendHealthStatus {
161 #[default]
163 Healthy,
164 Degraded,
166 Down,
168}
169
170impl From<u8> for BackendHealthStatus {
171 fn from(value: u8) -> Self {
172 match value {
173 1 => Self::Degraded,
174 2 => Self::Down,
175 _ => Self::Healthy,
176 }
177 }
178}
179
180impl From<BackendHealthStatus> for u8 {
181 fn from(status: BackendHealthStatus) -> Self {
182 match status {
183 BackendHealthStatus::Healthy => 0,
184 BackendHealthStatus::Degraded => 1,
185 BackendHealthStatus::Down => 2,
186 }
187 }
188}
189
190counter_type!(CommandCount);
195counter_type!(FailureCount);
196
197#[derive(
199 Debug,
200 Clone,
201 Copy,
202 PartialEq,
203 Eq,
204 PartialOrd,
205 Ord,
206 Default,
207 serde::Serialize,
208 serde::Deserialize,
209)]
210pub struct ErrorCount(u64);
211
212impl ErrorCount {
213 #[inline]
214 #[must_use]
215 pub const fn new(count: u64) -> Self {
216 Self(count)
217 }
218
219 #[inline]
220 #[must_use]
221 pub const fn get(self) -> u64 {
222 self.0
223 }
224
225 #[inline]
226 pub const fn increment(&mut self) {
227 self.0 += 1;
228 }
229
230 #[inline]
231 pub const fn add(&mut self, other: Self) {
232 self.0 += other.0;
233 }
234
235 #[must_use]
236 #[inline]
237 pub const fn saturating_sub(self, other: Self) -> Self {
238 Self(self.0.saturating_sub(other.0))
239 }
240
241 #[must_use]
242 #[inline]
243 pub const fn is_zero(self) -> bool {
244 self.0 == 0
245 }
246}
247
248impl std::fmt::Display for ErrorCount {
249 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
250 write!(f, "{}", self.0)
251 }
252}
253
254#[derive(
256 Debug,
257 Clone,
258 Copy,
259 PartialEq,
260 Eq,
261 PartialOrd,
262 Ord,
263 Default,
264 serde::Serialize,
265 serde::Deserialize,
266)]
267pub struct ArticleCount(u64);
268
269impl ArticleCount {
270 #[inline]
271 #[must_use]
272 pub const fn new(count: u64) -> Self {
273 Self(count)
274 }
275
276 #[inline]
277 #[must_use]
278 pub const fn get(self) -> u64 {
279 self.0
280 }
281
282 #[inline]
283 pub const fn increment(&mut self) {
284 self.0 += 1;
285 }
286
287 #[must_use]
289 pub const fn average_bytes(self, total_bytes: u64) -> Option<u64> {
290 total_bytes.checked_div(self.0)
291 }
292}
293
294impl std::fmt::Display for ArticleCount {
295 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
296 write!(f, "{}", self.0)
297 }
298}
299
300#[derive(
302 Debug,
303 Clone,
304 Copy,
305 PartialEq,
306 Eq,
307 PartialOrd,
308 Ord,
309 Default,
310 serde::Serialize,
311 serde::Deserialize,
312)]
313pub struct ActiveConnections(usize);
314
315impl ActiveConnections {
316 #[inline]
317 #[must_use]
318 pub const fn new(count: usize) -> Self {
319 Self(count)
320 }
321
322 #[inline]
323 #[must_use]
324 pub const fn get(self) -> usize {
325 self.0
326 }
327}
328
329impl std::fmt::Display for ActiveConnections {
330 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
331 write!(f, "{}", self.0)
332 }
333}
334
335timing_type!(TtfbMicros);
340timing_type!(SendMicros);
341timing_type!(RecvMicros);
342
343#[derive(
345 Debug,
346 Clone,
347 Copy,
348 PartialEq,
349 Eq,
350 PartialOrd,
351 Ord,
352 Default,
353 serde::Serialize,
354 serde::Deserialize,
355)]
356pub struct Microseconds(u64);
357
358impl Microseconds {
359 #[inline]
360 #[must_use]
361 pub const fn new(micros: u64) -> Self {
362 Self(micros)
363 }
364
365 #[inline]
366 #[must_use]
367 pub const fn get(self) -> u64 {
368 self.0
369 }
370
371 #[inline]
372 pub const fn add(&mut self, other: Self) {
373 self.0 += other.0;
374 }
375
376 #[inline]
377 #[must_use]
378 pub fn as_millis_f64(self) -> f64 {
379 count_as_f64_for_rate(self.0) / 1000.0
380 }
381}
382
383f64_type!(Milliseconds);
384
385impl Milliseconds {
386 #[inline]
387 #[must_use]
388 pub fn from_micros(micros: f64) -> Self {
389 Self(micros / 1000.0)
390 }
391}
392
393f64_type!(OverheadMillis);
394
395impl OverheadMillis {
396 #[must_use]
398 pub fn from_components(ttfb: Milliseconds, send: Milliseconds, recv: Milliseconds) -> Self {
399 Self(ttfb.0 - send.0 - recv.0)
400 }
401}
402
403#[derive(
409 Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Default, serde::Serialize, serde::Deserialize,
410)]
411pub struct BytesPerSecond(u64);
412
413impl BytesPerSecond {
414 #[inline]
415 #[must_use]
416 pub const fn new(bps: u64) -> Self {
417 Self(bps)
418 }
419
420 #[inline]
421 #[must_use]
422 pub const fn get(self) -> u64 {
423 self.0
424 }
425
426 #[must_use]
427 pub fn from_delta(bytes_delta: u64, seconds: f64) -> Self {
428 if seconds > 0.0 {
429 Self(bytes_per_second_to_u64(bytes_delta, seconds))
430 } else {
431 Self(0)
432 }
433 }
434}
435
436f64_type!(CommandsPerSecond);
437
438impl CommandsPerSecond {
439 #[must_use]
440 pub fn from_delta(commands_delta: u64, seconds: f64) -> Self {
441 if seconds > 0.0 {
442 Self(count_as_f64_for_rate(commands_delta) / seconds)
443 } else {
444 Self(0.0)
445 }
446 }
447}
448
449f64_type!(ErrorRatePercent);
450
451impl ErrorRatePercent {
452 #[must_use]
453 pub fn from_counts(errors: ErrorCount, commands: CommandCount) -> Self {
454 if commands.get() > 0 {
455 Self(
456 (count_as_f64_for_rate(errors.get()) / count_as_f64_for_rate(commands.get()))
457 * 100.0,
458 )
459 } else {
460 Self(0.0)
461 }
462 }
463
464 #[must_use]
465 pub fn from_raw_counts(errors: u64, commands: u64) -> Self {
466 if commands > 0 {
467 Self((count_as_f64_for_rate(errors) / count_as_f64_for_rate(commands)) * 100.0)
468 } else {
469 Self(0.0)
470 }
471 }
472
473 #[must_use]
474 pub fn is_high(self) -> bool {
475 self.0 > 5.0
476 }
477}
478
479#[cfg(test)]
480#[allow(clippy::float_cmp)] mod tests {
482 use super::*;
483
484 #[test]
486 fn test_command_count_new() {
487 let count = CommandCount::new(42);
488 assert_eq!(count.get(), 42);
489 }
490
491 #[test]
492 fn test_command_count_default() {
493 let count = CommandCount::default();
494 assert_eq!(count.get(), 0);
495 }
496
497 #[test]
498 fn test_command_count_increment() {
499 let mut count = CommandCount::new(10);
500 count.increment();
501 assert_eq!(count.get(), 11);
502 }
503
504 #[test]
505 fn test_command_count_saturating_sub() {
506 let count1 = CommandCount::new(100);
507 let count2 = CommandCount::new(30);
508 let result = count1.saturating_sub(count2);
509 assert_eq!(result.get(), 70);
510 }
511
512 #[test]
513 fn test_command_count_saturating_sub_underflow() {
514 let count1 = CommandCount::new(10);
515 let count2 = CommandCount::new(30);
516 let result = count1.saturating_sub(count2);
517 assert_eq!(result.get(), 0); }
519
520 #[test]
521 fn test_command_count_display() {
522 let count = CommandCount::new(1234);
523 assert_eq!(format!("{count}"), "1234");
524 }
525
526 #[test]
528 fn test_error_count_new() {
529 let count = ErrorCount::new(5);
530 assert_eq!(count.get(), 5);
531 }
532
533 #[test]
534 fn test_error_count_increment() {
535 let mut count = ErrorCount::new(0);
536 count.increment();
537 count.increment();
538 assert_eq!(count.get(), 2);
539 }
540
541 #[test]
542 fn test_error_count_is_zero() {
543 let zero = ErrorCount::new(0);
544 let nonzero = ErrorCount::new(1);
545
546 assert!(zero.is_zero());
547 assert!(!nonzero.is_zero());
548 }
549
550 #[test]
552 fn test_article_count_new() {
553 let count = ArticleCount::new(10);
554 assert_eq!(count.get(), 10);
555 }
556
557 #[test]
558 fn test_article_count_increment() {
559 let mut count = ArticleCount::new(5);
560 count.increment();
561 assert_eq!(count.get(), 6);
562 }
563
564 #[test]
565 fn test_article_count_average_bytes() {
566 let count = ArticleCount::new(10);
567 let avg = count.average_bytes(5000);
568 assert_eq!(avg, Some(500)); }
570
571 #[test]
572 fn test_article_count_average_bytes_zero_articles() {
573 let count = ArticleCount::new(0);
574 let avg = count.average_bytes(1000);
575 assert_eq!(avg, None);
576 }
577
578 #[test]
579 fn test_article_count_average_bytes_zero_bytes() {
580 let count = ArticleCount::new(10);
581 let avg = count.average_bytes(0);
582 assert_eq!(avg, Some(0));
583 }
584
585 #[test]
587 fn test_active_connections_new() {
588 let active = ActiveConnections::new(5);
589 assert_eq!(active.get(), 5);
590 }
591
592 #[test]
593 fn test_active_connections_default() {
594 let active = ActiveConnections::default();
595 assert_eq!(active.get(), 0);
596 }
597
598 #[test]
599 fn test_active_connections_display() {
600 let active = ActiveConnections::new(42);
601 assert_eq!(format!("{active}"), "42");
602 }
603
604 #[test]
606 fn test_ttfb_micros_new() {
607 let ttfb = TtfbMicros::new(1000);
608 assert_eq!(ttfb.get(), 1000);
609 }
610
611 #[test]
612 fn test_ttfb_micros_add() {
613 let mut ttfb = TtfbMicros::new(1000);
614 ttfb.add(TtfbMicros::new(500));
615 assert_eq!(ttfb.get(), 1500);
616 }
617
618 #[test]
619 fn test_ttfb_micros_average() {
620 let total = TtfbMicros::new(10000); let count = NonZeroU64::new(10).unwrap();
622 let avg = TtfbMicros::average(total, count);
623 assert!((avg.get() - 1.0).abs() < 0.01); }
625
626 #[test]
627 fn test_send_micros_average() {
628 let total = SendMicros::new(5000);
629 let count = NonZeroU64::new(10).unwrap();
630 let avg = SendMicros::average(total, count);
631 assert!((avg.get() - 0.5).abs() < 0.01); }
633
634 #[test]
635 fn test_recv_micros_average() {
636 let total = RecvMicros::new(15000);
637 let count = NonZeroU64::new(10).unwrap();
638 let avg = RecvMicros::average(total, count);
639 assert!((avg.get() - 1.5).abs() < 0.01); }
641
642 #[test]
644 fn test_microseconds_new() {
645 let micros = Microseconds::new(1000);
646 assert_eq!(micros.get(), 1000);
647 }
648
649 #[test]
650 fn test_microseconds_add() {
651 let mut micros = Microseconds::new(1000);
652 micros.add(Microseconds::new(500));
653 assert_eq!(micros.get(), 1500);
654 }
655
656 #[test]
657 fn test_microseconds_as_millis_f64() {
658 let micros = Microseconds::new(1500);
659 let millis = micros.as_millis_f64();
660 assert!((millis - 1.5).abs() < 0.01);
661 }
662
663 #[test]
665 fn test_milliseconds_new() {
666 let ms = Milliseconds::new(10.5);
667 assert!((ms.get() - 10.5).abs() < 0.01);
668 }
669
670 #[test]
671 fn test_milliseconds_from_micros() {
672 let ms = Milliseconds::from_micros(5000.0);
673 assert!((ms.get() - 5.0).abs() < 0.01);
674 }
675
676 #[test]
678 fn test_overhead_millis_from_components() {
679 let ttfb = Milliseconds::new(10.0);
680 let send = Milliseconds::new(3.0);
681 let recv = Milliseconds::new(5.0);
682
683 let overhead = OverheadMillis::from_components(ttfb, send, recv);
684 assert!((overhead.get() - 2.0).abs() < 0.01); }
686
687 #[test]
688 fn test_overhead_millis_negative() {
689 let ttfb = Milliseconds::new(5.0);
691 let send = Milliseconds::new(3.0);
692 let recv = Milliseconds::new(4.0);
693
694 let overhead = OverheadMillis::from_components(ttfb, send, recv);
695 assert!((overhead.get() + 2.0).abs() < 0.01); }
697
698 #[test]
700 fn test_bytes_per_second_new() {
701 let bps = BytesPerSecond::new(1000);
702 assert_eq!(bps.get(), 1000);
703 }
704
705 #[test]
706 fn test_bytes_per_second_from_delta() {
707 let bps = BytesPerSecond::from_delta(1000, 2.0);
708 assert_eq!(bps.get(), 500); }
710
711 #[test]
712 fn test_bytes_per_second_from_delta_zero_time() {
713 let bps = BytesPerSecond::from_delta(1000, 0.0);
714 assert_eq!(bps.get(), 0); }
716
717 #[test]
718 fn test_bytes_per_second_default() {
719 let bps = BytesPerSecond::default();
720 assert_eq!(bps.get(), 0);
721 }
722
723 #[test]
725 fn test_commands_per_second_new() {
726 let cps = CommandsPerSecond::new(10.5);
727 assert!((cps.get() - 10.5).abs() < 0.01);
728 }
729
730 #[test]
731 fn test_commands_per_second_from_delta() {
732 let cps = CommandsPerSecond::from_delta(100, 10.0);
733 assert!((cps.get() - 10.0).abs() < 0.01); }
735
736 #[test]
737 fn test_commands_per_second_from_delta_zero_time() {
738 let cps = CommandsPerSecond::from_delta(100, 0.0);
739 assert_eq!(cps.get(), 0.0);
740 }
741
742 #[test]
744 fn test_error_rate_percent_from_counts() {
745 let errors = ErrorCount::new(5);
746 let commands = CommandCount::new(100);
747 let rate = ErrorRatePercent::from_counts(errors, commands);
748 assert!((rate.get() - 5.0).abs() < 0.01); }
750
751 #[test]
752 fn test_error_rate_percent_from_counts_zero_commands() {
753 let errors = ErrorCount::new(10);
754 let commands = CommandCount::new(0);
755 let rate = ErrorRatePercent::from_counts(errors, commands);
756 assert_eq!(rate.get(), 0.0); }
758
759 #[test]
760 fn test_error_rate_percent_from_raw_counts() {
761 let rate = ErrorRatePercent::from_raw_counts(10, 100);
762 assert!((rate.get() - 10.0).abs() < 0.01); }
764
765 #[test]
766 fn test_error_rate_percent_from_raw_counts_zero_commands() {
767 let rate = ErrorRatePercent::from_raw_counts(5, 0);
768 assert_eq!(rate.get(), 0.0);
769 }
770
771 #[test]
772 fn test_error_rate_percent_is_high() {
773 let low = ErrorRatePercent::new(3.0);
774 let threshold = ErrorRatePercent::new(5.0);
775 let high = ErrorRatePercent::new(10.0);
776
777 assert!(!low.is_high());
778 assert!(!threshold.is_high()); assert!(high.is_high());
780 }
781
782 #[test]
783 fn test_error_rate_percent_is_high_edge_cases() {
784 let just_above = ErrorRatePercent::new(5.01);
785 let just_below = ErrorRatePercent::new(4.99);
786
787 assert!(just_above.is_high());
788 assert!(!just_below.is_high());
789 }
790
791 #[test]
793 fn test_command_count_ordering() {
794 let c1 = CommandCount::new(10);
795 let c2 = CommandCount::new(20);
796 let c3 = CommandCount::new(10);
797
798 assert!(c1 < c2);
799 assert!(c2 > c1);
800 assert_eq!(c1, c3);
801 }
802
803 #[test]
804 fn test_error_count_ordering() {
805 let e1 = ErrorCount::new(5);
806 let e2 = ErrorCount::new(10);
807
808 assert!(e1 < e2);
809 assert!(e2 > e1);
810 }
811
812 #[test]
813 fn test_article_count_ordering() {
814 let a1 = ArticleCount::new(100);
815 let a2 = ArticleCount::new(200);
816
817 assert!(a1 < a2);
818 assert!(a2 > a1);
819 }
820
821 #[test]
822 fn test_active_connections_ordering() {
823 let a1 = ActiveConnections::new(3);
824 let a2 = ActiveConnections::new(5);
825
826 assert!(a1 < a2);
827 assert!(a2 > a1);
828 }
829
830 #[test]
832 fn test_types_are_copy() {
833 let count = CommandCount::new(42);
834 let copied = count; assert_eq!(count.get(), copied.get());
836 }
837}