ClementTsang/bottom · error
Unexpected 'ps' output
Error message
Unexpected 'ps' output
What it means
backup_proc_cpu parses the output of a `ps` command (pid + cpu% pairs) on macOS. This panic fires when `ps` emits a line whose whitespace-split tokens do not pair up into even chunks of 2, or when a token count is odd, meaning `ps` produced output in an unexpected format.
Solutions
- Check what `ps -o pid= -o pcpu=` (or the exact command used) prints on the affected machine and confirm it emits clean two-column rows
- Ensure the standard macOS/BSD ps is installed and first in PATH
- Clear locale env vars (LC_ALL=C) that could change ps output format
- Report/patch the parser to skip header lines instead of panicking on malformed chunks
Example fix
// before
.for_each(|chunk| {
let chunk: Vec<&str> = chunk.collect();
if chunk.len() != 2 {
panic!("Unexpected 'ps' output");
}
// after
.for_each(|chunk| {
let chunk: Vec<&str> = chunk.collect();
if chunk.len() != 2 {
eprintln!("skipping malformed ps output: {:?}", chunk);
return;
} Defensive patterns
Strategy: try-catch
Validate before calling
// Rust: verify ps output shape before parsing
let out = Command::new("ps").args(["-o", "pid=,pcpu="]).output()?;
let ok = String::from_utf8_lossy(&out.stdout).lines().all(|l| l.split_whitespace().count() == 2); Try / catch
// This is a panic, not a Result: catch via catch_unwind or pre-validate
let r = std::panic::catch_unwind(|| backup_proc_cpu());
match r { Ok(v) => v, Err(_) => default_map() } Prevention
- Run the exact ps command manually on target systems to confirm output shape
- Set LC_ALL=C to stabilize ps output
- Pin to standard macOS ps; avoid busybox/procps variants
- Keep the library updated for ps-output parsing fixes
When it happens
Trigger: Calling process CPU collection when the `ps` binary on the system prints extra header lines, localized output, truncated/odd token counts, or any output where split_whitespace().chunks(2) yields a chunk with fewer than 2 tokens.
Common situations: Non-standard or busyboxed `ps` implementations, locale/HTML output options unsupported by the installed ps version, or `ps` emitting warning lines interleaved with data rows.
Understand the failure class
Background: "invalid response format", "malformed payload", "missing data field": when an API returns 200 but the response shape is wrong — this error's family across 23 libraries.
Related errors
AI-assisted analysis of ClementTsang/bottom@b77d317502 (2026-09-07).
Data as JSON: /api/errors/9500aa18a943e4a7.
Report an issue: GitHub.
Appendix: source
Thrown at src/collection/processes/macos.rs:38
fn backup_proc_cpu(pids: &[Pid]) -> io::Result<IntHashMap<Pid, f32>> {
let output = Command::new("ps")
.args(["-o", "pid=,pcpu=", "-p"])
.arg(
// Has to look like this since otherwise, it you hit a `unstable_name_collisions`
// warning.
Itertools::intersperse(pids.iter().map(i32::to_string), ",".to_string())
.collect::<String>(),
)
.output()?;
let mut result = IntHashMap::default();
String::from_utf8_lossy(&output.stdout)
.split_whitespace()
.chunks(2)
.into_iter()
.for_each(|chunk| {
let chunk: Vec<&str> = chunk.collect();
if chunk.len() != 2 {
panic!("Unexpected 'ps' output");
}
let pid = chunk[0].parse();
let usage = chunk[1].parse();
if let (Ok(pid), Ok(usage)) = (pid, usage) {
result.insert(pid, usage);
}
});
Ok(result)
}
fn parent_pid(process_val: &sysinfo::Process) -> Option<Pid> {
process_val
.parent()
.map(|p| p.as_u32() as _)
.or_else(|| fallback_macos_ppid(process_val.pid().as_u32() as _))
}
}
View on GitHub (pinned to b77d317502)