1use nutype::nutype;
4use std::fmt;
5
6#[allow(clippy::cast_precision_loss)] const fn pool_count_as_f64(value: usize) -> f64 {
8 value as f64
11}
12
13macro_rules! pool_count_type {
16 ($(#[$meta:meta])* $name:ident) => {
17 $(#[$meta])*
18 #[nutype(derive(
19 Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Display, From
20 ))]
21 pub struct $name(usize);
22
23 impl $name {
24 #[inline]
25 pub fn zero() -> Self {
26 Self::new(0)
27 }
28
29 #[inline]
30 #[must_use]
31 pub fn get(&self) -> usize {
32 self.into_inner()
33 }
34 }
35 };
36}
37
38pool_count_type!(
39 AvailableConnections
41);
42
43pool_count_type!(
44 MaxPoolSize
46);
47
48pool_count_type!(
49 CreatedConnections
51);
52
53pool_count_type!(
54 InUseConnections
56);
57
58impl InUseConnections {
59 #[inline]
61 #[must_use]
62 pub fn from_pool_stats(max: MaxPoolSize, available: AvailableConnections) -> Self {
63 Self::new(max.get().saturating_sub(available.get()))
64 }
65}
66
67#[derive(Debug, Clone, Copy, PartialEq)]
72pub struct PoolUtilization(f64);
73
74impl PoolUtilization {
75 #[inline]
80 #[must_use]
81 pub fn new(percentage: f64) -> Self {
82 assert!(
83 (0.0..=100.0).contains(&percentage),
84 "Utilization must be 0-100%, got {percentage}"
85 );
86 Self(percentage)
87 }
88
89 #[inline]
91 #[must_use]
92 pub fn from_pool_stats(max: MaxPoolSize, available: AvailableConnections) -> Self {
93 let max_size = max.get();
94 if max_size == 0 {
95 return Self(0.0);
96 }
97
98 let in_use = max_size.saturating_sub(available.get());
99 let utilization = (pool_count_as_f64(in_use) / pool_count_as_f64(max_size)) * 100.0;
100 Self(utilization)
101 }
102
103 #[inline]
104 #[must_use]
105 pub const fn as_percentage(self) -> f64 {
106 self.0
107 }
108
109 #[inline]
110 #[must_use]
111 pub fn is_full(self) -> bool {
112 self.0 >= 100.0
113 }
114
115 #[inline]
116 #[must_use]
117 pub fn is_empty(self) -> bool {
118 self.0 == 0.0
119 }
120
121 #[inline]
122 #[must_use]
123 pub fn is_high_load(self) -> bool {
124 self.0 >= 80.0
125 }
126}
127
128impl fmt::Display for PoolUtilization {
129 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
130 write!(f, "{:.1}%", self.0)
131 }
132}
133
134#[cfg(test)]
135#[allow(clippy::float_cmp)] mod tests {
137 use super::*;
138
139 #[test]
140 fn test_available_connections() {
141 let available = AvailableConnections::new(5);
142 assert_eq!(available.get(), 5);
143 assert_eq!(format!("{available}"), "5");
144
145 let zero = AvailableConnections::zero();
146 assert_eq!(zero.get(), 0);
147 }
148
149 #[test]
150 fn test_max_pool_size() {
151 let max = MaxPoolSize::new(10);
152 assert_eq!(max.get(), 10);
153 assert_eq!(format!("{max}"), "10");
154 }
155
156 #[test]
157 fn test_created_connections() {
158 let created = CreatedConnections::new(25);
159 assert_eq!(created.get(), 25);
160 assert_eq!(format!("{created}"), "25");
161
162 let zero = CreatedConnections::zero();
163 assert_eq!(zero.get(), 0);
164 }
165
166 #[test]
167 fn test_in_use_connections() {
168 let max = MaxPoolSize::new(10);
169 let available = AvailableConnections::new(3);
170 let in_use = InUseConnections::from_pool_stats(max, available);
171 assert_eq!(in_use.get(), 7);
172 }
173
174 #[test]
175 fn test_in_use_saturating() {
176 let max = MaxPoolSize::new(5);
177 let available = AvailableConnections::new(10);
178 let in_use = InUseConnections::from_pool_stats(max, available);
179 assert_eq!(in_use.get(), 0);
180 }
181
182 #[test]
183 fn test_pool_utilization() {
184 let max = MaxPoolSize::new(10);
185 let available = AvailableConnections::new(3);
186 let utilization = PoolUtilization::from_pool_stats(max, available);
187 assert_eq!(utilization.as_percentage(), 70.0);
188 assert_eq!(format!("{utilization}"), "70.0%");
189 }
190
191 #[test]
192 fn test_pool_utilization_full() {
193 let max = MaxPoolSize::new(10);
194 let available = AvailableConnections::new(0);
195 let utilization = PoolUtilization::from_pool_stats(max, available);
196 assert!(utilization.is_full());
197 assert!(utilization.is_high_load());
198 assert!(!utilization.is_empty());
199 }
200
201 #[test]
202 fn test_pool_utilization_empty() {
203 let max = MaxPoolSize::new(10);
204 let available = AvailableConnections::new(10);
205 let utilization = PoolUtilization::from_pool_stats(max, available);
206 assert!(utilization.is_empty());
207 assert!(!utilization.is_full());
208 assert!(!utilization.is_high_load());
209 }
210
211 #[test]
212 fn test_pool_utilization_high_load() {
213 let max = MaxPoolSize::new(10);
214 let available = AvailableConnections::new(1);
215 let utilization = PoolUtilization::from_pool_stats(max, available);
216 assert!(utilization.is_high_load());
217 assert!(!utilization.is_full());
218 }
219
220 #[test]
221 fn test_pool_utilization_zero_max() {
222 let max = MaxPoolSize::new(0);
223 let available = AvailableConnections::new(0);
224 let utilization = PoolUtilization::from_pool_stats(max, available);
225 assert_eq!(utilization.as_percentage(), 0.0);
226 }
227
228 #[test]
229 #[should_panic(expected = "Utilization must be 0-100%")]
230 fn test_pool_utilization_invalid() {
231 let _ = PoolUtilization::new(150.0);
232 }
233
234 #[test]
235 fn test_ordering() {
236 let small = AvailableConnections::new(1);
237 let large = AvailableConnections::new(10);
238 assert!(small < large);
239 assert_eq!(small, small);
240 }
241
242 #[test]
243 fn test_from_conversions() {
244 let available: AvailableConnections = 5usize.into();
245 assert_eq!(available.get(), 5);
246
247 let max: MaxPoolSize = 10usize.into();
248 assert_eq!(max.get(), 10);
249
250 let created: CreatedConnections = 25usize.into();
251 assert_eq!(created.get(), 25);
252 }
253}