use std::collections::BTreeMap;
#[derive(Debug, Default, Clone, serde::Serialize, serde::Deserialize)]
pub struct Phases {
pub build_s: BTreeMap<String, f64>,
pub trace_ms: BTreeMap<String, f64>,
pub activation_s: Option<f64>,
pub builds: u32,
}
pub fn parse(stderr: &str) -> Phases {
let mut phases = Phases::default();
for line in stderr.lines() {
let line = line.trim_start_matches("remote: ");
if let Some(rest) = line.strip_prefix("[trace] ") {
if let Some((label, ms)) = rest.rsplit_once(": ")
&& let Some(seconds) = duration(ms)
{
*phases.trace_ms.entry(label.to_string()).or_default() += seconds * 1e3;
}
} else if line.contains("knot_static::build:") {
if line.contains("completed generation build") {
phases.builds += 1;
}
for field in line.split_whitespace() {
if let Some((key, value)) = field.split_once('=')
&& key.ends_with("_seconds")
&& let Ok(seconds) = value.parse::<f64>()
{
*phases.build_s.entry(key.to_string()).or_default() += seconds;
}
}
} else if line.starts_with("activated generation ")
&& let Some(clock) = line
.trim_end_matches(')')
.rsplit(", ")
.next()
.and_then(duration)
{
phases.activation_s = Some(phases.activation_s.unwrap_or(0.0) + clock);
}
}
phases
}
fn duration(text: &str) -> Option<f64> {
let text = text.trim();
let split = text.find(|c: char| !(c.is_ascii_digit() || c == '.'))?;
let (number, unit) = text.split_at(split);
let scale = match unit {
"ns" => 1e-9,
"µs" | "us" => 1e-6,
"ms" => 1e-3,
"s" => 1.0,
_ => return None,
};
Some(number.parse::<f64>().ok()? * scale)
}