1use std::sync::atomic::{AtomicBool, AtomicU32, AtomicU64, Ordering};
26use std::time::Duration;
27
28#[derive(Debug, Clone)]
34pub struct ProgressiveConfig {
35 pub batch_size: u32,
37 pub interval: Duration,
39 pub total_timeout: Duration,
41}
42
43impl Default for ProgressiveConfig {
44 fn default() -> Self {
45 Self {
46 batch_size: 2,
47 interval: Duration::from_millis(10),
48 total_timeout: Duration::from_secs(30),
49 }
50 }
51}
52
53impl ProgressiveConfig {
54 pub fn new(batch_size: u32, interval: Duration, total_timeout: Duration) -> Self {
56 Self {
57 batch_size: batch_size.max(1),
58 interval,
59 total_timeout,
60 }
61 }
62
63 pub fn with_batch_size(mut self, size: u32) -> Self {
65 self.batch_size = size.max(1);
66 self
67 }
68
69 pub fn with_interval(mut self, interval: Duration) -> Self {
71 self.interval = interval;
72 self
73 }
74
75 pub fn with_total_timeout(mut self, timeout: Duration) -> Self {
77 self.total_timeout = timeout;
78 self
79 }
80}
81
82#[derive(Debug, Clone, Default)]
84pub struct PrewarmConfig {
85 pub auto_prewarm: bool,
87 pub progressive: Option<ProgressiveConfig>,
89}
90
91impl PrewarmConfig {
92 pub fn new() -> Self {
94 Self::default()
95 }
96
97 pub fn with_auto_prewarm(mut self, enabled: bool) -> Self {
99 self.auto_prewarm = enabled;
100 self
101 }
102
103 pub fn with_progressive(mut self, config: ProgressiveConfig) -> Self {
105 self.progressive = Some(config);
106 self
107 }
108}
109
110#[derive(Debug)]
116pub struct PrewarmProgress {
117 warmed: AtomicU32,
118 target: u32,
119 failed: AtomicU32,
120 elapsed_ns: AtomicU64,
121 is_completed: AtomicBool,
122}
123
124impl PrewarmProgress {
125 pub fn new(target: u32) -> Self {
127 Self {
128 warmed: AtomicU32::new(0),
129 target,
130 failed: AtomicU32::new(0),
131 elapsed_ns: AtomicU64::new(0),
132 is_completed: AtomicBool::new(false),
133 }
134 }
135
136 pub fn record_success(&self) {
138 self.warmed.fetch_add(1, Ordering::Relaxed);
139 }
140
141 pub fn record_failure(&self) {
143 self.failed.fetch_add(1, Ordering::Relaxed);
144 }
145
146 pub fn set_elapsed(&self, duration: Duration) {
148 self.elapsed_ns
149 .store(duration.as_nanos() as u64, Ordering::Relaxed);
150 }
151
152 pub fn mark_completed(&self) {
154 self.is_completed.store(true, Ordering::Release);
155 }
156
157 pub fn snapshot(&self) -> PrewarmProgressSnapshot {
159 PrewarmProgressSnapshot {
160 warmed: self.warmed.load(Ordering::Relaxed),
161 target: self.target,
162 failed: self.failed.load(Ordering::Relaxed),
163 elapsed: Duration::from_nanos(self.elapsed_ns.load(Ordering::Relaxed)),
164 is_completed: self.is_completed.load(Ordering::Acquire),
165 }
166 }
167}
168
169#[derive(Debug, Clone)]
171pub struct PrewarmProgressSnapshot {
172 pub warmed: u32,
174 pub target: u32,
176 pub failed: u32,
178 pub elapsed: Duration,
180 pub is_completed: bool,
182}
183
184impl PrewarmProgressSnapshot {
185 pub fn percent(&self) -> f64 {
187 if self.target == 0 {
188 1.0
189 } else {
190 (self.warmed + self.failed) as f64 / self.target as f64
191 }
192 }
193
194 pub fn all_succeeded(&self) -> bool {
196 self.is_completed && self.failed == 0 && self.warmed == self.target
197 }
198}
199
200#[derive(Debug, Clone)]
206pub struct BackendPrewarmResult {
207 pub backend: String,
209 pub warmed: u32,
211 pub failed: u32,
213 pub elapsed: Duration,
215 pub errors: Vec<String>,
217}
218
219#[derive(Debug, Clone)]
221pub struct PrewarmSummary {
222 pub results: Vec<BackendPrewarmResult>,
224}
225
226impl PrewarmSummary {
227 pub fn new() -> Self {
229 Self {
230 results: Vec::new(),
231 }
232 }
233
234 pub fn add(&mut self, result: BackendPrewarmResult) {
236 self.results.push(result);
237 }
238
239 pub fn total_warmed(&self) -> u32 {
241 self.results.iter().map(|r| r.warmed).sum()
242 }
243
244 pub fn total_failed(&self) -> u32 {
246 self.results.iter().map(|r| r.failed).sum()
247 }
248
249 pub fn all_succeeded(&self) -> bool {
251 !self.results.is_empty() && self.results.iter().all(|r| r.failed == 0)
252 }
253}
254
255impl Default for PrewarmSummary {
256 fn default() -> Self {
257 Self::new()
258 }
259}
260
261#[cfg(test)]
262mod tests {
263 use super::*;
264
265 #[test]
266 fn test_prewarm_config_defaults() {
267 let config = PrewarmConfig::default();
268 assert!(!config.auto_prewarm);
269 assert!(config.progressive.is_none());
270 }
271
272 #[test]
273 fn test_prewarm_config_builders() {
274 let config = PrewarmConfig::new()
275 .with_auto_prewarm(true)
276 .with_progressive(ProgressiveConfig::default());
277 assert!(config.auto_prewarm);
278 assert!(config.progressive.is_some());
279 }
280
281 #[test]
282 fn test_progressive_config_defaults() {
283 let config = ProgressiveConfig::default();
284 assert_eq!(config.batch_size, 2);
285 assert_eq!(config.interval, Duration::from_millis(10));
286 assert_eq!(config.total_timeout, Duration::from_secs(30));
287 }
288
289 #[test]
290 fn test_progressive_config_batch_size_min_1() {
291 let config = ProgressiveConfig::new(0, Duration::from_millis(5), Duration::from_secs(10));
292 assert_eq!(config.batch_size, 1);
293 }
294
295 #[test]
296 fn test_prewarm_progress_snapshot() {
297 let progress = PrewarmProgress::new(10);
298 progress.record_success();
299 progress.record_success();
300 progress.record_failure();
301 progress.set_elapsed(Duration::from_millis(100));
302 progress.mark_completed();
303
304 let snap = progress.snapshot();
305 assert_eq!(snap.warmed, 2);
306 assert_eq!(snap.target, 10);
307 assert_eq!(snap.failed, 1);
308 assert_eq!(snap.elapsed, Duration::from_millis(100));
309 assert!(snap.is_completed);
310 assert!((snap.percent() - 0.3).abs() < 0.001);
311 }
312
313 #[test]
314 fn test_prewarm_progress_all_succeeded() {
315 let progress = PrewarmProgress::new(3);
316 progress.record_success();
317 progress.record_success();
318 progress.record_success();
319 progress.mark_completed();
320
321 let snap = progress.snapshot();
322 assert!(snap.all_succeeded());
323 }
324
325 #[test]
326 fn test_prewarm_progress_not_all_succeeded_with_failure() {
327 let progress = PrewarmProgress::new(3);
328 progress.record_success();
329 progress.record_success();
330 progress.record_failure();
331 progress.mark_completed();
332
333 let snap = progress.snapshot();
334 assert!(!snap.all_succeeded());
335 }
336
337 #[test]
338 fn test_prewarm_summary_aggregation() {
339 let mut summary = PrewarmSummary::new();
340 summary.add(BackendPrewarmResult {
341 backend: "mysql".into(),
342 warmed: 5,
343 failed: 0,
344 elapsed: Duration::from_millis(50),
345 errors: vec![],
346 });
347 summary.add(BackendPrewarmResult {
348 backend: "pg".into(),
349 warmed: 3,
350 failed: 1,
351 elapsed: Duration::from_millis(40),
352 errors: vec!["connection refused".into()],
353 });
354
355 assert_eq!(summary.total_warmed(), 8);
356 assert_eq!(summary.total_failed(), 1);
357 assert!(!summary.all_succeeded());
358 }
359
360 #[test]
361 fn test_prewarm_summary_all_succeeded() {
362 let mut summary = PrewarmSummary::new();
363 summary.add(BackendPrewarmResult {
364 backend: "mysql".into(),
365 warmed: 5,
366 failed: 0,
367 elapsed: Duration::from_millis(50),
368 errors: vec![],
369 });
370 summary.add(BackendPrewarmResult {
371 backend: "pg".into(),
372 warmed: 3,
373 failed: 0,
374 elapsed: Duration::from_millis(40),
375 errors: vec![],
376 });
377
378 assert_eq!(summary.total_warmed(), 8);
379 assert_eq!(summary.total_failed(), 0);
380 assert!(summary.all_succeeded());
381 }
382
383 #[test]
384 fn test_prewarm_summary_empty() {
385 let summary = PrewarmSummary::new();
386 assert_eq!(summary.total_warmed(), 0);
387 assert_eq!(summary.total_failed(), 0);
388 assert!(!summary.all_succeeded());
389 }
390
391 #[test]
392 fn test_progressive_config_builders() {
393 let config = ProgressiveConfig::default()
394 .with_batch_size(5)
395 .with_interval(Duration::from_millis(20))
396 .with_total_timeout(Duration::from_secs(60));
397 assert_eq!(config.batch_size, 5);
398 assert_eq!(config.interval, Duration::from_millis(20));
399 assert_eq!(config.total_timeout, Duration::from_secs(60));
400 }
401
402 #[test]
403 fn test_progressive_config_with_batch_size_min_1() {
404 let config = ProgressiveConfig::default().with_batch_size(0);
405 assert_eq!(config.batch_size, 1);
406 }
407
408 #[test]
409 fn test_progressive_config_interval_zero() {
410 let config = ProgressiveConfig::new(2, Duration::ZERO, Duration::from_secs(10));
411 assert_eq!(config.interval, Duration::ZERO);
412 }
413
414 #[test]
415 fn test_progressive_config_total_timeout_zero() {
416 let config = ProgressiveConfig::new(2, Duration::from_millis(5), Duration::ZERO);
417 assert_eq!(config.total_timeout, Duration::ZERO);
418 }
419
420 #[test]
421 fn test_prewarm_progress_percent_zero() {
422 let progress = PrewarmProgress::new(5);
423 let snap = progress.snapshot();
424 assert!((snap.percent() - 0.0).abs() < 0.001);
425 }
426
427 #[test]
428 fn test_prewarm_progress_percent_full() {
429 let progress = PrewarmProgress::new(3);
430 progress.record_success();
431 progress.record_success();
432 progress.record_success();
433 progress.mark_completed();
434 let snap = progress.snapshot();
435 assert!((snap.percent() - 1.0).abs() < 0.001);
436 }
437
438 #[test]
439 fn test_prewarm_progress_warmed_plus_failed_le_target() {
440 let progress = PrewarmProgress::new(10);
441 for _ in 0..7 {
442 progress.record_success();
443 }
444 for _ in 0..3 {
445 progress.record_failure();
446 }
447 progress.mark_completed();
448 let snap = progress.snapshot();
449 assert!(snap.warmed + snap.failed <= snap.target);
450 assert_eq!(snap.warmed + snap.failed, 10);
451 }
452
453 #[test]
454 fn test_prewarm_progress_target_zero() {
455 let progress = PrewarmProgress::new(0);
456 let snap = progress.snapshot();
457 assert_eq!(snap.target, 0);
458 assert!(
459 (snap.percent() - 1.0).abs() < 0.001,
460 "target=0 时 percent 应为 1.0"
461 );
462 }
463
464 #[test]
465 fn test_backend_prewarm_result_fields() {
466 let result = BackendPrewarmResult {
467 backend: "mysql".into(),
468 warmed: 10,
469 failed: 2,
470 elapsed: Duration::from_millis(200),
471 errors: vec!["timeout".into(), "refused".into()],
472 };
473 assert_eq!(result.backend, "mysql");
474 assert_eq!(result.warmed, 10);
475 assert_eq!(result.failed, 2);
476 assert_eq!(result.errors.len(), 2);
477 }
478
479 #[test]
480 fn test_prewarm_summary_partial_failure() {
481 let mut summary = PrewarmSummary::new();
482 summary.add(BackendPrewarmResult {
483 backend: "mysql".into(),
484 warmed: 5,
485 failed: 0,
486 elapsed: Duration::from_millis(50),
487 errors: vec![],
488 });
489 summary.add(BackendPrewarmResult {
490 backend: "oracle".into(),
491 warmed: 0,
492 failed: 3,
493 elapsed: Duration::from_millis(30),
494 errors: vec!["unreachable".into()],
495 });
496 assert_eq!(summary.total_warmed(), 5);
497 assert_eq!(summary.total_failed(), 3);
498 assert!(!summary.all_succeeded());
499 assert_eq!(summary.results.len(), 2);
500 }
501}