1use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
7use std::sync::Arc;
8use std::time::Duration;
9
10#[derive(Debug, Clone)]
12pub struct PoolScalerConfig {
13 pub scale_up_threshold: f64,
15 pub scale_down_threshold: f64,
17 pub check_interval: Duration,
19 pub max_connections: usize,
21 pub min_connections: usize,
23}
24
25impl Default for PoolScalerConfig {
26 fn default() -> Self {
27 Self {
28 scale_up_threshold: 0.3,
29 scale_down_threshold: 0.7,
30 check_interval: Duration::from_secs(30),
31 max_connections: 100,
32 min_connections: 5,
33 }
34 }
35}
36
37#[derive(Debug, Clone)]
39pub struct PoolMetrics {
40 pub current_connections: usize,
42 pub idle_connections: usize,
44 pub timeout_count: u64,
46 pub total_acquire: u64,
48}
49
50impl PoolMetrics {
51 pub fn timeout_rate(&self) -> f64 {
53 if self.total_acquire == 0 {
54 0.0
55 } else {
56 self.timeout_count as f64 / self.total_acquire as f64
57 }
58 }
59
60 pub fn idle_rate(&self) -> f64 {
62 if self.current_connections == 0 {
63 0.0
64 } else {
65 self.idle_connections as f64 / self.current_connections as f64
66 }
67 }
68}
69
70pub struct PoolScaler {
72 config: PoolScalerConfig,
73 target_connections: Arc<AtomicUsize>,
74 running: Arc<AtomicBool>,
75 scale_up_count: Arc<AtomicUsize>,
76 scale_down_count: Arc<AtomicUsize>,
77}
78
79impl PoolScaler {
80 pub fn new(config: PoolScalerConfig) -> Self {
82 let initial = config.min_connections;
83 Self {
84 config,
85 target_connections: Arc::new(AtomicUsize::new(initial)),
86 running: Arc::new(AtomicBool::new(false)),
87 scale_up_count: Arc::new(AtomicUsize::new(0)),
88 scale_down_count: Arc::new(AtomicUsize::new(0)),
89 }
90 }
91
92 pub fn target_connections(&self) -> usize {
94 self.target_connections.load(Ordering::Acquire)
95 }
96
97 pub fn is_running(&self) -> bool {
99 self.running.load(Ordering::Relaxed)
100 }
101
102 pub fn scale_up_count(&self) -> usize {
104 self.scale_up_count.load(Ordering::Relaxed)
105 }
106
107 pub fn scale_down_count(&self) -> usize {
109 self.scale_down_count.load(Ordering::Relaxed)
110 }
111
112 pub fn scale_up(&self, metrics: &PoolMetrics) {
114 if metrics.timeout_rate() > self.config.scale_up_threshold {
115 let current = self.target_connections.load(Ordering::Relaxed);
116 let new_target = (current + (current / 4)).min(self.config.max_connections);
117 self.target_connections.store(new_target, Ordering::Release);
118 self.scale_up_count.fetch_add(1, Ordering::Relaxed);
119 }
120 }
121
122 pub fn scale_down(&self, metrics: &PoolMetrics) {
124 if metrics.idle_rate() > self.config.scale_down_threshold {
125 let current = self.target_connections.load(Ordering::Relaxed);
126 let new_target = (current - (current / 4)).max(self.config.min_connections);
127 self.target_connections.store(new_target, Ordering::Release);
128 self.scale_down_count.fetch_add(1, Ordering::Relaxed);
129 }
130 }
131
132 pub fn adjust(&self, metrics: &PoolMetrics) {
134 if metrics.timeout_rate() > self.config.scale_up_threshold {
135 self.scale_up(metrics);
136 } else if metrics.idle_rate() > self.config.scale_down_threshold {
137 self.scale_down(metrics);
138 }
139 }
140}
141
142impl std::fmt::Debug for PoolScaler {
143 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
144 let target = self.target_connections.load(Ordering::Acquire);
145 write!(
146 f,
147 "PoolScaler {{ target: {target}, running: {} }}",
148 self.running.load(Ordering::Relaxed)
149 )
150 }
151}
152
153#[cfg(test)]
158mod tests {
159 use super::*;
160
161 #[test]
162 fn test_pool_metrics_timeout_rate() {
163 let metrics = PoolMetrics {
164 current_connections: 10,
165 idle_connections: 3,
166 timeout_count: 5,
167 total_acquire: 100,
168 };
169 assert_eq!(metrics.timeout_rate(), 0.05);
170 }
171
172 #[test]
173 fn test_pool_metrics_timeout_rate_zero() {
174 let metrics = PoolMetrics {
175 current_connections: 10,
176 idle_connections: 3,
177 timeout_count: 0,
178 total_acquire: 0,
179 };
180 assert_eq!(metrics.timeout_rate(), 0.0);
181 }
182
183 #[test]
184 fn test_pool_metrics_idle_rate() {
185 let metrics = PoolMetrics {
186 current_connections: 10,
187 idle_connections: 7,
188 timeout_count: 0,
189 total_acquire: 100,
190 };
191 assert_eq!(metrics.idle_rate(), 0.7);
192 }
193
194 #[test]
195 fn test_config_default() {
196 let config = PoolScalerConfig::default();
197 assert_eq!(config.scale_up_threshold, 0.3);
198 assert_eq!(config.scale_down_threshold, 0.7);
199 assert_eq!(config.check_interval, Duration::from_secs(30));
200 }
201
202 #[test]
203 fn test_scaler_new() {
204 let config = PoolScalerConfig::default();
205 let scaler = PoolScaler::new(config);
206 assert_eq!(scaler.target_connections(), 5);
207 assert!(!scaler.is_running());
208 }
209
210 #[test]
211 fn test_scale_up() {
212 let config = PoolScalerConfig::default();
213 let scaler = PoolScaler::new(config);
214 scaler.target_connections.store(10, Ordering::Relaxed);
215 let metrics = PoolMetrics {
216 current_connections: 10,
217 idle_connections: 2,
218 timeout_count: 50,
219 total_acquire: 100,
220 };
221 scaler.scale_up(&metrics);
222 assert!(scaler.target_connections() > 10);
223 assert_eq!(scaler.scale_up_count(), 1);
224 }
225
226 #[test]
227 fn test_scale_down() {
228 let config = PoolScalerConfig::default();
229 let scaler = PoolScaler::new(config);
230 scaler.target_connections.store(20, Ordering::Relaxed);
231 let metrics = PoolMetrics {
232 current_connections: 20,
233 idle_connections: 18,
234 timeout_count: 0,
235 total_acquire: 100,
236 };
237 scaler.scale_down(&metrics);
238 assert!(scaler.target_connections() < 20);
239 assert_eq!(scaler.scale_down_count(), 1);
240 }
241
242 #[test]
243 fn test_scale_up_max_cap() {
244 let config = PoolScalerConfig::default();
245 let scaler = PoolScaler::new(config);
246 scaler.target_connections.store(95, Ordering::Relaxed);
247 let metrics = PoolMetrics {
248 current_connections: 95,
249 idle_connections: 0,
250 timeout_count: 50,
251 total_acquire: 100,
252 };
253 scaler.scale_up(&metrics);
254 assert_eq!(scaler.target_connections(), 100);
255 }
256
257 #[test]
258 fn test_scale_down_min_cap() {
259 let config = PoolScalerConfig::default();
260 let scaler = PoolScaler::new(config);
261 scaler.target_connections.store(6, Ordering::Relaxed);
262 let metrics = PoolMetrics {
263 current_connections: 6,
264 idle_connections: 5,
265 timeout_count: 0,
266 total_acquire: 100,
267 };
268 scaler.scale_down(&metrics);
269 assert_eq!(scaler.target_connections(), 5);
271 }
272
273 #[test]
274 fn test_adjust_scale_up() {
275 let config = PoolScalerConfig::default();
276 let scaler = PoolScaler::new(config);
277 scaler.target_connections.store(10, Ordering::Relaxed);
278 let metrics = PoolMetrics {
279 current_connections: 10,
280 idle_connections: 0,
281 timeout_count: 50,
282 total_acquire: 100,
283 };
284 scaler.adjust(&metrics);
285 assert_eq!(scaler.scale_up_count(), 1);
286 assert_eq!(scaler.scale_down_count(), 0);
287 }
288
289 #[test]
290 fn test_adjust_scale_down() {
291 let config = PoolScalerConfig::default();
292 let scaler = PoolScaler::new(config);
293 scaler.target_connections.store(20, Ordering::Relaxed);
294 let metrics = PoolMetrics {
295 current_connections: 20,
296 idle_connections: 18,
297 timeout_count: 0,
298 total_acquire: 100,
299 };
300 scaler.adjust(&metrics);
301 assert_eq!(scaler.scale_up_count(), 0);
302 assert_eq!(scaler.scale_down_count(), 1);
303 }
304}