use super::*;
pub(crate) fn print_topology_line() {
if let Some((n, l, c, t)) = parse_topo_from_cmdline() {
let total = l * c * t;
if n > 1 {
println!(
" topology: {n} NUMA nodes, {l} LLC{}, {c} core{}, {t} thread{} ({total} vCPU{})",
if l == 1 { "" } else { "s" },
if c == 1 { "" } else { "s" },
if t == 1 { "" } else { "s" },
if total == 1 { "" } else { "s" },
);
} else {
println!(
" topology: {l} LLC{}, {c} core{}, {t} thread{} ({total} vCPU{})",
if l == 1 { "" } else { "s" },
if c == 1 { "" } else { "s" },
if t == 1 { "" } else { "s" },
if total == 1 { "" } else { "s" },
);
}
} else if let Some(count) = count_online_cpus() {
println!(
" topology: {count} vCPU{}",
if count == 1 { "" } else { "s" }
);
}
}
pub(crate) fn parse_topo_from_cmdline() -> Option<(u32, u32, u32, u32)> {
let val = cmdline_val("KTSTR_TOPO")?;
let parts: Vec<&str> = val.split(',').collect();
if parts.len() != 4 {
return None;
}
let n: u32 = parts[0].parse().ok()?;
let l: u32 = parts[1].parse().ok()?;
let c: u32 = parts[2].parse().ok()?;
let t: u32 = parts[3].parse().ok()?;
Some((n, l, c, t))
}
#[cfg(feature = "wprof")]
pub(crate) fn spawn_wprof_if_configured() -> Option<std::thread::JoinHandle<Option<Vec<u8>>>> {
let args_str = cmdline_val("KTSTR_WPROF_ARGS")?;
let wprof_bin = std::path::Path::new("/bin/wprof");
if !wprof_bin.exists() {
tracing::warn!("KTSTR_WPROF_ARGS set but /bin/wprof missing from initramfs");
return None;
}
Some(
std::thread::Builder::new()
.name("wprof-capture".into())
.spawn(move || {
let mut cmd_args: Vec<String> = args_str.split('\x1f').map(String::from).collect();
cmd_args.extend([
"-T".to_string(),
"/tmp/wprof.pb".to_string(),
"-D".to_string(),
"/tmp/wprof.data".to_string(),
]);
tracing::debug!(args = ?cmd_args, "spawning /bin/wprof");
let status = std::process::Command::new("/bin/wprof")
.args(&cmd_args)
.stdout(std::process::Stdio::null())
.stderr(std::process::Stdio::inherit())
.status();
match status {
Ok(s) if s.success() => match std::fs::read("/tmp/wprof.pb") {
Ok(bytes) if !bytes.is_empty() => {
tracing::debug!(pb_bytes = bytes.len(), "wprof trace captured");
Some(bytes)
}
Ok(_) => {
tracing::warn!("wprof exited OK but /tmp/wprof.pb is empty");
None
}
Err(e) => {
tracing::warn!(%e, "read /tmp/wprof.pb after successful run");
None
}
},
Ok(s) => {
tracing::warn!(exit = s.code(), "wprof exited non-zero");
None
}
Err(e) => {
tracing::warn!(%e, "spawn /bin/wprof failed");
None
}
}
})
.expect("spawn wprof-capture thread"),
)
}
pub(crate) fn send_sys_rdy_with_retry(
budget: std::time::Duration,
vcpus: u32,
kern_addrs: &crate::vmm::wire::KernAddrs,
port_path: &std::path::Path,
) {
let loop_t0 = std::time::Instant::now();
let deadline = loop_t0 + budget;
let mut kern_addrs_sent = false;
loop {
if port_path.exists() {
if !kern_addrs_sent {
kern_addrs_sent = crate::vmm::guest_comms::send_kern_addrs(kern_addrs);
}
if kern_addrs_sent && crate::vmm::guest_comms::send_sys_rdy() {
return;
}
}
if std::time::Instant::now() >= deadline {
let port_exists_snapshot = port_path.exists();
tracing::warn!(
budget_ms = budget.as_millis() as u64,
vcpus,
elapsed_ms = loop_t0.elapsed().as_millis() as u64,
port_exists = port_exists_snapshot,
kern_addrs_sent,
"ktstr-init: send_sys_rdy failed within boot budget; \
see https://likewhatevs.github.io/ktstr/guide/troubleshooting.html#send_sys_rdy-timeout",
);
return;
}
std::thread::sleep(std::time::Duration::from_millis(100));
}
}
pub(crate) fn count_online_cpus() -> Option<u32> {
let content = fs::read_to_string("/sys/devices/system/cpu/online").ok()?;
parse_online_cpus(&content)
}
pub(crate) fn parse_online_cpus(content: &str) -> Option<u32> {
let trimmed = content.trim();
if trimmed.is_empty() {
return None;
}
let mut count = 0u32;
for range in trimmed.split(',') {
if let Some((start, end)) = range.split_once('-') {
let s: u32 = start.parse().ok()?;
let e: u32 = end.parse().ok()?;
count = count.checked_add(e.checked_sub(s)?.checked_add(1)?)?;
} else {
let _: u32 = range.parse().ok()?;
count = count.checked_add(1)?;
}
}
Some(count)
}
pub(crate) fn print_includes_line() {
let include_dir = Path::new("/include-files");
if !include_dir.is_dir() {
return;
}
let mut files: Vec<(String, bool)> = Vec::new();
for entry in walkdir::WalkDir::new(include_dir)
.min_depth(1)
.sort_by_file_name()
{
let Ok(entry) = entry else { continue };
if !entry.file_type().is_file() {
continue;
}
let rel = entry
.path()
.strip_prefix(include_dir)
.unwrap_or(entry.path());
let name = rel.to_string_lossy().to_string();
let executable = entry
.metadata()
.map(|m| {
use std::os::unix::fs::PermissionsExt;
m.permissions().mode() & 0o111 != 0
})
.unwrap_or(false);
files.push((name, executable));
}
if files.is_empty() {
return;
}
for (i, (name, executable)) in files.iter().enumerate() {
let marker = if *executable { " (executable)" } else { "" };
let path = format!("/include-files/{name}{marker}");
if i == 0 {
println!(" includes: {path}");
} else {
println!(" {path}");
}
}
}