scion_stack/path/manager/reliability.rs
1// Copyright 2025 Anapaya Systems
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use std::time::{Duration, SystemTime};
16
17use crate::path::{manager::algo::exponential_decay, types::Score};
18
19/// Duration after which the reliability score decays to half its value.
20/// After 20 half-lives, the score approaches zero.
21const EXPONENTIAL_DECAY_HALFLIFE: Duration = Duration::from_secs(90); // Full decay in ~30 min
22
23/// Reliability score for a path
24///
25/// A reliability score indicates how reliable a path is, based on reported issues.
26/// The score decays over time, allowing paths to recover over time if no further issues are
27/// reported.
28#[derive(Debug, Clone)]
29pub struct ReliabilityScore {
30 score: f32,
31 last_updated: SystemTime,
32}
33
34impl ReliabilityScore {
35 /// Returns the current reliability score, decayed to `now`.
36 pub fn score(&self, now: SystemTime) -> Score {
37 Score::new_clamped(exponential_decay(
38 self.score,
39 now.duration_since(self.last_updated)
40 .unwrap_or_else(|_| Duration::from_secs(0)),
41 EXPONENTIAL_DECAY_HALFLIFE,
42 ))
43 }
44
45 /// Creates a new ReliabilityScore with initial score of 0.0
46 ///
47 /// `now` is the current time for initialization, used for decay calculations.
48 pub fn new_with_time(now: SystemTime) -> Self {
49 ReliabilityScore {
50 score: 0.0,
51 last_updated: now,
52 }
53 }
54
55 /// Updates the reliability score based on the reported issue.
56 ///
57 /// `penalty` is the penalty score to apply usually a negative value.
58 /// `now` is the current time for decay calculations.
59 pub fn update(&mut self, penalty: Score, now: SystemTime) {
60 let current_score = self.score(now); // Get decayed score
61 let new_score = current_score.value() + penalty.value(); // Apply penalty
62
63 self.score = new_score.clamp(-1000.0, 1000.0); // For sanity, clamp score
64 self.last_updated = now;
65 }
66}