use ratatui::text::{Line, Span};
use super::super::app::App;
use crate::output::{human_bytes, human_duration};
#[must_use]
pub fn strip_line(app: &App, width: u16) -> Line<'static> {
Line::from(Span::styled(
super::flock::fit(&segments(app).join(" "), width),
app.palette().muted(),
))
}
fn segments(app: &App) -> Vec<String> {
let mut out = Vec::with_capacity(5);
match app.host() {
Some(host) => {
let (one, five, fifteen) = host.load;
out.push(match host.cores {
Some(cores) => {
format!("host load {one:.2} {five:.2} {fifteen:.2} / {cores} cores")
}
None => format!("host load {one:.2} {five:.2} {fifteen:.2}"),
});
out.push(format!(
"host mem {} / {}",
human_bytes(host.memory_used_bytes),
human_bytes(host.memory_total_bytes)
));
}
None if app.host_unsupported() => {
out.push("host usage is not available on this platform".to_string());
}
None => out.push("host not read yet".to_string()),
}
let rows = app.all_rows();
let cpu: Option<f32> = rows
.iter()
.filter_map(|row| row.info.cpu_percent)
.fold(None, |sum, value| Some(sum.unwrap_or(0.0) + value));
let mem: Option<u64> = rows
.iter()
.filter_map(|row| row.info.memory_bytes)
.fold(None, |sum, value| Some(sum.unwrap_or(0) + value));
out.push(cpu.map_or_else(
|| "flock cpu -".to_string(),
|cpu| format!("flock cpu {cpu:.1}%"),
));
out.push(mem.map_or_else(
|| "flock mem -".to_string(),
|mem| format!("flock mem {}", human_bytes(mem)),
));
if let Some(host) = app.host() {
out.push(format!(
"up {}",
human_duration(host.uptime_seconds * 1_000)
));
}
out
}
#[cfg(test)]
mod tests {
use super::super::MIN_TERM_WIDTH;
use super::super::fixtures::{flock_of, rendered, sample, with_host, with_host_none};
use super::*;
use shep_core::protocol::ProcessInfo;
use shep_core::status::ProcStatus;
#[test]
fn every_segment_names_whose_number_it_is() {
let app = with_host(sample(), flock_of(4, 1));
let line = rendered(&strip_line(&app, 200));
for segment in ["host load", "host mem", "flock cpu", "flock mem", "up "] {
assert!(line.contains(segment), "missing {segment:?} in {line:?}");
}
}
#[test]
fn a_narrow_strip_truncates_visibly_and_keeps_the_load_average() {
let app = with_host(sample(), flock_of(4, 1));
let narrow = rendered(&strip_line(&app, 40));
assert!(narrow.starts_with("host load"), "got {narrow:?}");
assert!(
narrow.ends_with('…'),
"a truncation the operator can see: {narrow:?}"
);
assert!(
!narrow.contains("up "),
"`up` is the first thing off the end"
);
let floor = rendered(&strip_line(&app, MIN_TERM_WIDTH));
assert!(floor.starts_with("host load"), "got {floor:?}");
let full = rendered(&strip_line(&app, 200));
assert!(!full.contains('…'));
assert!(
full.contains("up 6d"),
"the last segment is there: {full:?}"
);
}
#[test]
fn a_flock_with_no_readings_shows_a_dash_and_not_a_zero() {
let app = with_host(
sample(),
vec![ProcessInfo::builder(1, "web", ProcStatus::Errored).build()],
);
let line = rendered(&strip_line(&app, 200));
assert!(line.contains("flock cpu -"), "got {line:?}");
assert!(line.contains("flock mem -"), "got {line:?}");
assert!(!line.contains("0.0%"));
}
#[test]
fn an_unread_host_says_which_of_the_two_reasons_it_is() {
let unsupported = with_host_none(flock_of(4, 1), true);
assert!(
rendered(&strip_line(&unsupported, 200))
.contains("host usage is not available on this platform")
);
let not_yet = with_host_none(flock_of(4, 1), false);
assert!(rendered(&strip_line(¬_yet, 200)).contains("host not read yet"));
assert!(rendered(&strip_line(&unsupported, 200)).contains("flock cpu"));
}
#[test]
fn the_flock_totals_ignore_the_filter() {
let mut app = with_host(sample(), flock_of(4, 1));
let full = rendered(&strip_line(&app, 200));
assert!(full.contains("flock cpu 3.5%"), "sanity: got {full:?}");
app.set_filter_for_tests("zzz");
assert!(app.rows().is_empty(), "sanity: the filter matched nothing");
let filtered = rendered(&strip_line(&app, 200));
assert_eq!(
full, filtered,
"the strip must not change when the table's filter does: {filtered:?}"
);
assert!(
!filtered.contains("flock cpu -"),
"a filter matching nothing is not the same as no reading arriving: {filtered:?}"
);
}
}