| 1 |
|
| 2 |
|
| 3 |
|
| 4 |
|
| 5 |
|
| 6 |
|
| 7 |
|
| 8 |
|
| 9 |
|
| 10 |
|
| 11 |
|
| 12 |
|
| 13 |
|
| 14 |
|
| 15 |
|
| 16 |
|
| 17 |
|
| 18 |
|
| 19 |
|
| 20 |
|
| 21 |
|
| 22 |
|
| 23 |
|
| 24 |
pub const GIB: i64 = 1024 * 1024 * 1024; |
| 25 |
|
| 26 |
|
| 27 |
|
| 28 |
|
| 29 |
|
| 30 |
|
| 31 |
|
| 32 |
|
| 33 |
pub const OFFERED_GIB: &[i64] = &[250, 500, 1024, 2048, 5120, 10240]; |
| 34 |
|
| 35 |
|
| 36 |
|
| 37 |
|
| 38 |
|
| 39 |
|
| 40 |
|
| 41 |
|
| 42 |
|
| 43 |
|
| 44 |
const HEADROOM_NUMERATOR: i64 = 3; |
| 45 |
const HEADROOM_DENOMINATOR: i64 = 2; |
| 46 |
|
| 47 |
|
| 48 |
|
| 49 |
|
| 50 |
|
| 51 |
pub const NEARLY_FULL_PERCENT: i64 = 90; |
| 52 |
|
| 53 |
|
| 54 |
pub fn offered(min_bytes: i64, max_bytes: i64) -> impl Iterator<Item = i64> { |
| 55 |
OFFERED_GIB |
| 56 |
.iter() |
| 57 |
.map(|gib| gib * GIB) |
| 58 |
.filter(move |bytes| *bytes >= min_bytes && *bytes <= max_bytes) |
| 59 |
} |
| 60 |
|
| 61 |
|
| 62 |
|
| 63 |
|
| 64 |
|
| 65 |
|
| 66 |
|
| 67 |
pub fn proposed(need: Option<i64>, min_bytes: i64, max_bytes: i64) -> i64 { |
| 68 |
let floor = offered(min_bytes, max_bytes).next().unwrap_or(min_bytes); |
| 69 |
let Some(need) = need.filter(|bytes| *bytes > 0) else { |
| 70 |
return floor; |
| 71 |
}; |
| 72 |
let want = need |
| 73 |
.saturating_mul(HEADROOM_NUMERATOR) |
| 74 |
.saturating_div(HEADROOM_DENOMINATOR); |
| 75 |
offered(min_bytes, max_bytes) |
| 76 |
.find(|bytes| *bytes >= want) |
| 77 |
.or_else(|| offered(min_bytes, max_bytes).last()) |
| 78 |
.unwrap_or(floor) |
| 79 |
} |
| 80 |
|
| 81 |
|
| 82 |
pub fn nearly_full(used_bytes: i64, limit_bytes: i64) -> bool { |
| 83 |
limit_bytes > 0 |
| 84 |
&& used_bytes.saturating_mul(100) >= limit_bytes.saturating_mul(NEARLY_FULL_PERCENT) |
| 85 |
} |
| 86 |
|
| 87 |
#[cfg(test)] |
| 88 |
mod tests { |
| 89 |
use super::*; |
| 90 |
|
| 91 |
const MIN: i64 = 250 * GIB; |
| 92 |
const MAX: i64 = 10240 * GIB; |
| 93 |
|
| 94 |
#[test] |
| 95 |
fn a_need_gets_the_smallest_named_cap_that_covers_it_with_headroom() { |
| 96 |
|
| 97 |
assert_eq!(proposed(Some(400 * GIB), MIN, MAX), 1024 * GIB); |
| 98 |
|
| 99 |
assert_eq!(proposed(Some(300 * GIB), MIN, MAX), 500 * GIB); |
| 100 |
} |
| 101 |
|
| 102 |
#[test] |
| 103 |
fn nothing_to_size_against_proposes_the_floor() { |
| 104 |
|
| 105 |
|
| 106 |
|
| 107 |
assert_eq!(proposed(None, MIN, MAX), MIN); |
| 108 |
assert_eq!(proposed(Some(0), MIN, MAX), MIN); |
| 109 |
} |
| 110 |
|
| 111 |
#[test] |
| 112 |
fn a_library_past_every_named_cap_gets_the_largest() { |
| 113 |
assert_eq!(proposed(Some(9000 * GIB), MIN, MAX), 10240 * GIB); |
| 114 |
|
| 115 |
assert_eq!(proposed(Some(i64::MAX), MIN, MAX), 10240 * GIB); |
| 116 |
} |
| 117 |
|
| 118 |
#[test] |
| 119 |
fn the_named_caps_are_filtered_against_what_the_server_sells() { |
| 120 |
|
| 121 |
|
| 122 |
|
| 123 |
let caps: Vec<i64> = offered(600 * GIB, 3000 * GIB).collect(); |
| 124 |
assert_eq!(caps, vec![1024 * GIB, 2048 * GIB]); |
| 125 |
} |
| 126 |
|
| 127 |
#[test] |
| 128 |
fn a_pricing_range_containing_no_named_cap_still_proposes_something() { |
| 129 |
|
| 130 |
|
| 131 |
let odd = 77 * GIB; |
| 132 |
assert_eq!(offered(odd, odd + 1).count(), 0); |
| 133 |
assert_eq!(proposed(Some(10 * GIB), odd, odd + 1), odd); |
| 134 |
} |
| 135 |
|
| 136 |
#[test] |
| 137 |
fn nearly_full_is_ninety_percent_and_does_not_overflow() { |
| 138 |
assert!(!nearly_full(89 * GIB, 100 * GIB)); |
| 139 |
assert!(nearly_full(90 * GIB, 100 * GIB)); |
| 140 |
assert!(nearly_full(200 * GIB, 100 * GIB), "past full is still full"); |
| 141 |
assert!(!nearly_full(0, 0), "an unknown cap is not a full one"); |
| 142 |
|
| 143 |
|
| 144 |
assert!(nearly_full(i64::MAX, 100 * GIB)); |
| 145 |
} |
| 146 |
} |
| 147 |
|