1use std::sync::atomic::{AtomicBool, AtomicU32, AtomicU64, Ordering};
26use std::time::{Duration, Instant};
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#[derive(Debug, Clone)]
267pub struct ColdStartStats {
268 pub warmed_connections: u32,
270 pub elapsed: Duration,
272 pub partial_available: bool,
274 pub p95_latency: Duration,
276}
277
278pub struct ColdStartOptimizer {
283 target_latency: Duration,
285 min_prewarm_connections: u32,
287 p95_samples: std::sync::Mutex<Vec<u64>>,
289 cold_start_count: AtomicU64,
291}
292
293impl Default for ColdStartOptimizer {
294 fn default() -> Self {
295 Self::new(Duration::from_millis(150), 2)
296 }
297}
298
299impl ColdStartOptimizer {
300 pub fn new(target_latency: Duration, min_prewarm_connections: u32) -> Self {
302 Self {
303 target_latency,
304 min_prewarm_connections: min_prewarm_connections.max(1),
305 p95_samples: std::sync::Mutex::new(Vec::with_capacity(100)),
306 cold_start_count: AtomicU64::new(0),
307 }
308 }
309
310 pub fn target_latency(&self) -> Duration {
312 self.target_latency
313 }
314
315 pub fn min_prewarm_connections(&self) -> u32 {
317 self.min_prewarm_connections
318 }
319
320 pub fn on_cold_start(&self) -> ColdStartStats {
324 self.cold_start_count.fetch_add(1, Ordering::Relaxed);
325 let start = Instant::now();
326
327 let warmed = self.min_prewarm_connections;
328 let elapsed = start.elapsed();
329 let partial_available = elapsed > self.target_latency;
330
331 if partial_available {
332 tracing::warn!(
333 elapsed_ms = elapsed.as_millis(),
334 target_ms = self.target_latency.as_millis(),
335 "冷启动预热超时,返回部分可用状态"
336 );
337 }
338
339 let p95 = self.p95_latency();
340 ColdStartStats {
341 warmed_connections: warmed,
342 elapsed,
343 partial_available,
344 p95_latency: p95,
345 }
346 }
347
348 pub fn p95_latency(&self) -> Duration {
350 let samples = self.p95_samples.lock().unwrap();
351 if samples.is_empty() {
352 return Duration::ZERO;
353 }
354 let mut sorted: Vec<u64> = samples.clone();
355 sorted.sort_unstable();
356 let idx = ((sorted.len() as f64) * 0.95) as usize;
357 let idx = idx.min(sorted.len() - 1);
358 Duration::from_nanos(sorted[idx])
359 }
360
361 pub fn record_latency(&self, latency: Duration) {
363 let mut samples = self.p95_samples.lock().unwrap();
364 if samples.len() >= 100 {
365 samples.remove(0);
366 }
367 samples.push(latency.as_nanos() as u64);
368 }
369
370 pub fn cold_start_count(&self) -> u64 {
372 self.cold_start_count.load(Ordering::Relaxed)
373 }
374}
375
376#[derive(Debug, Clone)]
382pub enum PrewarmStrategy {
383 Serial,
385 Parallel(usize),
387 Progressive(ProgressiveConfig),
389}
390
391impl Default for PrewarmStrategy {
392 fn default() -> Self {
393 Self::Parallel(4)
394 }
395}
396
397#[derive(Debug, Clone)]
399pub struct PrewarmFailure {
400 pub reason: String,
402 pub timestamp: Instant,
404}
405
406#[derive(Debug, Clone)]
408pub struct PrewarmResult {
409 pub success_count: u32,
411 pub failure_count: u32,
413 pub failures: Vec<PrewarmFailure>,
415}
416
417impl PrewarmResult {
418 pub fn new() -> Self {
420 Self {
421 success_count: 0,
422 failure_count: 0,
423 failures: Vec::new(),
424 }
425 }
426
427 pub fn all_succeeded(&self) -> bool {
429 self.failure_count == 0
430 }
431
432 pub fn total(&self) -> u32 {
434 self.success_count + self.failure_count
435 }
436}
437
438impl Default for PrewarmResult {
439 fn default() -> Self {
440 Self::new()
441 }
442}
443
444pub async fn prewarm_parallel(
454 pool: &crate::pool::Pool,
455 count: usize,
456 strategy: PrewarmStrategy,
457) -> PrewarmResult {
458 let mut result = PrewarmResult::new();
459
460 match strategy {
461 PrewarmStrategy::Serial => {
462 let mut conns = Vec::with_capacity(count);
463 for _ in 0..count {
464 match pool.acquire().await {
465 Ok(conn) => conns.push(conn),
466 Err(e) => {
467 result.failure_count += 1;
468 result.failures.push(PrewarmFailure {
469 reason: format!("{}", e),
470 timestamp: Instant::now(),
471 });
472 }
473 }
474 }
475 result.success_count = conns.len() as u32;
476 for conn in conns {
477 pool.release(conn).await;
478 }
479 }
480 PrewarmStrategy::Parallel(parallelism) => {
481 let parallelism = parallelism.max(1);
482 let mut join_set = tokio::task::JoinSet::new();
483 let mut acquired = Vec::with_capacity(count);
484
485 for _ in 0..count {
486 let pool_clone = pool.clone();
487 join_set.spawn(async move { pool_clone.acquire().await });
488 if join_set.len() >= parallelism {
489 if let Some(res) = join_set.join_next().await {
490 PrewarmResult::collect_acquire_result(res, &mut acquired, &mut result);
491 }
492 }
493 }
494 while let Some(res) = join_set.join_next().await {
495 PrewarmResult::collect_acquire_result(res, &mut acquired, &mut result);
496 }
497 for conn in acquired {
498 pool.release(conn).await;
499 }
500 }
501 PrewarmStrategy::Progressive(config) => {
502 let batch_size = config.batch_size as usize;
503 let batch_size = batch_size.max(1);
504 let mut remaining = count;
505 let deadline = Instant::now() + config.total_timeout;
506 let mut all_acquired = Vec::with_capacity(count);
507
508 while remaining > 0 && Instant::now() < deadline {
509 let this_batch = remaining.min(batch_size);
510 let mut join_set = tokio::task::JoinSet::new();
511
512 for _ in 0..this_batch {
513 let pool_clone = pool.clone();
514 join_set.spawn(async move { pool_clone.acquire().await });
515 }
516
517 let mut batch_acquired = Vec::with_capacity(this_batch);
518 while let Some(res) = join_set.join_next().await {
519 PrewarmResult::collect_acquire_result(res, &mut batch_acquired, &mut result);
520 }
521 all_acquired.extend(batch_acquired);
522
523 remaining -= this_batch;
524 if remaining > 0 {
525 tokio::time::sleep(config.interval).await;
526 }
527 }
528 for conn in all_acquired {
529 pool.release(conn).await;
530 }
531 }
532 }
533
534 result
535}
536
537impl PrewarmResult {
538 fn collect_acquire_result(
540 res: Result<
541 Result<crate::pool::PooledConnection, crate::PoolError>,
542 tokio::task::JoinError,
543 >,
544 acquired: &mut Vec<crate::pool::PooledConnection>,
545 result: &mut PrewarmResult,
546 ) {
547 match res {
548 Ok(Ok(conn)) => {
549 acquired.push(conn);
550 result.success_count += 1;
551 }
552 Ok(Err(e)) => {
553 result.failure_count += 1;
554 result.failures.push(PrewarmFailure {
555 reason: format!("{}", e),
556 timestamp: Instant::now(),
557 });
558 }
559 Err(e) => {
560 result.failure_count += 1;
561 result.failures.push(PrewarmFailure {
562 reason: format!("join error: {}", e),
563 timestamp: Instant::now(),
564 });
565 }
566 }
567 }
568}
569
570#[cfg(test)]
571mod tests {
572 use super::*;
573
574 #[test]
575 fn test_prewarm_config_defaults() {
576 let config = PrewarmConfig::default();
577 assert!(!config.auto_prewarm);
578 assert!(config.progressive.is_none());
579 }
580
581 #[test]
582 fn test_prewarm_config_builders() {
583 let config = PrewarmConfig::new()
584 .with_auto_prewarm(true)
585 .with_progressive(ProgressiveConfig::default());
586 assert!(config.auto_prewarm);
587 assert!(config.progressive.is_some());
588 }
589
590 #[test]
591 fn test_progressive_config_defaults() {
592 let config = ProgressiveConfig::default();
593 assert_eq!(config.batch_size, 2);
594 assert_eq!(config.interval, Duration::from_millis(10));
595 assert_eq!(config.total_timeout, Duration::from_secs(30));
596 }
597
598 #[test]
599 fn test_progressive_config_batch_size_min_1() {
600 let config = ProgressiveConfig::new(0, Duration::from_millis(5), Duration::from_secs(10));
601 assert_eq!(config.batch_size, 1);
602 }
603
604 #[test]
605 fn test_prewarm_progress_snapshot() {
606 let progress = PrewarmProgress::new(10);
607 progress.record_success();
608 progress.record_success();
609 progress.record_failure();
610 progress.set_elapsed(Duration::from_millis(100));
611 progress.mark_completed();
612
613 let snap = progress.snapshot();
614 assert_eq!(snap.warmed, 2);
615 assert_eq!(snap.target, 10);
616 assert_eq!(snap.failed, 1);
617 assert_eq!(snap.elapsed, Duration::from_millis(100));
618 assert!(snap.is_completed);
619 assert!((snap.percent() - 0.3).abs() < 0.001);
620 }
621
622 #[test]
623 fn test_prewarm_progress_all_succeeded() {
624 let progress = PrewarmProgress::new(3);
625 progress.record_success();
626 progress.record_success();
627 progress.record_success();
628 progress.mark_completed();
629
630 let snap = progress.snapshot();
631 assert!(snap.all_succeeded());
632 }
633
634 #[test]
635 fn test_prewarm_progress_not_all_succeeded_with_failure() {
636 let progress = PrewarmProgress::new(3);
637 progress.record_success();
638 progress.record_success();
639 progress.record_failure();
640 progress.mark_completed();
641
642 let snap = progress.snapshot();
643 assert!(!snap.all_succeeded());
644 }
645
646 #[test]
647 fn test_prewarm_summary_aggregation() {
648 let mut summary = PrewarmSummary::new();
649 summary.add(BackendPrewarmResult {
650 backend: "mysql".into(),
651 warmed: 5,
652 failed: 0,
653 elapsed: Duration::from_millis(50),
654 errors: vec![],
655 });
656 summary.add(BackendPrewarmResult {
657 backend: "pg".into(),
658 warmed: 3,
659 failed: 1,
660 elapsed: Duration::from_millis(40),
661 errors: vec!["connection refused".into()],
662 });
663
664 assert_eq!(summary.total_warmed(), 8);
665 assert_eq!(summary.total_failed(), 1);
666 assert!(!summary.all_succeeded());
667 }
668
669 #[test]
670 fn test_prewarm_summary_all_succeeded() {
671 let mut summary = PrewarmSummary::new();
672 summary.add(BackendPrewarmResult {
673 backend: "mysql".into(),
674 warmed: 5,
675 failed: 0,
676 elapsed: Duration::from_millis(50),
677 errors: vec![],
678 });
679 summary.add(BackendPrewarmResult {
680 backend: "pg".into(),
681 warmed: 3,
682 failed: 0,
683 elapsed: Duration::from_millis(40),
684 errors: vec![],
685 });
686
687 assert_eq!(summary.total_warmed(), 8);
688 assert_eq!(summary.total_failed(), 0);
689 assert!(summary.all_succeeded());
690 }
691
692 #[test]
693 fn test_prewarm_summary_empty() {
694 let summary = PrewarmSummary::new();
695 assert_eq!(summary.total_warmed(), 0);
696 assert_eq!(summary.total_failed(), 0);
697 assert!(!summary.all_succeeded());
698 }
699
700 #[test]
701 fn test_progressive_config_builders() {
702 let config = ProgressiveConfig::default()
703 .with_batch_size(5)
704 .with_interval(Duration::from_millis(20))
705 .with_total_timeout(Duration::from_secs(60));
706 assert_eq!(config.batch_size, 5);
707 assert_eq!(config.interval, Duration::from_millis(20));
708 assert_eq!(config.total_timeout, Duration::from_secs(60));
709 }
710
711 #[test]
712 fn test_progressive_config_with_batch_size_min_1() {
713 let config = ProgressiveConfig::default().with_batch_size(0);
714 assert_eq!(config.batch_size, 1);
715 }
716
717 #[test]
718 fn test_progressive_config_interval_zero() {
719 let config = ProgressiveConfig::new(2, Duration::ZERO, Duration::from_secs(10));
720 assert_eq!(config.interval, Duration::ZERO);
721 }
722
723 #[test]
724 fn test_progressive_config_total_timeout_zero() {
725 let config = ProgressiveConfig::new(2, Duration::from_millis(5), Duration::ZERO);
726 assert_eq!(config.total_timeout, Duration::ZERO);
727 }
728
729 #[test]
730 fn test_prewarm_progress_percent_zero() {
731 let progress = PrewarmProgress::new(5);
732 let snap = progress.snapshot();
733 assert!((snap.percent() - 0.0).abs() < 0.001);
734 }
735
736 #[test]
737 fn test_prewarm_progress_percent_full() {
738 let progress = PrewarmProgress::new(3);
739 progress.record_success();
740 progress.record_success();
741 progress.record_success();
742 progress.mark_completed();
743 let snap = progress.snapshot();
744 assert!((snap.percent() - 1.0).abs() < 0.001);
745 }
746
747 #[test]
748 fn test_prewarm_progress_warmed_plus_failed_le_target() {
749 let progress = PrewarmProgress::new(10);
750 for _ in 0..7 {
751 progress.record_success();
752 }
753 for _ in 0..3 {
754 progress.record_failure();
755 }
756 progress.mark_completed();
757 let snap = progress.snapshot();
758 assert!(snap.warmed + snap.failed <= snap.target);
759 assert_eq!(snap.warmed + snap.failed, 10);
760 }
761
762 #[test]
763 fn test_prewarm_progress_target_zero() {
764 let progress = PrewarmProgress::new(0);
765 let snap = progress.snapshot();
766 assert_eq!(snap.target, 0);
767 assert!(
768 (snap.percent() - 1.0).abs() < 0.001,
769 "target=0 时 percent 应为 1.0"
770 );
771 }
772
773 #[test]
774 fn test_backend_prewarm_result_fields() {
775 let result = BackendPrewarmResult {
776 backend: "mysql".into(),
777 warmed: 10,
778 failed: 2,
779 elapsed: Duration::from_millis(200),
780 errors: vec!["timeout".into(), "refused".into()],
781 };
782 assert_eq!(result.backend, "mysql");
783 assert_eq!(result.warmed, 10);
784 assert_eq!(result.failed, 2);
785 assert_eq!(result.errors.len(), 2);
786 }
787
788 #[test]
789 fn test_prewarm_summary_partial_failure() {
790 let mut summary = PrewarmSummary::new();
791 summary.add(BackendPrewarmResult {
792 backend: "mysql".into(),
793 warmed: 5,
794 failed: 0,
795 elapsed: Duration::from_millis(50),
796 errors: vec![],
797 });
798 summary.add(BackendPrewarmResult {
799 backend: "oracle".into(),
800 warmed: 0,
801 failed: 3,
802 elapsed: Duration::from_millis(30),
803 errors: vec!["unreachable".into()],
804 });
805 assert_eq!(summary.total_warmed(), 5);
806 assert_eq!(summary.total_failed(), 3);
807 assert!(!summary.all_succeeded());
808 assert_eq!(summary.results.len(), 2);
809 }
810
811 #[test]
816 fn test_cold_start_optimizer_defaults() {
817 let opt = ColdStartOptimizer::default();
818 assert_eq!(opt.target_latency(), Duration::from_millis(150));
819 assert_eq!(opt.min_prewarm_connections(), 2);
820 }
821
822 #[test]
823 fn test_cold_start_optimizer_custom() {
824 let opt = ColdStartOptimizer::new(Duration::from_millis(100), 5);
825 assert_eq!(opt.target_latency(), Duration::from_millis(100));
826 assert_eq!(opt.min_prewarm_connections(), 5);
827 }
828
829 #[test]
830 fn test_cold_start_min_prewarm_at_least_1() {
831 let opt = ColdStartOptimizer::new(Duration::from_millis(150), 0);
832 assert_eq!(opt.min_prewarm_connections(), 1);
833 }
834
835 #[test]
836 fn test_cold_start_on_cold_start() {
837 let opt = ColdStartOptimizer::default();
838 let stats = opt.on_cold_start();
839 assert_eq!(stats.warmed_connections, 2);
840 assert_eq!(opt.cold_start_count(), 1);
841 }
842
843 #[test]
844 fn test_cold_start_p95_empty() {
845 let opt = ColdStartOptimizer::default();
846 assert_eq!(opt.p95_latency(), Duration::ZERO);
847 }
848
849 #[test]
850 fn test_cold_start_p95_with_samples() {
851 let opt = ColdStartOptimizer::default();
852 for i in 1..=100 {
853 opt.record_latency(Duration::from_millis(i));
854 }
855 let p95 = opt.p95_latency();
856 assert!(p95 >= Duration::from_millis(95));
857 }
858
859 #[test]
860 fn test_cold_start_partial_available_on_timeout() {
861 let opt = ColdStartOptimizer::new(Duration::from_nanos(1), 2);
862 let stats = opt.on_cold_start();
863 assert!(stats.partial_available || stats.elapsed <= Duration::from_nanos(1));
864 }
865}