use serde::{Deserialize, Serialize};
use crate::config::ByteSize;
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct SizingQuery {
pub since: Option<u64>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Sizing {
pub since: u64,
pub until: u64,
pub margin: u32,
pub sample_interval: u64,
pub min_jobs: u32,
pub memory_max: Option<u64>,
pub profiles: Vec<ProfileSizing>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ProfileSizing {
pub name: String,
pub memory: u64,
pub cpus: u32,
pub slots: u32,
pub peaks: Peaks,
pub wait: Spread,
pub busy: u32,
pub advice: Advice,
pub jobs: Vec<JobSizing>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct JobSizing {
pub repository: String,
pub workflow: Option<String>,
pub name: String,
pub peaks: Peaks,
pub wait: Spread,
pub advice: Advice,
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct Peaks {
pub jobs: u64,
pub at_cap: u64,
pub memory: Spread,
pub cpu: Spread,
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct Spread {
pub p50: u64,
pub p95: u64,
pub p99: u64,
pub max: u64,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case", tag = "kind")]
pub enum Advice {
TooFew {
jobs: u64,
},
Keep {
memory: u64,
},
Lower {
memory: u64,
slots: u32,
},
Raise {
memory: u64,
slots: u32,
},
SliceTooSmall {
memory: u64,
},
}
impl Advice {
pub fn change(&self) -> Option<Resize> {
match *self {
Advice::Lower { memory, slots } | Advice::Raise { memory, slots } => {
Some(Resize { memory, slots })
}
Advice::TooFew { .. } | Advice::Keep { .. } | Advice::SliceTooSmall { .. } => None,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub struct Resize {
pub memory: u64,
pub slots: u32,
}
impl Sizing {
pub fn advice_line(&self, profile: &ProfileSizing) -> String {
let size = |bytes: u64| ByteSize(bytes).to_string();
let peaks = &profile.peaks;
let need = format!("its largest peak + {}%", self.margin);
let at_cap = format!(
"{} of {} jobs peaked at their cap",
peaks.at_cap, peaks.jobs
);
match profile.advice {
Advice::TooFew { jobs } => format!(
"{jobs} jobs, too few to advise on (it takes {}); largest peak {} of a {} cap",
self.min_jobs,
size(peaks.memory.max),
size(profile.memory)
),
Advice::Keep { .. } if peaks.at_cap > 0 => format!(
"keep {}: {at_cap}, so they may need more than they show",
size(profile.memory)
),
Advice::Keep { memory } => {
format!("keep {}: {need} is {}", size(profile.memory), size(memory))
}
Advice::Lower { memory, slots } => {
let share = profile.memory * u64::from(profile.slots);
let then = if slots > profile.slots {
format!("{slots} slots fit in the same {}", size(share))
} else {
format!(
"frees {} of the budget",
size(share - memory * u64::from(slots))
)
};
format!(
"lower memory to {} from {}, {need}; {then}",
size(memory),
size(profile.memory)
)
}
Advice::Raise { memory, slots } => {
let why = if peaks.at_cap > 0 {
at_cap
} else {
format!("{need} is above the {} cap", size(profile.memory))
};
let slots = if slots < profile.slots {
format!("; {slots} of its {} slots fit the slice", profile.slots)
} else {
String::new()
};
format!("raise memory to {}: {why}{slots}", size(memory))
}
Advice::SliceTooSmall { memory } => format!(
"needs {memory_needed} ({need}), more than the slice holds for one slot: raise the slice's \
MemoryMax first",
memory_needed = size(memory)
),
}
}
}