From d7816aa18beb8157720e3d90ec8a8febc5bfc18a Mon Sep 17 00:00:00 2001 From: Dym03 Date: Sat, 7 Mar 2026 21:12:39 +0100 Subject: [PATCH 1/3] Add worker managed cpu utilization --- .../screens/cluster/worker/cpu_util_table.rs | 98 ++++++++++++++++--- .../ui/screens/cluster/worker/mod.rs | 89 ++++++++++++++--- .../src/dashboard/ui/widgets/progressbar.rs | 19 ++++ 3 files changed, 182 insertions(+), 24 deletions(-) diff --git a/crates/hyperqueue/src/dashboard/ui/screens/cluster/worker/cpu_util_table.rs b/crates/hyperqueue/src/dashboard/ui/screens/cluster/worker/cpu_util_table.rs index fc478cf6f..3601ce7c0 100644 --- a/crates/hyperqueue/src/dashboard/ui/screens/cluster/worker/cpu_util_table.rs +++ b/crates/hyperqueue/src/dashboard/ui/screens/cluster/worker/cpu_util_table.rs @@ -1,14 +1,16 @@ use crate::common::format::human_size; +use crate::dashboard::ui::screens::cluster::worker::UtilizationRenderMode; use ratatui::layout::{Constraint, Rect}; use ratatui::style::Style; use ratatui::widgets::{Cell, Row, Table}; use std::cmp; use tako::hwstats::MemoryStats; +use tako::resources::ResourceIndex; use crate::dashboard::ui::styles; use crate::dashboard::ui::terminal::DashboardFrame; use crate::dashboard::ui::widgets::progressbar::{ - ProgressPrintStyle, get_progress_bar_color, render_progress_bar_at, + ProgressPrintStyle, get_progress_bar_color, get_progress_bar_cpu_color, render_progress_bar_at, }; use crate::dashboard::utils::calculate_average; @@ -19,6 +21,8 @@ const CPU_METER_WIDTH: u8 = CPU_METER_PROGRESSBAR_WIDTH + 4; pub fn render_cpu_util_table( cpu_util_list: &[f64], mem_util: &MemoryStats, + used_cpus: &[ResourceIndex], + util_render_mode: &UtilizationRenderMode, rect: Rect, frame: &mut DashboardFrame, table_style: Style, @@ -31,10 +35,15 @@ pub fn render_cpu_util_table( let width = constraints.len(); let height = (cpu_util_list.len() as f64 / width as f64).ceil() as usize; - let mut rows: Vec> = vec![vec![]; height]; - for (position, &cpu_util) in cpu_util_list.iter().enumerate() { - let row = position % height; - rows[row].push((cpu_util, position)); + let mut rows: Vec> = vec![vec![]; height]; + if *util_render_mode == UtilizationRenderMode::Worker { + rows = get_utilization_sorted_by_usage(cpu_util_list, used_cpus, height) + } else { + for (position, &cpu_util) in cpu_util_list.iter().enumerate() { + let row = position % height; + let used = used_cpus.contains(&ResourceIndex::new(position as u32)); + rows[row].push((cpu_util, position, used)); + } } let rows: Vec = rows @@ -42,27 +51,34 @@ pub fn render_cpu_util_table( .map(|targets| { let columns: Vec = targets .into_iter() - .map(|(cpu_util, position)| { + .map(|(cpu_util, position, used)| { let progress = cpu_util / 100.00; + let style = match util_render_mode { + UtilizationRenderMode::Global => get_progress_bar_color(progress), + UtilizationRenderMode::Worker => get_progress_bar_cpu_color(progress, used), + }; + Cell::from(render_progress_bar_at( Some(format!("{position:>3} ")), progress, CPU_METER_PROGRESSBAR_WIDTH, ProgressPrintStyle::default(), )) - .style(get_progress_bar_color(progress)) + .style(style) }) .collect(); Row::new(columns) }) .collect(); - let avg_cpu = calculate_average(cpu_util_list); - let mem_used = mem_util.total - mem_util.free; + let (which_util, num_cpus, avg_cpu) = + create_title_info(cpu_util_list, used_cpus, util_render_mode); + let title = styles::table_title(format!( - "Worker Utilization ({} CPUs), Avg CPU = {:.0}%, Mem = {:.0}% ({}/{})", - cpu_util_list.len(), + "{} Utilization ({} CPUs), Avg CPU = {:.0}%, Mem = {:.0}% ({}/{})", + which_util, + num_cpus, avg_cpu, (mem_used as f64 / mem_util.total as f64) * 100.0, human_size(mem_used), @@ -89,3 +105,63 @@ fn get_column_constraints(rect: Rect, num_cpus: usize) -> Vec { ) .collect() } + +fn get_utilization_sorted_by_usage( + cpu_util_list: &[f64], + used_cpus: &[ResourceIndex], + height: usize, +) -> Vec> { + let mut all_cpus: Vec<(f64, usize, bool)> = cpu_util_list + .iter() + .enumerate() + .map(|(position, &cpu_util)| { + let used = used_cpus.contains(&ResourceIndex::new(position as u32)); + (cpu_util, position, used) + }) + .collect(); + + all_cpus.sort_by_key(|&(_, _, used)| std::cmp::Reverse(used)); + + let mut rows: Vec> = vec![vec![]; height]; + for (index, cpu_data) in all_cpus.into_iter().enumerate() { + let row = index % height; + rows[row].push(cpu_data); + } + rows +} + +fn create_title_info( + cpu_util_list: &[f64], + used_cpus: &[ResourceIndex], + util_render_mode: &UtilizationRenderMode, +) -> (String, usize, f64) { + let which_util = match util_render_mode { + UtilizationRenderMode::Global => "Node".to_string(), + UtilizationRenderMode::Worker => "Worker".to_string(), + }; + + let num_cpus = match util_render_mode { + UtilizationRenderMode::Global => cpu_util_list.len(), + UtilizationRenderMode::Worker => used_cpus.len(), + }; + + let avg_usage = match util_render_mode { + UtilizationRenderMode::Global => calculate_average(cpu_util_list), + UtilizationRenderMode::Worker => { + let used_cpu_util_list: Vec = cpu_util_list + .iter() + .enumerate() + .filter_map(|(idx, utilization)| { + if used_cpus.contains(&ResourceIndex::new(idx as u32)) { + Some(*utilization) + } else { + None + } + }) + .collect(); + calculate_average(&used_cpu_util_list) + } + }; + + (which_util, num_cpus, avg_usage) +} diff --git a/crates/hyperqueue/src/dashboard/ui/screens/cluster/worker/mod.rs b/crates/hyperqueue/src/dashboard/ui/screens/cluster/worker/mod.rs index d5e608e54..d7fca02b5 100644 --- a/crates/hyperqueue/src/dashboard/ui/screens/cluster/worker/mod.rs +++ b/crates/hyperqueue/src/dashboard/ui/screens/cluster/worker/mod.rs @@ -12,6 +12,7 @@ use crate::dashboard::ui::widgets::text::draw_text; use crossterm::event::{KeyCode, KeyEvent}; use ratatui::layout::{Constraint, Direction, Layout, Rect}; use tako::hwstats::MemoryStats; +use tako::resources::{CPU_RESOURCE_NAME, ResourceIndex}; use tako::{JobTaskId, WorkerId}; mod cpu_util_table; @@ -26,6 +27,7 @@ pub struct WorkerDetail { worker_tasks_table: TasksTable, utilization: Option, + utilization_render_mode: UtilizationRenderMode, } impl Default for WorkerDetail { @@ -36,6 +38,29 @@ impl Default for WorkerDetail { worker_config_table: Default::default(), worker_tasks_table: TasksTable::non_interactive(), utilization: None, + utilization_render_mode: UtilizationRenderMode::Worker, + } + } +} + +#[derive(PartialEq)] +enum UtilizationRenderMode { + Global, + Worker, +} + +impl UtilizationRenderMode { + fn next(&mut self) { + *self = match self { + UtilizationRenderMode::Global => UtilizationRenderMode::Worker, + UtilizationRenderMode::Worker => UtilizationRenderMode::Global, + } + } + + fn next_text(&self) -> &str { + match self { + UtilizationRenderMode::Global => "Show worker CPU utilization", + UtilizationRenderMode::Worker => "Show global CPU utilization", } } } @@ -43,6 +68,7 @@ impl Default for WorkerDetail { struct Utilization { cpu: Vec, memory: MemoryStats, + used_cpus: Vec, } impl WorkerDetail { @@ -66,12 +92,24 @@ impl WorkerDetail { frame, style_header_text(), ); - draw_text(": Back", layout.footer, frame, style_footer()); + + draw_text( + format!( + ": Back, : {}", + self.utilization_render_mode.next_text() + ) + .as_str(), + layout.footer, + frame, + style_footer(), + ); if let Some(util) = &self.utilization { render_cpu_util_table( &util.cpu, &util.memory, + &util.used_cpus, + &self.utilization_render_mode, layout.current_utilization, frame, table_style_deselected(), @@ -95,21 +133,45 @@ impl WorkerDetail { if let Some(worker_id) = self.worker_id { self.utilization_history.update(data, worker_id); - if let Some((cpu_util, mem_util)) = data + if let Some(overview) = data .workers() .query_worker_overview_at(worker_id, data.current_time()) - .and_then(|overview| overview.item.hw_state.as_ref()) - .map(|hw_state| { - ( - &hw_state.state.cpu_usage.cpu_per_core_percent_usage, - &hw_state.state.memory_usage, - ) - }) { - self.utilization = Some(Utilization { - cpu: cpu_util.iter().map(|&v| v as f64).collect(), - memory: mem_util.clone(), - }); + let worker_used_cpus: Vec = match self.utilization_render_mode { + UtilizationRenderMode::Worker => overview + .item + .running_tasks + .iter() + .flat_map(|(_id, task_resource_alloc)| { + task_resource_alloc + .resources + .iter() + .filter_map(|resource_alloc| { + if resource_alloc.resource == CPU_RESOURCE_NAME { + Some(resource_alloc.indices.iter().map(|(index, _)| *index)) + } else { + None + } + }) + }) + .flatten() + .collect(), + UtilizationRenderMode::Global => vec![], + }; + + if let Some(hw_state) = overview.item.hw_state.as_ref() { + self.utilization = Some(Utilization { + cpu: hw_state + .state + .cpu_usage + .cpu_per_core_percent_usage + .iter() + .map(|&v| v as f64) + .collect(), + memory: hw_state.state.memory_usage.clone(), + used_cpus: worker_used_cpus, + }) + } } let tasks_info: Vec<(JobTaskId, &TaskInfo)> = @@ -127,6 +189,7 @@ impl WorkerDetail { pub fn handle_key(&mut self, key: KeyEvent) { match key.code { KeyCode::Backspace => self.worker_tasks_table.clear_selection(), + KeyCode::Char('c') => self.utilization_render_mode.next(), _ => self.worker_tasks_table.handle_key(key), } } diff --git a/crates/hyperqueue/src/dashboard/ui/widgets/progressbar.rs b/crates/hyperqueue/src/dashboard/ui/widgets/progressbar.rs index 4d97126f4..ce5c8e155 100644 --- a/crates/hyperqueue/src/dashboard/ui/widgets/progressbar.rs +++ b/crates/hyperqueue/src/dashboard/ui/widgets/progressbar.rs @@ -30,6 +30,25 @@ pub fn get_progress_bar_color(progress: f64) -> Style { } } +pub fn get_progress_bar_cpu_color(progress: f64, used: bool) -> Style { + let color = if !used { + Color::Gray + } else if progress <= GREEN_THRESHOLD { + Color::Green + } else if progress <= YELLOW_THRESHOLD { + Color::Yellow + } else { + Color::Red + }; + + Style { + fg: Some(color), + bg: None, + add_modifier: Modifier::empty(), + sub_modifier: Modifier::empty(), + } +} + /** * Creates a string progress bar for 0 < progress < 1 */ From 9361df1a4e45058c7d3571436c0b89d480ff7cea Mon Sep 17 00:00:00 2001 From: Dym03 Date: Sat, 21 Mar 2026 12:19:46 +0100 Subject: [PATCH 2/3] Add third view for only asssigned cpus --- .../screens/cluster/worker/cpu_util_table.rs | 116 ++++++++----- .../ui/screens/cluster/worker/mod.rs | 160 +++++++++++++++--- .../src/dashboard/ui/widgets/progressbar.rs | 34 +++- 3 files changed, 235 insertions(+), 75 deletions(-) diff --git a/crates/hyperqueue/src/dashboard/ui/screens/cluster/worker/cpu_util_table.rs b/crates/hyperqueue/src/dashboard/ui/screens/cluster/worker/cpu_util_table.rs index 3601ce7c0..61305ec26 100644 --- a/crates/hyperqueue/src/dashboard/ui/screens/cluster/worker/cpu_util_table.rs +++ b/crates/hyperqueue/src/dashboard/ui/screens/cluster/worker/cpu_util_table.rs @@ -1,5 +1,5 @@ use crate::common::format::human_size; -use crate::dashboard::ui::screens::cluster::worker::UtilizationRenderMode; +use crate::dashboard::ui::screens::cluster::worker::{CpuScope, CpuViewMode}; use ratatui::layout::{Constraint, Rect}; use ratatui::style::Style; use ratatui::widgets::{Cell, Row, Table}; @@ -10,7 +10,7 @@ use tako::resources::ResourceIndex; use crate::dashboard::ui::styles; use crate::dashboard::ui::terminal::DashboardFrame; use crate::dashboard::ui::widgets::progressbar::{ - ProgressPrintStyle, get_progress_bar_color, get_progress_bar_cpu_color, render_progress_bar_at, + ProgressPrintStyle, get_cpu_progress_bar_color, render_progress_bar_at, }; use crate::dashboard::utils::calculate_average; @@ -22,7 +22,8 @@ pub fn render_cpu_util_table( cpu_util_list: &[f64], mem_util: &MemoryStats, used_cpus: &[ResourceIndex], - util_render_mode: &UtilizationRenderMode, + util_render_mode: &CpuViewMode, + cpu_scope: &CpuScope, rect: Rect, frame: &mut DashboardFrame, table_style: Style, @@ -36,13 +37,46 @@ pub fn render_cpu_util_table( let height = (cpu_util_list.len() as f64 / width as f64).ceil() as usize; let mut rows: Vec> = vec![vec![]; height]; - if *util_render_mode == UtilizationRenderMode::Worker { - rows = get_utilization_sorted_by_usage(cpu_util_list, used_cpus, height) - } else { - for (position, &cpu_util) in cpu_util_list.iter().enumerate() { - let row = position % height; - let used = used_cpus.contains(&ResourceIndex::new(position as u32)); - rows[row].push((cpu_util, position, used)); + + match util_render_mode { + CpuViewMode::Global => { + for (position, &cpu_util) in cpu_util_list.iter().enumerate() { + let row = position % height; + let used = used_cpus.contains(&ResourceIndex::new(position as u32)); + rows[row].push((cpu_util, position, used)); + } + } + CpuViewMode::WorkerManaged => match cpu_scope { + CpuScope::Node => { + rows = get_utilization_sorted_by_usage(cpu_util_list, used_cpus, height) + } + CpuScope::Subset(managed_cpus) => { + let managed_cpu_util_list: Vec = cpu_util_list + .iter() + .enumerate() + .filter_map(|(idx, &cpu_util)| { + if managed_cpus.contains(&ResourceIndex::new(idx as u32)) { + Some(cpu_util) + } else { + None + } + }) + .collect(); + + rows = get_utilization_sorted_by_usage(&managed_cpu_util_list, used_cpus, height) + } + }, + CpuViewMode::WorkerAssigned => { + let mut r = 0; + for (position, &cpu_util) in cpu_util_list.iter().enumerate() { + let row = r % height; + let used = used_cpus.contains(&ResourceIndex::new(position as u32)); + + if used { + rows[row].push((cpu_util, position, used)); + r += 1; + } + } } } @@ -53,10 +87,8 @@ pub fn render_cpu_util_table( .into_iter() .map(|(cpu_util, position, used)| { let progress = cpu_util / 100.00; - let style = match util_render_mode { - UtilizationRenderMode::Global => get_progress_bar_color(progress), - UtilizationRenderMode::Worker => get_progress_bar_cpu_color(progress, used), - }; + + let style = get_cpu_progress_bar_color(progress, used, util_render_mode); Cell::from(render_progress_bar_at( Some(format!("{position:>3} ")), @@ -73,7 +105,7 @@ pub fn render_cpu_util_table( let mem_used = mem_util.total - mem_util.free; let (which_util, num_cpus, avg_cpu) = - create_title_info(cpu_util_list, used_cpus, util_render_mode); + create_title_info(cpu_util_list, used_cpus, util_render_mode, cpu_scope); let title = styles::table_title(format!( "{} Utilization ({} CPUs), Avg CPU = {:.0}%, Mem = {:.0}% ({}/{})", @@ -133,35 +165,35 @@ fn get_utilization_sorted_by_usage( fn create_title_info( cpu_util_list: &[f64], used_cpus: &[ResourceIndex], - util_render_mode: &UtilizationRenderMode, + util_render_mode: &CpuViewMode, + cpu_manager_state: &CpuScope, ) -> (String, usize, f64) { let which_util = match util_render_mode { - UtilizationRenderMode::Global => "Node".to_string(), - UtilizationRenderMode::Worker => "Worker".to_string(), - }; - - let num_cpus = match util_render_mode { - UtilizationRenderMode::Global => cpu_util_list.len(), - UtilizationRenderMode::Worker => used_cpus.len(), - }; - - let avg_usage = match util_render_mode { - UtilizationRenderMode::Global => calculate_average(cpu_util_list), - UtilizationRenderMode::Worker => { - let used_cpu_util_list: Vec = cpu_util_list - .iter() - .enumerate() - .filter_map(|(idx, utilization)| { - if used_cpus.contains(&ResourceIndex::new(idx as u32)) { - Some(*utilization) - } else { - None - } - }) - .collect(); - calculate_average(&used_cpu_util_list) - } - }; + CpuViewMode::Global => "Node", + CpuViewMode::WorkerManaged => "Worker Managed", + CpuViewMode::WorkerAssigned => "Worker Assigned", + } + .to_string(); + + let filtered_utils: Vec = cpu_util_list + .iter() + .enumerate() + .filter(|(idx, _util)| { + let res_idx = ResourceIndex::new(*idx as u32); + match util_render_mode { + CpuViewMode::Global => true, + CpuViewMode::WorkerAssigned => used_cpus.contains(&res_idx), + CpuViewMode::WorkerManaged => match cpu_manager_state { + CpuScope::Node => true, + CpuScope::Subset(managed) => managed.contains(&res_idx), + }, + } + }) + .map(|(_, &util)| util) + .collect(); + + let num_cpus = filtered_utils.len(); + let avg_usage = calculate_average(&filtered_utils); (which_util, num_cpus, avg_usage) } diff --git a/crates/hyperqueue/src/dashboard/ui/screens/cluster/worker/mod.rs b/crates/hyperqueue/src/dashboard/ui/screens/cluster/worker/mod.rs index d7fca02b5..c7f8f7667 100644 --- a/crates/hyperqueue/src/dashboard/ui/screens/cluster/worker/mod.rs +++ b/crates/hyperqueue/src/dashboard/ui/screens/cluster/worker/mod.rs @@ -12,7 +12,9 @@ use crate::dashboard::ui::widgets::text::draw_text; use crossterm::event::{KeyCode, KeyEvent}; use ratatui::layout::{Constraint, Direction, Layout, Rect}; use tako::hwstats::MemoryStats; -use tako::resources::{CPU_RESOURCE_NAME, ResourceIndex}; +use tako::resources::{ + CPU_RESOURCE_NAME, ResourceDescriptorItem, ResourceDescriptorKind, ResourceIndex, +}; use tako::{JobTaskId, WorkerId}; mod cpu_util_table; @@ -27,7 +29,8 @@ pub struct WorkerDetail { worker_tasks_table: TasksTable, utilization: Option, - utilization_render_mode: UtilizationRenderMode, + cpu_view_mode: CpuViewMode, + cpu_scope: CpuScope, } impl Default for WorkerDetail { @@ -38,29 +41,112 @@ impl Default for WorkerDetail { worker_config_table: Default::default(), worker_tasks_table: TasksTable::non_interactive(), utilization: None, - utilization_render_mode: UtilizationRenderMode::Worker, + cpu_view_mode: CpuViewMode::WorkerManaged, + cpu_scope: CpuScope::Node, } } } #[derive(PartialEq)] -enum UtilizationRenderMode { +pub enum CpuViewMode { Global, - Worker, + WorkerManaged, + WorkerAssigned, +} + +#[derive(Debug)] +pub enum CpuScope { + Node, + Subset(Vec), } -impl UtilizationRenderMode { - fn next(&mut self) { - *self = match self { - UtilizationRenderMode::Global => UtilizationRenderMode::Worker, - UtilizationRenderMode::Worker => UtilizationRenderMode::Global, +impl CpuScope { + fn estimate_scope( + detected_cpus: &mut Vec, + managed_cpus: Vec<&ResourceDescriptorItem>, + ) -> Option { + let mut all_managed_cpus = vec![]; + for resource in managed_cpus { + match &resource.kind { + ResourceDescriptorKind::List { values } => { + if let Ok(indices) = values + .iter() + .map(|s| s.parse::()) + .collect::, _>>() + { + all_managed_cpus.extend(indices); + } else { + return None; + } + } + ResourceDescriptorKind::Range { start, end } => { + for idx in (u32::from(*start))..=(u32::from(*end)) { + all_managed_cpus.push(ResourceIndex::new(idx)); + } + } + ResourceDescriptorKind::Groups { groups } => { + for group in groups { + if let Ok(indices) = group + .iter() + .map(|s| s.parse::()) + .collect::, _>>() + { + all_managed_cpus.extend(indices); + } else { + return None; + } + } + } + // Based on Resource kind `sum` cannot be used with CPUs. CPUs must have identity + ResourceDescriptorKind::Sum { .. } => unreachable!(), + } + } + + detected_cpus.sort(); + all_managed_cpus.sort(); + + if *detected_cpus == all_managed_cpus { + Some(CpuScope::Node) + } else { + Some(CpuScope::Subset(all_managed_cpus)) } } +} - fn next_text(&self) -> &str { - match self { - UtilizationRenderMode::Global => "Show worker CPU utilization", - UtilizationRenderMode::Worker => "Show global CPU utilization", +impl CpuViewMode { + fn next(&mut self, cpu_manager_state: &CpuScope) { + match cpu_manager_state { + CpuScope::Node => { + *self = match self { + CpuViewMode::WorkerManaged => CpuViewMode::WorkerAssigned, + CpuViewMode::WorkerAssigned => CpuViewMode::WorkerManaged, + CpuViewMode::Global => CpuViewMode::WorkerManaged, // To skip out of the global in case the state changes + } + } + CpuScope::Subset(_items) => { + *self = match self { + CpuViewMode::Global => CpuViewMode::WorkerManaged, + CpuViewMode::WorkerManaged => CpuViewMode::WorkerAssigned, + CpuViewMode::WorkerAssigned => CpuViewMode::Global, + } + } + } + } + + fn next_text(&self, cpu_manager_state: &CpuScope) -> &str { + match cpu_manager_state { + CpuScope::Node => { + match self { + CpuViewMode::WorkerManaged => "Show worker assigned CPU utilization", + CpuViewMode::WorkerAssigned => "Show worker managed CPU utilization", + CpuViewMode::Global => "Show worker managed CPU utilization", // To skip out of the global in case the state changes + } + } + CpuScope::Subset(_items) => match self { + CpuViewMode::Global => "Show worker managed CPU utilization", + CpuViewMode::WorkerManaged => "Show worker assigned CPU utilization", + CpuViewMode::WorkerAssigned => "Show global CPU utilization", + }, } } } @@ -96,7 +182,7 @@ impl WorkerDetail { draw_text( format!( ": Back, : {}", - self.utilization_render_mode.next_text() + self.cpu_view_mode.next_text(&self.cpu_scope) ) .as_str(), layout.footer, @@ -109,7 +195,8 @@ impl WorkerDetail { &util.cpu, &util.memory, &util.used_cpus, - &self.utilization_render_mode, + &self.cpu_view_mode, + &self.cpu_scope, layout.current_utilization, frame, table_style_deselected(), @@ -132,13 +219,19 @@ impl WorkerDetail { pub fn update(&mut self, data: &DashboardData) { if let Some(worker_id) = self.worker_id { self.utilization_history.update(data, worker_id); + let mut worker_config = None; + + if let Some(configuration) = data.workers().query_worker_config_for(worker_id) { + self.worker_config_table.update(configuration); + worker_config = Some(configuration); + } if let Some(overview) = data .workers() .query_worker_overview_at(worker_id, data.current_time()) { - let worker_used_cpus: Vec = match self.utilization_render_mode { - UtilizationRenderMode::Worker => overview + let worker_used_cpus: Vec = match self.cpu_view_mode { + CpuViewMode::WorkerManaged | CpuViewMode::WorkerAssigned => overview .item .running_tasks .iter() @@ -156,7 +249,7 @@ impl WorkerDetail { }) .flatten() .collect(), - UtilizationRenderMode::Global => vec![], + CpuViewMode::Global => vec![], }; if let Some(hw_state) = overview.item.hw_state.as_ref() { @@ -172,16 +265,35 @@ impl WorkerDetail { used_cpus: worker_used_cpus, }) } + + if let Some(configuration) = worker_config { + let managed_cpus: Vec<&ResourceDescriptorItem> = configuration + .resources + .resources + .iter() + .filter(|resource| resource.name == CPU_RESOURCE_NAME) + .collect(); + if let Some(hw_state) = overview.item.hw_state.as_ref() { + let mut detected_cpus: Vec = hw_state + .state + .cpu_usage + .cpu_per_core_percent_usage + .iter() + .enumerate() + .map(|(idx, _)| ResourceIndex::new(idx as u32)) + .collect(); + let cpu_scope = CpuScope::estimate_scope(&mut detected_cpus, managed_cpus); + if let Some(cpu_scope) = cpu_scope { + self.cpu_scope = cpu_scope; + } + } + } } let tasks_info: Vec<(JobTaskId, &TaskInfo)> = data.query_task_history_for_worker(worker_id).collect(); self.worker_tasks_table .update(tasks_info, data.current_time()); - - if let Some(configuration) = data.workers().query_worker_config_for(worker_id) { - self.worker_config_table.update(configuration); - } } } @@ -189,7 +301,7 @@ impl WorkerDetail { pub fn handle_key(&mut self, key: KeyEvent) { match key.code { KeyCode::Backspace => self.worker_tasks_table.clear_selection(), - KeyCode::Char('c') => self.utilization_render_mode.next(), + KeyCode::Char('c') => self.cpu_view_mode.next(&self.cpu_scope), _ => self.worker_tasks_table.handle_key(key), } } diff --git a/crates/hyperqueue/src/dashboard/ui/widgets/progressbar.rs b/crates/hyperqueue/src/dashboard/ui/widgets/progressbar.rs index ce5c8e155..393aca440 100644 --- a/crates/hyperqueue/src/dashboard/ui/widgets/progressbar.rs +++ b/crates/hyperqueue/src/dashboard/ui/widgets/progressbar.rs @@ -1,6 +1,8 @@ use ratatui::style::{Color, Modifier, Style}; use unicode_width::UnicodeWidthStr; +use crate::dashboard::ui::screens::cluster::worker::CpuViewMode; + const GREEN_THRESHOLD: f64 = 0.5; const YELLOW_THRESHOLD: f64 = 0.7; @@ -30,15 +32,29 @@ pub fn get_progress_bar_color(progress: f64) -> Style { } } -pub fn get_progress_bar_cpu_color(progress: f64, used: bool) -> Style { - let color = if !used { - Color::Gray - } else if progress <= GREEN_THRESHOLD { - Color::Green - } else if progress <= YELLOW_THRESHOLD { - Color::Yellow - } else { - Color::Red +pub fn get_cpu_progress_bar_color( + progress: f64, + used: bool, + util_render_mode: &CpuViewMode, +) -> Style { + let color = match util_render_mode { + CpuViewMode::Global | CpuViewMode::WorkerAssigned => { + if progress <= GREEN_THRESHOLD { + Color::Green + } else if progress <= YELLOW_THRESHOLD { + Color::Yellow + } else { + Color::Red + } + } + CpuViewMode::WorkerManaged => match (progress, used) { + (progress, true) if progress <= GREEN_THRESHOLD => Color::Green, + (progress, true) if progress <= YELLOW_THRESHOLD => Color::Yellow, + (_, true) => Color::Red, + (progress, false) if progress <= GREEN_THRESHOLD => Color::LightBlue, + (progress, false) if progress <= YELLOW_THRESHOLD => Color::Cyan, + (_, false) => Color::Magenta, + }, }; Style { From b3c250c2d0e3439ed0282115ab829e4c9179f93b Mon Sep 17 00:00:00 2001 From: Dym03 Date: Sun, 22 Mar 2026 20:17:01 +0100 Subject: [PATCH 3/3] Rewrite Cpu Util Table to class --- .../screens/cluster/worker/cpu_util_table.rs | 437 +++++++++++++----- .../ui/screens/cluster/worker/mod.rs | 214 +-------- .../src/dashboard/ui/widgets/progressbar.rs | 2 +- 3 files changed, 331 insertions(+), 322 deletions(-) diff --git a/crates/hyperqueue/src/dashboard/ui/screens/cluster/worker/cpu_util_table.rs b/crates/hyperqueue/src/dashboard/ui/screens/cluster/worker/cpu_util_table.rs index 61305ec26..93b2b077c 100644 --- a/crates/hyperqueue/src/dashboard/ui/screens/cluster/worker/cpu_util_table.rs +++ b/crates/hyperqueue/src/dashboard/ui/screens/cluster/worker/cpu_util_table.rs @@ -1,13 +1,17 @@ use crate::common::format::human_size; -use crate::dashboard::ui::screens::cluster::worker::{CpuScope, CpuViewMode}; +use crate::dashboard::data::DashboardData; +use itertools::Itertools; use ratatui::layout::{Constraint, Rect}; -use ratatui::style::Style; use ratatui::widgets::{Cell, Row, Table}; use std::cmp; +use tako::WorkerId; use tako::hwstats::MemoryStats; -use tako::resources::ResourceIndex; +use tako::resources::{ + CPU_RESOURCE_NAME, ResourceDescriptorItem, ResourceDescriptorKind, ResourceIndex, +}; +use tako::worker::WorkerConfiguration; -use crate::dashboard::ui::styles; +use crate::dashboard::ui::styles::{self, table_style_deselected}; use crate::dashboard::ui::terminal::DashboardFrame; use crate::dashboard::ui::widgets::progressbar::{ ProgressPrintStyle, get_cpu_progress_bar_color, render_progress_bar_at, @@ -18,112 +22,331 @@ const CPU_METER_PROGRESSBAR_WIDTH: u8 = 18; // 4 characters for the label const CPU_METER_WIDTH: u8 = CPU_METER_PROGRESSBAR_WIDTH + 4; -pub fn render_cpu_util_table( - cpu_util_list: &[f64], - mem_util: &MemoryStats, - used_cpus: &[ResourceIndex], - util_render_mode: &CpuViewMode, - cpu_scope: &CpuScope, - rect: Rect, - frame: &mut DashboardFrame, - table_style: Style, -) { - if cpu_util_list.is_empty() { - return; +#[derive(Default)] +pub struct CpuUtilTable { + utilization: Option, + cpu_view_mode: CpuViewMode, + cpu_scope: CpuScope, +} + +#[derive(Default, PartialEq)] +pub enum CpuViewMode { + Global, + #[default] + WorkerManaged, + WorkerAssigned, +} + +impl CpuViewMode { + pub fn get_visible_indices( + &self, + cpu_scope: &CpuScope, + total_cpus: usize, + used_cpus: &[ResourceIndex], + ) -> Vec { + match self { + CpuViewMode::Global => (0..total_cpus) + .map(|idx| ResourceIndex::new(idx as u32)) + .collect(), + CpuViewMode::WorkerManaged => match cpu_scope { + CpuScope::Node => (0..total_cpus) + .map(|idx| ResourceIndex::new(idx as u32)) + .collect(), + CpuScope::Subset(managed_cpus) => managed_cpus.clone(), + }, + CpuViewMode::WorkerAssigned => used_cpus.to_vec(), + } } - let constraints = get_column_constraints(rect, cpu_util_list.len()); +} + +#[derive(Debug, Default)] +pub enum CpuScope { + #[default] + Node, + Subset(Vec), +} + +impl CpuScope { + fn estimate_scope( + detected_cpus: &mut Vec, + managed_cpus: Vec<&ResourceDescriptorItem>, + ) -> Option { + let mut all_managed_cpus = vec![]; + for resource in managed_cpus { + match &resource.kind { + ResourceDescriptorKind::List { values } => { + if let Ok(indices) = values + .iter() + .map(|s| s.parse::()) + .collect::, _>>() + { + all_managed_cpus.extend(indices); + } else { + return None; + } + } + ResourceDescriptorKind::Range { start, end } => { + for idx in (u32::from(*start))..=(u32::from(*end)) { + all_managed_cpus.push(ResourceIndex::new(idx)); + } + } + ResourceDescriptorKind::Groups { groups } => { + for group in groups { + if let Ok(indices) = group + .iter() + .map(|s| s.parse::()) + .collect::, _>>() + { + all_managed_cpus.extend(indices); + } else { + return None; + } + } + } + // Based on Resource kind `sum` cannot be used with CPUs. CPUs must have identity + ResourceDescriptorKind::Sum { .. } => unreachable!(), + } + } - let width = constraints.len(); - let height = (cpu_util_list.len() as f64 / width as f64).ceil() as usize; + detected_cpus.sort(); + all_managed_cpus.sort(); - let mut rows: Vec> = vec![vec![]; height]; + if *detected_cpus == all_managed_cpus { + Some(CpuScope::Node) + } else { + Some(CpuScope::Subset(all_managed_cpus)) + } + } +} - match util_render_mode { - CpuViewMode::Global => { - for (position, &cpu_util) in cpu_util_list.iter().enumerate() { - let row = position % height; - let used = used_cpus.contains(&ResourceIndex::new(position as u32)); - rows[row].push((cpu_util, position, used)); +impl CpuViewMode { + fn next(&mut self, cpu_manager_state: &CpuScope) { + match cpu_manager_state { + CpuScope::Node => { + *self = match self { + CpuViewMode::WorkerManaged => CpuViewMode::WorkerAssigned, + CpuViewMode::WorkerAssigned => CpuViewMode::WorkerManaged, + CpuViewMode::Global => CpuViewMode::WorkerManaged, // To skip out of the global in case the state changes + } + } + CpuScope::Subset(_items) => { + *self = match self { + CpuViewMode::Global => CpuViewMode::WorkerManaged, + CpuViewMode::WorkerManaged => CpuViewMode::WorkerAssigned, + CpuViewMode::WorkerAssigned => CpuViewMode::Global, + } } } - CpuViewMode::WorkerManaged => match cpu_scope { + } + + fn next_text(&self, cpu_manager_state: &CpuScope) -> &str { + match cpu_manager_state { CpuScope::Node => { - rows = get_utilization_sorted_by_usage(cpu_util_list, used_cpus, height) + match self { + CpuViewMode::WorkerManaged => "Show worker assigned CPU utilization", + CpuViewMode::WorkerAssigned => "Show worker managed CPU utilization", + CpuViewMode::Global => "Show worker managed CPU utilization", // To skip out of the global in case the state changes + } } - CpuScope::Subset(managed_cpus) => { - let managed_cpu_util_list: Vec = cpu_util_list + CpuScope::Subset(_items) => match self { + CpuViewMode::Global => "Show worker managed CPU utilization", + CpuViewMode::WorkerManaged => "Show worker assigned CPU utilization", + CpuViewMode::WorkerAssigned => "Show global CPU utilization", + }, + } + } + + fn set_default(&mut self) { + *self = CpuViewMode::WorkerManaged; + } +} + +struct Utilization { + cpu: Vec, + memory: MemoryStats, + used_cpus: Vec, +} + +impl CpuUtilTable { + pub fn update( + &mut self, + data: &DashboardData, + worker_id: WorkerId, + worker_config: Option<&WorkerConfiguration>, + ) { + if let Some(overview) = data + .workers() + .query_worker_overview_at(worker_id, data.current_time()) + { + let worker_used_cpus: Vec = match self.cpu_view_mode { + CpuViewMode::WorkerManaged | CpuViewMode::WorkerAssigned => overview + .item + .running_tasks .iter() - .enumerate() - .filter_map(|(idx, &cpu_util)| { - if managed_cpus.contains(&ResourceIndex::new(idx as u32)) { - Some(cpu_util) - } else { - None - } + .flat_map(|(_id, task_resource_alloc)| { + task_resource_alloc + .resources + .iter() + .filter_map(|resource_alloc| { + if resource_alloc.resource == CPU_RESOURCE_NAME { + Some(resource_alloc.indices.iter().map(|(index, _)| *index)) + } else { + None + } + }) }) - .collect(); + .flatten() + .collect(), + CpuViewMode::Global => vec![], + }; - rows = get_utilization_sorted_by_usage(&managed_cpu_util_list, used_cpus, height) + if let Some(hw_state) = overview.item.hw_state.as_ref() { + self.utilization = Some(Utilization { + cpu: hw_state + .state + .cpu_usage + .cpu_per_core_percent_usage + .iter() + .map(|&v| v as f64) + .collect(), + memory: hw_state.state.memory_usage.clone(), + used_cpus: worker_used_cpus, + }) } - }, - CpuViewMode::WorkerAssigned => { - let mut r = 0; - for (position, &cpu_util) in cpu_util_list.iter().enumerate() { - let row = r % height; - let used = used_cpus.contains(&ResourceIndex::new(position as u32)); - - if used { - rows[row].push((cpu_util, position, used)); - r += 1; + + if let Some(configuration) = worker_config { + let managed_cpus: Vec<&ResourceDescriptorItem> = configuration + .resources + .resources + .iter() + .filter(|resource| resource.name == CPU_RESOURCE_NAME) + .collect(); + if let Some(hw_state) = overview.item.hw_state.as_ref() { + let mut detected_cpus: Vec = hw_state + .state + .cpu_usage + .cpu_per_core_percent_usage + .iter() + .enumerate() + .map(|(idx, _)| ResourceIndex::new(idx as u32)) + .collect(); + let cpu_scope = CpuScope::estimate_scope(&mut detected_cpus, managed_cpus); + if let Some(cpu_scope) = cpu_scope { + self.cpu_scope = cpu_scope; + } } } } } - let rows: Vec = rows - .into_iter() - .map(|targets| { - let columns: Vec = targets + pub fn draw(&mut self, rect: Rect, frame: &mut DashboardFrame) { + if let Some(util) = &self.utilization { + if util.cpu.is_empty() { + return; + } + + let visible_indices = self.cpu_view_mode.get_visible_indices( + &self.cpu_scope, + util.cpu.len(), + &util.used_cpus, + ); + + let cell_data: Vec<(u32, f64, bool)> = visible_indices .into_iter() - .map(|(cpu_util, position, used)| { - let progress = cpu_util / 100.00; - - let style = get_cpu_progress_bar_color(progress, used, util_render_mode); - - Cell::from(render_progress_bar_at( - Some(format!("{position:>3} ")), - progress, - CPU_METER_PROGRESSBAR_WIDTH, - ProgressPrintStyle::default(), - )) - .style(style) + .map(|idx| { + let val = util + .cpu + .get(idx.as_num() as usize) + .copied() + .unwrap_or_default(); + let is_used = util.used_cpus.contains(&idx); + (idx.as_num(), val, is_used) }) + .sorted_by_key(|&(idx, _, used)| (std::cmp::Reverse(used), idx)) .collect(); - Row::new(columns) - }) - .collect(); - let mem_used = mem_util.total - mem_util.free; - let (which_util, num_cpus, avg_cpu) = - create_title_info(cpu_util_list, used_cpus, util_render_mode, cpu_scope); - - let title = styles::table_title(format!( - "{} Utilization ({} CPUs), Avg CPU = {:.0}%, Mem = {:.0}% ({}/{})", - which_util, - num_cpus, - avg_cpu, - (mem_used as f64 / mem_util.total as f64) * 100.0, - human_size(mem_used), - human_size(mem_util.total) - )); - let body_block = styles::table_block_with_title(title); - - let table = Table::new(rows, constraints) - .block(body_block) - .row_highlight_style(styles::style_table_highlight()) - .style(table_style); - - frame.render_widget(table, rect); + let constraints = get_column_constraints(rect, cell_data.len()); + + let width = constraints.len(); + + let rows: Vec = if width > 0 { + cell_data + .chunks(width) + .map(|chunk| { + let cells: Vec = chunk + .iter() + .map(|(id, cpu_util, used)| { + let progress = cpu_util / 100.0; + let style = get_cpu_progress_bar_color( + progress, + *used, + &self.cpu_view_mode, + ); + + Cell::from(render_progress_bar_at( + Some(format!("{id:>3} ")), + progress, + CPU_METER_PROGRESSBAR_WIDTH, + ProgressPrintStyle::default(), + )) + .style(style) + }) + .collect(); + Row::new(cells) + }) + .collect() + } else { + vec![] + }; + + let mem_used = util.memory.total - util.memory.free; + let (which_util, num_cpus, avg_cpu) = create_title_info( + &util.cpu, + &util.used_cpus, + &self.cpu_view_mode, + &self.cpu_scope, + ); + + let title = styles::table_title(format!( + "{} Utilization ({} CPUs), Avg CPU = {:.0}%, Mem = {:.0}% ({}/{})", + which_util, + num_cpus, + avg_cpu, + (mem_used as f64 / util.memory.total as f64) * 100.0, + human_size(mem_used), + human_size(util.memory.total) + )); + let body_block = styles::table_block_with_title(title); + + let table = Table::new(rows, constraints) + .block(body_block) + .row_highlight_style(styles::style_table_highlight()) + .style(table_style_deselected()); + + frame.render_widget(table, rect); + } + } + + pub fn next_view(&mut self) { + self.cpu_view_mode.next(&self.cpu_scope); + } + + pub fn next_text(&mut self) -> &str { + self.cpu_view_mode.next_text(&self.cpu_scope) + } + + pub fn clear_table(&mut self) { + self.clear_util(); + self.set_default_view(); + } + + fn clear_util(&mut self) { + self.utilization = None; + } + + fn set_default_view(&mut self) { + self.cpu_view_mode.set_default(); + } } /// Creates the column sizes for the cpu_util_table, each column divides the row equally. @@ -131,35 +354,15 @@ fn get_column_constraints(rect: Rect, num_cpus: usize) -> Vec { let max_columns = (rect.width / CPU_METER_WIDTH as u16) as usize; let num_columns = cmp::min(max_columns, num_cpus); - std::iter::repeat_n( - Constraint::Percentage((100 / num_columns) as u16), - num_columns, - ) - .collect() -} - -fn get_utilization_sorted_by_usage( - cpu_util_list: &[f64], - used_cpus: &[ResourceIndex], - height: usize, -) -> Vec> { - let mut all_cpus: Vec<(f64, usize, bool)> = cpu_util_list - .iter() - .enumerate() - .map(|(position, &cpu_util)| { - let used = used_cpus.contains(&ResourceIndex::new(position as u32)); - (cpu_util, position, used) - }) - .collect(); - - all_cpus.sort_by_key(|&(_, _, used)| std::cmp::Reverse(used)); - - let mut rows: Vec> = vec![vec![]; height]; - for (index, cpu_data) in all_cpus.into_iter().enumerate() { - let row = index % height; - rows[row].push(cpu_data); + if num_columns > 0 { + std::iter::repeat_n( + Constraint::Percentage((100 / num_columns) as u16), + num_columns, + ) + .collect() + } else { + vec![] } - rows } fn create_title_info( diff --git a/crates/hyperqueue/src/dashboard/ui/screens/cluster/worker/mod.rs b/crates/hyperqueue/src/dashboard/ui/screens/cluster/worker/mod.rs index c7f8f7667..012dc0b4d 100644 --- a/crates/hyperqueue/src/dashboard/ui/screens/cluster/worker/mod.rs +++ b/crates/hyperqueue/src/dashboard/ui/screens/cluster/worker/mod.rs @@ -1,23 +1,17 @@ use crate::dashboard::data::DashboardData; use crate::dashboard::data::timelines::job_timeline::TaskInfo; -use crate::dashboard::ui::screens::cluster::worker::cpu_util_table::render_cpu_util_table; +use crate::dashboard::ui::screens::cluster::worker::cpu_util_table::CpuUtilTable; use crate::dashboard::ui::screens::cluster::worker::worker_config_table::WorkerConfigTable; use crate::dashboard::ui::screens::cluster::worker::worker_utilization_chart::WorkerUtilizationChart; -use crate::dashboard::ui::styles::{ - style_footer, style_header_text, table_style_deselected, table_style_selected, -}; +use crate::dashboard::ui::styles::{style_footer, style_header_text, table_style_selected}; use crate::dashboard::ui::terminal::DashboardFrame; use crate::dashboard::ui::widgets::tasks_table::TasksTable; use crate::dashboard::ui::widgets::text::draw_text; use crossterm::event::{KeyCode, KeyEvent}; use ratatui::layout::{Constraint, Direction, Layout, Rect}; -use tako::hwstats::MemoryStats; -use tako::resources::{ - CPU_RESOURCE_NAME, ResourceDescriptorItem, ResourceDescriptorKind, ResourceIndex, -}; use tako::{JobTaskId, WorkerId}; -mod cpu_util_table; +pub mod cpu_util_table; mod worker_config_table; mod worker_utilization_chart; @@ -27,10 +21,7 @@ pub struct WorkerDetail { utilization_history: WorkerUtilizationChart, worker_config_table: WorkerConfigTable, worker_tasks_table: TasksTable, - - utilization: Option, - cpu_view_mode: CpuViewMode, - cpu_scope: CpuScope, + cpu_util_table: CpuUtilTable, } impl Default for WorkerDetail { @@ -40,127 +31,15 @@ impl Default for WorkerDetail { utilization_history: Default::default(), worker_config_table: Default::default(), worker_tasks_table: TasksTable::non_interactive(), - utilization: None, - cpu_view_mode: CpuViewMode::WorkerManaged, - cpu_scope: CpuScope::Node, + cpu_util_table: Default::default(), } } } -#[derive(PartialEq)] -pub enum CpuViewMode { - Global, - WorkerManaged, - WorkerAssigned, -} - -#[derive(Debug)] -pub enum CpuScope { - Node, - Subset(Vec), -} - -impl CpuScope { - fn estimate_scope( - detected_cpus: &mut Vec, - managed_cpus: Vec<&ResourceDescriptorItem>, - ) -> Option { - let mut all_managed_cpus = vec![]; - for resource in managed_cpus { - match &resource.kind { - ResourceDescriptorKind::List { values } => { - if let Ok(indices) = values - .iter() - .map(|s| s.parse::()) - .collect::, _>>() - { - all_managed_cpus.extend(indices); - } else { - return None; - } - } - ResourceDescriptorKind::Range { start, end } => { - for idx in (u32::from(*start))..=(u32::from(*end)) { - all_managed_cpus.push(ResourceIndex::new(idx)); - } - } - ResourceDescriptorKind::Groups { groups } => { - for group in groups { - if let Ok(indices) = group - .iter() - .map(|s| s.parse::()) - .collect::, _>>() - { - all_managed_cpus.extend(indices); - } else { - return None; - } - } - } - // Based on Resource kind `sum` cannot be used with CPUs. CPUs must have identity - ResourceDescriptorKind::Sum { .. } => unreachable!(), - } - } - - detected_cpus.sort(); - all_managed_cpus.sort(); - - if *detected_cpus == all_managed_cpus { - Some(CpuScope::Node) - } else { - Some(CpuScope::Subset(all_managed_cpus)) - } - } -} - -impl CpuViewMode { - fn next(&mut self, cpu_manager_state: &CpuScope) { - match cpu_manager_state { - CpuScope::Node => { - *self = match self { - CpuViewMode::WorkerManaged => CpuViewMode::WorkerAssigned, - CpuViewMode::WorkerAssigned => CpuViewMode::WorkerManaged, - CpuViewMode::Global => CpuViewMode::WorkerManaged, // To skip out of the global in case the state changes - } - } - CpuScope::Subset(_items) => { - *self = match self { - CpuViewMode::Global => CpuViewMode::WorkerManaged, - CpuViewMode::WorkerManaged => CpuViewMode::WorkerAssigned, - CpuViewMode::WorkerAssigned => CpuViewMode::Global, - } - } - } - } - - fn next_text(&self, cpu_manager_state: &CpuScope) -> &str { - match cpu_manager_state { - CpuScope::Node => { - match self { - CpuViewMode::WorkerManaged => "Show worker assigned CPU utilization", - CpuViewMode::WorkerAssigned => "Show worker managed CPU utilization", - CpuViewMode::Global => "Show worker managed CPU utilization", // To skip out of the global in case the state changes - } - } - CpuScope::Subset(_items) => match self { - CpuViewMode::Global => "Show worker managed CPU utilization", - CpuViewMode::WorkerManaged => "Show worker assigned CPU utilization", - CpuViewMode::WorkerAssigned => "Show global CPU utilization", - }, - } - } -} - -struct Utilization { - cpu: Vec, - memory: MemoryStats, - used_cpus: Vec, -} - impl WorkerDetail { pub fn clear_worker_id(&mut self) { self.worker_id = None; - self.utilization = None; + self.cpu_util_table.clear_table(); } pub fn set_worker_id(&mut self, worker_id: WorkerId) { @@ -182,7 +61,7 @@ impl WorkerDetail { draw_text( format!( ": Back, : {}", - self.cpu_view_mode.next_text(&self.cpu_scope) + self.cpu_util_table.next_text() ) .as_str(), layout.footer, @@ -190,18 +69,7 @@ impl WorkerDetail { style_footer(), ); - if let Some(util) = &self.utilization { - render_cpu_util_table( - &util.cpu, - &util.memory, - &util.used_cpus, - &self.cpu_view_mode, - &self.cpu_scope, - layout.current_utilization, - frame, - table_style_deselected(), - ); - } + self.cpu_util_table.draw(layout.current_utilization, frame); self.utilization_history .draw(layout.utilization_history, frame); @@ -226,69 +94,7 @@ impl WorkerDetail { worker_config = Some(configuration); } - if let Some(overview) = data - .workers() - .query_worker_overview_at(worker_id, data.current_time()) - { - let worker_used_cpus: Vec = match self.cpu_view_mode { - CpuViewMode::WorkerManaged | CpuViewMode::WorkerAssigned => overview - .item - .running_tasks - .iter() - .flat_map(|(_id, task_resource_alloc)| { - task_resource_alloc - .resources - .iter() - .filter_map(|resource_alloc| { - if resource_alloc.resource == CPU_RESOURCE_NAME { - Some(resource_alloc.indices.iter().map(|(index, _)| *index)) - } else { - None - } - }) - }) - .flatten() - .collect(), - CpuViewMode::Global => vec![], - }; - - if let Some(hw_state) = overview.item.hw_state.as_ref() { - self.utilization = Some(Utilization { - cpu: hw_state - .state - .cpu_usage - .cpu_per_core_percent_usage - .iter() - .map(|&v| v as f64) - .collect(), - memory: hw_state.state.memory_usage.clone(), - used_cpus: worker_used_cpus, - }) - } - - if let Some(configuration) = worker_config { - let managed_cpus: Vec<&ResourceDescriptorItem> = configuration - .resources - .resources - .iter() - .filter(|resource| resource.name == CPU_RESOURCE_NAME) - .collect(); - if let Some(hw_state) = overview.item.hw_state.as_ref() { - let mut detected_cpus: Vec = hw_state - .state - .cpu_usage - .cpu_per_core_percent_usage - .iter() - .enumerate() - .map(|(idx, _)| ResourceIndex::new(idx as u32)) - .collect(); - let cpu_scope = CpuScope::estimate_scope(&mut detected_cpus, managed_cpus); - if let Some(cpu_scope) = cpu_scope { - self.cpu_scope = cpu_scope; - } - } - } - } + self.cpu_util_table.update(data, worker_id, worker_config); let tasks_info: Vec<(JobTaskId, &TaskInfo)> = data.query_task_history_for_worker(worker_id).collect(); @@ -301,7 +107,7 @@ impl WorkerDetail { pub fn handle_key(&mut self, key: KeyEvent) { match key.code { KeyCode::Backspace => self.worker_tasks_table.clear_selection(), - KeyCode::Char('c') => self.cpu_view_mode.next(&self.cpu_scope), + KeyCode::Char('c') => self.cpu_util_table.next_view(), _ => self.worker_tasks_table.handle_key(key), } } diff --git a/crates/hyperqueue/src/dashboard/ui/widgets/progressbar.rs b/crates/hyperqueue/src/dashboard/ui/widgets/progressbar.rs index 393aca440..dc3f56dd3 100644 --- a/crates/hyperqueue/src/dashboard/ui/widgets/progressbar.rs +++ b/crates/hyperqueue/src/dashboard/ui/widgets/progressbar.rs @@ -1,7 +1,7 @@ use ratatui::style::{Color, Modifier, Style}; use unicode_width::UnicodeWidthStr; -use crate::dashboard::ui::screens::cluster::worker::CpuViewMode; +use crate::dashboard::ui::screens::cluster::worker::cpu_util_table::CpuViewMode; const GREEN_THRESHOLD: f64 = 0.5; const YELLOW_THRESHOLD: f64 = 0.7;