- Implement get_top_cpu_process() and get_top_ram_process() functions in SystemCollector - Add top_cpu_process and top_ram_process fields to SystemSummary data structure - Update System widget to display top processes as description rows - Show process name and percentage usage for highest CPU and RAM consumers - Skip kernel threads and filter out processes with minimal usage (<0.1%)
125 lines
4.4 KiB
Rust
125 lines
4.4 KiB
Rust
use ratatui::layout::Rect;
|
|
use ratatui::Frame;
|
|
|
|
use crate::app::HostDisplayData;
|
|
use crate::data::metrics::SystemMetrics;
|
|
use crate::ui::widget::{
|
|
render_placeholder, render_combined_widget_data,
|
|
status_level_from_agent_status, connection_status_message, WidgetDataSet, WidgetStatus, StatusLevel,
|
|
};
|
|
use crate::app::ConnectionStatus;
|
|
|
|
pub fn render(frame: &mut Frame, host: Option<&HostDisplayData>, area: Rect) {
|
|
match host {
|
|
Some(data) => {
|
|
match (&data.connection_status, data.system.as_ref()) {
|
|
(ConnectionStatus::Connected, Some(metrics)) => {
|
|
render_metrics(frame, data, metrics, area);
|
|
}
|
|
(ConnectionStatus::Connected, None) => {
|
|
render_placeholder(
|
|
frame,
|
|
area,
|
|
"System",
|
|
&format!("Host {} awaiting system metrics", data.name),
|
|
);
|
|
}
|
|
(status, _) => {
|
|
render_placeholder(
|
|
frame,
|
|
area,
|
|
"System",
|
|
&format!("Host {}: {}", data.name, connection_status_message(status, &data.last_error)),
|
|
);
|
|
}
|
|
}
|
|
}
|
|
None => render_placeholder(frame, area, "System", "No hosts configured"),
|
|
}
|
|
}
|
|
|
|
fn render_metrics(
|
|
frame: &mut Frame,
|
|
_host: &HostDisplayData,
|
|
metrics: &SystemMetrics,
|
|
area: Rect,
|
|
) {
|
|
let summary = &metrics.summary;
|
|
|
|
// Use agent-calculated statuses
|
|
let memory_status = status_level_from_agent_status(summary.memory_status.as_ref());
|
|
let cpu_status = status_level_from_agent_status(summary.cpu_status.as_ref());
|
|
|
|
// Determine overall widget status based on worst case from agent statuses
|
|
let overall_status_level = match (memory_status, cpu_status) {
|
|
(StatusLevel::Error, _) | (_, StatusLevel::Error) => StatusLevel::Error,
|
|
(StatusLevel::Warning, _) | (_, StatusLevel::Warning) => StatusLevel::Warning,
|
|
(StatusLevel::Ok, StatusLevel::Ok) => StatusLevel::Ok,
|
|
_ => StatusLevel::Unknown,
|
|
};
|
|
let overall_status = Some(WidgetStatus::new(overall_status_level));
|
|
|
|
// Single dataset with RAM, CPU load, CPU temp as columns
|
|
let mut system_dataset = WidgetDataSet::new(
|
|
vec!["RAM usage".to_string(), "CPU load".to_string(), "CPU temp".to_string()],
|
|
overall_status.clone()
|
|
);
|
|
|
|
// Use agent-provided C-states and logged-in users as description
|
|
let mut description_lines = Vec::new();
|
|
|
|
// Add C-states with prefix on first line, indent subsequent lines
|
|
if let Some(cstates) = &summary.cpu_cstate {
|
|
for (i, cstate_line) in cstates.iter().enumerate() {
|
|
if i == 0 {
|
|
description_lines.push(format!("C-State: {}", cstate_line));
|
|
} else {
|
|
description_lines.push(format!(" {}", cstate_line));
|
|
}
|
|
}
|
|
}
|
|
|
|
// Add logged-in users to description
|
|
if let Some(users) = &summary.logged_in_users {
|
|
if !users.is_empty() {
|
|
let user_line = if users.len() == 1 {
|
|
format!("Logged in: {}", users[0])
|
|
} else {
|
|
format!("Logged in: {} users ({})", users.len(), users.join(", "))
|
|
};
|
|
description_lines.push(user_line);
|
|
}
|
|
}
|
|
|
|
// Add top CPU process
|
|
if let Some(cpu_proc) = &summary.top_cpu_process {
|
|
description_lines.push(format!("Top CPU: {}", cpu_proc));
|
|
}
|
|
|
|
// Add top RAM process
|
|
if let Some(ram_proc) = &summary.top_ram_process {
|
|
description_lines.push(format!("Top RAM: {}", ram_proc));
|
|
}
|
|
|
|
system_dataset.add_row(
|
|
overall_status.clone(),
|
|
description_lines,
|
|
vec![
|
|
format!("{:.1} / {:.1} GB", summary.memory_used_mb / 1000.0, summary.memory_total_mb / 1000.0),
|
|
format!("{:.2} • {:.2} • {:.2}", summary.cpu_load_1, summary.cpu_load_5, summary.cpu_load_15),
|
|
format_optional_metric(summary.cpu_temp_c, "°C"),
|
|
],
|
|
);
|
|
|
|
// Render single dataset
|
|
render_combined_widget_data(frame, area, "System".to_string(), overall_status, vec![system_dataset]);
|
|
}
|
|
|
|
fn format_optional_metric(value: Option<f32>, unit: &str) -> String {
|
|
match value {
|
|
Some(number) => format!("{:.1}{}", number, unit),
|
|
None => "—".to_string(),
|
|
}
|
|
}
|
|
|