pub const TASK_CONTEXT_QUOTA_PERCENTS: [u8; 6] = [20, 25, 30, 10, 10, 5];
pub const TASK_CONTEXT_QUOTA_NAMES: [&str; 6] = [
"atlas",
"surface",
"source",
"flow",
"verify",
"uncertainty",
];
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct FilledBucket {
pub name: &'static str,
pub percent: u8,
pub quota: usize,
pub granted: usize,
pub truncated: usize,
pub rolled_forward: usize,
}
pub fn fill_with_rollover(total: usize, percents: &[u8], names: &[&'static str], used: &[usize]) -> Vec<FilledBucket> {
assert_eq!(percents.len(), used.len());
assert_eq!(percents.len(), names.len());
let sum: u16 = percents.iter().map(|p| *p as u16).sum();
assert_eq!(sum, 100, "quota percents must sum to 100");
let mut leftover = 0usize;
let mut granted_so_far = 0usize;
let mut out = Vec::with_capacity(percents.len());
for (i, (p, u)) in percents.iter().zip(used.iter()).enumerate() {
let base = if i + 1 == percents.len() {
total.saturating_sub(granted_so_far + leftover)
.saturating_add(leftover)
} else {
total * (*p as usize) / 100 + leftover
};
let take = (*u).min(base);
let truncated = u.saturating_sub(base);
let rolled = base.saturating_sub(*u);
granted_so_far += take;
leftover = rolled;
out.push(FilledBucket {
name: names[i],
percent: *p,
quota: base,
granted: take,
truncated,
rolled_forward: if i + 1 == percents.len() { 0 } else { rolled },
});
}
out
}
pub fn fill_task_context_quotas(total: usize, used: &[usize; 6]) -> Vec<FilledBucket> {
fill_with_rollover(
total,
&TASK_CONTEXT_QUOTA_PERCENTS,
&TASK_CONTEXT_QUOTA_NAMES,
used,
)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn percents_sum_to_one_hundred() {
let sum: u16 = TASK_CONTEXT_QUOTA_PERCENTS.iter().map(|p| *p as u16).sum();
assert_eq!(sum, 100);
assert_eq!(TASK_CONTEXT_QUOTA_NAMES.len(), TASK_CONTEXT_QUOTA_PERCENTS.len());
}
#[test]
fn unused_quota_rolls_forward_and_truncation_is_disclosed() {
let filled = fill_with_rollover(
100,
&[40, 30, 30],
&["a", "b", "c"],
&[10, 50, 10],
);
assert_eq!(filled[0].granted, 10);
assert_eq!(filled[0].truncated, 0);
assert_eq!(filled[0].rolled_forward, 30);
assert_eq!(filled[1].quota, 60, "30% + 30 rolled");
assert_eq!(filled[1].granted, 50);
assert_eq!(filled[1].truncated, 0);
assert_eq!(filled[1].rolled_forward, 10);
assert_eq!(filled[2].granted, 10);
assert_eq!(filled[2].truncated, 0);
let over = fill_with_rollover(100, &[50, 50], &["x", "y"], &[80, 80]);
assert_eq!(over[0].granted, 50);
assert_eq!(over[0].truncated, 30);
assert_eq!(over[1].granted, 50);
assert_eq!(over[1].truncated, 30);
let task = fill_task_context_quotas(1000, &[0, 0, 0, 0, 0, 0]);
assert_eq!(task.iter().map(|b| b.granted).sum::<usize>(), 0);
assert!(task.iter().any(|b| b.name == "verify"));
}
}