Skip to main content

celers_beat/
wfq.rs

1//! Weighted Fair Queuing (WFQ) types
2//!
3//! Provides WFQ configuration, task weights, and state management for
4//! fair scheduling based on task importance.
5
6use chrono::{DateTime, Utc};
7
8// ============================================================================
9// Weighted Fair Queuing (WFQ)
10// ============================================================================
11
12/// Weighted Fair Queuing configuration
13///
14/// WFQ provides fair scheduling based on task weights, ensuring that higher-weight
15/// tasks get proportionally more execution time while preventing starvation of
16/// lower-weight tasks.
17///
18/// # Algorithm
19/// - Each task has a weight (default: 1.0, range: 0.1-10.0)
20/// - Virtual time tracks fairness: virtual_finish_time = virtual_start_time + (execution_cost / weight)
21/// - Tasks with lowest virtual finish time are scheduled first
22/// - Prevents starvation while respecting priorities
23#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
24pub struct WFQConfig {
25    /// Enable Weighted Fair Queuing
26    pub enabled: bool,
27
28    /// Default weight for tasks without explicit weight
29    pub default_weight: f64,
30
31    /// Minimum allowed weight
32    pub min_weight: f64,
33
34    /// Maximum allowed weight
35    pub max_weight: f64,
36}
37
38impl Default for WFQConfig {
39    fn default() -> Self {
40        Self {
41            enabled: false,
42            default_weight: 1.0,
43            min_weight: 0.1,
44            max_weight: 10.0,
45        }
46    }
47}
48
49/// Task weight for Weighted Fair Queuing
50///
51/// Higher weights mean higher importance and more execution time allocation.
52/// Weight must be between 0.1 and 10.0.
53#[derive(Debug, Clone, Copy, serde::Serialize, serde::Deserialize)]
54pub struct TaskWeight(f64);
55
56impl TaskWeight {
57    /// Create new task weight with validation
58    pub fn new(weight: f64) -> Result<Self, String> {
59        if !(0.1..=10.0).contains(&weight) {
60            return Err(format!(
61                "Weight must be between 0.1 and 10.0, got {}",
62                weight
63            ));
64        }
65        Ok(Self(weight))
66    }
67
68    /// Get the weight value
69    pub fn value(&self) -> f64 {
70        self.0
71    }
72}
73
74impl Default for TaskWeight {
75    fn default() -> Self {
76        Self(1.0)
77    }
78}
79
80/// Weighted Fair Queuing state for a task
81#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
82pub struct WFQState {
83    /// Task weight (higher = more important)
84    #[serde(default)]
85    pub weight: TaskWeight,
86
87    /// Virtual start time
88    #[serde(default)]
89    pub virtual_start_time: f64,
90
91    /// Virtual finish time (determines scheduling order)
92    #[serde(default)]
93    pub virtual_finish_time: f64,
94
95    /// Total execution time consumed (seconds)
96    #[serde(default)]
97    pub total_execution_time: f64,
98}
99
100impl Default for WFQState {
101    fn default() -> Self {
102        Self {
103            weight: TaskWeight::default(),
104            virtual_start_time: 0.0,
105            virtual_finish_time: 0.0,
106            total_execution_time: 0.0,
107        }
108    }
109}
110
111impl WFQState {
112    /// Create new WFQ state with specified weight
113    pub fn with_weight(weight: f64) -> Result<Self, String> {
114        Ok(Self {
115            weight: TaskWeight::new(weight)?,
116            ..Default::default()
117        })
118    }
119
120    /// Update virtual time after task execution
121    ///
122    /// # Arguments
123    /// * `execution_duration_secs` - Actual execution time in seconds
124    /// * `global_virtual_time` - Current global virtual time
125    pub fn update_after_execution(
126        &mut self,
127        execution_duration_secs: f64,
128        global_virtual_time: f64,
129    ) {
130        self.total_execution_time += execution_duration_secs;
131        self.virtual_start_time = global_virtual_time.max(self.virtual_finish_time);
132
133        // Virtual finish time = virtual start time + (cost / weight)
134        // Cost is the execution duration
135        self.virtual_finish_time =
136            self.virtual_start_time + (execution_duration_secs / self.weight.value());
137    }
138
139    /// Get the virtual finish time for scheduling decisions
140    pub fn finish_time(&self) -> f64 {
141        self.virtual_finish_time
142    }
143}
144
145/// Task information for WFQ scheduling
146#[derive(Debug, Clone)]
147pub struct WFQTaskInfo {
148    /// Task name
149    pub name: String,
150
151    /// Virtual finish time
152    pub virtual_finish_time: f64,
153
154    /// Task weight
155    pub weight: f64,
156
157    /// Next scheduled run time
158    pub next_run_time: DateTime<Utc>,
159}