p2p_chat/sync/engine/discovery/ranking.rs
1//! This module contains logic for ranking mailbox providers based on their
2//! perceived performance and reliability.
3use std::cmp::Ordering;
4
5use libp2p::PeerId;
6
7use super::super::SyncEngine;
8
9impl SyncEngine {
10 /// Ranks a given set of candidate mailbox providers.
11 ///
12 /// Providers are filtered by whether they can be attempted (not backed off)
13 /// and then sorted by their calculated score in descending order.
14 ///
15 /// # Arguments
16 ///
17 /// * `candidates` - An iterator over `PeerId`s of potential mailbox providers.
18 ///
19 /// # Returns
20 ///
21 /// A `Vec` of `PeerId`s, ranked from best to worst.
22 pub(super) fn rank_mailboxes<I>(&self, candidates: I) -> Vec<PeerId>
23 where
24 I: IntoIterator<Item = PeerId>,
25 {
26 let mut providers: Vec<_> = candidates
27 .into_iter()
28 .filter(|peer| self.backoff_manager.can_attempt(peer))
29 .collect();
30
31 providers.sort_by(|a, b| {
32 let score_a = self.calculate_mailbox_score(*a);
33 let score_b = self.calculate_mailbox_score(*b);
34 score_b.partial_cmp(&score_a).unwrap_or(Ordering::Equal)
35 });
36
37 providers
38 }
39
40 /// Calculates a performance score for a single mailbox provider.
41 ///
42 /// The score takes into account:
43 /// - Success rate (70% weight)
44 /// - Recency of last success (20% weight, with bonus for recent success)
45 /// - Average response time (10% weight, bonus for faster responses)
46 /// - Penalty for consecutive failures
47 /// - A significant penalty if the peer is currently in backoff.
48 ///
49 /// The score is clamped between 0.0 and 1.0.
50 ///
51 /// # Arguments
52 ///
53 /// * `peer_id` - The `PeerId` of the mailbox provider.
54 ///
55 /// # Returns
56 ///
57 /// A `f64` representing the performance score of the mailbox.
58 fn calculate_mailbox_score(&self, peer_id: PeerId) -> f64 {
59 let mut score = 0.5;
60
61 if let Some(perf) = self.mailbox_performance.get(&peer_id) {
62 let total_attempts = perf.success_count + perf.failure_count;
63
64 if total_attempts > 0 {
65 let success_rate = perf.success_count as f64 / total_attempts as f64;
66 score = success_rate * 0.7;
67
68 if let Some(last_success) = perf.last_success {
69 let age_hours = last_success.elapsed().as_secs() as f64 / 3600.0;
70 let recency_bonus = (1.0 / (1.0 + age_hours)).min(0.3);
71 score += recency_bonus * 0.2;
72 }
73
74 let response_ms = perf.avg_response_time.as_millis() as f64;
75 let speed_score = (3000.0 - response_ms.min(3000.0)) / 3000.0;
76 score += speed_score * 0.1;
77
78 let failure_penalty = (perf.consecutive_failures as f64 * 0.1).min(0.3);
79 score -= failure_penalty;
80 }
81 }
82
83 if !self.backoff_manager.can_attempt(&peer_id) {
84 score *= 0.1;
85 }
86
87 score.clamp(0.0, 1.0)
88 }
89}