1use std::time::Duration;
15
16pub const DISK_SLOW_FACTOR: u32 = 8;
20pub const DISK_SLOW_MIN: Duration = Duration::from_millis(20);
23pub const CPU_SATURATED_DELAY: Duration = Duration::from_micros(16_667);
25pub const FREE_SPACE_FLOOR_DIVISOR: u64 = 50;
27pub const FREE_SPACE_FLOOR_MIN: u64 = 256 * 1024 * 1024;
29pub const FREE_SPACE_FLOOR_MAX: u64 = 4 * 1024 * 1024 * 1024;
31
32#[derive(Debug, Clone, Copy, PartialEq, Eq)]
34pub struct ResourceReadings {
35 pub durable_lag: Duration,
37 pub commit_floor: Duration,
39 pub volume: VolumeSpace,
40 pub queued_runs: u32,
42 pub schedule_delay: Duration,
44}
45
46#[derive(Debug, Clone, Copy, PartialEq, Eq)]
48pub struct VolumeSpace {
49 pub free_bytes: u64,
50 pub total_bytes: u64,
51}
52
53#[derive(Debug, Clone, Copy, PartialEq, Eq)]
55pub enum Degradation {
56 DiskSlow {
57 durable_lag: Duration,
58 commit_floor: Duration,
59 },
60 DiskLow {
61 free_bytes: u64,
62 floor_bytes: u64,
63 },
64 MemoryTight {
65 queued_runs: u32,
66 },
67 CpuSaturated {
68 schedule_delay: Duration,
69 },
70}
71
72#[derive(Debug, Clone, Copy, PartialEq, Eq)]
74pub enum Recovery {
75 ReduceDiskLoad,
76 FreeDiskSpace { at_least_bytes: u64 },
77 FreeMemory,
78 ReduceCpuLoad,
79}
80
81impl Degradation {
82 pub fn recovery(&self) -> Recovery {
83 match *self {
84 Degradation::DiskSlow { .. } => Recovery::ReduceDiskLoad,
85 Degradation::DiskLow {
86 free_bytes,
87 floor_bytes,
88 } => Recovery::FreeDiskSpace {
89 at_least_bytes: floor_bytes.saturating_sub(free_bytes),
90 },
91 Degradation::MemoryTight { .. } => Recovery::FreeMemory,
92 Degradation::CpuSaturated { .. } => Recovery::ReduceCpuLoad,
93 }
94 }
95}
96
97pub fn assess(readings: &ResourceReadings) -> impl Iterator<Item = Degradation> {
100 [
101 disk_slow(readings),
102 disk_low(readings.volume),
103 memory_tight(readings),
104 cpu_saturated(readings),
105 ]
106 .into_iter()
107 .flatten()
108}
109
110pub fn admit_work(volume: VolumeSpace) -> Result<(), Degradation> {
117 disk_low(volume).map_or(Ok(()), Err)
118}
119
120const _: () = assert!(FREE_SPACE_FLOOR_MIN <= FREE_SPACE_FLOOR_MAX);
123
124pub fn free_space_floor(volume_bytes: u64) -> u64 {
126 (volume_bytes / FREE_SPACE_FLOOR_DIVISOR).clamp(FREE_SPACE_FLOOR_MIN, FREE_SPACE_FLOOR_MAX)
127}
128
129fn disk_slow(r: &ResourceReadings) -> Option<Degradation> {
130 let relative_line = r.commit_floor.saturating_mul(DISK_SLOW_FACTOR);
131 (r.durable_lag > relative_line.max(DISK_SLOW_MIN)).then_some(Degradation::DiskSlow {
132 durable_lag: r.durable_lag,
133 commit_floor: r.commit_floor,
134 })
135}
136
137fn disk_low(volume: VolumeSpace) -> Option<Degradation> {
138 let floor_bytes = free_space_floor(volume.total_bytes);
139 (volume.free_bytes < floor_bytes).then_some(Degradation::DiskLow {
140 free_bytes: volume.free_bytes,
141 floor_bytes,
142 })
143}
144
145fn memory_tight(r: &ResourceReadings) -> Option<Degradation> {
146 (r.queued_runs > 0).then_some(Degradation::MemoryTight {
147 queued_runs: r.queued_runs,
148 })
149}
150
151fn cpu_saturated(r: &ResourceReadings) -> Option<Degradation> {
152 (r.schedule_delay > CPU_SATURATED_DELAY).then_some(Degradation::CpuSaturated {
153 schedule_delay: r.schedule_delay,
154 })
155}
156
157#[cfg(test)]
158mod tests;