From dc8a55da010dc6207ea58d8ae93c9fff06582362 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Mon, 14 Oct 2024 23:37:24 +0200 Subject: [PATCH 01/31] Bring dashboard into a compilable state --- crates/hyperqueue/src/dashboard/data/data.rs | 2 +- crates/hyperqueue/src/dashboard/data/fetch.rs | 31 ++++++------- .../data/timelines/alloc_timeline.rs | 10 ++--- .../dashboard/data/timelines/job_timeline.rs | 43 ++++++++++++------- .../data/timelines/worker_timeline.rs | 8 ++-- .../ui/fragments/job/job_info_display.rs | 38 ++++++++-------- .../dashboard/ui/fragments/job/jobs_table.rs | 2 +- 7 files changed, 74 insertions(+), 60 deletions(-) diff --git a/crates/hyperqueue/src/dashboard/data/data.rs b/crates/hyperqueue/src/dashboard/data/data.rs index 7f7a6c3e8..48853d9a2 100644 --- a/crates/hyperqueue/src/dashboard/data/data.rs +++ b/crates/hyperqueue/src/dashboard/data/data.rs @@ -27,7 +27,7 @@ pub struct DashboardData { impl DashboardData { pub fn push_new_events(&mut self, mut events: Vec) { - events.sort_unstable_by_key(|e| e.time()); + events.sort_unstable_by_key(|e| e.time.time()); // Update data views self.worker_timeline.handle_new_events(&events); diff --git a/crates/hyperqueue/src/dashboard/data/fetch.rs b/crates/hyperqueue/src/dashboard/data/fetch.rs index a1032ba4c..58a64a62a 100644 --- a/crates/hyperqueue/src/dashboard/data/fetch.rs +++ b/crates/hyperqueue/src/dashboard/data/fetch.rs @@ -4,7 +4,7 @@ use crate::server::event::{Event, EventId}; use crate::transfer::connection::{ClientConnection, ClientSession}; use crate::transfer::messages::{FromClientMessage, ToClientMessage}; use std::time::Duration; -use tako::gateway::MonitoringEventRequest; +// use tako::gateway::MonitoringEventRequest; use tokio::sync::mpsc::UnboundedSender; pub async fn create_data_fetch_process( @@ -18,11 +18,11 @@ pub async fn create_data_fetch_process( loop { let events = fetch_events_after(session.connection(), fetched_until).await?; - fetched_until = events - .iter() - .map(|event| event.id()) - .max() - .or(fetched_until); + // fetched_until = events + // .iter() + // .map(|event| event.id()) + // .max() + // .or(fetched_until); sender.send(DashboardEvent::FetchedEvents(events))?; tick_duration.tick().await; @@ -34,13 +34,14 @@ async fn fetch_events_after( connection: &mut ClientConnection, after_id: Option, ) -> crate::Result> { - rpc_call!( - connection, - FromClientMessage::MonitoringEvents ( - MonitoringEventRequest { - after_id, - }), - ToClientMessage::MonitoringEventsResponse(response) => response - ) - .await + // rpc_call!( + // connection, + // FromClientMessage::MonitoringEvents ( + // MonitoringEventRequest { + // after_id, + // }), + // ToClientMessage::MonitoringEventsResponse(response) => response + // ) + // .await + todo!() } diff --git a/crates/hyperqueue/src/dashboard/data/timelines/alloc_timeline.rs b/crates/hyperqueue/src/dashboard/data/timelines/alloc_timeline.rs index b83b281a1..8cf590ebd 100644 --- a/crates/hyperqueue/src/dashboard/data/timelines/alloc_timeline.rs +++ b/crates/hyperqueue/src/dashboard/data/timelines/alloc_timeline.rs @@ -102,7 +102,7 @@ impl AllocationTimeline { *id, AllocationQueueInfo { queue_params: *params.clone(), - creation_time: event.time, + creation_time: event.time.into(), removal_time: None, allocations: Default::default(), }, @@ -110,7 +110,7 @@ impl AllocationTimeline { } EventPayload::AllocationQueueRemoved(queue_id) => { let queue_state = self.queue_timelines.get_mut(queue_id).unwrap(); - queue_state.removal_time = Some(event.time); + queue_state.removal_time = Some(event.time.into()); } EventPayload::AllocationQueued { queue_id, @@ -121,7 +121,7 @@ impl AllocationTimeline { queue_state.add_queued_allocation( allocation_id.clone(), *worker_count, - event.time, + event.time.into(), ); } EventPayload::AllocationStarted(queue_id, allocation_id) => { @@ -129,7 +129,7 @@ impl AllocationTimeline { queue_state.update_allocation_state( allocation_id, AllocationStatus::Running, - event.time, + event.time.into(), ); } EventPayload::AllocationFinished(queue_id, allocation_id) => { @@ -137,7 +137,7 @@ impl AllocationTimeline { queue_state.update_allocation_state( allocation_id, AllocationStatus::Finished, - event.time, + event.time.into(), ); } _ => {} diff --git a/crates/hyperqueue/src/dashboard/data/timelines/job_timeline.rs b/crates/hyperqueue/src/dashboard/data/timelines/job_timeline.rs index 2c30265b0..8fd2ad552 100644 --- a/crates/hyperqueue/src/dashboard/data/timelines/job_timeline.rs +++ b/crates/hyperqueue/src/dashboard/data/timelines/job_timeline.rs @@ -1,4 +1,4 @@ -use crate::server::event::payload::{EventPayload, JobInfo}; +use crate::server::event::payload::EventPayload; use crate::server::event::Event; use crate::{JobId, JobTaskId, TakoTaskId, WorkerId}; use chrono::{DateTime, Utc}; @@ -6,7 +6,7 @@ use std::time::SystemTime; use tako::Map; pub struct DashboardJobInfo { - pub job_info: JobInfo, + // pub job_info: JobInfo, pub job_tasks_info: Map, pub job_creation_time: SystemTime, @@ -53,28 +53,35 @@ impl JobTimeline { pub fn handle_new_events(&mut self, events: &[Event]) { for event in events { match &event.payload { - EventPayload::Submit(job_id, job_info) => { - self.job_timeline.insert( - *job_id, - DashboardJobInfo { - job_info: *job_info.clone(), - job_tasks_info: Default::default(), - job_creation_time: event.time, - completion_date: None, - }, - ); + EventPayload::Submit { + job_id, + closed_job, + serialized_desc, + } => { + todo!(); + // self.job_timeline.insert( + // *job_id, + // DashboardJobInfo { + // job_info: *job_info.clone(), + // job_tasks_info: Default::default(), + // job_creation_time: event.time, + // completion_date: None, + // }, + // ); } - EventPayload::JobCompleted(job_id, completion_date) => { + EventPayload::JobCompleted(job_id) => { if let Some(job_info) = self.job_timeline.get_mut(job_id) { - job_info.completion_date = Some(*completion_date) + // job_info.completion_date = Some(*completion_date) + todo!() } } EventPayload::TaskStarted { job_id, task_id, - worker_id, + instance_id, + workers, } => { todo!() /*if let Some((_, info)) = self @@ -102,7 +109,11 @@ impl JobTimeline { &event.time, );*/ } - EventPayload::TaskFailed { job_id, task_id } => { + EventPayload::TaskFailed { + job_id, + task_id, + error, + } => { todo!() /*update_task_status( &mut self.job_timeline, diff --git a/crates/hyperqueue/src/dashboard/data/timelines/worker_timeline.rs b/crates/hyperqueue/src/dashboard/data/timelines/worker_timeline.rs index ed1d8c49c..6fd566595 100644 --- a/crates/hyperqueue/src/dashboard/data/timelines/worker_timeline.rs +++ b/crates/hyperqueue/src/dashboard/data/timelines/worker_timeline.rs @@ -52,7 +52,7 @@ impl WorkerTimeline { self.workers.insert( *id, WorkerRecord { - connection_time: event.time, + connection_time: event.time.into(), worker_config: *info.clone(), worker_overviews: Default::default(), disconnect_info: None, @@ -61,12 +61,14 @@ impl WorkerTimeline { } EventPayload::WorkerLost(lost_id, reason) => { if let Some(worker) = self.workers.get_mut(lost_id) { - worker.set_loss_details(event.time, reason.clone()); + worker.set_loss_details(event.time.into(), reason.clone()); } } EventPayload::WorkerOverviewReceived(overview) => { if let Some(worker) = self.workers.get_mut(&overview.id) { - worker.worker_overviews.push(event.time, overview.clone()); + worker + .worker_overviews + .push(event.time.into(), overview.clone()); } } _ => {} diff --git a/crates/hyperqueue/src/dashboard/ui/fragments/job/job_info_display.rs b/crates/hyperqueue/src/dashboard/ui/fragments/job/job_info_display.rs index 8977fb936..5583e76f6 100644 --- a/crates/hyperqueue/src/dashboard/ui/fragments/job/job_info_display.rs +++ b/crates/hyperqueue/src/dashboard/ui/fragments/job/job_info_display.rs @@ -46,21 +46,21 @@ fn create_rows(params: &DashboardJobInfo) -> Vec { let end_time: DateTime = time.into(); end_time.format("%b %e, %T").to_string().into() }); - let log_path = params - .job_info - .job_desc - .log - .as_ref() - .map(|log_path| log_path.to_string_lossy().to_string()) - .unwrap_or_default(); + // let log_path = params + // .job_info + // .job_desc + // .log + // .as_ref() + // .map(|log_path| log_path.to_string_lossy().to_string()) + // .unwrap_or_default(); vec![ JobInfoDataRow { label: "Job Type: ", - data: match params.job_info.job_desc.task_desc { - JobTaskDescription::Array { .. } => "Array".into(), - JobTaskDescription::Graph { .. } => "Graph".into(), - }, + data: todo!(), // match params.job_info.job_desc.task_desc { + // JobTaskDescription::Array { .. } => "Array".into(), + // JobTaskDescription::Graph { .. } => "Graph".into(), + // }, }, JobInfoDataRow { label: "Creation Time: ", @@ -76,17 +76,17 @@ fn create_rows(params: &DashboardJobInfo) -> Vec { }, JobInfoDataRow { label: "Log Path: ", - data: log_path.into(), + data: todo!(), //log_path.into(), }, JobInfoDataRow { label: "Max Fails: ", - data: params - .job_info - .job_desc - .max_fails - .map(|fails| fails.to_string()) - .unwrap_or_default() - .into(), + data: todo!(), // params + // .job_info + // .job_desc + // .max_fails + // .map(|fails| fails.to_string()) + // .unwrap_or_default() + // .into(), }, ] } diff --git a/crates/hyperqueue/src/dashboard/ui/fragments/job/jobs_table.rs b/crates/hyperqueue/src/dashboard/ui/fragments/job/jobs_table.rs index 1020dcac1..5fe65bb37 100644 --- a/crates/hyperqueue/src/dashboard/ui/fragments/job/jobs_table.rs +++ b/crates/hyperqueue/src/dashboard/ui/fragments/job/jobs_table.rs @@ -78,7 +78,7 @@ fn create_rows(job_infos: Vec<(&JobId, &DashboardJobInfo)>) -> Vec { .iter() .map(|(job_id, info)| JobInfoRow { id: **job_id, - name: info.job_info.job_desc.name.clone(), + name: todo!(), //info.job_info.job_desc.name.clone(), }) .collect() } From 32aa5e71564f92dfcb8c2b06cb09207154609d5a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Mon, 14 Oct 2024 23:38:48 +0200 Subject: [PATCH 02/31] Update ratatui to 0.28 --- Cargo.lock | 84 +++++++------------ crates/hyperqueue/Cargo.toml | 2 +- .../src/dashboard/ui/widgets/chart.rs | 8 +- 3 files changed, 37 insertions(+), 57 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8179d4ef8..8490980b2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -366,13 +366,14 @@ dependencies = [ [[package]] name = "compact_str" -version = "0.7.1" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f86b9c4c00838774a6d902ef931eff7470720c51d90c2e32cfe15dc304737b3f" +checksum = "6050c3a16ddab2e412160b31f2c871015704239bca62f72f6e5f0be631d3f644" dependencies = [ "castaway", "cfg-if", "itoa", + "rustversion", "ryu", "static_assertions", ] @@ -1099,6 +1100,16 @@ dependencies = [ "similar", ] +[[package]] +name = "instability" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b23a0c8dfe501baac4adf6ebbfa6eddf8f0c07f56b058cc1288017e32397846c" +dependencies = [ + "quote", + "syn 2.0.79", +] + [[package]] name = "instant" version = "0.1.13" @@ -1145,15 +1156,6 @@ dependencies = [ "either", ] -[[package]] -name = "itertools" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" -dependencies = [ - "either", -] - [[package]] name = "itertools" version = "0.13.0" @@ -1216,17 +1218,6 @@ version = "0.2.159" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "561d97a539a36e26a9a5fad1ea11a3039a67714694aaa379433e580854bc3dc5" -[[package]] -name = "libredox" -version = "0.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3af92c55d7d839293953fcd0fda5ecfe93297cfde6ffbdec13b41d99c0ba6607" -dependencies = [ - "bitflags 2.6.0", - "libc", - "redox_syscall 0.4.1", -] - [[package]] name = "libredox" version = "0.1.3" @@ -1235,6 +1226,7 @@ checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" dependencies = [ "bitflags 2.6.0", "libc", + "redox_syscall 0.5.7", ] [[package]] @@ -1397,6 +1389,12 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8f8bdf33df195859076e54ab11ee78a1b208382d3a26ec40d142ffc1ecc49ef" +[[package]] +name = "numtoa" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6aa2c4e539b869820a2b82e1aef6ff40aa85e65decdd5185e83fb4b1249cd00f" + [[package]] name = "object" version = "0.36.5" @@ -1770,19 +1768,20 @@ dependencies = [ [[package]] name = "ratatui" -version = "0.26.3" +version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f44c9e68fd46eda15c646fbb85e1040b657a58cdc8c98db1d97a55930d991eef" +checksum = "fdef7f9be5c0122f890d58bdf4d964349ba6a6161f705907526d891efabba57d" dependencies = [ "bitflags 2.6.0", "cassowary", "compact_str", - "itertools 0.12.1", + "instability", + "itertools 0.13.0", "lru", "paste 1.0.15", - "stability", "strum", - "termion 3.0.0", + "strum_macros", + "termion 4.0.3", "unicode-segmentation", "unicode-truncate", "unicode-width", @@ -1817,15 +1816,6 @@ dependencies = [ "bitflags 1.3.2", ] -[[package]] -name = "redox_syscall" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" -dependencies = [ - "bitflags 1.3.2", -] - [[package]] name = "redox_syscall" version = "0.5.7" @@ -1848,7 +1838,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" dependencies = [ "getrandom", - "libredox 0.1.3", + "libredox", "thiserror", ] @@ -2093,16 +2083,6 @@ dependencies = [ "windows-sys 0.52.0", ] -[[package]] -name = "stability" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d904e7009df136af5297832a3ace3370cd14ff1546a232f4f185036c2736fcac" -dependencies = [ - "quote", - "syn 2.0.79", -] - [[package]] name = "stacker" version = "0.1.17" @@ -2251,20 +2231,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "077185e2eac69c3f8379a4298e1e07cd36beb962290d4a51199acf0fdc10607e" dependencies = [ "libc", - "numtoa", + "numtoa 0.1.0", "redox_syscall 0.2.16", "redox_termios", ] [[package]] name = "termion" -version = "3.0.0" +version = "4.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "417813675a504dfbbf21bfde32c03e5bf9f2413999962b479023c02848c1c7a5" +checksum = "7eaa98560e51a2cf4f0bb884d8b2098a9ea11ecf3b7078e9c68242c74cc923a7" dependencies = [ "libc", - "libredox 0.0.2", - "numtoa", + "libredox", + "numtoa 0.2.4", "redox_termios", ] diff --git a/crates/hyperqueue/Cargo.toml b/crates/hyperqueue/Cargo.toml index 72d58cea5..e3be71539 100644 --- a/crates/hyperqueue/Cargo.toml +++ b/crates/hyperqueue/Cargo.toml @@ -57,7 +57,7 @@ itertools = "0.13.0" lru = "0.12" # Dashboard -ratatui = { version = "0.26", default-features = false, features = ["termion"], optional = true } +ratatui = { version = "0.28", default-features = false, features = ["termion"], optional = true } termion = { version = "1.5", optional = true } unicode-width = { version = "0.1.5", optional = true } diff --git a/crates/hyperqueue/src/dashboard/ui/widgets/chart.rs b/crates/hyperqueue/src/dashboard/ui/widgets/chart.rs index 66773ef6a..f9a2747cf 100644 --- a/crates/hyperqueue/src/dashboard/ui/widgets/chart.rs +++ b/crates/hyperqueue/src/dashboard/ui/widgets/chart.rs @@ -203,8 +203,8 @@ pub fn x_axis_time_chart(range: TimeRange) -> Axis<'static> { .style(Style::default().fg(Color::Gray)) .bounds([get_time_as_secs(range.start), get_time_as_secs(range.end)]) .labels(vec![ - format_time_hms(range.start).into(), - format_time_hms(range.end).into(), + format_time_hms(range.start), + format_time_hms(range.end), ]) } @@ -221,9 +221,9 @@ pub fn y_axis_steps(min: f64, max: f64, step_count: u32) -> Axis<'static> { .map(|step| { let value = step as f64 * interval; let value = value.round() as u64; - value.to_string().into() + value.to_string() }) - .collect(), + .collect::>(), ) .labels_alignment(Alignment::Right) } From 18125ad4f8451f5638b1ff0ea9f2e412e72ccec9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Mon, 14 Oct 2024 23:55:54 +0200 Subject: [PATCH 03/31] Replace termion with crossterm --- Cargo.lock | 84 +++++++++---------- crates/hyperqueue/Cargo.toml | 6 +- crates/hyperqueue/src/dashboard/events.rs | 4 +- .../auto_allocator/allocations_info_table.rs | 10 +-- .../ui/fragments/auto_allocator/fragment.rs | 15 ++-- .../auto_allocator/queue_info_table.rs | 10 +-- .../dashboard/ui/fragments/job/fragment.rs | 15 ++-- .../dashboard/ui/fragments/job/jobs_table.rs | 13 ++- crates/hyperqueue/src/dashboard/ui/screen.rs | 4 +- .../dashboard/ui/screens/autoalloc_screen.rs | 4 +- .../src/dashboard/ui/screens/job_screen.rs | 10 +-- .../ui/screens/overview_screen/mod.rs | 10 +-- .../overview_screen/overview/fragment.rs | 10 +-- .../overview_screen/worker/fragment.rs | 12 +-- .../src/dashboard/ui/screens/root_screen.rs | 20 ++--- .../hyperqueue/src/dashboard/ui/terminal.rs | 17 ++-- .../src/dashboard/ui/widgets/tasks_table.rs | 10 +-- crates/hyperqueue/src/dashboard/ui_loop.rs | 82 +++++------------- 18 files changed, 141 insertions(+), 195 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8490980b2..7c63e94ef 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -497,6 +497,31 @@ version = "0.8.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" +[[package]] +name = "crossterm" +version = "0.28.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "829d955a0bb380ef178a640b91779e3987da38c9aea133b20614cfed8cdea9c6" +dependencies = [ + "bitflags 2.6.0", + "crossterm_winapi", + "mio", + "parking_lot 0.12.3", + "rustix", + "signal-hook", + "signal-hook-mio", + "winapi", +] + +[[package]] +name = "crossterm_winapi" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b" +dependencies = [ + "winapi", +] + [[package]] name = "csv" version = "1.3.0" @@ -981,6 +1006,7 @@ dependencies = [ "const_format", "core_affinity", "criterion", + "crossterm", "derive_builder", "dirs", "env_logger", @@ -1011,7 +1037,6 @@ dependencies = [ "smallvec", "tako", "tempfile", - "termion 1.5.6", "textwrap 0.16.1", "thiserror", "tokio", @@ -1226,7 +1251,6 @@ checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" dependencies = [ "bitflags 2.6.0", "libc", - "redox_syscall 0.5.7", ] [[package]] @@ -1304,6 +1328,7 @@ checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" dependencies = [ "hermit-abi 0.3.9", "libc", + "log", "wasi", "windows-sys 0.52.0", ] @@ -1383,18 +1408,6 @@ dependencies = [ "libc", ] -[[package]] -name = "numtoa" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8f8bdf33df195859076e54ab11ee78a1b208382d3a26ec40d142ffc1ecc49ef" - -[[package]] -name = "numtoa" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6aa2c4e539b869820a2b82e1aef6ff40aa85e65decdd5185e83fb4b1249cd00f" - [[package]] name = "object" version = "0.36.5" @@ -1775,13 +1788,13 @@ dependencies = [ "bitflags 2.6.0", "cassowary", "compact_str", + "crossterm", "instability", "itertools 0.13.0", "lru", "paste 1.0.15", "strum", "strum_macros", - "termion 4.0.3", "unicode-segmentation", "unicode-truncate", "unicode-width", @@ -1825,12 +1838,6 @@ dependencies = [ "bitflags 2.6.0", ] -[[package]] -name = "redox_termios" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20145670ba436b55d91fc92d25e71160fbfbdd57831631c8d7d36377a476f1cb" - [[package]] name = "redox_users" version = "0.4.6" @@ -2034,6 +2041,17 @@ dependencies = [ "signal-hook-registry", ] +[[package]] +name = "signal-hook-mio" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34db1a06d485c9142248b7a054f034b349b212551f3dfd19c94d45a754a217cd" +dependencies = [ + "libc", + "mio", + "signal-hook", +] + [[package]] name = "signal-hook-registry" version = "1.4.2" @@ -2224,30 +2242,6 @@ dependencies = [ "winapi-util", ] -[[package]] -name = "termion" -version = "1.5.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "077185e2eac69c3f8379a4298e1e07cd36beb962290d4a51199acf0fdc10607e" -dependencies = [ - "libc", - "numtoa 0.1.0", - "redox_syscall 0.2.16", - "redox_termios", -] - -[[package]] -name = "termion" -version = "4.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7eaa98560e51a2cf4f0bb884d8b2098a9ea11ecf3b7078e9c68242c74cc923a7" -dependencies = [ - "libc", - "libredox", - "numtoa 0.2.4", - "redox_termios", -] - [[package]] name = "textwrap" version = "0.11.0" diff --git a/crates/hyperqueue/Cargo.toml b/crates/hyperqueue/Cargo.toml index e3be71539..823eee1f9 100644 --- a/crates/hyperqueue/Cargo.toml +++ b/crates/hyperqueue/Cargo.toml @@ -57,8 +57,8 @@ itertools = "0.13.0" lru = "0.12" # Dashboard -ratatui = { version = "0.28", default-features = false, features = ["termion"], optional = true } -termion = { version = "1.5", optional = true } +ratatui = { version = "0.28", default-features = false, features = ["crossterm"], optional = true } +crossterm = { version = "0.28", optional = true } unicode-width = { version = "0.1.5", optional = true } # Tako @@ -79,7 +79,7 @@ zero-worker = [] # Use the jemalloc allocator jemalloc = ["jemallocator"] # Enable the dashboard -dashboard = ["dep:ratatui", "dep:termion", "dep:unicode-width"] +dashboard = ["dep:ratatui", "dep:crossterm", "dep:unicode-width"] [[bench]] name = "benchmark" diff --git a/crates/hyperqueue/src/dashboard/events.rs b/crates/hyperqueue/src/dashboard/events.rs index 27f317e2a..06cedab17 100644 --- a/crates/hyperqueue/src/dashboard/events.rs +++ b/crates/hyperqueue/src/dashboard/events.rs @@ -1,10 +1,10 @@ use crate::server::event::Event; -use termion::event::Key; +use crossterm::event::KeyEvent; #[derive(Debug, Clone)] pub enum DashboardEvent { /// The event when a key is pressed - KeyPressEvent(Key), + KeyPressEvent(KeyEvent), /// Updates the dashboard ui with the latest data UiTick, /// New events were fetched from the server diff --git a/crates/hyperqueue/src/dashboard/ui/fragments/auto_allocator/allocations_info_table.rs b/crates/hyperqueue/src/dashboard/ui/fragments/auto_allocator/allocations_info_table.rs index 18f78b8df..806d8ec77 100644 --- a/crates/hyperqueue/src/dashboard/ui/fragments/auto_allocator/allocations_info_table.rs +++ b/crates/hyperqueue/src/dashboard/ui/fragments/auto_allocator/allocations_info_table.rs @@ -5,11 +5,11 @@ use crate::dashboard::ui::terminal::DashboardFrame; use crate::dashboard::ui::widgets::table::{StatefulTable, TableColumnHeaders}; use crate::server::autoalloc::AllocationId; use chrono::{DateTime, Local}; +use crossterm::event::{KeyCode, KeyEvent}; use ratatui::layout::{Constraint, Rect}; use ratatui::style::Style; use ratatui::widgets::{Cell, Row}; use std::time::SystemTime; -use termion::event::Key; #[derive(Default)] pub struct AllocationInfoTable { @@ -88,10 +88,10 @@ impl AllocationInfoTable { table_style, ); } - pub fn handle_key(&mut self, key: Key) { - match key { - Key::Down => self.select_next_allocation(), - Key::Up => self.select_previous_allocation(), + pub fn handle_key(&mut self, key: KeyEvent) { + match key.code { + KeyCode::Down => self.select_next_allocation(), + KeyCode::Up => self.select_previous_allocation(), _ => {} } } diff --git a/crates/hyperqueue/src/dashboard/ui/fragments/auto_allocator/fragment.rs b/crates/hyperqueue/src/dashboard/ui/fragments/auto_allocator/fragment.rs index aa10d7d0d..92ea337ac 100644 --- a/crates/hyperqueue/src/dashboard/ui/fragments/auto_allocator/fragment.rs +++ b/crates/hyperqueue/src/dashboard/ui/fragments/auto_allocator/fragment.rs @@ -1,12 +1,11 @@ -use std::default::Default; -use std::time::SystemTime; -use termion::event::Key; - use crate::dashboard::ui::styles::{ style_footer, style_header_text, table_style_deselected, table_style_selected, }; use crate::dashboard::ui::terminal::DashboardFrame; use crate::dashboard::ui::widgets::text::draw_text; +use crossterm::event::{KeyCode, KeyEvent}; +use std::default::Default; +use std::time::SystemTime; use crate::dashboard::data::timelines::alloc_timeline::AllocationQueueInfo; use crate::dashboard::data::DashboardData; @@ -99,18 +98,18 @@ impl AutoAllocatorFragment { } /// Handles key presses for the components of the screen - pub fn handle_key(&mut self, key: Key) { + pub fn handle_key(&mut self, key: KeyEvent) { match self.component_in_focus { FocusedComponent::QueueParamsTable => self.queue_info_table.handle_key(key), FocusedComponent::AllocationInfoTable => self.allocations_info_table.handle_key(key), }; - match key { - Key::Char('1') => { + match key.code{ + KeyCode::Char('1') => { self.component_in_focus = FocusedComponent::QueueParamsTable; self.allocations_info_table.clear_selection(); } - Key::Char('2') => self.component_in_focus = FocusedComponent::AllocationInfoTable, + KeyCode::Char('2') => self.component_in_focus = FocusedComponent::AllocationInfoTable, _ => {} } } diff --git a/crates/hyperqueue/src/dashboard/ui/fragments/auto_allocator/queue_info_table.rs b/crates/hyperqueue/src/dashboard/ui/fragments/auto_allocator/queue_info_table.rs index 9b14731da..2fce19743 100644 --- a/crates/hyperqueue/src/dashboard/ui/fragments/auto_allocator/queue_info_table.rs +++ b/crates/hyperqueue/src/dashboard/ui/fragments/auto_allocator/queue_info_table.rs @@ -3,10 +3,10 @@ use crate::dashboard::ui::terminal::DashboardFrame; use crate::dashboard::ui::widgets::table::{StatefulTable, TableColumnHeaders}; use crate::server::autoalloc::QueueId; use chrono::{DateTime, Local}; +use crossterm::event::{KeyCode, KeyEvent}; use ratatui::layout::{Constraint, Rect}; use ratatui::style::Style; use ratatui::widgets::{Cell, Row}; -use termion::event::Key; #[derive(Default)] pub struct AllocationQueueInfoTable { @@ -64,10 +64,10 @@ impl AllocationQueueInfoTable { ); } - pub fn handle_key(&mut self, key: Key) { - match key { - Key::Down => self.select_next_queue(), - Key::Up => self.select_previous_queue(), + pub fn handle_key(&mut self, key: KeyEvent) { + match key.code { + KeyCode::Down => self.select_next_queue(), + KeyCode::Up => self.select_previous_queue(), _ => {} } } diff --git a/crates/hyperqueue/src/dashboard/ui/fragments/job/fragment.rs b/crates/hyperqueue/src/dashboard/ui/fragments/job/fragment.rs index 4579f0aed..96c578ffa 100644 --- a/crates/hyperqueue/src/dashboard/ui/fragments/job/fragment.rs +++ b/crates/hyperqueue/src/dashboard/ui/fragments/job/fragment.rs @@ -1,12 +1,11 @@ -use std::default::Default; -use std::time::SystemTime; -use termion::event::Key; - use crate::dashboard::ui::styles::{ style_footer, style_header_text, table_style_deselected, table_style_selected, }; use crate::dashboard::ui::terminal::DashboardFrame; use crate::dashboard::ui::widgets::text::draw_text; +use crossterm::event::{KeyCode, KeyEvent}; +use std::default::Default; +use std::time::SystemTime; use crate::dashboard::data::timelines::job_timeline::TaskInfo; use crate::dashboard::data::DashboardData; @@ -87,13 +86,13 @@ impl JobFragment { } /// Handles key presses for the components of the screen - pub fn handle_key(&mut self, key: Key) { - match key { - Key::Char('1') => { + pub fn handle_key(&mut self, key: KeyEvent) { + match key.code { + KeyCode::Char('1') => { self.component_in_focus = FocusedComponent::JobsTable; self.job_tasks_table.clear_selection(); } - Key::Char('2') => self.component_in_focus = FocusedComponent::JobTasksTable, + KeyCode::Char('2') => self.component_in_focus = FocusedComponent::JobTasksTable, _ => {} } match self.component_in_focus { diff --git a/crates/hyperqueue/src/dashboard/ui/fragments/job/jobs_table.rs b/crates/hyperqueue/src/dashboard/ui/fragments/job/jobs_table.rs index 5fe65bb37..51a736802 100644 --- a/crates/hyperqueue/src/dashboard/ui/fragments/job/jobs_table.rs +++ b/crates/hyperqueue/src/dashboard/ui/fragments/job/jobs_table.rs @@ -6,12 +6,11 @@ use crate::dashboard::ui::terminal::DashboardFrame; use crate::dashboard::ui::widgets::table::{StatefulTable, TableColumnHeaders}; use crate::JobId; -use std::time::SystemTime; - +use crossterm::event::{KeyCode, KeyEvent}; use ratatui::layout::{Constraint, Rect}; use ratatui::style::Style; use ratatui::widgets::{Cell, Row}; -use termion::event::Key; +use std::time::SystemTime; #[derive(Default)] pub struct JobsTable { @@ -39,10 +38,10 @@ impl JobsTable { selection.map(|row| row.id) } - pub fn handle_key(&mut self, key: Key) { - match key { - Key::Down => self.select_next_job(), - Key::Up => self.select_previous_job(), + pub fn handle_key(&mut self, key: KeyEvent) { + match key.code { + KeyCode::Down => self.select_next_job(), + KeyCode::Up => self.select_previous_job(), _ => {} } } diff --git a/crates/hyperqueue/src/dashboard/ui/screen.rs b/crates/hyperqueue/src/dashboard/ui/screen.rs index 07f272d3e..dfc60e366 100644 --- a/crates/hyperqueue/src/dashboard/ui/screen.rs +++ b/crates/hyperqueue/src/dashboard/ui/screen.rs @@ -1,5 +1,5 @@ +use crossterm::event::KeyEvent; use ratatui::layout::Rect; -use termion::event::Key; use crate::dashboard::data::DashboardData; use crate::dashboard::ui::terminal::DashboardFrame; @@ -7,5 +7,5 @@ use crate::dashboard::ui::terminal::DashboardFrame; pub trait Screen { fn draw(&mut self, in_area: Rect, frame: &mut DashboardFrame); fn update(&mut self, data: &DashboardData); - fn handle_key(&mut self, key: Key); + fn handle_key(&mut self, key: KeyEvent); } diff --git a/crates/hyperqueue/src/dashboard/ui/screens/autoalloc_screen.rs b/crates/hyperqueue/src/dashboard/ui/screens/autoalloc_screen.rs index d1b0234c8..3a632f4cd 100644 --- a/crates/hyperqueue/src/dashboard/ui/screens/autoalloc_screen.rs +++ b/crates/hyperqueue/src/dashboard/ui/screens/autoalloc_screen.rs @@ -2,8 +2,8 @@ use crate::dashboard::data::DashboardData; use crate::dashboard::ui::fragments::auto_allocator::fragment::AutoAllocatorFragment; use crate::dashboard::ui::screen::Screen; use crate::dashboard::ui::terminal::DashboardFrame; +use crossterm::event::KeyEvent; use ratatui::layout::Rect; -use termion::event::Key; #[derive(Default)] pub struct AutoAllocScreen { @@ -19,7 +19,7 @@ impl Screen for AutoAllocScreen { self.auto_allocator_fragment.update(data); } - fn handle_key(&mut self, key: Key) { + fn handle_key(&mut self, key: KeyEvent) { self.auto_allocator_fragment.handle_key(key); } } diff --git a/crates/hyperqueue/src/dashboard/ui/screens/job_screen.rs b/crates/hyperqueue/src/dashboard/ui/screens/job_screen.rs index 279c7cd67..7606aeb15 100644 --- a/crates/hyperqueue/src/dashboard/ui/screens/job_screen.rs +++ b/crates/hyperqueue/src/dashboard/ui/screens/job_screen.rs @@ -3,8 +3,8 @@ use crate::dashboard::ui::fragments::job::fragment::JobFragment; use crate::dashboard::ui::screen::Screen; use crate::dashboard::ui::screens::overview_screen::worker::fragment::WorkerOverviewFragment; use crate::dashboard::ui::terminal::DashboardFrame; +use crossterm::event::{KeyCode, KeyEvent}; use ratatui::layout::Rect; -use termion::event::Key; pub struct JobScreen { job_overview_fragment: JobFragment, @@ -33,20 +33,20 @@ impl Screen for JobScreen { } } - fn handle_key(&mut self, key: Key) { + fn handle_key(&mut self, key: KeyEvent) { match self.active_fragment { ScreenState::JobInfo => self.job_overview_fragment.handle_key(key), ScreenState::TaskWorkerDetail => self.task_runner_worker.handle_key(key), } - match key { - Key::Char('i') => { + match key.code { + KeyCode::Char('i') => { if let Some((_, selected_worker)) = self.job_overview_fragment.get_selected_task() { self.task_runner_worker.set_worker_id(selected_worker); self.active_fragment = ScreenState::TaskWorkerDetail; } } - Key::Backspace => { + KeyCode::Backspace => { self.task_runner_worker.clear_worker_id(); self.active_fragment = ScreenState::JobInfo } diff --git a/crates/hyperqueue/src/dashboard/ui/screens/overview_screen/mod.rs b/crates/hyperqueue/src/dashboard/ui/screens/overview_screen/mod.rs index 811188ef1..edbf95a98 100644 --- a/crates/hyperqueue/src/dashboard/ui/screens/overview_screen/mod.rs +++ b/crates/hyperqueue/src/dashboard/ui/screens/overview_screen/mod.rs @@ -3,8 +3,8 @@ use crate::dashboard::ui::screen::Screen; use crate::dashboard::ui::screens::overview_screen::overview::fragment::ClusterOverviewFragment; use crate::dashboard::ui::screens::overview_screen::worker::fragment::WorkerOverviewFragment; use crate::dashboard::ui::terminal::DashboardFrame; +use crossterm::event::{KeyCode, KeyEvent}; use ratatui::layout::Rect; -use termion::event::Key; pub mod overview; pub mod worker; @@ -39,20 +39,20 @@ impl Screen for WorkerOverviewScreen { } } - fn handle_key(&mut self, key: Key) { + fn handle_key(&mut self, key: KeyEvent) { match self.active_fragment { ScreenState::ClusterOverview => self.cluster_overview.handle_key(key), ScreenState::WorkerInfo => self.worker_overview.handle_key(key), } - match key { - Key::Char('i') => { + match key.code { + KeyCode::Char('i') => { if let Some(selected_worker) = self.cluster_overview.get_selected_worker() { self.worker_overview.set_worker_id(selected_worker); self.active_fragment = ScreenState::WorkerInfo; } } - Key::Backspace => { + KeyCode::Backspace => { self.worker_overview.clear_worker_id(); self.active_fragment = ScreenState::ClusterOverview } diff --git a/crates/hyperqueue/src/dashboard/ui/screens/overview_screen/overview/fragment.rs b/crates/hyperqueue/src/dashboard/ui/screens/overview_screen/overview/fragment.rs index 0b2f9cea5..75b5c98ba 100644 --- a/crates/hyperqueue/src/dashboard/ui/screens/overview_screen/overview/fragment.rs +++ b/crates/hyperqueue/src/dashboard/ui/screens/overview_screen/overview/fragment.rs @@ -1,5 +1,5 @@ +use crossterm::event::{KeyCode, KeyEvent}; use ratatui::layout::{Constraint, Direction, Layout, Rect}; -use termion::event::Key; use tako::WorkerId; @@ -37,12 +37,12 @@ impl ClusterOverviewFragment { } /// Handles key presses for the components of the screen - pub fn handle_key(&mut self, key: Key) { - match key { - Key::Down => { + pub fn handle_key(&mut self, key: KeyEvent) { + match key.code { + KeyCode::Down => { self.worker_util_table.select_next_worker(); } - Key::Up => { + KeyCode::Up => { self.worker_util_table.select_previous_worker(); } _ => {} diff --git a/crates/hyperqueue/src/dashboard/ui/screens/overview_screen/worker/fragment.rs b/crates/hyperqueue/src/dashboard/ui/screens/overview_screen/worker/fragment.rs index 83a6f0db0..f89629a3c 100644 --- a/crates/hyperqueue/src/dashboard/ui/screens/overview_screen/worker/fragment.rs +++ b/crates/hyperqueue/src/dashboard/ui/screens/overview_screen/worker/fragment.rs @@ -1,4 +1,4 @@ -use termion::event::Key; +use crossterm::event::{KeyCode, KeyEvent}; use crate::dashboard::ui::styles::{ style_footer, style_header_text, table_style_deselected, table_style_selected, @@ -111,11 +111,11 @@ impl WorkerOverviewFragment { } /// Handles key presses for the components of the screen - pub fn handle_key(&mut self, key: Key) { - match key { - Key::Down => self.worker_tasks_table.select_next_task(), - Key::Up => self.worker_tasks_table.select_previous_task(), - Key::Backspace => self.worker_tasks_table.clear_selection(), + pub fn handle_key(&mut self, key: KeyEvent) { + match key.code { + KeyCode::Down => self.worker_tasks_table.select_next_task(), + KeyCode::Up => self.worker_tasks_table.select_previous_task(), + KeyCode::Backspace => self.worker_tasks_table.clear_selection(), _ => {} } diff --git a/crates/hyperqueue/src/dashboard/ui/screens/root_screen.rs b/crates/hyperqueue/src/dashboard/ui/screens/root_screen.rs index 68afe0932..d67561847 100644 --- a/crates/hyperqueue/src/dashboard/ui/screens/root_screen.rs +++ b/crates/hyperqueue/src/dashboard/ui/screens/root_screen.rs @@ -5,6 +5,7 @@ use crate::dashboard::ui::screens::job_screen::JobScreen; use crate::dashboard::ui::screens::overview_screen::WorkerOverviewScreen; use crate::dashboard::ui::terminal::{DashboardFrame, DashboardTerminal}; use chrono::Local; +use crossterm::event::{KeyCode, KeyEvent}; use ratatui::layout::{Alignment, Constraint, Direction, Layout, Rect}; use ratatui::style::{Color, Modifier, Style}; use ratatui::text::{Line, Span, Text}; @@ -12,7 +13,6 @@ use ratatui::widgets::{Block, Borders, Paragraph, Tabs, Wrap}; use ratatui::Frame; use std::ops::ControlFlow; use std::time::Duration; -use termion::event::Key; const KEY_TIMELINE_SOONER: char = 'o'; const KEY_TIMELINE_LATER: char = 'p'; @@ -58,30 +58,30 @@ impl RootScreen { pub fn handle_key( &mut self, - input: Key, + input: KeyEvent, data: &mut DashboardData, ) -> ControlFlow> { - if input == Key::Char('q') { + if input.code == KeyCode::Char('q') { return ControlFlow::Break(Ok(())); } - match input { - Key::Char('j') => { + match input.code { + KeyCode::Char('j') => { self.current_screen = SelectedScreen::JobOverview; } - Key::Char('a') => { + KeyCode::Char('a') => { self.current_screen = SelectedScreen::AutoAllocator; } - Key::Char('w') => { + KeyCode::Char('w') => { self.current_screen = SelectedScreen::WorkerOverview; } - Key::Char(c) if c == KEY_TIMELINE_SOONER => data.set_time_range( + KeyCode::Char(c) if c == KEY_TIMELINE_SOONER => data.set_time_range( data.current_time_range() .sooner(Duration::from_secs(60 * 5)), ), - Key::Char(c) if c == KEY_TIMELINE_LATER => { + KeyCode::Char(c) if c == KEY_TIMELINE_LATER => { data.set_time_range(data.current_time_range().later(Duration::from_secs(60 * 5))) } - Key::Char('r') => data.set_live_time_mode(), + KeyCode::Char('r') => data.set_live_time_mode(), _ => { self.get_current_screen_mut().handle_key(input); diff --git a/crates/hyperqueue/src/dashboard/ui/terminal.rs b/crates/hyperqueue/src/dashboard/ui/terminal.rs index bdac24267..e6d0a37df 100644 --- a/crates/hyperqueue/src/dashboard/ui/terminal.rs +++ b/crates/hyperqueue/src/dashboard/ui/terminal.rs @@ -1,17 +1,10 @@ -use std::io::{stdout, Stdout}; +use ratatui::{DefaultTerminal, Frame}; -use ratatui::backend::TermionBackend as TerminalBackend; -use ratatui::{Frame, Terminal}; -use termion::input::MouseTerminal; -use termion::raw::{IntoRawMode, RawTerminal}; -use termion::screen::AlternateScreen; - -pub type Backend = TerminalBackend>>>; -pub type DashboardTerminal = Terminal; +pub type DashboardTerminal = DefaultTerminal; pub type DashboardFrame<'a> = Frame<'a>; pub fn initialize_terminal() -> std::io::Result { - let stdout = AlternateScreen::from(MouseTerminal::from(stdout().into_raw_mode()?)); - let backend = TerminalBackend::new(stdout); - Terminal::new(backend) + let mut terminal = ratatui::init(); + terminal.clear()?; + Ok(terminal) } diff --git a/crates/hyperqueue/src/dashboard/ui/widgets/tasks_table.rs b/crates/hyperqueue/src/dashboard/ui/widgets/tasks_table.rs index 30d84d90b..da25992f2 100644 --- a/crates/hyperqueue/src/dashboard/ui/widgets/tasks_table.rs +++ b/crates/hyperqueue/src/dashboard/ui/widgets/tasks_table.rs @@ -5,12 +5,12 @@ use crate::dashboard::ui::terminal::DashboardFrame; use crate::dashboard::ui::widgets::table::{StatefulTable, TableColumnHeaders}; use crate::JobTaskId; use chrono::{DateTime, Local}; +use crossterm::event::{KeyCode, KeyEvent}; use ratatui::layout::{Constraint, Rect}; use ratatui::style::{Color, Modifier, Style}; use ratatui::widgets::{Cell, Row}; use std::time::SystemTime; use tako::WorkerId; -use termion::event::Key; // Task State Strings const RUNNING: &str = "RUNNING"; @@ -45,10 +45,10 @@ impl TasksTable { selection.map(|row| (row.task_id, row.worker_id)) } - pub fn handle_key(&mut self, key: Key) { - match key { - Key::Down => self.select_next_task(), - Key::Up => self.select_previous_task(), + pub fn handle_key(&mut self, key: KeyEvent) { + match key.code { + KeyCode::Down => self.select_next_task(), + KeyCode::Up => self.select_previous_task(), _ => {} } } diff --git a/crates/hyperqueue/src/dashboard/ui_loop.rs b/crates/hyperqueue/src/dashboard/ui_loop.rs index 183025d17..d6e569846 100644 --- a/crates/hyperqueue/src/dashboard/ui_loop.rs +++ b/crates/hyperqueue/src/dashboard/ui_loop.rs @@ -1,10 +1,9 @@ +use crossterm::event; +use crossterm::event::Event::Key; +use crossterm::event::KeyEventKind; use std::io::Write; use std::ops::ControlFlow; use std::{io, thread}; - -use termion::input::TermRead; -use termion::raw::IntoRawMode; -use termion::screen::ToMainScreen; use tokio::sync::mpsc::UnboundedSender; use tokio::time::Duration; @@ -18,27 +17,35 @@ use crate::server::bootstrap::get_client_session; /// Starts the dashboard UI with a keyboard listener and tick provider pub async fn start_ui_loop(gsettings: &GlobalSettings) -> anyhow::Result<()> { - setup_panics(); - - let connection = get_client_session(gsettings.server_directory()).await?; + // let connection = get_client_session(gsettings.server_directory()).await?; // TODO: When we start the dashboard and connect to the server, the server may have already forgotten // some of its events. Therefore we should bootstrap the state with the most recent overview snapshot. let mut dashboard_data = DashboardData::default(); let (tx, mut rx) = tokio::sync::mpsc::unbounded_channel(); - start_key_event_listener(tx.clone()); let mut root_screen = RootScreen::default(); - let ui_ticker = send_event_repeatedly( - Duration::from_millis(100), - tx.clone(), - DashboardEvent::UiTick, - ); - let data_fetch_process = create_data_fetch_process(Duration::from_secs(1), connection, tx); + // let ui_ticker = send_event_repeatedly( + // Duration::from_millis(100), + // tx.clone(), + // DashboardEvent::UiTick, + // ); + // let data_fetch_process = create_data_fetch_process(Duration::from_secs(1), connection, tx); let mut terminal = initialize_terminal()?; + let res = loop { + root_screen.draw(&mut terminal, &dashboard_data); + if let Key(key) = event::read()? { + if let ControlFlow::Break(res) = root_screen.handle_key(key, &mut dashboard_data) { + break res; + } + } + }; + ratatui::restore(); + return res; + let event_loop = async { while let Some(dashboard_event) = rx.recv().await { match dashboard_event { @@ -49,9 +56,7 @@ pub async fn start_ui_loop(gsettings: &GlobalSettings) -> anyhow::Result<()> { return res; } } - DashboardEvent::UiTick => { - root_screen.draw(&mut terminal, &dashboard_data); - } + DashboardEvent::UiTick => {} DashboardEvent::FetchedEvents(events) => { dashboard_data.push_new_events(events); } @@ -59,34 +64,6 @@ pub async fn start_ui_loop(gsettings: &GlobalSettings) -> anyhow::Result<()> { } Ok(()) }; - - tokio::select! { - _ = ui_ticker => { - log::warn!("UI event process has ended"); - Ok(()) - } - result = data_fetch_process => { - log::warn!("Data fetch process has ended"); - result - } - result = event_loop => { - log::warn!("Dashboard event loop has ended"); - result - } - } -} - -/// Handles key press events when the dashboard_ui is active -fn start_key_event_listener(tx: UnboundedSender) -> thread::JoinHandle<()> { - thread::spawn(move || { - let stdin = io::stdin(); - for key in stdin.keys().flatten() { - if let Err(err) = tx.send(DashboardEvent::KeyPressEvent(key)) { - eprintln!("Error in sending dashboard key: {err}"); - return; - } - } - }) } /// Sends a dashboard event repeatedly, with the specified interval. @@ -104,18 +81,3 @@ async fn send_event_repeatedly( tick_duration.tick().await; } } - -/// Makes sure that panics are actually logged to stdout and not swallowed. -fn setup_panics() { - let default_hook = std::panic::take_hook(); - std::panic::set_hook(Box::new(move |info| { - print!("{}", ToMainScreen); - io::stdout() - .into_raw_mode() - .unwrap() - .suspend_raw_mode() - .unwrap_or_else(|e| log::error!("Could not suspend raw mode: {}", e)); - io::stdout().flush().unwrap(); - default_hook(info); - })); -} From 82f35f133c36d1dc8e6654bd367af6104fda1265 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Thu, 17 Oct 2024 23:07:00 +0200 Subject: [PATCH 04/31] Refactor dashboard initialization and event streaming --- Cargo.lock | 1 + crates/hyperqueue/Cargo.toml | 2 +- crates/hyperqueue/src/bin/hq.rs | 25 +++-- crates/hyperqueue/src/common/cli.rs | 21 +++- crates/hyperqueue/src/dashboard/data/fetch.rs | 73 +++++++------- crates/hyperqueue/src/dashboard/ui_loop.rs | 98 +++++++++---------- 6 files changed, 119 insertions(+), 101 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7c63e94ef..aa2a3af81 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -505,6 +505,7 @@ checksum = "829d955a0bb380ef178a640b91779e3987da38c9aea133b20614cfed8cdea9c6" dependencies = [ "bitflags 2.6.0", "crossterm_winapi", + "futures-core", "mio", "parking_lot 0.12.3", "rustix", diff --git a/crates/hyperqueue/Cargo.toml b/crates/hyperqueue/Cargo.toml index 823eee1f9..c95301dd5 100644 --- a/crates/hyperqueue/Cargo.toml +++ b/crates/hyperqueue/Cargo.toml @@ -58,7 +58,7 @@ lru = "0.12" # Dashboard ratatui = { version = "0.28", default-features = false, features = ["crossterm"], optional = true } -crossterm = { version = "0.28", optional = true } +crossterm = { version = "0.28", features = ["event-stream"], optional = true } unicode-width = { version = "0.1.5", optional = true } # Tako diff --git a/crates/hyperqueue/src/bin/hq.rs b/crates/hyperqueue/src/bin/hq.rs index e314a2a0d..369b3d8a9 100644 --- a/crates/hyperqueue/src/bin/hq.rs +++ b/crates/hyperqueue/src/bin/hq.rs @@ -37,14 +37,16 @@ use hyperqueue::client::task::{ TaskListOpts, TaskOpts, }; use hyperqueue::common::cli::{ - get_task_id_selector, get_task_selector, ColorPolicy, CommonOpts, GenerateCompletionOpts, - HwDetectOpts, JobCommand, JobOpts, JobProgressOpts, JobWaitOpts, OptsWithMatches, RootOptions, - SubCommand, WorkerAddressOpts, WorkerCommand, WorkerInfoOpts, WorkerListOpts, WorkerOpts, - WorkerStopOpts, WorkerWaitOpts, + get_task_id_selector, get_task_selector, ColorPolicy, CommonOpts, DashboardCommand, + DashboardOpts, GenerateCompletionOpts, HwDetectOpts, JobCommand, JobOpts, JobProgressOpts, + JobWaitOpts, OptsWithMatches, RootOptions, SubCommand, WorkerAddressOpts, WorkerCommand, + WorkerInfoOpts, WorkerListOpts, WorkerOpts, WorkerStopOpts, WorkerWaitOpts, }; use hyperqueue::common::setup::setup_logging; use hyperqueue::common::utils::fs::absolute_path; use hyperqueue::server::bootstrap::get_client_session; +use hyperqueue::server::event::log::JournalReader; +use hyperqueue::server::event::Event; use hyperqueue::transfer::messages::{ FromClientMessage, IdSelector, JobInfoRequest, ToClientMessage, }; @@ -296,10 +298,21 @@ async fn command_worker_address( ///Starts the hq Dashboard async fn command_dashboard_start( gsettings: &GlobalSettings, - _opts: hyperqueue::common::cli::DashboardOpts, + opts: hyperqueue::common::cli::DashboardOpts, ) -> anyhow::Result<()> { use hyperqueue::dashboard::start_ui_loop; - start_ui_loop(gsettings).await?; + + match opts.subcmd { + DashboardCommand::Replay { journal } => { + println!("Loading journal {}", journal.display()); + let mut journal = JournalReader::open(&journal)?; + let events: Vec = journal.collect::>()?; + println!("Loaded {} events", events.len()); + + start_ui_loop(gsettings, events, false).await?; + } + } + Ok(()) } diff --git a/crates/hyperqueue/src/common/cli.rs b/crates/hyperqueue/src/common/cli.rs index 723ff637d..f86776436 100644 --- a/crates/hyperqueue/src/common/cli.rs +++ b/crates/hyperqueue/src/common/cli.rs @@ -180,11 +180,6 @@ pub struct RootOptions { pub subcmd: SubCommand, } -/// HyperQueue Dashboard -#[allow(clippy::large_enum_variant)] -#[derive(Parser)] -pub struct DashboardOpts {} - #[allow(clippy::large_enum_variant)] #[derive(Parser)] pub enum SubCommand { @@ -212,6 +207,22 @@ pub enum SubCommand { GenerateCompletion(GenerateCompletionOpts), } +/// HyperQueue Dashboard +#[derive(Parser)] +pub struct DashboardOpts { + #[clap(subcommand)] + pub subcmd: DashboardCommand, +} + +#[derive(Parser)] +pub enum DashboardCommand { + /// Replay events from a recorded journal file. + Replay { + /// Path to a journal file created via `hq server start --journal=`. + journal: PathBuf, + }, +} + // Worker CLI options #[derive(Parser)] diff --git a/crates/hyperqueue/src/dashboard/data/fetch.rs b/crates/hyperqueue/src/dashboard/data/fetch.rs index 58a64a62a..50c262912 100644 --- a/crates/hyperqueue/src/dashboard/data/fetch.rs +++ b/crates/hyperqueue/src/dashboard/data/fetch.rs @@ -1,47 +1,50 @@ -use crate::dashboard::events::DashboardEvent; -use crate::rpc_call; -use crate::server::event::{Event, EventId}; -use crate::transfer::connection::{ClientConnection, ClientSession}; +use crate::server::event::Event; +use crate::transfer::connection::ClientSession; use crate::transfer::messages::{FromClientMessage, ToClientMessage}; use std::time::Duration; -// use tako::gateway::MonitoringEventRequest; -use tokio::sync::mpsc::UnboundedSender; +use tokio::sync::mpsc::Sender; pub async fn create_data_fetch_process( - refresh_interval: Duration, mut session: ClientSession, - sender: UnboundedSender, + sender: Sender>, ) -> anyhow::Result<()> { - let mut tick_duration = tokio::time::interval(refresh_interval); + session + .connection() + .send(FromClientMessage::StreamEvents) + .await?; - let mut fetched_until: Option = None; + const CAPACITY: usize = 1024; + + let mut events = Vec::with_capacity(CAPACITY); + let mut tick = tokio::time::interval(Duration::from_secs(1)); + + let conn = session.connection(); loop { - let events = fetch_events_after(session.connection(), fetched_until).await?; - // fetched_until = events - // .iter() - // .map(|event| event.id()) - // .max() - // .or(fetched_until); + tokio::select! { + _ = tick.tick() => { + if !events.is_empty() { + sender.send(events).await?; + events = Vec::with_capacity(CAPACITY); + } + } + // Hopefully this is cancellation safe... + message = conn.receive() => { + let Some(message) = message else { break; }; - sender.send(DashboardEvent::FetchedEvents(events))?; - tick_duration.tick().await; + let message = message?; + let ToClientMessage::Event(event) = message else { + return Err(anyhow::anyhow!( + "Dashboard received unexpected message {message:?}" + )); + }; + events.push(event); + if events.len() == CAPACITY { + sender.send(events).await?; + events = Vec::with_capacity(CAPACITY); + } + } + } } -} - -/// Gets the events from the server after the event_id specified -async fn fetch_events_after( - connection: &mut ClientConnection, - after_id: Option, -) -> crate::Result> { - // rpc_call!( - // connection, - // FromClientMessage::MonitoringEvents ( - // MonitoringEventRequest { - // after_id, - // }), - // ToClientMessage::MonitoringEventsResponse(response) => response - // ) - // .await - todo!() + Ok(()) } diff --git a/crates/hyperqueue/src/dashboard/ui_loop.rs b/crates/hyperqueue/src/dashboard/ui_loop.rs index d6e569846..903d4070e 100644 --- a/crates/hyperqueue/src/dashboard/ui_loop.rs +++ b/crates/hyperqueue/src/dashboard/ui_loop.rs @@ -1,83 +1,73 @@ use crossterm::event; use crossterm::event::Event::Key; -use crossterm::event::KeyEventKind; -use std::io::Write; +use futures::StreamExt; +use std::future::Future; use std::ops::ControlFlow; -use std::{io, thread}; -use tokio::sync::mpsc::UnboundedSender; +use std::pin::Pin; use tokio::time::Duration; use crate::client::globalsettings::GlobalSettings; use crate::dashboard::data::create_data_fetch_process; use crate::dashboard::data::DashboardData; -use crate::dashboard::events::DashboardEvent; use crate::dashboard::ui::screens::root_screen::RootScreen; use crate::dashboard::ui::terminal::initialize_terminal; use crate::server::bootstrap::get_client_session; +use crate::server::event::Event; /// Starts the dashboard UI with a keyboard listener and tick provider -pub async fn start_ui_loop(gsettings: &GlobalSettings) -> anyhow::Result<()> { - // let connection = get_client_session(gsettings.server_directory()).await?; - - // TODO: When we start the dashboard and connect to the server, the server may have already forgotten - // some of its events. Therefore we should bootstrap the state with the most recent overview snapshot. +pub async fn start_ui_loop( + gsettings: &GlobalSettings, + events: Vec, + stream: bool, +) -> anyhow::Result<()> { let mut dashboard_data = DashboardData::default(); + dashboard_data.push_new_events(events); - let (tx, mut rx) = tokio::sync::mpsc::unbounded_channel(); let mut root_screen = RootScreen::default(); - // let ui_ticker = send_event_repeatedly( - // Duration::from_millis(100), - // tx.clone(), - // DashboardEvent::UiTick, - // ); - // let data_fetch_process = create_data_fetch_process(Duration::from_secs(1), connection, tx); + let (tx, mut rx) = tokio::sync::mpsc::channel(1024); + + let mut data_fetch_process: Pin>>> = if stream { + let connection = get_client_session(gsettings.server_directory()).await?; + Box::pin(create_data_fetch_process(connection, tx)) + } else { + Box::pin(std::future::pending()) + }; let mut terminal = initialize_terminal()?; + let mut reader = event::EventStream::new(); + let mut tick = tokio::time::interval(Duration::from_millis(100)); let res = loop { - root_screen.draw(&mut terminal, &dashboard_data); - if let Key(key) = event::read()? { - if let ControlFlow::Break(res) = root_screen.handle_key(key, &mut dashboard_data) { + tokio::select! { + msg = rx.recv() => { + let Some(events) = msg else { + break Ok(()); + }; + dashboard_data.push_new_events(events); + } + res = &mut data_fetch_process => { break res; } - } - }; - ratatui::restore(); - return res; - - let event_loop = async { - while let Some(dashboard_event) = rx.recv().await { - match dashboard_event { - DashboardEvent::KeyPressEvent(input) => { - if let ControlFlow::Break(res) = - root_screen.handle_key(input, &mut dashboard_data) - { - return res; + event = reader.next() => { + let Some(event) = event else { + break Ok(()); + }; + let event = match event { + Ok(event) => event, + Err(error) => break Err(error.into()), + }; + if let Key(key) = event { + if let ControlFlow::Break(res) = root_screen.handle_key(key, &mut dashboard_data) { + break res; } } - DashboardEvent::UiTick => {} - DashboardEvent::FetchedEvents(events) => { - dashboard_data.push_new_events(events); - } + } + _ = tick.tick() => { + root_screen.draw(&mut terminal, &dashboard_data); } } - Ok(()) }; -} - -/// Sends a dashboard event repeatedly, with the specified interval. -async fn send_event_repeatedly( - interval: Duration, - sender: UnboundedSender, - event_type: DashboardEvent, -) { - let mut tick_duration = tokio::time::interval(interval); - loop { - if let Err(e) = sender.send(event_type.clone()) { - log::error!("Error in producing dashboard events: {}", e); - return; - } - tick_duration.tick().await; - } + ratatui::restore(); + res } From befb7c768adc0f70981b91b12df7986d0817d6c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Fri, 18 Oct 2024 13:01:53 +0200 Subject: [PATCH 05/31] Add `stream` subcommand to `hq dashboard` and make it the default --- crates/hyperqueue/src/bin/hq.rs | 11 +++++++---- crates/hyperqueue/src/common/cli.rs | 10 ++++++++-- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/crates/hyperqueue/src/bin/hq.rs b/crates/hyperqueue/src/bin/hq.rs index 369b3d8a9..25b446fda 100644 --- a/crates/hyperqueue/src/bin/hq.rs +++ b/crates/hyperqueue/src/bin/hq.rs @@ -298,18 +298,21 @@ async fn command_worker_address( ///Starts the hq Dashboard async fn command_dashboard_start( gsettings: &GlobalSettings, - opts: hyperqueue::common::cli::DashboardOpts, + opts: DashboardOpts, ) -> anyhow::Result<()> { use hyperqueue::dashboard::start_ui_loop; - match opts.subcmd { - DashboardCommand::Replay { journal } => { + match opts.subcmd.unwrap_or_default() { + DashboardCommand::Replay { journal, stream } => { println!("Loading journal {}", journal.display()); let mut journal = JournalReader::open(&journal)?; let events: Vec = journal.collect::>()?; println!("Loaded {} events", events.len()); - start_ui_loop(gsettings, events, false).await?; + start_ui_loop(gsettings, events, stream).await?; + } + DashboardCommand::Stream => { + start_ui_loop(gsettings, vec![], true).await?; } } diff --git a/crates/hyperqueue/src/common/cli.rs b/crates/hyperqueue/src/common/cli.rs index f86776436..e76cc3a84 100644 --- a/crates/hyperqueue/src/common/cli.rs +++ b/crates/hyperqueue/src/common/cli.rs @@ -211,15 +211,21 @@ pub enum SubCommand { #[derive(Parser)] pub struct DashboardOpts { #[clap(subcommand)] - pub subcmd: DashboardCommand, + pub subcmd: Option, } -#[derive(Parser)] +#[derive(Parser, Default)] pub enum DashboardCommand { + /// Stream events from a server. + #[default] + Stream, /// Replay events from a recorded journal file. Replay { /// Path to a journal file created via `hq server start --journal=`. journal: PathBuf, + /// Also stream new events from a server after replaying events from the journal. + #[clap(long)] + stream: bool, }, } From b5c8526e0b80ea0934a54a23c1d7560cf3f66dd8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Fri, 18 Oct 2024 13:21:07 +0200 Subject: [PATCH 06/31] Rename `get_*` worker timeline methods to `query_*` --- .../src/dashboard/data/timelines/worker_timeline.rs | 12 ++++++------ .../overview_screen/overview/worker_count_chart.rs | 2 +- .../screens/overview_screen/overview/worker_table.rs | 6 +++--- .../ui/screens/overview_screen/worker/fragment.rs | 4 ++-- .../worker/worker_utilization_chart.rs | 2 +- 5 files changed, 13 insertions(+), 13 deletions(-) diff --git a/crates/hyperqueue/src/dashboard/data/timelines/worker_timeline.rs b/crates/hyperqueue/src/dashboard/data/timelines/worker_timeline.rs index 6fd566595..e8def1f8b 100644 --- a/crates/hyperqueue/src/dashboard/data/timelines/worker_timeline.rs +++ b/crates/hyperqueue/src/dashboard/data/timelines/worker_timeline.rs @@ -76,22 +76,22 @@ impl WorkerTimeline { } } - pub fn get_worker_config_for(&self, worker_id: WorkerId) -> Option<&WorkerConfiguration> { + pub fn query_worker_config_for(&self, worker_id: WorkerId) -> Option<&WorkerConfiguration> { self.workers.get(&worker_id).map(|w| &w.worker_config) } - pub fn get_connected_worker_ids_at( + pub fn query_connected_worker_ids_at( &self, time: SystemTime, ) -> impl Iterator + '_ { - self.get_known_worker_ids_at(time) + self.query_known_worker_ids_at(time) .filter_map(|(id, status)| match status { WorkerStatus::Connected => Some(id), WorkerStatus::Disconnected(_) => None, }) } - pub fn get_known_worker_ids_at( + pub fn query_known_worker_ids_at( &self, time: SystemTime, ) -> impl Iterator + '_ { @@ -117,7 +117,7 @@ impl WorkerTimeline { }) } - pub fn get_worker_overview_at( + pub fn query_worker_overview_at( &self, worker_id: WorkerId, time: SystemTime, @@ -127,7 +127,7 @@ impl WorkerTimeline { .and_then(|worker| worker.worker_overviews.get_most_recent_at(time)) } - pub fn get_worker_overviews_at( + pub fn query_worker_overviews_at( &self, worker_id: WorkerId, range: TimeRange, diff --git a/crates/hyperqueue/src/dashboard/ui/screens/overview_screen/overview/worker_count_chart.rs b/crates/hyperqueue/src/dashboard/ui/screens/overview_screen/overview/worker_count_chart.rs index c5dee5edc..6114ef34d 100644 --- a/crates/hyperqueue/src/dashboard/ui/screens/overview_screen/overview/worker_count_chart.rs +++ b/crates/hyperqueue/src/dashboard/ui/screens/overview_screen/overview/worker_count_chart.rs @@ -33,7 +33,7 @@ impl WorkerCountChart { .map(|step| range.start + interval * step) .map(|time| WorkerCountRecord { time, - count: data.workers().get_connected_worker_ids_at(time).count(), + count: data.workers().query_connected_worker_ids_at(time).count(), }) .collect(); self.range = range; diff --git a/crates/hyperqueue/src/dashboard/ui/screens/overview_screen/overview/worker_table.rs b/crates/hyperqueue/src/dashboard/ui/screens/overview_screen/overview/worker_table.rs index f27250d60..9d7d7cef8 100644 --- a/crates/hyperqueue/src/dashboard/ui/screens/overview_screen/overview/worker_table.rs +++ b/crates/hyperqueue/src/dashboard/ui/screens/overview_screen/overview/worker_table.rs @@ -43,9 +43,9 @@ impl WorkerTable { let mut rows: Vec = data .workers() - .get_known_worker_ids_at(current_time) + .query_known_worker_ids_at(current_time) .map(|(worker_id, status)| { - let config = data.workers().get_worker_config_for(worker_id); + let config = data.workers().query_worker_config_for(worker_id); let hostname = config.map(|config| config.hostname.clone()); let state = match status { @@ -53,7 +53,7 @@ impl WorkerTable { WorkerStatus::Connected => { let overview = data .workers() - .get_worker_overview_at(worker_id, current_time) + .query_worker_overview_at(worker_id, current_time) .map(|item| &item.item); let hw_state = overview.and_then(|o| o.hw_state.as_ref()); let average_cpu_usage = hw_state.map(get_average_cpu_usage_for_worker); diff --git a/crates/hyperqueue/src/dashboard/ui/screens/overview_screen/worker/fragment.rs b/crates/hyperqueue/src/dashboard/ui/screens/overview_screen/worker/fragment.rs index f89629a3c..a5851d458 100644 --- a/crates/hyperqueue/src/dashboard/ui/screens/overview_screen/worker/fragment.rs +++ b/crates/hyperqueue/src/dashboard/ui/screens/overview_screen/worker/fragment.rs @@ -87,7 +87,7 @@ impl WorkerOverviewFragment { if let Some((cpu_util, mem_util)) = data .workers() - .get_worker_overview_at(worker_id, data.current_time()) + .query_worker_overview_at(worker_id, data.current_time()) .and_then(|overview| overview.item.hw_state.as_ref()) .map(|hw_state| { ( @@ -104,7 +104,7 @@ impl WorkerOverviewFragment { data.query_task_history_for_worker(worker_id).collect(); self.worker_tasks_table.update(tasks_info); - if let Some(configuration) = data.workers().get_worker_config_for(worker_id) { + if let Some(configuration) = data.workers().query_worker_config_for(worker_id) { self.worker_config_table.update(configuration); } } diff --git a/crates/hyperqueue/src/dashboard/ui/screens/overview_screen/worker/worker_utilization_chart.rs b/crates/hyperqueue/src/dashboard/ui/screens/overview_screen/worker/worker_utilization_chart.rs index 91fbd3979..9a6994928 100644 --- a/crates/hyperqueue/src/dashboard/ui/screens/overview_screen/worker/worker_utilization_chart.rs +++ b/crates/hyperqueue/src/dashboard/ui/screens/overview_screen/worker/worker_utilization_chart.rs @@ -109,7 +109,7 @@ impl WorkerUtilizationChart { let time_range = data.current_time_range(); self.overviews = data .workers() - .get_worker_overviews_at(worker_id, time_range) + .query_worker_overviews_at(worker_id, time_range) .unwrap_or(&[]) .to_vec(); self.range = time_range; From 93fe46a684e964cc1ba33fb924bb6eaa2f8c2467 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Fri, 18 Oct 2024 13:23:12 +0200 Subject: [PATCH 07/31] Set time mode based on the input data (fixed for replay and live for streaming) --- crates/hyperqueue/src/dashboard/data/data.rs | 28 ++++++++----------- crates/hyperqueue/src/dashboard/data/mod.rs | 2 ++ crates/hyperqueue/src/dashboard/mod.rs | 4 +++ .../src/dashboard/ui/screens/root_screen.rs | 3 +- crates/hyperqueue/src/dashboard/ui_loop.rs | 14 ++++++++-- 5 files changed, 32 insertions(+), 19 deletions(-) diff --git a/crates/hyperqueue/src/dashboard/data/data.rs b/crates/hyperqueue/src/dashboard/data/data.rs index 48853d9a2..2f6615858 100644 --- a/crates/hyperqueue/src/dashboard/data/data.rs +++ b/crates/hyperqueue/src/dashboard/data/data.rs @@ -12,8 +12,6 @@ use crate::{JobId, JobTaskId}; use std::time::{Duration, SystemTime}; use tako::WorkerId; -const DEFAULT_LIVE_DURATION: Duration = Duration::from_secs(60 * 10); - pub struct DashboardData { /// Tracks worker connection and loss events worker_timeline: WorkerTimeline, @@ -26,8 +24,17 @@ pub struct DashboardData { } impl DashboardData { + pub fn from_time_mode(time_mode: TimeMode) -> Self { + Self { + worker_timeline: Default::default(), + job_timeline: Default::default(), + alloc_timeline: Default::default(), + time_mode, + } + } + pub fn push_new_events(&mut self, mut events: Vec) { - events.sort_unstable_by_key(|e| e.time.time()); + events.sort_unstable_by_key(|e| e.time); // Update data views self.worker_timeline.handle_new_events(&events); @@ -92,8 +99,8 @@ impl DashboardData { self.time_mode = TimeMode::Fixed(range); } - pub fn set_live_time_mode(&mut self) { - self.time_mode = TimeMode::Live(DEFAULT_LIVE_DURATION); + pub fn set_live_time_mode(&mut self, duration: Duration) { + self.time_mode = TimeMode::Live(duration); } pub fn is_live_time_mode(&self) -> bool { @@ -112,14 +119,3 @@ impl DashboardData { &self.worker_timeline } } - -impl Default for DashboardData { - fn default() -> Self { - Self { - worker_timeline: Default::default(), - job_timeline: Default::default(), - alloc_timeline: Default::default(), - time_mode: TimeMode::Live(DEFAULT_LIVE_DURATION), - } - } -} diff --git a/crates/hyperqueue/src/dashboard/data/mod.rs b/crates/hyperqueue/src/dashboard/data/mod.rs index 26cf3a775..3e8fad4cb 100644 --- a/crates/hyperqueue/src/dashboard/data/mod.rs +++ b/crates/hyperqueue/src/dashboard/data/mod.rs @@ -13,3 +13,5 @@ mod time_interval; pub mod timelines; type Time = SystemTime; + +pub use time_interval::TimeMode; diff --git a/crates/hyperqueue/src/dashboard/mod.rs b/crates/hyperqueue/src/dashboard/mod.rs index b7be65447..83a5a4f53 100644 --- a/crates/hyperqueue/src/dashboard/mod.rs +++ b/crates/hyperqueue/src/dashboard/mod.rs @@ -5,3 +5,7 @@ mod ui_loop; mod utils; pub use ui_loop::start_ui_loop; + +use std::time::Duration; + +const DEFAULT_LIVE_DURATION: Duration = Duration::from_secs(60 * 10); diff --git a/crates/hyperqueue/src/dashboard/ui/screens/root_screen.rs b/crates/hyperqueue/src/dashboard/ui/screens/root_screen.rs index d67561847..2848ab574 100644 --- a/crates/hyperqueue/src/dashboard/ui/screens/root_screen.rs +++ b/crates/hyperqueue/src/dashboard/ui/screens/root_screen.rs @@ -4,6 +4,7 @@ use crate::dashboard::ui::screens::autoalloc_screen::AutoAllocScreen; use crate::dashboard::ui::screens::job_screen::JobScreen; use crate::dashboard::ui::screens::overview_screen::WorkerOverviewScreen; use crate::dashboard::ui::terminal::{DashboardFrame, DashboardTerminal}; +use crate::dashboard::DEFAULT_LIVE_DURATION; use chrono::Local; use crossterm::event::{KeyCode, KeyEvent}; use ratatui::layout::{Alignment, Constraint, Direction, Layout, Rect}; @@ -81,7 +82,7 @@ impl RootScreen { KeyCode::Char(c) if c == KEY_TIMELINE_LATER => { data.set_time_range(data.current_time_range().later(Duration::from_secs(60 * 5))) } - KeyCode::Char('r') => data.set_live_time_mode(), + KeyCode::Char('r') => data.set_live_time_mode(DEFAULT_LIVE_DURATION), _ => { self.get_current_screen_mut().handle_key(input); diff --git a/crates/hyperqueue/src/dashboard/ui_loop.rs b/crates/hyperqueue/src/dashboard/ui_loop.rs index 903d4070e..51fd6ea7a 100644 --- a/crates/hyperqueue/src/dashboard/ui_loop.rs +++ b/crates/hyperqueue/src/dashboard/ui_loop.rs @@ -7,10 +7,11 @@ use std::pin::Pin; use tokio::time::Duration; use crate::client::globalsettings::GlobalSettings; -use crate::dashboard::data::create_data_fetch_process; use crate::dashboard::data::DashboardData; +use crate::dashboard::data::{create_data_fetch_process, TimeMode, TimeRange}; use crate::dashboard::ui::screens::root_screen::RootScreen; use crate::dashboard::ui::terminal::initialize_terminal; +use crate::dashboard::DEFAULT_LIVE_DURATION; use crate::server::bootstrap::get_client_session; use crate::server::event::Event; @@ -20,7 +21,16 @@ pub async fn start_ui_loop( events: Vec, stream: bool, ) -> anyhow::Result<()> { - let mut dashboard_data = DashboardData::default(); + let time_mode = if stream || events.is_empty() { + TimeMode::Live(DEFAULT_LIVE_DURATION) + } else { + TimeMode::Fixed(TimeRange { + start: events[0].time.into(), + end: events.last().unwrap().time.into(), + }) + }; + + let mut dashboard_data = DashboardData::from_time_mode(time_mode); dashboard_data.push_new_events(events); let mut root_screen = RootScreen::default(); From dead5f34828d40e55da65a24885abf1d66cc44b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Fri, 18 Oct 2024 13:23:26 +0200 Subject: [PATCH 08/31] Send remaining events in data fetching --- crates/hyperqueue/src/dashboard/data/fetch.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/crates/hyperqueue/src/dashboard/data/fetch.rs b/crates/hyperqueue/src/dashboard/data/fetch.rs index 50c262912..4bd324008 100644 --- a/crates/hyperqueue/src/dashboard/data/fetch.rs +++ b/crates/hyperqueue/src/dashboard/data/fetch.rs @@ -46,5 +46,8 @@ pub async fn create_data_fetch_process( } } } + if !events.is_empty() { + sender.send(events).await?; + } Ok(()) } From 97e54c0069c5e5919061206139000d3ec23e468f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Fri, 18 Oct 2024 13:23:56 +0200 Subject: [PATCH 09/31] Remove unused method and only compile helper function in `TimeBasedVec` when compiling tests --- crates/hyperqueue/src/dashboard/data/time_based_vec.rs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/crates/hyperqueue/src/dashboard/data/time_based_vec.rs b/crates/hyperqueue/src/dashboard/data/time_based_vec.rs index ad79a8e8a..cafd0aaab 100644 --- a/crates/hyperqueue/src/dashboard/data/time_based_vec.rs +++ b/crates/hyperqueue/src/dashboard/data/time_based_vec.rs @@ -29,7 +29,7 @@ impl TimeBasedVec { } } - /// Returns the most recent item that has happened before `time`. + /// Returns the most recent item that has happened before or at `time`. pub fn get_most_recent_at(&self, time: Time) -> Option<&ItemWithTime> { let index = self.items.partition_point(|item| item.time <= time); if index == 0 { @@ -47,15 +47,13 @@ impl TimeBasedVec { &self.items[start_index..end_index] } - pub fn last(&self) -> Option<&ItemWithTime> { - self.items.last() - } - - pub fn into_vec(self) -> Vec { + #[cfg(test)] + fn into_vec(self) -> Vec { self.into() } } +#[cfg(test)] impl From> for Vec { fn from(value: TimeBasedVec) -> Self { value.items.into_iter().map(|item| item.item).collect() From 3e9bcb82148409c3877b0e122a0a477154488d06 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Fri, 18 Oct 2024 14:00:46 +0200 Subject: [PATCH 10/31] Improve layout of root screen and make worker screen the default --- crates/hyperqueue/src/dashboard/mod.rs | 4 + .../src/dashboard/ui/screens/root_screen.rs | 74 +++++++++++-------- 2 files changed, 49 insertions(+), 29 deletions(-) diff --git a/crates/hyperqueue/src/dashboard/mod.rs b/crates/hyperqueue/src/dashboard/mod.rs index 83a5a4f53..ca28a43d6 100644 --- a/crates/hyperqueue/src/dashboard/mod.rs +++ b/crates/hyperqueue/src/dashboard/mod.rs @@ -8,4 +8,8 @@ pub use ui_loop::start_ui_loop; use std::time::Duration; +// The time range in which the live timeline is display ([now() - duration, now()]) const DEFAULT_LIVE_DURATION: Duration = Duration::from_secs(60 * 10); + +// The amount of time that is subtracted/added when moving the timeline +const TIMELINE_MOVE_OFFSET: Duration = Duration::from_secs(60 * 5); diff --git a/crates/hyperqueue/src/dashboard/ui/screens/root_screen.rs b/crates/hyperqueue/src/dashboard/ui/screens/root_screen.rs index 2848ab574..56ac1902b 100644 --- a/crates/hyperqueue/src/dashboard/ui/screens/root_screen.rs +++ b/crates/hyperqueue/src/dashboard/ui/screens/root_screen.rs @@ -4,19 +4,20 @@ use crate::dashboard::ui::screens::autoalloc_screen::AutoAllocScreen; use crate::dashboard::ui::screens::job_screen::JobScreen; use crate::dashboard::ui::screens::overview_screen::WorkerOverviewScreen; use crate::dashboard::ui::terminal::{DashboardFrame, DashboardTerminal}; -use crate::dashboard::DEFAULT_LIVE_DURATION; +use crate::dashboard::{DEFAULT_LIVE_DURATION, TIMELINE_MOVE_OFFSET}; use chrono::Local; use crossterm::event::{KeyCode, KeyEvent}; -use ratatui::layout::{Alignment, Constraint, Direction, Layout, Rect}; +use ratatui::layout::{Alignment, Constraint, Direction, Flex, Layout, Rect}; use ratatui::style::{Color, Modifier, Style}; use ratatui::text::{Line, Span, Text}; use ratatui::widgets::{Block, Borders, Paragraph, Tabs, Wrap}; use ratatui::Frame; +use std::fmt::Write; use std::ops::ControlFlow; -use std::time::Duration; const KEY_TIMELINE_SOONER: char = 'o'; const KEY_TIMELINE_LATER: char = 'p'; +const KEY_TIMELINE_LIVE: char = 'r'; #[derive(Default)] pub struct RootScreen { @@ -27,16 +28,18 @@ pub struct RootScreen { current_screen: SelectedScreen, } -pub struct RootChunks { - pub screen_tabs: Rect, - pub timeline: Rect, - pub screen: Rect, +struct RootChunks { + // Tab selection, in the top-left corner + screen_tabs: Rect, + // Timeline information and shortcuts, in the top-right corner + timeline: Rect, + screen: Rect, } #[derive(Clone, Copy, Default)] -pub enum SelectedScreen { - #[default] +enum SelectedScreen { JobOverview, + #[default] WorkerOverview, AutoAllocator, } @@ -75,14 +78,15 @@ impl RootScreen { KeyCode::Char('w') => { self.current_screen = SelectedScreen::WorkerOverview; } - KeyCode::Char(c) if c == KEY_TIMELINE_SOONER => data.set_time_range( - data.current_time_range() - .sooner(Duration::from_secs(60 * 5)), - ), + KeyCode::Char(c) if c == KEY_TIMELINE_SOONER => { + data.set_time_range(data.current_time_range().sooner(TIMELINE_MOVE_OFFSET)) + } KeyCode::Char(c) if c == KEY_TIMELINE_LATER => { - data.set_time_range(data.current_time_range().later(Duration::from_secs(60 * 5))) + data.set_time_range(data.current_time_range().later(TIMELINE_MOVE_OFFSET)) + } + KeyCode::Char(c) if c == KEY_TIMELINE_LIVE => { + data.set_live_time_mode(DEFAULT_LIVE_DURATION) } - KeyCode::Char('r') => data.set_live_time_mode(DEFAULT_LIVE_DURATION), _ => { self.get_current_screen_mut().handle_key(input); @@ -100,17 +104,18 @@ impl RootScreen { } } -pub fn get_root_screen_chunks(frame: &DashboardFrame) -> RootChunks { +fn get_root_screen_chunks(frame: &DashboardFrame) -> RootChunks { let root_screen_chunks = Layout::default() - .constraints(vec![Constraint::Length(4), Constraint::Percentage(100)]) + .constraints(vec![Constraint::Length(4), Constraint::Fill(1)]) .direction(Direction::Vertical) - .split(frame.size()); + .split(frame.area()); let top = root_screen_chunks[0]; let screen = root_screen_chunks[1]; let top_chunks = Layout::default() - .constraints(vec![Constraint::Percentage(80), Constraint::Percentage(20)]) + .constraints(vec![Constraint::Min(28), Constraint::Min(20)]) + .flex(Flex::SpaceBetween) .direction(Direction::Horizontal) .split(top); @@ -122,7 +127,7 @@ pub fn get_root_screen_chunks(frame: &DashboardFrame) -> RootChunks { } /// Renders the top `tab bar` of the dashboard. -pub fn render_screen_tabs(current_screen: SelectedScreen, rect: Rect, frame: &mut DashboardFrame) { +fn render_screen_tabs(current_screen: SelectedScreen, rect: Rect, frame: &mut DashboardFrame) { let screen_names = ["Jobs", "AutoAllocator", "Workers"]; let screen_titles: Vec = screen_names .iter() @@ -157,7 +162,7 @@ pub fn render_screen_tabs(current_screen: SelectedScreen, rect: Rect, frame: &mu fn render_timeline(data: &DashboardData, rect: Rect, frame: &mut Frame) { let chunks = Layout::default() - .constraints(vec![Constraint::Length(1), Constraint::Percentage(100)]) + .constraints(vec![Constraint::Length(1), Constraint::Fill(1)]) .direction(Direction::Vertical) .split(rect); @@ -165,13 +170,22 @@ fn render_timeline(data: &DashboardData, rect: Rect, frame: &mut Frame) { let start: chrono::DateTime = range.start.into(); let end: chrono::DateTime = range.end.into(); - const FORMAT: &str = "%d.%m. %H:%M:%S"; + let skip_day = chunks[0].width < 35 + || (start.date_naive() == end.date_naive() + && start.date_naive() == Local::now().date_naive()); + let date_format: &str = if skip_day { + "%H:%M:%S" + } else { + "%d.%m. %H:%M:%S" + }; - let formatted = format!("{} - {}", start.format(FORMAT), end.format(FORMAT)); - let range_paragraph = Paragraph::new(vec![Line::from(formatted)]) - .style(Style::default()) - .block(Block::default()) - .alignment(Alignment::Center) + let range = format!( + "{} - {}", + start.format(date_format), + end.format(date_format) + ); + let range_paragraph = Paragraph::new(Text::from(range)) + .alignment(Alignment::Right) .wrap(Wrap { trim: true }); frame.render_widget(range_paragraph, chunks[0]); @@ -181,9 +195,11 @@ fn render_timeline(data: &DashboardData, rect: Rect, frame: &mut Frame) { <{KEY_TIMELINE_LATER}> +5 minutes"# ); if !data.is_live_time_mode() { - shortcuts.push_str("\n live view"); + write!(shortcuts, "\n<{KEY_TIMELINE_LIVE}> live view").unwrap(); } - let shortcuts_paragraph = Paragraph::new(Text::from(shortcuts)).wrap(Wrap { trim: true }); + let shortcuts_paragraph = Paragraph::new(Text::from(shortcuts)) + .alignment(Alignment::Right) + .wrap(Wrap { trim: true }); frame.render_widget(shortcuts_paragraph, chunks[1]); } From 99e797914dad91bd6b8c80e3f7c1d7bfa7c3c244 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Fri, 18 Oct 2024 14:01:35 +0200 Subject: [PATCH 11/31] Remove unused module --- crates/hyperqueue/src/dashboard/events.rs | 12 ------------ crates/hyperqueue/src/dashboard/mod.rs | 1 - 2 files changed, 13 deletions(-) delete mode 100644 crates/hyperqueue/src/dashboard/events.rs diff --git a/crates/hyperqueue/src/dashboard/events.rs b/crates/hyperqueue/src/dashboard/events.rs deleted file mode 100644 index 06cedab17..000000000 --- a/crates/hyperqueue/src/dashboard/events.rs +++ /dev/null @@ -1,12 +0,0 @@ -use crate::server::event::Event; -use crossterm::event::KeyEvent; - -#[derive(Debug, Clone)] -pub enum DashboardEvent { - /// The event when a key is pressed - KeyPressEvent(KeyEvent), - /// Updates the dashboard ui with the latest data - UiTick, - /// New events were fetched from the server - FetchedEvents(Vec), -} diff --git a/crates/hyperqueue/src/dashboard/mod.rs b/crates/hyperqueue/src/dashboard/mod.rs index ca28a43d6..278fc1fc2 100644 --- a/crates/hyperqueue/src/dashboard/mod.rs +++ b/crates/hyperqueue/src/dashboard/mod.rs @@ -1,5 +1,4 @@ mod data; -mod events; mod ui; mod ui_loop; mod utils; From f5e018126b8eb05a405e2382b8b4138511000864 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Fri, 18 Oct 2024 14:04:26 +0200 Subject: [PATCH 12/31] Rename `overview_screen` to `worker_overview_screen` --- crates/hyperqueue/src/dashboard/ui/screens/job_screen.rs | 2 +- crates/hyperqueue/src/dashboard/ui/screens/mod.rs | 2 +- crates/hyperqueue/src/dashboard/ui/screens/root_screen.rs | 2 +- .../{overview_screen => worker_overview_screen}/mod.rs | 4 ++-- .../overview/fragment.rs | 4 ++-- .../overview/mod.rs | 0 .../overview/worker_count_chart.rs | 0 .../overview/worker_table.rs | 0 .../worker/cpu_util_table.rs | 0 .../worker/fragment.rs | 6 +++--- .../worker/mod.rs | 0 .../worker/worker_config_table.rs | 0 .../worker/worker_utilization_chart.rs | 0 13 files changed, 10 insertions(+), 10 deletions(-) rename crates/hyperqueue/src/dashboard/ui/screens/{overview_screen => worker_overview_screen}/mod.rs (90%) rename crates/hyperqueue/src/dashboard/ui/screens/{overview_screen => worker_overview_screen}/overview/fragment.rs (93%) rename crates/hyperqueue/src/dashboard/ui/screens/{overview_screen => worker_overview_screen}/overview/mod.rs (100%) rename crates/hyperqueue/src/dashboard/ui/screens/{overview_screen => worker_overview_screen}/overview/worker_count_chart.rs (100%) rename crates/hyperqueue/src/dashboard/ui/screens/{overview_screen => worker_overview_screen}/overview/worker_table.rs (100%) rename crates/hyperqueue/src/dashboard/ui/screens/{overview_screen => worker_overview_screen}/worker/cpu_util_table.rs (100%) rename crates/hyperqueue/src/dashboard/ui/screens/{overview_screen => worker_overview_screen}/worker/fragment.rs (94%) rename crates/hyperqueue/src/dashboard/ui/screens/{overview_screen => worker_overview_screen}/worker/mod.rs (100%) rename crates/hyperqueue/src/dashboard/ui/screens/{overview_screen => worker_overview_screen}/worker/worker_config_table.rs (100%) rename crates/hyperqueue/src/dashboard/ui/screens/{overview_screen => worker_overview_screen}/worker/worker_utilization_chart.rs (100%) diff --git a/crates/hyperqueue/src/dashboard/ui/screens/job_screen.rs b/crates/hyperqueue/src/dashboard/ui/screens/job_screen.rs index 7606aeb15..54338e9d3 100644 --- a/crates/hyperqueue/src/dashboard/ui/screens/job_screen.rs +++ b/crates/hyperqueue/src/dashboard/ui/screens/job_screen.rs @@ -1,7 +1,7 @@ use crate::dashboard::data::DashboardData; use crate::dashboard::ui::fragments::job::fragment::JobFragment; use crate::dashboard::ui::screen::Screen; -use crate::dashboard::ui::screens::overview_screen::worker::fragment::WorkerOverviewFragment; +use crate::dashboard::ui::screens::worker_overview_screen::worker::fragment::WorkerOverviewFragment; use crate::dashboard::ui::terminal::DashboardFrame; use crossterm::event::{KeyCode, KeyEvent}; use ratatui::layout::Rect; diff --git a/crates/hyperqueue/src/dashboard/ui/screens/mod.rs b/crates/hyperqueue/src/dashboard/ui/screens/mod.rs index 6c01491b0..92b5f08e2 100644 --- a/crates/hyperqueue/src/dashboard/ui/screens/mod.rs +++ b/crates/hyperqueue/src/dashboard/ui/screens/mod.rs @@ -1,4 +1,4 @@ pub mod autoalloc_screen; pub mod job_screen; -pub mod overview_screen; pub mod root_screen; +pub mod worker_overview_screen; diff --git a/crates/hyperqueue/src/dashboard/ui/screens/root_screen.rs b/crates/hyperqueue/src/dashboard/ui/screens/root_screen.rs index 56ac1902b..b9460dd59 100644 --- a/crates/hyperqueue/src/dashboard/ui/screens/root_screen.rs +++ b/crates/hyperqueue/src/dashboard/ui/screens/root_screen.rs @@ -2,7 +2,7 @@ use crate::dashboard::data::DashboardData; use crate::dashboard::ui::screen::Screen; use crate::dashboard::ui::screens::autoalloc_screen::AutoAllocScreen; use crate::dashboard::ui::screens::job_screen::JobScreen; -use crate::dashboard::ui::screens::overview_screen::WorkerOverviewScreen; +use crate::dashboard::ui::screens::worker_overview_screen::WorkerOverviewScreen; use crate::dashboard::ui::terminal::{DashboardFrame, DashboardTerminal}; use crate::dashboard::{DEFAULT_LIVE_DURATION, TIMELINE_MOVE_OFFSET}; use chrono::Local; diff --git a/crates/hyperqueue/src/dashboard/ui/screens/overview_screen/mod.rs b/crates/hyperqueue/src/dashboard/ui/screens/worker_overview_screen/mod.rs similarity index 90% rename from crates/hyperqueue/src/dashboard/ui/screens/overview_screen/mod.rs rename to crates/hyperqueue/src/dashboard/ui/screens/worker_overview_screen/mod.rs index edbf95a98..8985e4047 100644 --- a/crates/hyperqueue/src/dashboard/ui/screens/overview_screen/mod.rs +++ b/crates/hyperqueue/src/dashboard/ui/screens/worker_overview_screen/mod.rs @@ -1,7 +1,7 @@ use crate::dashboard::data::DashboardData; use crate::dashboard::ui::screen::Screen; -use crate::dashboard::ui::screens::overview_screen::overview::fragment::ClusterOverviewFragment; -use crate::dashboard::ui::screens::overview_screen::worker::fragment::WorkerOverviewFragment; +use crate::dashboard::ui::screens::worker_overview_screen::overview::fragment::ClusterOverviewFragment; +use crate::dashboard::ui::screens::worker_overview_screen::worker::fragment::WorkerOverviewFragment; use crate::dashboard::ui::terminal::DashboardFrame; use crossterm::event::{KeyCode, KeyEvent}; use ratatui::layout::Rect; diff --git a/crates/hyperqueue/src/dashboard/ui/screens/overview_screen/overview/fragment.rs b/crates/hyperqueue/src/dashboard/ui/screens/worker_overview_screen/overview/fragment.rs similarity index 93% rename from crates/hyperqueue/src/dashboard/ui/screens/overview_screen/overview/fragment.rs rename to crates/hyperqueue/src/dashboard/ui/screens/worker_overview_screen/overview/fragment.rs index 75b5c98ba..c770a64b7 100644 --- a/crates/hyperqueue/src/dashboard/ui/screens/overview_screen/overview/fragment.rs +++ b/crates/hyperqueue/src/dashboard/ui/screens/worker_overview_screen/overview/fragment.rs @@ -4,8 +4,8 @@ use ratatui::layout::{Constraint, Direction, Layout, Rect}; use tako::WorkerId; use crate::dashboard::data::DashboardData; -use crate::dashboard::ui::screens::overview_screen::overview::worker_count_chart::WorkerCountChart; -use crate::dashboard::ui::screens::overview_screen::overview::worker_table::WorkerTable; +use crate::dashboard::ui::screens::worker_overview_screen::overview::worker_count_chart::WorkerCountChart; +use crate::dashboard::ui::screens::worker_overview_screen::overview::worker_table::WorkerTable; use crate::dashboard::ui::styles::style_footer; use crate::dashboard::ui::terminal::DashboardFrame; use crate::dashboard::ui::widgets::text::draw_text; diff --git a/crates/hyperqueue/src/dashboard/ui/screens/overview_screen/overview/mod.rs b/crates/hyperqueue/src/dashboard/ui/screens/worker_overview_screen/overview/mod.rs similarity index 100% rename from crates/hyperqueue/src/dashboard/ui/screens/overview_screen/overview/mod.rs rename to crates/hyperqueue/src/dashboard/ui/screens/worker_overview_screen/overview/mod.rs diff --git a/crates/hyperqueue/src/dashboard/ui/screens/overview_screen/overview/worker_count_chart.rs b/crates/hyperqueue/src/dashboard/ui/screens/worker_overview_screen/overview/worker_count_chart.rs similarity index 100% rename from crates/hyperqueue/src/dashboard/ui/screens/overview_screen/overview/worker_count_chart.rs rename to crates/hyperqueue/src/dashboard/ui/screens/worker_overview_screen/overview/worker_count_chart.rs diff --git a/crates/hyperqueue/src/dashboard/ui/screens/overview_screen/overview/worker_table.rs b/crates/hyperqueue/src/dashboard/ui/screens/worker_overview_screen/overview/worker_table.rs similarity index 100% rename from crates/hyperqueue/src/dashboard/ui/screens/overview_screen/overview/worker_table.rs rename to crates/hyperqueue/src/dashboard/ui/screens/worker_overview_screen/overview/worker_table.rs diff --git a/crates/hyperqueue/src/dashboard/ui/screens/overview_screen/worker/cpu_util_table.rs b/crates/hyperqueue/src/dashboard/ui/screens/worker_overview_screen/worker/cpu_util_table.rs similarity index 100% rename from crates/hyperqueue/src/dashboard/ui/screens/overview_screen/worker/cpu_util_table.rs rename to crates/hyperqueue/src/dashboard/ui/screens/worker_overview_screen/worker/cpu_util_table.rs diff --git a/crates/hyperqueue/src/dashboard/ui/screens/overview_screen/worker/fragment.rs b/crates/hyperqueue/src/dashboard/ui/screens/worker_overview_screen/worker/fragment.rs similarity index 94% rename from crates/hyperqueue/src/dashboard/ui/screens/overview_screen/worker/fragment.rs rename to crates/hyperqueue/src/dashboard/ui/screens/worker_overview_screen/worker/fragment.rs index a5851d458..154304385 100644 --- a/crates/hyperqueue/src/dashboard/ui/screens/overview_screen/worker/fragment.rs +++ b/crates/hyperqueue/src/dashboard/ui/screens/worker_overview_screen/worker/fragment.rs @@ -8,11 +8,11 @@ use crate::dashboard::ui::widgets::text::draw_text; use crate::dashboard::data::timelines::job_timeline::TaskInfo; use crate::dashboard::data::DashboardData; -use crate::dashboard::ui::screens::overview_screen::worker::cpu_util_table::{ +use crate::dashboard::ui::screens::worker_overview_screen::worker::cpu_util_table::{ get_column_constraints, render_cpu_util_table, }; -use crate::dashboard::ui::screens::overview_screen::worker::worker_config_table::WorkerConfigTable; -use crate::dashboard::ui::screens::overview_screen::worker::worker_utilization_chart::WorkerUtilizationChart; +use crate::dashboard::ui::screens::worker_overview_screen::worker::worker_config_table::WorkerConfigTable; +use crate::dashboard::ui::screens::worker_overview_screen::worker::worker_utilization_chart::WorkerUtilizationChart; use crate::dashboard::ui::widgets::tasks_table::TasksTable; use crate::JobTaskId; use ratatui::layout::{Constraint, Direction, Layout, Rect}; diff --git a/crates/hyperqueue/src/dashboard/ui/screens/overview_screen/worker/mod.rs b/crates/hyperqueue/src/dashboard/ui/screens/worker_overview_screen/worker/mod.rs similarity index 100% rename from crates/hyperqueue/src/dashboard/ui/screens/overview_screen/worker/mod.rs rename to crates/hyperqueue/src/dashboard/ui/screens/worker_overview_screen/worker/mod.rs diff --git a/crates/hyperqueue/src/dashboard/ui/screens/overview_screen/worker/worker_config_table.rs b/crates/hyperqueue/src/dashboard/ui/screens/worker_overview_screen/worker/worker_config_table.rs similarity index 100% rename from crates/hyperqueue/src/dashboard/ui/screens/overview_screen/worker/worker_config_table.rs rename to crates/hyperqueue/src/dashboard/ui/screens/worker_overview_screen/worker/worker_config_table.rs diff --git a/crates/hyperqueue/src/dashboard/ui/screens/overview_screen/worker/worker_utilization_chart.rs b/crates/hyperqueue/src/dashboard/ui/screens/worker_overview_screen/worker/worker_utilization_chart.rs similarity index 100% rename from crates/hyperqueue/src/dashboard/ui/screens/overview_screen/worker/worker_utilization_chart.rs rename to crates/hyperqueue/src/dashboard/ui/screens/worker_overview_screen/worker/worker_utilization_chart.rs From d84fc79b4c5b087c5b60febc359862fab70564a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Fri, 18 Oct 2024 14:07:04 +0200 Subject: [PATCH 13/31] Move and rename stuff around --- .../mod.rs | 6 +- .../screens/cluster_overview/overview/mod.rs | 88 +++++++++++++++++ .../overview/worker_count_chart.rs | 0 .../overview/worker_table.rs | 0 .../worker/cpu_util_table.rs | 0 .../worker/fragment.rs | 6 +- .../worker/mod.rs | 0 .../worker/worker_config_table.rs | 0 .../worker/worker_utilization_chart.rs | 0 .../src/dashboard/ui/screens/job_screen.rs | 2 +- .../src/dashboard/ui/screens/mod.rs | 2 +- .../src/dashboard/ui/screens/root_screen.rs | 2 +- .../overview/fragment.rs | 96 ------------------- .../worker_overview_screen/overview/mod.rs | 3 - 14 files changed, 97 insertions(+), 108 deletions(-) rename crates/hyperqueue/src/dashboard/ui/screens/{worker_overview_screen => cluster_overview}/mod.rs (88%) create mode 100644 crates/hyperqueue/src/dashboard/ui/screens/cluster_overview/overview/mod.rs rename crates/hyperqueue/src/dashboard/ui/screens/{worker_overview_screen => cluster_overview}/overview/worker_count_chart.rs (100%) rename crates/hyperqueue/src/dashboard/ui/screens/{worker_overview_screen => cluster_overview}/overview/worker_table.rs (100%) rename crates/hyperqueue/src/dashboard/ui/screens/{worker_overview_screen => cluster_overview}/worker/cpu_util_table.rs (100%) rename crates/hyperqueue/src/dashboard/ui/screens/{worker_overview_screen => cluster_overview}/worker/fragment.rs (94%) rename crates/hyperqueue/src/dashboard/ui/screens/{worker_overview_screen => cluster_overview}/worker/mod.rs (100%) rename crates/hyperqueue/src/dashboard/ui/screens/{worker_overview_screen => cluster_overview}/worker/worker_config_table.rs (100%) rename crates/hyperqueue/src/dashboard/ui/screens/{worker_overview_screen => cluster_overview}/worker/worker_utilization_chart.rs (100%) delete mode 100644 crates/hyperqueue/src/dashboard/ui/screens/worker_overview_screen/overview/fragment.rs delete mode 100644 crates/hyperqueue/src/dashboard/ui/screens/worker_overview_screen/overview/mod.rs diff --git a/crates/hyperqueue/src/dashboard/ui/screens/worker_overview_screen/mod.rs b/crates/hyperqueue/src/dashboard/ui/screens/cluster_overview/mod.rs similarity index 88% rename from crates/hyperqueue/src/dashboard/ui/screens/worker_overview_screen/mod.rs rename to crates/hyperqueue/src/dashboard/ui/screens/cluster_overview/mod.rs index 8985e4047..9c298e75f 100644 --- a/crates/hyperqueue/src/dashboard/ui/screens/worker_overview_screen/mod.rs +++ b/crates/hyperqueue/src/dashboard/ui/screens/cluster_overview/mod.rs @@ -1,9 +1,9 @@ use crate::dashboard::data::DashboardData; use crate::dashboard::ui::screen::Screen; -use crate::dashboard::ui::screens::worker_overview_screen::overview::fragment::ClusterOverviewFragment; -use crate::dashboard::ui::screens::worker_overview_screen::worker::fragment::WorkerOverviewFragment; +use crate::dashboard::ui::screens::cluster_overview::worker::fragment::WorkerOverviewFragment; use crate::dashboard::ui::terminal::DashboardFrame; use crossterm::event::{KeyCode, KeyEvent}; +use overview::ClusterOverview; use ratatui::layout::Rect; pub mod overview; @@ -11,7 +11,7 @@ pub mod worker; #[derive(Default)] pub struct WorkerOverviewScreen { - cluster_overview: ClusterOverviewFragment, + cluster_overview: ClusterOverview, worker_overview: WorkerOverviewFragment, active_fragment: ScreenState, diff --git a/crates/hyperqueue/src/dashboard/ui/screens/cluster_overview/overview/mod.rs b/crates/hyperqueue/src/dashboard/ui/screens/cluster_overview/overview/mod.rs new file mode 100644 index 000000000..1b7b71b95 --- /dev/null +++ b/crates/hyperqueue/src/dashboard/ui/screens/cluster_overview/overview/mod.rs @@ -0,0 +1,88 @@ +use crate::dashboard::data::DashboardData; +use crate::dashboard::ui::screens::cluster_overview::overview::worker_count_chart::WorkerCountChart; +use crate::dashboard::ui::screens::cluster_overview::overview::worker_table::WorkerTable; +use crate::dashboard::ui::styles::style_footer; +use crate::dashboard::ui::terminal::DashboardFrame; +use crate::dashboard::ui::widgets::text::draw_text; +use crossterm::event::{KeyCode, KeyEvent}; +use ratatui::layout::{Constraint, Direction, Layout, Rect}; +use tako::WorkerId; + +mod worker_count_chart; +mod worker_table; + +#[derive(Default)] +pub struct ClusterOverview { + worker_count_chart: WorkerCountChart, + worker_list_table: WorkerTable, +} + +impl ClusterOverview { + pub fn draw(&mut self, in_area: Rect, frame: &mut DashboardFrame) { + let layout = OverviewLayout::new(&in_area); + draw_text( + "<\u{21F5}> select worker, worker details", + layout.footer_chunk, + frame, + style_footer(), + ); + + self.worker_count_chart + .draw(layout.worker_count_chunk, frame); + self.worker_list_table.draw(layout.worker_list_chunk, frame); + } + + pub fn update(&mut self, data: &DashboardData) { + self.worker_count_chart.update(data); + self.worker_list_table.update(data); + } + + /// Handles key presses for the components of the screen + pub fn handle_key(&mut self, key: KeyEvent) { + match key.code { + KeyCode::Down => { + self.worker_list_table.select_next_worker(); + } + KeyCode::Up => { + self.worker_list_table.select_previous_worker(); + } + _ => {} + } + } + + pub fn get_selected_worker(&self) -> Option { + self.worker_list_table.get_selected_item() + } +} + +/// _________________________ +/// | Worker count | +/// |-----------------------| +/// | Worker list | +/// |-----------------------| +/// | Footer | +/// ------------------------- +struct OverviewLayout { + worker_count_chunk: Rect, + worker_list_chunk: Rect, + footer_chunk: Rect, +} + +impl OverviewLayout { + fn new(rect: &Rect) -> Self { + let base_chunks = Layout::default() + .constraints(vec![ + Constraint::Percentage(45), + Constraint::Percentage(50), + Constraint::Percentage(5), + ]) + .direction(Direction::Vertical) + .split(*rect); + + Self { + worker_count_chunk: base_chunks[0], + worker_list_chunk: base_chunks[1], + footer_chunk: base_chunks[2], + } + } +} diff --git a/crates/hyperqueue/src/dashboard/ui/screens/worker_overview_screen/overview/worker_count_chart.rs b/crates/hyperqueue/src/dashboard/ui/screens/cluster_overview/overview/worker_count_chart.rs similarity index 100% rename from crates/hyperqueue/src/dashboard/ui/screens/worker_overview_screen/overview/worker_count_chart.rs rename to crates/hyperqueue/src/dashboard/ui/screens/cluster_overview/overview/worker_count_chart.rs diff --git a/crates/hyperqueue/src/dashboard/ui/screens/worker_overview_screen/overview/worker_table.rs b/crates/hyperqueue/src/dashboard/ui/screens/cluster_overview/overview/worker_table.rs similarity index 100% rename from crates/hyperqueue/src/dashboard/ui/screens/worker_overview_screen/overview/worker_table.rs rename to crates/hyperqueue/src/dashboard/ui/screens/cluster_overview/overview/worker_table.rs diff --git a/crates/hyperqueue/src/dashboard/ui/screens/worker_overview_screen/worker/cpu_util_table.rs b/crates/hyperqueue/src/dashboard/ui/screens/cluster_overview/worker/cpu_util_table.rs similarity index 100% rename from crates/hyperqueue/src/dashboard/ui/screens/worker_overview_screen/worker/cpu_util_table.rs rename to crates/hyperqueue/src/dashboard/ui/screens/cluster_overview/worker/cpu_util_table.rs diff --git a/crates/hyperqueue/src/dashboard/ui/screens/worker_overview_screen/worker/fragment.rs b/crates/hyperqueue/src/dashboard/ui/screens/cluster_overview/worker/fragment.rs similarity index 94% rename from crates/hyperqueue/src/dashboard/ui/screens/worker_overview_screen/worker/fragment.rs rename to crates/hyperqueue/src/dashboard/ui/screens/cluster_overview/worker/fragment.rs index 154304385..605e27144 100644 --- a/crates/hyperqueue/src/dashboard/ui/screens/worker_overview_screen/worker/fragment.rs +++ b/crates/hyperqueue/src/dashboard/ui/screens/cluster_overview/worker/fragment.rs @@ -8,11 +8,11 @@ use crate::dashboard::ui::widgets::text::draw_text; use crate::dashboard::data::timelines::job_timeline::TaskInfo; use crate::dashboard::data::DashboardData; -use crate::dashboard::ui::screens::worker_overview_screen::worker::cpu_util_table::{ +use crate::dashboard::ui::screens::cluster_overview::worker::cpu_util_table::{ get_column_constraints, render_cpu_util_table, }; -use crate::dashboard::ui::screens::worker_overview_screen::worker::worker_config_table::WorkerConfigTable; -use crate::dashboard::ui::screens::worker_overview_screen::worker::worker_utilization_chart::WorkerUtilizationChart; +use crate::dashboard::ui::screens::cluster_overview::worker::worker_config_table::WorkerConfigTable; +use crate::dashboard::ui::screens::cluster_overview::worker::worker_utilization_chart::WorkerUtilizationChart; use crate::dashboard::ui::widgets::tasks_table::TasksTable; use crate::JobTaskId; use ratatui::layout::{Constraint, Direction, Layout, Rect}; diff --git a/crates/hyperqueue/src/dashboard/ui/screens/worker_overview_screen/worker/mod.rs b/crates/hyperqueue/src/dashboard/ui/screens/cluster_overview/worker/mod.rs similarity index 100% rename from crates/hyperqueue/src/dashboard/ui/screens/worker_overview_screen/worker/mod.rs rename to crates/hyperqueue/src/dashboard/ui/screens/cluster_overview/worker/mod.rs diff --git a/crates/hyperqueue/src/dashboard/ui/screens/worker_overview_screen/worker/worker_config_table.rs b/crates/hyperqueue/src/dashboard/ui/screens/cluster_overview/worker/worker_config_table.rs similarity index 100% rename from crates/hyperqueue/src/dashboard/ui/screens/worker_overview_screen/worker/worker_config_table.rs rename to crates/hyperqueue/src/dashboard/ui/screens/cluster_overview/worker/worker_config_table.rs diff --git a/crates/hyperqueue/src/dashboard/ui/screens/worker_overview_screen/worker/worker_utilization_chart.rs b/crates/hyperqueue/src/dashboard/ui/screens/cluster_overview/worker/worker_utilization_chart.rs similarity index 100% rename from crates/hyperqueue/src/dashboard/ui/screens/worker_overview_screen/worker/worker_utilization_chart.rs rename to crates/hyperqueue/src/dashboard/ui/screens/cluster_overview/worker/worker_utilization_chart.rs diff --git a/crates/hyperqueue/src/dashboard/ui/screens/job_screen.rs b/crates/hyperqueue/src/dashboard/ui/screens/job_screen.rs index 54338e9d3..30196cbd0 100644 --- a/crates/hyperqueue/src/dashboard/ui/screens/job_screen.rs +++ b/crates/hyperqueue/src/dashboard/ui/screens/job_screen.rs @@ -1,7 +1,7 @@ use crate::dashboard::data::DashboardData; use crate::dashboard::ui::fragments::job::fragment::JobFragment; use crate::dashboard::ui::screen::Screen; -use crate::dashboard::ui::screens::worker_overview_screen::worker::fragment::WorkerOverviewFragment; +use crate::dashboard::ui::screens::cluster_overview::worker::fragment::WorkerOverviewFragment; use crate::dashboard::ui::terminal::DashboardFrame; use crossterm::event::{KeyCode, KeyEvent}; use ratatui::layout::Rect; diff --git a/crates/hyperqueue/src/dashboard/ui/screens/mod.rs b/crates/hyperqueue/src/dashboard/ui/screens/mod.rs index 92b5f08e2..363b47dd1 100644 --- a/crates/hyperqueue/src/dashboard/ui/screens/mod.rs +++ b/crates/hyperqueue/src/dashboard/ui/screens/mod.rs @@ -1,4 +1,4 @@ pub mod autoalloc_screen; +pub mod cluster_overview; pub mod job_screen; pub mod root_screen; -pub mod worker_overview_screen; diff --git a/crates/hyperqueue/src/dashboard/ui/screens/root_screen.rs b/crates/hyperqueue/src/dashboard/ui/screens/root_screen.rs index b9460dd59..1d12044cf 100644 --- a/crates/hyperqueue/src/dashboard/ui/screens/root_screen.rs +++ b/crates/hyperqueue/src/dashboard/ui/screens/root_screen.rs @@ -1,8 +1,8 @@ use crate::dashboard::data::DashboardData; use crate::dashboard::ui::screen::Screen; use crate::dashboard::ui::screens::autoalloc_screen::AutoAllocScreen; +use crate::dashboard::ui::screens::cluster_overview::WorkerOverviewScreen; use crate::dashboard::ui::screens::job_screen::JobScreen; -use crate::dashboard::ui::screens::worker_overview_screen::WorkerOverviewScreen; use crate::dashboard::ui::terminal::{DashboardFrame, DashboardTerminal}; use crate::dashboard::{DEFAULT_LIVE_DURATION, TIMELINE_MOVE_OFFSET}; use chrono::Local; diff --git a/crates/hyperqueue/src/dashboard/ui/screens/worker_overview_screen/overview/fragment.rs b/crates/hyperqueue/src/dashboard/ui/screens/worker_overview_screen/overview/fragment.rs deleted file mode 100644 index c770a64b7..000000000 --- a/crates/hyperqueue/src/dashboard/ui/screens/worker_overview_screen/overview/fragment.rs +++ /dev/null @@ -1,96 +0,0 @@ -use crossterm::event::{KeyCode, KeyEvent}; -use ratatui::layout::{Constraint, Direction, Layout, Rect}; - -use tako::WorkerId; - -use crate::dashboard::data::DashboardData; -use crate::dashboard::ui::screens::worker_overview_screen::overview::worker_count_chart::WorkerCountChart; -use crate::dashboard::ui::screens::worker_overview_screen::overview::worker_table::WorkerTable; -use crate::dashboard::ui::styles::style_footer; -use crate::dashboard::ui::terminal::DashboardFrame; -use crate::dashboard::ui::widgets::text::draw_text; - -#[derive(Default)] -pub struct ClusterOverviewFragment { - worker_util_table: WorkerTable, - cluster_overview: WorkerCountChart, -} - -impl ClusterOverviewFragment { - pub fn draw(&mut self, in_area: Rect, frame: &mut DashboardFrame) { - let layout = OverviewFragmentLayout::new(&in_area); - draw_text( - "<\u{21F5}> select worker, worker details", - layout.footer_chunk, - frame, - style_footer(), - ); - - self.cluster_overview.draw(layout.worker_count_chunk, frame); - self.worker_util_table - .draw(layout.worker_util_table_chunk, frame); - } - - pub fn update(&mut self, data: &DashboardData) { - self.worker_util_table.update(data); - self.cluster_overview.update(data); - } - - /// Handles key presses for the components of the screen - pub fn handle_key(&mut self, key: KeyEvent) { - match key.code { - KeyCode::Down => { - self.worker_util_table.select_next_worker(); - } - KeyCode::Up => { - self.worker_util_table.select_previous_worker(); - } - _ => {} - } - } - - pub fn get_selected_worker(&self) -> Option { - self.worker_util_table.get_selected_item() - } -} - -/** -* __________________________ - | Chart | Info | - |--------Header---------| - |-----------------------| - | BODY | - ------------------------- - **/ -struct OverviewFragmentLayout { - worker_count_chunk: Rect, - _task_timeline_chart: Rect, - worker_util_table_chunk: Rect, - footer_chunk: Rect, -} - -impl OverviewFragmentLayout { - fn new(rect: &Rect) -> Self { - let base_chunks = ratatui::layout::Layout::default() - .constraints(vec![ - Constraint::Percentage(45), - Constraint::Percentage(50), - Constraint::Percentage(5), - ]) - .direction(Direction::Vertical) - .split(*rect); - - let info_chunks = Layout::default() - .constraints(vec![Constraint::Percentage(50), Constraint::Percentage(50)]) - .direction(Direction::Horizontal) - .margin(0) - .split(base_chunks[0]); - - Self { - worker_count_chunk: info_chunks[0], - _task_timeline_chart: info_chunks[1], - worker_util_table_chunk: base_chunks[1], - footer_chunk: base_chunks[2], - } - } -} diff --git a/crates/hyperqueue/src/dashboard/ui/screens/worker_overview_screen/overview/mod.rs b/crates/hyperqueue/src/dashboard/ui/screens/worker_overview_screen/overview/mod.rs deleted file mode 100644 index 678403a03..000000000 --- a/crates/hyperqueue/src/dashboard/ui/screens/worker_overview_screen/overview/mod.rs +++ /dev/null @@ -1,3 +0,0 @@ -pub mod fragment; -pub mod worker_count_chart; -pub mod worker_table; From d5d6c2d690153db910b6b7663cc7f000a47d9136 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Fri, 18 Oct 2024 14:25:35 +0200 Subject: [PATCH 14/31] Improve default timeline window for replayed visualizations --- .../src/dashboard/data/time_interval.rs | 23 ++++++++++++++++++- crates/hyperqueue/src/dashboard/ui_loop.rs | 5 ++-- 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/crates/hyperqueue/src/dashboard/data/time_interval.rs b/crates/hyperqueue/src/dashboard/data/time_interval.rs index 3e90c220b..43374db42 100644 --- a/crates/hyperqueue/src/dashboard/data/time_interval.rs +++ b/crates/hyperqueue/src/dashboard/data/time_interval.rs @@ -1,6 +1,8 @@ use crate::dashboard::data::Time; +use chrono::{TimeZone, Utc}; +use std::fmt::{Display, Formatter}; use std::ops::{Add, Sub}; -use std::time::{Duration, SystemTime}; +use std::time::{Duration, SystemTime, UNIX_EPOCH}; #[derive(Copy, Clone, Debug)] pub struct TimeRange { @@ -23,6 +25,25 @@ impl TimeRange { } } +impl Display for TimeRange { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!( + f, + "[{:?}, {:?}]", + Utc.timestamp_opt( + self.start.duration_since(UNIX_EPOCH).unwrap().as_secs() as i64, + 0 + ) + .unwrap(), + Utc.timestamp_opt( + self.end.duration_since(UNIX_EPOCH).unwrap().as_secs() as i64, + 0 + ) + .unwrap() + ) + } +} + impl Default for TimeRange { fn default() -> Self { let now = SystemTime::now(); diff --git a/crates/hyperqueue/src/dashboard/ui_loop.rs b/crates/hyperqueue/src/dashboard/ui_loop.rs index 51fd6ea7a..28dca1b30 100644 --- a/crates/hyperqueue/src/dashboard/ui_loop.rs +++ b/crates/hyperqueue/src/dashboard/ui_loop.rs @@ -24,9 +24,10 @@ pub async fn start_ui_loop( let time_mode = if stream || events.is_empty() { TimeMode::Live(DEFAULT_LIVE_DURATION) } else { + let end = events.last().unwrap().time.into(); TimeMode::Fixed(TimeRange { - start: events[0].time.into(), - end: events.last().unwrap().time.into(), + start: end - Duration::from_secs(60 * 5), + end, }) }; From 3e201af6ab0f4fe22c81018bdc6a538112fe99a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Fri, 18 Oct 2024 14:28:21 +0200 Subject: [PATCH 15/31] Only allow switching to live view if the dashboard is streaming --- crates/hyperqueue/src/dashboard/data/data.rs | 9 ++++++++- .../hyperqueue/src/dashboard/ui/screens/root_screen.rs | 4 ++-- crates/hyperqueue/src/dashboard/ui_loop.rs | 2 +- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/crates/hyperqueue/src/dashboard/data/data.rs b/crates/hyperqueue/src/dashboard/data/data.rs index 2f6615858..1838bdde0 100644 --- a/crates/hyperqueue/src/dashboard/data/data.rs +++ b/crates/hyperqueue/src/dashboard/data/data.rs @@ -21,15 +21,18 @@ pub struct DashboardData { alloc_timeline: AllocationTimeline, /// Determines the active time range time_mode: TimeMode, + /// Is streaming from a live server enabled? + stream_enabled: bool, } impl DashboardData { - pub fn from_time_mode(time_mode: TimeMode) -> Self { + pub fn new(time_mode: TimeMode, stream_enabled: bool) -> Self { Self { worker_timeline: Default::default(), job_timeline: Default::default(), alloc_timeline: Default::default(), time_mode, + stream_enabled, } } @@ -103,6 +106,10 @@ impl DashboardData { self.time_mode = TimeMode::Live(duration); } + pub fn stream_enabled(&self) -> bool { + self.stream_enabled + } + pub fn is_live_time_mode(&self) -> bool { matches!(self.time_mode, TimeMode::Live(_)) } diff --git a/crates/hyperqueue/src/dashboard/ui/screens/root_screen.rs b/crates/hyperqueue/src/dashboard/ui/screens/root_screen.rs index 1d12044cf..f481f1bd8 100644 --- a/crates/hyperqueue/src/dashboard/ui/screens/root_screen.rs +++ b/crates/hyperqueue/src/dashboard/ui/screens/root_screen.rs @@ -84,7 +84,7 @@ impl RootScreen { KeyCode::Char(c) if c == KEY_TIMELINE_LATER => { data.set_time_range(data.current_time_range().later(TIMELINE_MOVE_OFFSET)) } - KeyCode::Char(c) if c == KEY_TIMELINE_LIVE => { + KeyCode::Char(c) if c == KEY_TIMELINE_LIVE && data.stream_enabled() => { data.set_live_time_mode(DEFAULT_LIVE_DURATION) } @@ -194,7 +194,7 @@ fn render_timeline(data: &DashboardData, rect: Rect, frame: &mut Frame) { r#"<{KEY_TIMELINE_SOONER}> -5 minutes <{KEY_TIMELINE_LATER}> +5 minutes"# ); - if !data.is_live_time_mode() { + if !data.is_live_time_mode() && data.stream_enabled() { write!(shortcuts, "\n<{KEY_TIMELINE_LIVE}> live view").unwrap(); } diff --git a/crates/hyperqueue/src/dashboard/ui_loop.rs b/crates/hyperqueue/src/dashboard/ui_loop.rs index 28dca1b30..b162e7471 100644 --- a/crates/hyperqueue/src/dashboard/ui_loop.rs +++ b/crates/hyperqueue/src/dashboard/ui_loop.rs @@ -31,7 +31,7 @@ pub async fn start_ui_loop( }) }; - let mut dashboard_data = DashboardData::from_time_mode(time_mode); + let mut dashboard_data = DashboardData::new(time_mode, stream); dashboard_data.push_new_events(events); let mut root_screen = RootScreen::default(); From 7155ed217fcd11566c5d62860d1a55211bff2e45 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Fri, 18 Oct 2024 14:30:23 +0200 Subject: [PATCH 16/31] Display if the dashboard is in stream or replay mode --- .../src/dashboard/ui/screens/root_screen.rs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/crates/hyperqueue/src/dashboard/ui/screens/root_screen.rs b/crates/hyperqueue/src/dashboard/ui/screens/root_screen.rs index f481f1bd8..dbd03e34f 100644 --- a/crates/hyperqueue/src/dashboard/ui/screens/root_screen.rs +++ b/crates/hyperqueue/src/dashboard/ui/screens/root_screen.rs @@ -190,15 +190,16 @@ fn render_timeline(data: &DashboardData, rect: Rect, frame: &mut Frame) { frame.render_widget(range_paragraph, chunks[0]); - let mut shortcuts = format!( - r#"<{KEY_TIMELINE_SOONER}> -5 minutes -<{KEY_TIMELINE_LATER}> +5 minutes"# - ); + let mode = match data.stream_enabled() { + true => "[stream]", + false => "[replay]", + }; + let mut text = format!("{mode}\n<{KEY_TIMELINE_SOONER}> -5m, <{KEY_TIMELINE_LATER}> +5m"); if !data.is_live_time_mode() && data.stream_enabled() { - write!(shortcuts, "\n<{KEY_TIMELINE_LIVE}> live view").unwrap(); + write!(text, "\n<{KEY_TIMELINE_LIVE}> live view").unwrap(); } - let shortcuts_paragraph = Paragraph::new(Text::from(shortcuts)) + let shortcuts_paragraph = Paragraph::new(Text::from(text)) .alignment(Alignment::Right) .wrap(Wrap { trim: true }); frame.render_widget(shortcuts_paragraph, chunks[1]); From 9be9bcda32eea6e4784f376bf259b6db1d8764ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Fri, 18 Oct 2024 15:28:09 +0200 Subject: [PATCH 17/31] Fix worker table column order --- .../ui/screens/cluster_overview/overview/worker_table.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/hyperqueue/src/dashboard/ui/screens/cluster_overview/overview/worker_table.rs b/crates/hyperqueue/src/dashboard/ui/screens/cluster_overview/overview/worker_table.rs index 9d7d7cef8..1167a2928 100644 --- a/crates/hyperqueue/src/dashboard/ui/screens/cluster_overview/overview/worker_table.rs +++ b/crates/hyperqueue/src/dashboard/ui/screens/cluster_overview/overview/worker_table.rs @@ -113,8 +113,8 @@ impl WorkerTable { inline_help: "", table_headers: Some(vec![ "ID", - "Status", "Hostname", + "Status", "Running tasks", "CPU util", "Mem util", From 5c94653ab61a8ef1717395fd98edc60c37959b46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Fri, 18 Oct 2024 18:42:09 +0200 Subject: [PATCH 18/31] Re-enable job queries and update design of worker detail --- .../dashboard/data/timelines/job_timeline.rs | 102 +++++----- .../ui/screens/cluster_overview/mod.rs | 4 +- .../overview/worker_count_chart.rs | 6 +- .../cluster_overview/overview/worker_table.rs | 4 +- .../cluster_overview/worker/cpu_util_table.rs | 8 +- .../cluster_overview/worker/fragment.rs | 175 ----------------- .../ui/screens/cluster_overview/worker/mod.rs | 176 +++++++++++++++++- .../worker/worker_config_table.rs | 22 +-- .../worker/worker_utilization_chart.rs | 3 +- .../src/dashboard/ui/screens/job_screen.rs | 4 +- .../src/dashboard/ui/widgets/chart.rs | 2 +- 11 files changed, 244 insertions(+), 262 deletions(-) delete mode 100644 crates/hyperqueue/src/dashboard/ui/screens/cluster_overview/worker/fragment.rs diff --git a/crates/hyperqueue/src/dashboard/data/timelines/job_timeline.rs b/crates/hyperqueue/src/dashboard/data/timelines/job_timeline.rs index 8fd2ad552..58447fe16 100644 --- a/crates/hyperqueue/src/dashboard/data/timelines/job_timeline.rs +++ b/crates/hyperqueue/src/dashboard/data/timelines/job_timeline.rs @@ -1,13 +1,12 @@ use crate::server::event::payload::EventPayload; use crate::server::event::Event; -use crate::{JobId, JobTaskId, TakoTaskId, WorkerId}; +use crate::{JobId, JobTaskId, WorkerId}; use chrono::{DateTime, Utc}; use std::time::SystemTime; use tako::Map; pub struct DashboardJobInfo { - // pub job_info: JobInfo, - pub job_tasks_info: Map, + job_tasks_info: Map, pub job_creation_time: SystemTime, pub completion_date: Option>, @@ -55,72 +54,64 @@ impl JobTimeline { match &event.payload { EventPayload::Submit { job_id, - closed_job, - serialized_desc, + closed_job: _, + serialized_desc: _, } => { - todo!(); - // self.job_timeline.insert( - // *job_id, - // DashboardJobInfo { - // job_info: *job_info.clone(), - // job_tasks_info: Default::default(), - // job_creation_time: event.time, - // completion_date: None, - // }, - // ); + self.job_timeline.insert( + *job_id, + DashboardJobInfo { + job_tasks_info: Default::default(), + job_creation_time: event.time.into(), + completion_date: None, + }, + ); } EventPayload::JobCompleted(job_id) => { if let Some(job_info) = self.job_timeline.get_mut(job_id) { - // job_info.completion_date = Some(*completion_date) - todo!() + job_info.completion_date = Some(event.time); } } EventPayload::TaskStarted { job_id, task_id, - instance_id, + instance_id: _, workers, } => { - todo!() - /*if let Some((_, info)) = self - .job_timeline - .iter_mut() - .find(|(_, info)| info.job_info.task_ids.contains(task_id)) - { + if let Some(info) = self.job_timeline.get_mut(job_id) { info.job_tasks_info.insert( *task_id, TaskInfo { - worker_id: *worker_id, - start_time: event.time, + worker_id: workers[0], + start_time: event.time.into(), end_time: None, task_end_state: None, }, ); - }*/ + } } EventPayload::TaskFinished { job_id, task_id } => { - todo!() - /*update_task_status( + update_task_status( &mut self.job_timeline, - task_id, + *job_id, + *task_id, DashboardTaskState::Finished, - &event.time, - );*/ + event.time, + ); } EventPayload::TaskFailed { job_id, task_id, - error, + error: _, } => { - todo!() - /*update_task_status( + update_task_status( &mut self.job_timeline, - task_id, + *job_id, + *task_id, DashboardTaskState::Failed, - &event.time, - );*/ + event.time, + ); } _ => {} } @@ -142,18 +133,12 @@ impl JobTimeline { worker_id: WorkerId, at_time: SystemTime, ) -> impl Iterator + '_ { - todo!(); - std::iter::empty() - // self.get_jobs_created_before(at_time) - // .flat_map(move |(_, info)| { - // let base_id = info.job_info.base_task_id.as_num(); - // info.job_tasks_info.iter().map(move |(task_id, info)| { - // (JobTaskId::new(task_id.as_num() - base_id + 1), info) - // }) - // }) - // .filter(move |(_, task_info)| { - // task_info.worker_id == worker_id && task_info.start_time <= at_time - // }) + self.get_jobs_created_before(at_time) + .flat_map(|(_, info)| info.job_tasks_info.iter()) + .filter(move |(id, task_info)| { + task_info.worker_id == worker_id && task_info.start_time <= at_time + }) + .map(|(id, info)| (*id, info)) } pub fn get_job_info_for_job(&self, job_id: JobId) -> Option<&DashboardJobInfo> { @@ -172,17 +157,14 @@ impl JobTimeline { fn update_task_status( job_timeline: &mut Map, - task_id: &TakoTaskId, + job_id: JobId, + task_id: JobTaskId, task_status: DashboardTaskState, - at_time: &SystemTime, + at_time: DateTime, ) { - todo!() - /*if let Some((_, job_info)) = job_timeline - .iter_mut() - .find(|(_, info)| info.job_info.task_ids.contains(task_id)) - { - if let Some(task_info) = job_info.job_tasks_info.get_mut(task_id) { - task_info.set_end_time_and_status(at_time, task_status); + if let Some(job_info) = job_timeline.get_mut(&job_id) { + if let Some(task_info) = job_info.job_tasks_info.get_mut(&task_id) { + task_info.set_end_time_and_status(&at_time.into(), task_status); } - };*/ + } } diff --git a/crates/hyperqueue/src/dashboard/ui/screens/cluster_overview/mod.rs b/crates/hyperqueue/src/dashboard/ui/screens/cluster_overview/mod.rs index 9c298e75f..fd60ee857 100644 --- a/crates/hyperqueue/src/dashboard/ui/screens/cluster_overview/mod.rs +++ b/crates/hyperqueue/src/dashboard/ui/screens/cluster_overview/mod.rs @@ -1,10 +1,10 @@ use crate::dashboard::data::DashboardData; use crate::dashboard::ui::screen::Screen; -use crate::dashboard::ui::screens::cluster_overview::worker::fragment::WorkerOverviewFragment; use crate::dashboard::ui::terminal::DashboardFrame; use crossterm::event::{KeyCode, KeyEvent}; use overview::ClusterOverview; use ratatui::layout::Rect; +use worker::WorkerDetail; pub mod overview; pub mod worker; @@ -12,7 +12,7 @@ pub mod worker; #[derive(Default)] pub struct WorkerOverviewScreen { cluster_overview: ClusterOverview, - worker_overview: WorkerOverviewFragment, + worker_overview: WorkerDetail, active_fragment: ScreenState, } diff --git a/crates/hyperqueue/src/dashboard/ui/screens/cluster_overview/overview/worker_count_chart.rs b/crates/hyperqueue/src/dashboard/ui/screens/cluster_overview/overview/worker_count_chart.rs index 6114ef34d..7cf1ecea1 100644 --- a/crates/hyperqueue/src/dashboard/ui/screens/cluster_overview/overview/worker_count_chart.rs +++ b/crates/hyperqueue/src/dashboard/ui/screens/cluster_overview/overview/worker_count_chart.rs @@ -46,7 +46,7 @@ impl WorkerCountChart { .map(|record| record.count) .max() .unwrap_or(0) - .max(8) as f64; + .max(4) as f64; let worker_counts: Vec<(f64, f64)> = self .worker_records @@ -65,7 +65,7 @@ impl WorkerCountChart { .block( Block::default() .title(Span::styled( - "Worker Connection History", + "Running worker count", Style::default() .fg(Color::White) .add_modifier(Modifier::BOLD), @@ -73,7 +73,7 @@ impl WorkerCountChart { .borders(Borders::ALL), ) .x_axis(x_axis_time_chart(self.range)) - .y_axis(y_axis_steps(0.0, max_workers_in_view, 5)); + .y_axis(y_axis_steps(0.0, max_workers_in_view, 4)); frame.render_widget(chart, rect); } } diff --git a/crates/hyperqueue/src/dashboard/ui/screens/cluster_overview/overview/worker_table.rs b/crates/hyperqueue/src/dashboard/ui/screens/cluster_overview/overview/worker_table.rs index 1167a2928..db95528c1 100644 --- a/crates/hyperqueue/src/dashboard/ui/screens/cluster_overview/overview/worker_table.rs +++ b/crates/hyperqueue/src/dashboard/ui/screens/cluster_overview/overview/worker_table.rs @@ -122,8 +122,8 @@ impl WorkerTable { column_widths: vec![ Constraint::Percentage(10), Constraint::Percentage(20), - Constraint::Percentage(20), - Constraint::Percentage(10), + Constraint::Percentage(15), + Constraint::Percentage(15), Constraint::Percentage(20), Constraint::Percentage(20), ], diff --git a/crates/hyperqueue/src/dashboard/ui/screens/cluster_overview/worker/cpu_util_table.rs b/crates/hyperqueue/src/dashboard/ui/screens/cluster_overview/worker/cpu_util_table.rs index 640fcadf6..70388c7af 100644 --- a/crates/hyperqueue/src/dashboard/ui/screens/cluster_overview/worker/cpu_util_table.rs +++ b/crates/hyperqueue/src/dashboard/ui/screens/cluster_overview/worker/cpu_util_table.rs @@ -21,9 +21,13 @@ pub fn render_cpu_util_table( mem_util: &MemoryStats, rect: Rect, frame: &mut DashboardFrame, - constraints: &[Constraint], table_style: Style, ) { + if cpu_util_list.is_empty() { + return; + } + let constraints = get_column_constraints(rect, cpu_util_list.len()); + let width = constraints.len(); let height = (cpu_util_list.len() as f64 / width as f64).ceil() as usize; @@ -81,7 +85,7 @@ pub fn render_cpu_util_table( } /// Creates the column sizes for the cpu_util_table, each column divides the row equally. -pub fn get_column_constraints(rect: Rect, num_cpus: usize) -> Vec { +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); diff --git a/crates/hyperqueue/src/dashboard/ui/screens/cluster_overview/worker/fragment.rs b/crates/hyperqueue/src/dashboard/ui/screens/cluster_overview/worker/fragment.rs deleted file mode 100644 index 605e27144..000000000 --- a/crates/hyperqueue/src/dashboard/ui/screens/cluster_overview/worker/fragment.rs +++ /dev/null @@ -1,175 +0,0 @@ -use crossterm::event::{KeyCode, KeyEvent}; - -use crate::dashboard::ui::styles::{ - style_footer, style_header_text, table_style_deselected, table_style_selected, -}; -use crate::dashboard::ui::terminal::DashboardFrame; -use crate::dashboard::ui::widgets::text::draw_text; - -use crate::dashboard::data::timelines::job_timeline::TaskInfo; -use crate::dashboard::data::DashboardData; -use crate::dashboard::ui::screens::cluster_overview::worker::cpu_util_table::{ - get_column_constraints, render_cpu_util_table, -}; -use crate::dashboard::ui::screens::cluster_overview::worker::worker_config_table::WorkerConfigTable; -use crate::dashboard::ui::screens::cluster_overview::worker::worker_utilization_chart::WorkerUtilizationChart; -use crate::dashboard::ui::widgets::tasks_table::TasksTable; -use crate::JobTaskId; -use ratatui::layout::{Constraint, Direction, Layout, Rect}; -use tako::hwstats::MemoryStats; -use tako::WorkerId; - -#[derive(Default)] -pub struct WorkerOverviewFragment { - /// The worker info screen shows data for this worker - worker_id: Option, - utilization_history: WorkerUtilizationChart, - worker_config_table: WorkerConfigTable, - worker_tasks_table: TasksTable, - - worker_per_core_cpu_util: Vec, - worker_mem_util: MemoryStats, -} - -impl WorkerOverviewFragment { - pub fn clear_worker_id(&mut self) { - self.worker_id = None; - } - - pub fn set_worker_id(&mut self, worker_id: WorkerId) { - self.worker_id = Some(worker_id); - } -} - -impl WorkerOverviewFragment { - pub fn draw(&mut self, in_area: Rect, frame: &mut DashboardFrame) { - let layout = WorkerFragmentLayout::new(&in_area); - draw_text( - format!( - "Details for Worker {}", - self.worker_id.unwrap_or_default().as_num() - ) - .as_str(), - layout.header, - frame, - style_header_text(), - ); - draw_text(": Back", layout.footer, frame, style_footer()); - - let cpu_usage_columns = get_column_constraints( - layout.current_utilization, - self.worker_per_core_cpu_util.len(), - ); - render_cpu_util_table( - &self.worker_per_core_cpu_util, - &self.worker_mem_util, - layout.current_utilization, - frame, - &cpu_usage_columns, - table_style_deselected(), - ); - - self.utilization_history - .draw(layout.utilization_history, frame); - - self.worker_tasks_table.draw( - "Tasks On Worker", - layout.tasks, - frame, - table_style_selected(), - ); - self.worker_config_table.draw(layout.configuration, frame); - } - - pub fn update(&mut self, data: &DashboardData) { - if let Some(worker_id) = self.worker_id { - self.utilization_history.update(data, worker_id); - - if let Some((cpu_util, mem_util)) = 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.worker_per_core_cpu_util = cpu_util.iter().map(|&v| v as f64).collect(); - self.worker_mem_util = mem_util.clone(); - } - - let tasks_info: Vec<(JobTaskId, &TaskInfo)> = - data.query_task_history_for_worker(worker_id).collect(); - self.worker_tasks_table.update(tasks_info); - - if let Some(configuration) = data.workers().query_worker_config_for(worker_id) { - self.worker_config_table.update(configuration); - } - } - } - - /// Handles key presses for the components of the screen - pub fn handle_key(&mut self, key: KeyEvent) { - match key.code { - KeyCode::Down => self.worker_tasks_table.select_next_task(), - KeyCode::Up => self.worker_tasks_table.select_previous_task(), - KeyCode::Backspace => self.worker_tasks_table.clear_selection(), - - _ => {} - } - } -} - -/** -* __________________________ - |--------Header---------| - | Cpu Util | - |-----------------------| - | Worker Info | - |-----------------------| - |--------Footer---------| - |-----------------------| - **/ -struct WorkerFragmentLayout { - header: Rect, - utilization_history: Rect, - current_utilization: Rect, - tasks: Rect, - configuration: Rect, - footer: Rect, -} - -impl WorkerFragmentLayout { - fn new(rect: &Rect) -> Self { - let base_chunks = Layout::default() - .constraints(vec![ - Constraint::Percentage(5), - Constraint::Percentage(50), - Constraint::Percentage(40), - Constraint::Percentage(5), - ]) - .direction(Direction::Vertical) - .split(*rect); - - let utilization_chunks = Layout::default() - .constraints(vec![Constraint::Percentage(50), Constraint::Percentage(50)]) - .direction(Direction::Horizontal) - .split(base_chunks[1]); - - let bottom_chunks = Layout::default() - .constraints(vec![Constraint::Percentage(50), Constraint::Percentage(50)]) - .direction(Direction::Horizontal) - .split(base_chunks[2]); - - Self { - header: base_chunks[0], - utilization_history: utilization_chunks[0], - current_utilization: utilization_chunks[1], - tasks: bottom_chunks[0], - configuration: bottom_chunks[1], - footer: base_chunks[3], - } - } -} diff --git a/crates/hyperqueue/src/dashboard/ui/screens/cluster_overview/worker/mod.rs b/crates/hyperqueue/src/dashboard/ui/screens/cluster_overview/worker/mod.rs index f1296c10c..1d2ba1fec 100644 --- a/crates/hyperqueue/src/dashboard/ui/screens/cluster_overview/worker/mod.rs +++ b/crates/hyperqueue/src/dashboard/ui/screens/cluster_overview/worker/mod.rs @@ -1,4 +1,174 @@ -pub mod cpu_util_table; -pub mod fragment; -pub mod worker_config_table; +use crate::dashboard::data::timelines::job_timeline::TaskInfo; +use crate::dashboard::data::DashboardData; +use crate::dashboard::ui::screens::cluster_overview::worker::cpu_util_table::render_cpu_util_table; +use crate::dashboard::ui::screens::cluster_overview::worker::worker_config_table::WorkerConfigTable; +use crate::dashboard::ui::screens::cluster_overview::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::terminal::DashboardFrame; +use crate::dashboard::ui::widgets::tasks_table::TasksTable; +use crate::dashboard::ui::widgets::text::draw_text; +use crate::JobTaskId; +use crossterm::event::{KeyCode, KeyEvent}; +use ratatui::layout::{Constraint, Direction, Layout, Rect}; +use tako::hwstats::MemoryStats; +use tako::WorkerId; + +mod cpu_util_table; +mod worker_config_table; mod worker_utilization_chart; + +#[derive(Default)] +pub struct WorkerDetail { + /// The worker detail is shown for this worker + worker_id: Option, + utilization_history: WorkerUtilizationChart, + worker_config_table: WorkerConfigTable, + worker_tasks_table: TasksTable, + + utilization: Option, +} + +struct Utilization { + cpu: Vec, + memory: MemoryStats, +} + +impl WorkerDetail { + pub fn clear_worker_id(&mut self) { + self.worker_id = None; + self.utilization = None; + } + + pub fn set_worker_id(&mut self, worker_id: WorkerId) { + self.worker_id = Some(worker_id); + } +} + +impl WorkerDetail { + pub fn draw(&mut self, in_area: Rect, frame: &mut DashboardFrame) { + assert!(self.worker_id.is_some()); + let layout = WorkerDetailLayout::new(&in_area); + draw_text( + format!("Worker {}", self.worker_id.unwrap_or_default().as_num()).as_str(), + layout.header, + frame, + style_header_text(), + ); + draw_text(": Back", layout.footer, frame, style_footer()); + + if let Some(util) = &self.utilization { + render_cpu_util_table( + &util.cpu, + &util.memory, + layout.current_utilization, + frame, + table_style_deselected(), + ); + } + + self.utilization_history + .draw(layout.utilization_history, frame); + + self.worker_tasks_table.draw( + "Tasks On Worker", + layout.tasks, + frame, + table_style_selected(), + ); + self.worker_config_table.draw(layout.configuration, frame); + } + + pub fn update(&mut self, data: &DashboardData) { + if let Some(worker_id) = self.worker_id { + self.utilization_history.update(data, worker_id); + + if let Some((cpu_util, mem_util)) = 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 tasks_info: Vec<(JobTaskId, &TaskInfo)> = + data.query_task_history_for_worker(worker_id).collect(); + self.worker_tasks_table.update(tasks_info); + + if let Some(configuration) = data.workers().query_worker_config_for(worker_id) { + self.worker_config_table.update(configuration); + } + } + } + + /// Handles key presses for the components of the screen + pub fn handle_key(&mut self, key: KeyEvent) { + match key.code { + KeyCode::Down => self.worker_tasks_table.select_next_task(), + KeyCode::Up => self.worker_tasks_table.select_previous_task(), + KeyCode::Backspace => self.worker_tasks_table.clear_selection(), + + _ => {} + } + } +} + +/// _________________________ +/// | Header | +/// | Cpu Util | +/// |-----------------------| +/// | Worker Info | +/// |-----------------------| +/// | Footer | +/// |-----------------------| +struct WorkerDetailLayout { + header: Rect, + utilization_history: Rect, + current_utilization: Rect, + tasks: Rect, + configuration: Rect, + footer: Rect, +} + +impl WorkerDetailLayout { + fn new(rect: &Rect) -> Self { + let base_chunks = Layout::default() + .constraints(vec![ + Constraint::Percentage(5), + Constraint::Percentage(50), + Constraint::Percentage(40), + Constraint::Percentage(5), + ]) + .direction(Direction::Vertical) + .split(*rect); + + let utilization_chunks = Layout::default() + .constraints(vec![Constraint::Percentage(50), Constraint::Percentage(50)]) + .direction(Direction::Horizontal) + .split(base_chunks[1]); + + let bottom_chunks = Layout::default() + .constraints(vec![Constraint::Percentage(50), Constraint::Percentage(50)]) + .direction(Direction::Horizontal) + .split(base_chunks[2]); + + Self { + header: base_chunks[0], + utilization_history: utilization_chunks[0], + current_utilization: utilization_chunks[1], + tasks: bottom_chunks[0], + configuration: bottom_chunks[1], + footer: base_chunks[3], + } + } +} diff --git a/crates/hyperqueue/src/dashboard/ui/screens/cluster_overview/worker/worker_config_table.rs b/crates/hyperqueue/src/dashboard/ui/screens/cluster_overview/worker/worker_config_table.rs index 90455d1b9..483c83023 100644 --- a/crates/hyperqueue/src/dashboard/ui/screens/cluster_overview/worker/worker_config_table.rs +++ b/crates/hyperqueue/src/dashboard/ui/screens/cluster_overview/worker/worker_config_table.rs @@ -12,8 +12,8 @@ pub struct WorkerConfigTable { #[derive(Default, Debug)] struct WorkerConfigDataRow { - pub label: &'static str, - pub data: String, + label: &'static str, + data: String, } impl WorkerConfigTable { @@ -42,27 +42,27 @@ fn create_rows(worker_info: &WorkerConfiguration) -> Vec { let missing_data_str = String::new(); vec![ WorkerConfigDataRow { - label: "Listen Address: ", + label: "Listen Address:", data: worker_info.listen_address.to_string(), }, WorkerConfigDataRow { - label: "Hostname: ", + label: "Hostname:", data: worker_info.hostname.to_string(), }, WorkerConfigDataRow { - label: "Work Dir: ", - data: (worker_info + label: "Workdir:", + data: worker_info .work_dir .to_str() .unwrap_or(missing_data_str.as_str()) - .to_string()), + .to_string(), }, WorkerConfigDataRow { - label: "Heartbeat Interval: ", + label: "Heartbeat Interval:", data: humantime::format_duration(worker_info.heartbeat_interval).to_string(), }, WorkerConfigDataRow { - label: "Send Overview Interval: ", + label: "Send Overview Interval:", data: worker_info .overview_configuration .as_ref() @@ -72,14 +72,14 @@ fn create_rows(worker_info: &WorkerConfiguration) -> Vec { .unwrap_or_else(|| missing_data_str.clone()), }, WorkerConfigDataRow { - label: "Idle Timeout: ", + label: "Idle Timeout:", data: worker_info .idle_timeout .map(|interval| humantime::format_duration(interval).to_string()) .unwrap_or_else(|| missing_data_str.clone()), }, WorkerConfigDataRow { - label: "Time Limit: ", + label: "Time Limit:", data: worker_info .time_limit .map(|interval| humantime::format_duration(interval).to_string()) diff --git a/crates/hyperqueue/src/dashboard/ui/screens/cluster_overview/worker/worker_utilization_chart.rs b/crates/hyperqueue/src/dashboard/ui/screens/cluster_overview/worker/worker_utilization_chart.rs index 9a6994928..addb417b4 100644 --- a/crates/hyperqueue/src/dashboard/ui/screens/cluster_overview/worker/worker_utilization_chart.rs +++ b/crates/hyperqueue/src/dashboard/ui/screens/cluster_overview/worker/worker_utilization_chart.rs @@ -2,7 +2,7 @@ use ratatui::layout::{Constraint, Rect}; use ratatui::style::{Color, Modifier, Style}; use ratatui::symbols; use ratatui::text::Span; -use ratatui::widgets::{Block, Borders, Chart, Dataset, GraphType}; +use ratatui::widgets::{Block, Borders, Chart, Dataset, GraphType, LegendPosition}; use tako::hwstats::WorkerHwStateMessage; use tako::worker::WorkerOverview; @@ -89,6 +89,7 @@ impl WorkerUtilizationChart { let chart = Chart::new(datasets) .style(chart_style_deselected()) + .legend_position(Some(LegendPosition::TopLeft)) .hidden_legend_constraints((Constraint::Ratio(1, 1), Constraint::Ratio(1, 1))) .block( Block::default() diff --git a/crates/hyperqueue/src/dashboard/ui/screens/job_screen.rs b/crates/hyperqueue/src/dashboard/ui/screens/job_screen.rs index 30196cbd0..6ee91169d 100644 --- a/crates/hyperqueue/src/dashboard/ui/screens/job_screen.rs +++ b/crates/hyperqueue/src/dashboard/ui/screens/job_screen.rs @@ -1,14 +1,14 @@ use crate::dashboard::data::DashboardData; use crate::dashboard::ui::fragments::job::fragment::JobFragment; use crate::dashboard::ui::screen::Screen; -use crate::dashboard::ui::screens::cluster_overview::worker::fragment::WorkerOverviewFragment; +use crate::dashboard::ui::screens::cluster_overview::worker::WorkerDetail; use crate::dashboard::ui::terminal::DashboardFrame; use crossterm::event::{KeyCode, KeyEvent}; use ratatui::layout::Rect; pub struct JobScreen { job_overview_fragment: JobFragment, - task_runner_worker: WorkerOverviewFragment, + task_runner_worker: WorkerDetail, active_fragment: ScreenState, } diff --git a/crates/hyperqueue/src/dashboard/ui/widgets/chart.rs b/crates/hyperqueue/src/dashboard/ui/widgets/chart.rs index f9a2747cf..4a2d789dc 100644 --- a/crates/hyperqueue/src/dashboard/ui/widgets/chart.rs +++ b/crates/hyperqueue/src/dashboard/ui/widgets/chart.rs @@ -220,7 +220,7 @@ pub fn y_axis_steps(min: f64, max: f64, step_count: u32) -> Axis<'static> { (0..=step_count) .map(|step| { let value = step as f64 * interval; - let value = value.round() as u64; + let value = value.ceil() as u64; value.to_string() }) .collect::>(), From ffb95b753b058351ec2aa85d89d64684586a2fe1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Fri, 18 Oct 2024 18:43:23 +0200 Subject: [PATCH 19/31] Send event updates every half a second, instead of every second --- crates/hyperqueue/src/dashboard/data/fetch.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/hyperqueue/src/dashboard/data/fetch.rs b/crates/hyperqueue/src/dashboard/data/fetch.rs index 4bd324008..56965b692 100644 --- a/crates/hyperqueue/src/dashboard/data/fetch.rs +++ b/crates/hyperqueue/src/dashboard/data/fetch.rs @@ -16,7 +16,7 @@ pub async fn create_data_fetch_process( const CAPACITY: usize = 1024; let mut events = Vec::with_capacity(CAPACITY); - let mut tick = tokio::time::interval(Duration::from_secs(1)); + let mut tick = tokio::time::interval(Duration::from_millis(500)); let conn = session.connection(); From 5b5505fb1269b97ecb30b930e8b944c4da368348 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Fri, 18 Oct 2024 18:49:40 +0200 Subject: [PATCH 20/31] Hide useless legend --- .../ui/screens/cluster_overview/overview/worker_count_chart.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/hyperqueue/src/dashboard/ui/screens/cluster_overview/overview/worker_count_chart.rs b/crates/hyperqueue/src/dashboard/ui/screens/cluster_overview/overview/worker_count_chart.rs index 7cf1ecea1..96cda4159 100644 --- a/crates/hyperqueue/src/dashboard/ui/screens/cluster_overview/overview/worker_count_chart.rs +++ b/crates/hyperqueue/src/dashboard/ui/screens/cluster_overview/overview/worker_count_chart.rs @@ -62,6 +62,7 @@ impl WorkerCountChart { let chart = Chart::new(datasets) .style(chart_style_deselected()) + .legend_position(None) .block( Block::default() .title(Span::styled( From 2df64d0271d020548b12c67623147d9ed826850c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Fri, 18 Oct 2024 19:09:32 +0200 Subject: [PATCH 21/31] Improve visualization of worker detail --- .../auto_allocator/allocations_info_table.rs | 1 - .../auto_allocator/queue_info_table.rs | 1 - .../auto_allocator/queue_params_display.rs | 1 - .../dashboard/ui/fragments/job/fragment.rs | 23 ++++++- .../ui/fragments/job/job_info_display.rs | 1 - .../dashboard/ui/fragments/job/jobs_table.rs | 1 - .../cluster_overview/overview/worker_table.rs | 1 - .../cluster_overview/worker/cpu_util_table.rs | 10 +-- .../ui/screens/cluster_overview/worker/mod.rs | 19 ++++-- .../worker/worker_config_table.rs | 1 - .../src/dashboard/ui/widgets/table.rs | 1 - .../src/dashboard/ui/widgets/tasks_table.rs | 68 +++++++++++-------- 12 files changed, 76 insertions(+), 52 deletions(-) diff --git a/crates/hyperqueue/src/dashboard/ui/fragments/auto_allocator/allocations_info_table.rs b/crates/hyperqueue/src/dashboard/ui/fragments/auto_allocator/allocations_info_table.rs index 806d8ec77..d8c855211 100644 --- a/crates/hyperqueue/src/dashboard/ui/fragments/auto_allocator/allocations_info_table.rs +++ b/crates/hyperqueue/src/dashboard/ui/fragments/auto_allocator/allocations_info_table.rs @@ -43,7 +43,6 @@ impl AllocationInfoTable { frame, TableColumnHeaders { title: "Allocations <2>", - inline_help: "", table_headers: Some(vec![ "Allocation ID", "#Workers", diff --git a/crates/hyperqueue/src/dashboard/ui/fragments/auto_allocator/queue_info_table.rs b/crates/hyperqueue/src/dashboard/ui/fragments/auto_allocator/queue_info_table.rs index 2fce19743..8db255fd8 100644 --- a/crates/hyperqueue/src/dashboard/ui/fragments/auto_allocator/queue_info_table.rs +++ b/crates/hyperqueue/src/dashboard/ui/fragments/auto_allocator/queue_info_table.rs @@ -38,7 +38,6 @@ impl AllocationQueueInfoTable { frame, TableColumnHeaders { title: "Allocation Queues <1>", - inline_help: "", table_headers: Some(vec![ "Descriptor ID", "#Allocations", diff --git a/crates/hyperqueue/src/dashboard/ui/fragments/auto_allocator/queue_params_display.rs b/crates/hyperqueue/src/dashboard/ui/fragments/auto_allocator/queue_params_display.rs index aa106fc10..bbbc64499 100644 --- a/crates/hyperqueue/src/dashboard/ui/fragments/auto_allocator/queue_params_display.rs +++ b/crates/hyperqueue/src/dashboard/ui/fragments/auto_allocator/queue_params_display.rs @@ -29,7 +29,6 @@ impl QueueParamsTable { frame, TableColumnHeaders { title: "Allocation Queue Params", - inline_help: "", table_headers: None, column_widths: vec![Constraint::Percentage(30), Constraint::Percentage(70)], }, diff --git a/crates/hyperqueue/src/dashboard/ui/fragments/job/fragment.rs b/crates/hyperqueue/src/dashboard/ui/fragments/job/fragment.rs index 96c578ffa..a65ad1d2a 100644 --- a/crates/hyperqueue/src/dashboard/ui/fragments/job/fragment.rs +++ b/crates/hyperqueue/src/dashboard/ui/fragments/job/fragment.rs @@ -15,11 +15,11 @@ use crate::dashboard::ui::widgets::tasks_table::TasksTable; use crate::dashboard::ui::fragments::job::job_info_display::JobInfoTable; use crate::dashboard::ui::fragments::job::job_tasks_chart::JobTaskChart; +use crate::server::job::Job; use crate::JobTaskId; use ratatui::layout::{Constraint, Direction, Layout, Rect}; use tako::WorkerId; -#[derive(Default)] pub struct JobFragment { job_task_chart: JobTaskChart, @@ -30,6 +30,18 @@ pub struct JobFragment { component_in_focus: FocusedComponent, } +impl Default for JobFragment { + fn default() -> Self { + Self { + job_task_chart: Default::default(), + jobs_list: Default::default(), + job_info_table: Default::default(), + job_tasks_table: TasksTable::interactive(), + component_in_focus: Default::default(), + } + } +} + #[derive(Default)] enum FocusedComponent { #[default] @@ -51,8 +63,13 @@ impl JobFragment { self.job_task_chart.draw(layout.chart_chunk, frame); self.jobs_list .draw(layout.job_list_chunk, frame, tasks_table_style); - self.job_tasks_table - .draw("Tasks <2>", layout.job_tasks_chunk, frame, jobs_table_style); + self.job_tasks_table.draw( + "Tasks <2>", + layout.job_tasks_chunk, + frame, + true, + jobs_table_style, + ); draw_text( "<\u{21F5}> select, <1> Jobs, <2> Started Tasks, worker details for selected task", diff --git a/crates/hyperqueue/src/dashboard/ui/fragments/job/job_info_display.rs b/crates/hyperqueue/src/dashboard/ui/fragments/job/job_info_display.rs index 5583e76f6..0dbecb9c8 100644 --- a/crates/hyperqueue/src/dashboard/ui/fragments/job/job_info_display.rs +++ b/crates/hyperqueue/src/dashboard/ui/fragments/job/job_info_display.rs @@ -31,7 +31,6 @@ impl JobInfoTable { frame, TableColumnHeaders { title: "Job Details", - inline_help: "", table_headers: None, column_widths: vec![Constraint::Percentage(30), Constraint::Percentage(70)], }, diff --git a/crates/hyperqueue/src/dashboard/ui/fragments/job/jobs_table.rs b/crates/hyperqueue/src/dashboard/ui/fragments/job/jobs_table.rs index 51a736802..67a0e318b 100644 --- a/crates/hyperqueue/src/dashboard/ui/fragments/job/jobs_table.rs +++ b/crates/hyperqueue/src/dashboard/ui/fragments/job/jobs_table.rs @@ -52,7 +52,6 @@ impl JobsTable { frame, TableColumnHeaders { title: "Jobs <1>", - inline_help: "", table_headers: Some(vec!["Job ID", "Name"]), column_widths: vec![Constraint::Percentage(20), Constraint::Percentage(80)], }, diff --git a/crates/hyperqueue/src/dashboard/ui/screens/cluster_overview/overview/worker_table.rs b/crates/hyperqueue/src/dashboard/ui/screens/cluster_overview/overview/worker_table.rs index db95528c1..438904e51 100644 --- a/crates/hyperqueue/src/dashboard/ui/screens/cluster_overview/overview/worker_table.rs +++ b/crates/hyperqueue/src/dashboard/ui/screens/cluster_overview/overview/worker_table.rs @@ -110,7 +110,6 @@ impl WorkerTable { frame, TableColumnHeaders { title: "Worker list", - inline_help: "", table_headers: Some(vec![ "ID", "Hostname", diff --git a/crates/hyperqueue/src/dashboard/ui/screens/cluster_overview/worker/cpu_util_table.rs b/crates/hyperqueue/src/dashboard/ui/screens/cluster_overview/worker/cpu_util_table.rs index 70388c7af..89091b1ef 100644 --- a/crates/hyperqueue/src/dashboard/ui/screens/cluster_overview/worker/cpu_util_table.rs +++ b/crates/hyperqueue/src/dashboard/ui/screens/cluster_overview/worker/cpu_util_table.rs @@ -58,18 +58,12 @@ pub fn render_cpu_util_table( .collect(); let avg_cpu = calculate_average(cpu_util_list); - let avg_progressbar = render_progress_bar_at( - None, - avg_cpu / 100.00, - CPU_METER_PROGRESSBAR_WIDTH, - ProgressPrintStyle::default(), - ); let mem_used = mem_util.total - mem_util.free; let title = styles::table_title(format!( - "Worker Utilization ({} CPUs), Avg CPU = {}, Mem = {:.0}% ({}/{})", + "Worker Utilization ({} CPUs), Avg CPU = {:.0}%, Mem = {:.0}% ({}/{})", cpu_util_list.len(), - avg_progressbar, + avg_cpu, (mem_used as f64 / mem_util.total as f64) * 100.0, human_size(mem_used), human_size(mem_util.total) diff --git a/crates/hyperqueue/src/dashboard/ui/screens/cluster_overview/worker/mod.rs b/crates/hyperqueue/src/dashboard/ui/screens/cluster_overview/worker/mod.rs index 1d2ba1fec..4a4c4ed85 100644 --- a/crates/hyperqueue/src/dashboard/ui/screens/cluster_overview/worker/mod.rs +++ b/crates/hyperqueue/src/dashboard/ui/screens/cluster_overview/worker/mod.rs @@ -19,7 +19,6 @@ mod cpu_util_table; mod worker_config_table; mod worker_utilization_chart; -#[derive(Default)] pub struct WorkerDetail { /// The worker detail is shown for this worker worker_id: Option, @@ -30,6 +29,18 @@ pub struct WorkerDetail { utilization: Option, } +impl Default for WorkerDetail { + fn default() -> Self { + Self { + worker_id: None, + utilization_history: Default::default(), + worker_config_table: Default::default(), + worker_tasks_table: TasksTable::non_interactive(), + utilization: None, + } + } +} + struct Utilization { cpu: Vec, memory: MemoryStats, @@ -75,6 +86,7 @@ impl WorkerDetail { "Tasks On Worker", layout.tasks, frame, + false, table_style_selected(), ); self.worker_config_table.draw(layout.configuration, frame); @@ -114,11 +126,8 @@ impl WorkerDetail { /// Handles key presses for the components of the screen pub fn handle_key(&mut self, key: KeyEvent) { match key.code { - KeyCode::Down => self.worker_tasks_table.select_next_task(), - KeyCode::Up => self.worker_tasks_table.select_previous_task(), KeyCode::Backspace => self.worker_tasks_table.clear_selection(), - - _ => {} + _ => self.worker_tasks_table.handle_key(key), } } } diff --git a/crates/hyperqueue/src/dashboard/ui/screens/cluster_overview/worker/worker_config_table.rs b/crates/hyperqueue/src/dashboard/ui/screens/cluster_overview/worker/worker_config_table.rs index 483c83023..6a5c3cce9 100644 --- a/crates/hyperqueue/src/dashboard/ui/screens/cluster_overview/worker/worker_config_table.rs +++ b/crates/hyperqueue/src/dashboard/ui/screens/cluster_overview/worker/worker_config_table.rs @@ -28,7 +28,6 @@ impl WorkerConfigTable { frame, TableColumnHeaders { title: "Worker Configuration", - inline_help: "", table_headers: None, column_widths: vec![Constraint::Percentage(30), Constraint::Percentage(70)], }, diff --git a/crates/hyperqueue/src/dashboard/ui/widgets/table.rs b/crates/hyperqueue/src/dashboard/ui/widgets/table.rs index de0fb7adb..f29f7b746 100644 --- a/crates/hyperqueue/src/dashboard/ui/widgets/table.rs +++ b/crates/hyperqueue/src/dashboard/ui/widgets/table.rs @@ -10,7 +10,6 @@ static HIGHLIGHT: &str = "=> "; pub struct TableColumnHeaders { pub title: &'static str, - pub inline_help: &'static str, pub table_headers: Option>, pub column_widths: Vec, } diff --git a/crates/hyperqueue/src/dashboard/ui/widgets/tasks_table.rs b/crates/hyperqueue/src/dashboard/ui/widgets/tasks_table.rs index da25992f2..e33a97e4a 100644 --- a/crates/hyperqueue/src/dashboard/ui/widgets/tasks_table.rs +++ b/crates/hyperqueue/src/dashboard/ui/widgets/tasks_table.rs @@ -17,12 +17,25 @@ const RUNNING: &str = "RUNNING"; const FINISHED: &str = "FINISHED"; const FAILED: &str = "FAILED"; -#[derive(Default)] pub struct TasksTable { table: StatefulTable, + interactive: bool, } impl TasksTable { + pub fn interactive() -> Self { + Self { + table: Default::default(), + interactive: true, + } + } + pub fn non_interactive() -> Self { + Self { + table: Default::default(), + interactive: false, + } + } + pub fn update(&mut self, tasks_info: Vec<(JobTaskId, &TaskInfo)>) { let rows = create_rows(tasks_info); self.table.set_items(rows); @@ -46,10 +59,12 @@ impl TasksTable { } pub fn handle_key(&mut self, key: KeyEvent) { - match key.code { - KeyCode::Down => self.select_next_task(), - KeyCode::Up => self.select_previous_task(), - _ => {} + if self.interactive { + match key.code { + KeyCode::Down => self.select_next_task(), + KeyCode::Up => self.select_previous_task(), + _ => {} + } } } @@ -58,41 +73,38 @@ impl TasksTable { title: &'static str, rect: Rect, frame: &mut DashboardFrame, + with_worker: bool, table_style: Style, ) { + let mut headers = vec!["Task ID"]; + if with_worker { + headers.push("Worker ID"); + } + headers.extend(["State", "Start", "End", "Makespan"]); + let mut column_widths = vec![Constraint::Max(8), Constraint::Max(10)]; + column_widths.extend(std::iter::repeat(Constraint::Fill(1)).take(headers.len() - 2)); + self.table.draw( rect, frame, TableColumnHeaders { title, - inline_help: "", - table_headers: Some(vec![ - "Task ID", - "Worker ID", - "State", - "Start", - "End", - "Makespan", - ]), - column_widths: vec![ - Constraint::Percentage(16), - Constraint::Percentage(16), - Constraint::Percentage(16), - Constraint::Percentage(16), - Constraint::Percentage(16), - Constraint::Percentage(16), - ], + table_headers: Some(headers), + column_widths, }, |task_row| { - Row::new(vec![ - Cell::from(task_row.task_id.to_string()), - Cell::from(task_row.worker_id.to_string()), + let mut cols = vec![Cell::from(task_row.task_id.to_string())]; + if with_worker { + cols.push(Cell::from(task_row.worker_id.to_string())); + } + cols.extend([ Cell::from(task_row.task_state.as_str()) .style(get_task_state_color(&task_row.task_state)), Cell::from(task_row.start_time.as_str()), Cell::from(task_row.end_time.as_str()), Cell::from(task_row.run_time.as_str()), - ]) + ]); + Row::new(cols) }, table_style, ); @@ -129,7 +141,7 @@ fn create_rows(mut rows: Vec<(JobTaskId, &TaskInfo)>) -> Vec { .end_time .map(|time| { let end_time: DateTime = time.into(); - end_time.format("%b %e, %T").to_string() + end_time.format("%d.%m. %H:%M:%S").to_string() }) .unwrap_or_else(|| "".to_string()); @@ -148,7 +160,7 @@ fn create_rows(mut rows: Vec<(JobTaskId, &TaskInfo)>) -> Vec { DashboardTaskState::Failed => FAILED.to_string(), }, run_time: human_duration(chrono::Duration::from_std(run_time).unwrap()), - start_time: start_time.format("%b %e, %T").to_string(), + start_time: start_time.format("%d.%m. %H:%M:%S").to_string(), end_time, } }) From b9a05c847ff59fad336044f6f9d93ad36094864d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Fri, 18 Oct 2024 19:26:07 +0200 Subject: [PATCH 22/31] Add zoom in and zoom out functionality --- .../src/dashboard/data/time_based_vec.rs | 4 +- .../src/dashboard/data/time_interval.rs | 16 +++++- .../dashboard/ui/fragments/job/fragment.rs | 1 - .../overview/worker_count_chart.rs | 4 +- .../src/dashboard/ui/screens/root_screen.rs | 50 +++++++++++++++---- .../src/dashboard/ui/widgets/chart.rs | 9 ++-- crates/hyperqueue/src/dashboard/ui_loop.rs | 5 +- 7 files changed, 64 insertions(+), 25 deletions(-) diff --git a/crates/hyperqueue/src/dashboard/data/time_based_vec.rs b/crates/hyperqueue/src/dashboard/data/time_based_vec.rs index cafd0aaab..0191345e6 100644 --- a/crates/hyperqueue/src/dashboard/data/time_based_vec.rs +++ b/crates/hyperqueue/src/dashboard/data/time_based_vec.rs @@ -41,8 +41,8 @@ impl TimeBasedVec { /// Returns the items that have happened between `start` and `end` (both inclusive). pub fn get_time_range(&self, range: TimeRange) -> &[ItemWithTime] { - let start_index = self.items.partition_point(|item| item.time < range.start); - let end_index = self.items.partition_point(|item| item.time <= range.end); + let start_index = self.items.partition_point(|item| item.time < range.start()); + let end_index = self.items.partition_point(|item| item.time <= range.end()); &self.items[start_index..end_index] } diff --git a/crates/hyperqueue/src/dashboard/data/time_interval.rs b/crates/hyperqueue/src/dashboard/data/time_interval.rs index 43374db42..4cb04b42d 100644 --- a/crates/hyperqueue/src/dashboard/data/time_interval.rs +++ b/crates/hyperqueue/src/dashboard/data/time_interval.rs @@ -6,8 +6,8 @@ use std::time::{Duration, SystemTime, UNIX_EPOCH}; #[derive(Copy, Clone, Debug)] pub struct TimeRange { - pub start: Time, - pub end: Time, + start: Time, + end: Time, } impl TimeRange { @@ -16,6 +16,14 @@ impl TimeRange { Self { start, end } } + pub fn start(&self) -> Time { + self.start + } + + pub fn end(&self) -> Time { + self.end + } + pub fn sooner(&self, duration: Duration) -> Self { TimeRange::new(self.start.sub(duration), self.end.sub(duration)) } @@ -23,6 +31,10 @@ impl TimeRange { pub fn later(&self, duration: Duration) -> Self { TimeRange::new(self.start.add(duration), self.end.add(duration)) } + + pub fn duration(&self) -> Duration { + self.end.duration_since(self.start).unwrap() + } } impl Display for TimeRange { diff --git a/crates/hyperqueue/src/dashboard/ui/fragments/job/fragment.rs b/crates/hyperqueue/src/dashboard/ui/fragments/job/fragment.rs index a65ad1d2a..a3ed78bfc 100644 --- a/crates/hyperqueue/src/dashboard/ui/fragments/job/fragment.rs +++ b/crates/hyperqueue/src/dashboard/ui/fragments/job/fragment.rs @@ -15,7 +15,6 @@ use crate::dashboard::ui::widgets::tasks_table::TasksTable; use crate::dashboard::ui::fragments::job::job_info_display::JobInfoTable; use crate::dashboard::ui::fragments::job::job_tasks_chart::JobTaskChart; -use crate::server::job::Job; use crate::JobTaskId; use ratatui::layout::{Constraint, Direction, Layout, Rect}; use tako::WorkerId; diff --git a/crates/hyperqueue/src/dashboard/ui/screens/cluster_overview/overview/worker_count_chart.rs b/crates/hyperqueue/src/dashboard/ui/screens/cluster_overview/overview/worker_count_chart.rs index 96cda4159..733f27863 100644 --- a/crates/hyperqueue/src/dashboard/ui/screens/cluster_overview/overview/worker_count_chart.rs +++ b/crates/hyperqueue/src/dashboard/ui/screens/cluster_overview/overview/worker_count_chart.rs @@ -28,9 +28,9 @@ const TIME_STEPS: u32 = 25; impl WorkerCountChart { pub fn update(&mut self, data: &DashboardData) { let range = data.current_time_range(); - let interval = range.end.duration_since(range.start).unwrap() / TIME_STEPS; + let interval = range.end().duration_since(range.start()).unwrap() / TIME_STEPS; self.worker_records = (0..TIME_STEPS) - .map(|step| range.start + interval * step) + .map(|step| range.start() + interval * step) .map(|time| WorkerCountRecord { time, count: data.workers().query_connected_worker_ids_at(time).count(), diff --git a/crates/hyperqueue/src/dashboard/ui/screens/root_screen.rs b/crates/hyperqueue/src/dashboard/ui/screens/root_screen.rs index dbd03e34f..ef1afeccc 100644 --- a/crates/hyperqueue/src/dashboard/ui/screens/root_screen.rs +++ b/crates/hyperqueue/src/dashboard/ui/screens/root_screen.rs @@ -1,4 +1,4 @@ -use crate::dashboard::data::DashboardData; +use crate::dashboard::data::{DashboardData, TimeRange}; use crate::dashboard::ui::screen::Screen; use crate::dashboard::ui::screens::autoalloc_screen::AutoAllocScreen; use crate::dashboard::ui::screens::cluster_overview::WorkerOverviewScreen; @@ -17,6 +17,8 @@ use std::ops::ControlFlow; const KEY_TIMELINE_SOONER: char = 'o'; const KEY_TIMELINE_LATER: char = 'p'; +const KEY_TIMELINE_ZOOM_IN: char = 'k'; +const KEY_TIMELINE_ZOOM_OUT: char = 'l'; const KEY_TIMELINE_LIVE: char = 'r'; #[derive(Default)] @@ -78,16 +80,21 @@ impl RootScreen { KeyCode::Char('w') => { self.current_screen = SelectedScreen::WorkerOverview; } - KeyCode::Char(c) if c == KEY_TIMELINE_SOONER => { + KeyCode::Char(KEY_TIMELINE_SOONER) => { data.set_time_range(data.current_time_range().sooner(TIMELINE_MOVE_OFFSET)) } - KeyCode::Char(c) if c == KEY_TIMELINE_LATER => { + KeyCode::Char(KEY_TIMELINE_LATER) => { data.set_time_range(data.current_time_range().later(TIMELINE_MOVE_OFFSET)) } - KeyCode::Char(c) if c == KEY_TIMELINE_LIVE && data.stream_enabled() => { + KeyCode::Char(KEY_TIMELINE_ZOOM_IN) => { + data.set_time_range(zoom_in(data.current_time_range())) + } + KeyCode::Char(KEY_TIMELINE_ZOOM_OUT) => { + data.set_time_range(zoom_out(data.current_time_range())) + } + KeyCode::Char(KEY_TIMELINE_LIVE) if data.stream_enabled() => { data.set_live_time_mode(DEFAULT_LIVE_DURATION) } - _ => { self.get_current_screen_mut().handle_key(input); } @@ -104,6 +111,26 @@ impl RootScreen { } } +fn zoom_in(range: TimeRange) -> TimeRange { + let duration = range.duration(); + if duration.as_secs() <= 10 { + return range; + } + let start = range.start() + duration / 4; + let end = range.end() - duration / 4; + TimeRange::new(start, end) +} + +fn zoom_out(range: TimeRange) -> TimeRange { + let duration = range.duration(); + if duration.as_secs() >= 3600 * 24 * 7 { + return range; + } + let start = range.start() - duration / 2; + let end = range.end() + duration / 2; + TimeRange::new(start, end) +} + fn get_root_screen_chunks(frame: &DashboardFrame) -> RootChunks { let root_screen_chunks = Layout::default() .constraints(vec![Constraint::Length(4), Constraint::Fill(1)]) @@ -114,7 +141,7 @@ fn get_root_screen_chunks(frame: &DashboardFrame) -> RootChunks { let screen = root_screen_chunks[1]; let top_chunks = Layout::default() - .constraints(vec![Constraint::Min(28), Constraint::Min(20)]) + .constraints(vec![Constraint::Max(40), Constraint::Min(20)]) .flex(Flex::SpaceBetween) .direction(Direction::Horizontal) .split(top); @@ -167,8 +194,8 @@ fn render_timeline(data: &DashboardData, rect: Rect, frame: &mut Frame) { .split(rect); let range = data.current_time_range(); - let start: chrono::DateTime = range.start.into(); - let end: chrono::DateTime = range.end.into(); + let start: chrono::DateTime = range.start().into(); + let end: chrono::DateTime = range.end().into(); let skip_day = chunks[0].width < 35 || (start.date_naive() == end.date_naive() @@ -180,9 +207,10 @@ fn render_timeline(data: &DashboardData, rect: Rect, frame: &mut Frame) { }; let range = format!( - "{} - {}", + "{} - {} ({})", start.format(date_format), - end.format(date_format) + end.format(date_format), + humantime::format_duration(range.duration()) ); let range_paragraph = Paragraph::new(Text::from(range)) .alignment(Alignment::Right) @@ -194,7 +222,7 @@ fn render_timeline(data: &DashboardData, rect: Rect, frame: &mut Frame) { true => "[stream]", false => "[replay]", }; - let mut text = format!("{mode}\n<{KEY_TIMELINE_SOONER}> -5m, <{KEY_TIMELINE_LATER}> +5m"); + let mut text = format!("{mode}\n<{KEY_TIMELINE_SOONER}> -5m, <{KEY_TIMELINE_LATER}> +5m, <{KEY_TIMELINE_ZOOM_IN}> zoom in, <{KEY_TIMELINE_ZOOM_OUT}> zoom out"); if !data.is_live_time_mode() && data.stream_enabled() { write!(text, "\n<{KEY_TIMELINE_LIVE}> live view").unwrap(); } diff --git a/crates/hyperqueue/src/dashboard/ui/widgets/chart.rs b/crates/hyperqueue/src/dashboard/ui/widgets/chart.rs index 4a2d789dc..c8916594e 100644 --- a/crates/hyperqueue/src/dashboard/ui/widgets/chart.rs +++ b/crates/hyperqueue/src/dashboard/ui/widgets/chart.rs @@ -201,10 +201,13 @@ fn format_time_hms(time: SystemTime) -> String { pub fn x_axis_time_chart(range: TimeRange) -> Axis<'static> { Axis::default() .style(Style::default().fg(Color::Gray)) - .bounds([get_time_as_secs(range.start), get_time_as_secs(range.end)]) + .bounds([ + get_time_as_secs(range.start()), + get_time_as_secs(range.end()), + ]) .labels(vec![ - format_time_hms(range.start), - format_time_hms(range.end), + format_time_hms(range.start()), + format_time_hms(range.end()), ]) } diff --git a/crates/hyperqueue/src/dashboard/ui_loop.rs b/crates/hyperqueue/src/dashboard/ui_loop.rs index b162e7471..22cb3ffc8 100644 --- a/crates/hyperqueue/src/dashboard/ui_loop.rs +++ b/crates/hyperqueue/src/dashboard/ui_loop.rs @@ -25,10 +25,7 @@ pub async fn start_ui_loop( TimeMode::Live(DEFAULT_LIVE_DURATION) } else { let end = events.last().unwrap().time.into(); - TimeMode::Fixed(TimeRange { - start: end - Duration::from_secs(60 * 5), - end, - }) + TimeMode::Fixed(TimeRange::new(end - Duration::from_secs(60 * 5), end)) }; let mut dashboard_data = DashboardData::new(time_mode, stream); From 6d2d935d9eb2e2092db9a3ae86fbd4b9fc789de2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Fri, 18 Oct 2024 19:44:22 +0200 Subject: [PATCH 23/31] Improve timeline offsetting --- crates/hyperqueue/src/dashboard/mod.rs | 3 --- .../worker/worker_utilization_chart.rs | 8 ++++---- .../src/dashboard/ui/screens/root_screen.rs | 15 +++++++++++---- 3 files changed, 15 insertions(+), 11 deletions(-) diff --git a/crates/hyperqueue/src/dashboard/mod.rs b/crates/hyperqueue/src/dashboard/mod.rs index 278fc1fc2..291c15b2f 100644 --- a/crates/hyperqueue/src/dashboard/mod.rs +++ b/crates/hyperqueue/src/dashboard/mod.rs @@ -9,6 +9,3 @@ use std::time::Duration; // The time range in which the live timeline is display ([now() - duration, now()]) const DEFAULT_LIVE_DURATION: Duration = Duration::from_secs(60 * 10); - -// The amount of time that is subtracted/added when moving the timeline -const TIMELINE_MOVE_OFFSET: Duration = Duration::from_secs(60 * 5); diff --git a/crates/hyperqueue/src/dashboard/ui/screens/cluster_overview/worker/worker_utilization_chart.rs b/crates/hyperqueue/src/dashboard/ui/screens/cluster_overview/worker/worker_utilization_chart.rs index addb417b4..f48579481 100644 --- a/crates/hyperqueue/src/dashboard/ui/screens/cluster_overview/worker/worker_utilization_chart.rs +++ b/crates/hyperqueue/src/dashboard/ui/screens/cluster_overview/worker/worker_utilization_chart.rs @@ -1,8 +1,9 @@ use ratatui::layout::{Constraint, Rect}; use ratatui::style::{Color, Modifier, Style}; -use ratatui::symbols; use ratatui::text::Span; -use ratatui::widgets::{Block, Borders, Chart, Dataset, GraphType, LegendPosition}; +use ratatui::widgets::{ + Block, Borders, Chart, Dataset, GraphType, LegendPosition, +}; use tako::hwstats::WorkerHwStateMessage; use tako::worker::WorkerOverview; @@ -40,9 +41,8 @@ impl WorkerUtilizationChart { fn create_dataset<'a>(items: &'a [(f64, f64)], name: &'a str, color: Color) -> Dataset<'a> { Dataset::default() .name(name) - .marker(symbols::Marker::Dot) + .graph_type(GraphType::Line) .style(Style::default().fg(color)) - .graph_type(GraphType::Scatter) .data(items) } diff --git a/crates/hyperqueue/src/dashboard/ui/screens/root_screen.rs b/crates/hyperqueue/src/dashboard/ui/screens/root_screen.rs index ef1afeccc..4b0b5a7fd 100644 --- a/crates/hyperqueue/src/dashboard/ui/screens/root_screen.rs +++ b/crates/hyperqueue/src/dashboard/ui/screens/root_screen.rs @@ -4,7 +4,7 @@ use crate::dashboard::ui::screens::autoalloc_screen::AutoAllocScreen; use crate::dashboard::ui::screens::cluster_overview::WorkerOverviewScreen; use crate::dashboard::ui::screens::job_screen::JobScreen; use crate::dashboard::ui::terminal::{DashboardFrame, DashboardTerminal}; -use crate::dashboard::{DEFAULT_LIVE_DURATION, TIMELINE_MOVE_OFFSET}; +use crate::dashboard::DEFAULT_LIVE_DURATION; use chrono::Local; use crossterm::event::{KeyCode, KeyEvent}; use ratatui::layout::{Alignment, Constraint, Direction, Flex, Layout, Rect}; @@ -14,6 +14,7 @@ use ratatui::widgets::{Block, Borders, Paragraph, Tabs, Wrap}; use ratatui::Frame; use std::fmt::Write; use std::ops::ControlFlow; +use std::time::Duration; const KEY_TIMELINE_SOONER: char = 'o'; const KEY_TIMELINE_LATER: char = 'p'; @@ -81,10 +82,10 @@ impl RootScreen { self.current_screen = SelectedScreen::WorkerOverview; } KeyCode::Char(KEY_TIMELINE_SOONER) => { - data.set_time_range(data.current_time_range().sooner(TIMELINE_MOVE_OFFSET)) + data.set_time_range(data.current_time_range().sooner(offset_duration(data))) } KeyCode::Char(KEY_TIMELINE_LATER) => { - data.set_time_range(data.current_time_range().later(TIMELINE_MOVE_OFFSET)) + data.set_time_range(data.current_time_range().later(offset_duration(data))) } KeyCode::Char(KEY_TIMELINE_ZOOM_IN) => { data.set_time_range(zoom_in(data.current_time_range())) @@ -222,7 +223,9 @@ fn render_timeline(data: &DashboardData, rect: Rect, frame: &mut Frame) { true => "[stream]", false => "[replay]", }; - let mut text = format!("{mode}\n<{KEY_TIMELINE_SOONER}> -5m, <{KEY_TIMELINE_LATER}> +5m, <{KEY_TIMELINE_ZOOM_IN}> zoom in, <{KEY_TIMELINE_ZOOM_OUT}> zoom out"); + let mut text = format!("{mode}\n<{KEY_TIMELINE_SOONER}> -{offset}m, <{KEY_TIMELINE_LATER}> +{offset}m, <{KEY_TIMELINE_ZOOM_IN}> zoom in, <{KEY_TIMELINE_ZOOM_OUT}> zoom out", + offset= offset_duration(data).as_secs() / 60 + ); if !data.is_live_time_mode() && data.stream_enabled() { write!(text, "\n<{KEY_TIMELINE_LIVE}> live view").unwrap(); } @@ -232,3 +235,7 @@ fn render_timeline(data: &DashboardData, rect: Rect, frame: &mut Frame) { .wrap(Wrap { trim: true }); frame.render_widget(shortcuts_paragraph, chunks[1]); } + +fn offset_duration(data: &DashboardData) -> Duration { + data.current_time_range().duration() / 4 +} From 9f2819cf405774ec86723fbf371e61f904e87086 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Fri, 18 Oct 2024 23:37:29 +0200 Subject: [PATCH 24/31] Improve job overview --- crates/hyperqueue/src/dashboard/data/data.rs | 2 +- .../dashboard/data/timelines/job_timeline.rs | 28 ++-- .../ui/fragments/job/job_info_display.rs | 91 ------------ .../src/dashboard/ui/fragments/job/mod.rs | 4 - .../src/dashboard/ui/fragments/mod.rs | 1 - .../{cluster_overview => cluster}/mod.rs | 0 .../overview/mod.rs | 4 +- .../overview/worker_count_chart.rs | 0 .../overview/worker_table.rs | 0 .../worker/cpu_util_table.rs | 0 .../worker/mod.rs | 6 +- .../worker/worker_config_table.rs | 0 .../worker/worker_utilization_chart.rs | 4 +- .../src/dashboard/ui/screens/job_screen.rs | 66 --------- .../ui/screens/jobs/job_info_display.rs | 136 ++++++++++++++++++ .../job => screens/jobs}/job_tasks_chart.rs | 0 .../job => screens/jobs}/jobs_table.rs | 21 +-- .../src/dashboard/ui/screens/jobs/mod.rs | 64 +++++++++ .../fragment.rs => screens/jobs/overview.rs} | 69 ++++----- .../src/dashboard/ui/screens/mod.rs | 4 +- .../src/dashboard/ui/screens/root_screen.rs | 6 +- 21 files changed, 274 insertions(+), 232 deletions(-) delete mode 100644 crates/hyperqueue/src/dashboard/ui/fragments/job/job_info_display.rs delete mode 100644 crates/hyperqueue/src/dashboard/ui/fragments/job/mod.rs rename crates/hyperqueue/src/dashboard/ui/screens/{cluster_overview => cluster}/mod.rs (100%) rename crates/hyperqueue/src/dashboard/ui/screens/{cluster_overview => cluster}/overview/mod.rs (92%) rename crates/hyperqueue/src/dashboard/ui/screens/{cluster_overview => cluster}/overview/worker_count_chart.rs (100%) rename crates/hyperqueue/src/dashboard/ui/screens/{cluster_overview => cluster}/overview/worker_table.rs (100%) rename crates/hyperqueue/src/dashboard/ui/screens/{cluster_overview => cluster}/worker/cpu_util_table.rs (100%) rename crates/hyperqueue/src/dashboard/ui/screens/{cluster_overview => cluster}/worker/mod.rs (94%) rename crates/hyperqueue/src/dashboard/ui/screens/{cluster_overview => cluster}/worker/worker_config_table.rs (100%) rename crates/hyperqueue/src/dashboard/ui/screens/{cluster_overview => cluster}/worker/worker_utilization_chart.rs (97%) delete mode 100644 crates/hyperqueue/src/dashboard/ui/screens/job_screen.rs create mode 100644 crates/hyperqueue/src/dashboard/ui/screens/jobs/job_info_display.rs rename crates/hyperqueue/src/dashboard/ui/{fragments/job => screens/jobs}/job_tasks_chart.rs (100%) rename crates/hyperqueue/src/dashboard/ui/{fragments/job => screens/jobs}/jobs_table.rs (77%) create mode 100644 crates/hyperqueue/src/dashboard/ui/screens/jobs/mod.rs rename crates/hyperqueue/src/dashboard/ui/{fragments/job/fragment.rs => screens/jobs/overview.rs} (72%) diff --git a/crates/hyperqueue/src/dashboard/data/data.rs b/crates/hyperqueue/src/dashboard/data/data.rs index 1838bdde0..849b0e503 100644 --- a/crates/hyperqueue/src/dashboard/data/data.rs +++ b/crates/hyperqueue/src/dashboard/data/data.rs @@ -53,7 +53,7 @@ impl DashboardData { pub fn query_jobs_created_before( &self, time: SystemTime, - ) -> impl Iterator + '_ { + ) -> impl Iterator + '_ { self.job_timeline.get_jobs_created_before(time) } diff --git a/crates/hyperqueue/src/dashboard/data/timelines/job_timeline.rs b/crates/hyperqueue/src/dashboard/data/timelines/job_timeline.rs index 58447fe16..751aec045 100644 --- a/crates/hyperqueue/src/dashboard/data/timelines/job_timeline.rs +++ b/crates/hyperqueue/src/dashboard/data/timelines/job_timeline.rs @@ -1,15 +1,18 @@ use crate::server::event::payload::EventPayload; use crate::server::event::Event; +use crate::transfer::messages::{JobDescription, JobSubmitDescription}; use crate::{JobId, JobTaskId, WorkerId}; use chrono::{DateTime, Utc}; use std::time::SystemTime; use tako::Map; pub struct DashboardJobInfo { - job_tasks_info: Map, + pub job: JobDescription, + pub submit_data: JobSubmitDescription, pub job_creation_time: SystemTime, - pub completion_date: Option>, + + job_tasks_info: Map, } pub struct TaskInfo { @@ -55,11 +58,16 @@ impl JobTimeline { EventPayload::Submit { job_id, closed_job: _, - serialized_desc: _, + serialized_desc, } => { + let submit = serialized_desc + .deserialize() + .expect("Invalid serialized submit"); self.job_timeline.insert( *job_id, DashboardJobInfo { + job: submit.job_desc, + submit_data: submit.submit_desc, job_tasks_info: Default::default(), job_creation_time: event.time.into(), completion_date: None, @@ -123,9 +131,12 @@ impl JobTimeline { job_id: JobId, time: SystemTime, ) -> impl Iterator + '_ { - // Implementation removed as it was wrong - todo!(); - std::iter::empty() + self.job_timeline + .get(&job_id) + .into_iter() + .flat_map(|job| job.job_tasks_info.iter()) + .filter(move |(_, info)| info.start_time < time) + .map(|(id, info)| (*id, info)) } pub fn get_worker_task_history( @@ -135,7 +146,7 @@ impl JobTimeline { ) -> impl Iterator + '_ { self.get_jobs_created_before(at_time) .flat_map(|(_, info)| info.job_tasks_info.iter()) - .filter(move |(id, task_info)| { + .filter(move |(_, task_info)| { task_info.worker_id == worker_id && task_info.start_time <= at_time }) .map(|(id, info)| (*id, info)) @@ -148,10 +159,11 @@ impl JobTimeline { pub fn get_jobs_created_before( &self, time: SystemTime, - ) -> impl Iterator + '_ { + ) -> impl Iterator + '_ { self.job_timeline .iter() .filter(move |(_, info)| info.job_creation_time <= time) + .map(|(id, info)| (*id, info)) } } diff --git a/crates/hyperqueue/src/dashboard/ui/fragments/job/job_info_display.rs b/crates/hyperqueue/src/dashboard/ui/fragments/job/job_info_display.rs deleted file mode 100644 index 0dbecb9c8..000000000 --- a/crates/hyperqueue/src/dashboard/ui/fragments/job/job_info_display.rs +++ /dev/null @@ -1,91 +0,0 @@ -use crate::dashboard::data::timelines::job_timeline::DashboardJobInfo; -use crate::dashboard::ui::styles::table_style_deselected; -use crate::dashboard::ui::terminal::DashboardFrame; -use crate::dashboard::ui::widgets::table::{StatefulTable, TableColumnHeaders}; -use crate::transfer::messages::JobTaskDescription; -use chrono::{DateTime, Local}; -use ratatui::layout::{Constraint, Rect}; -use ratatui::widgets::{Cell, Row}; -use std::borrow::Cow; - -#[derive(Default)] -pub struct JobInfoTable { - table: StatefulTable, -} - -#[derive(Default, Debug)] -struct JobInfoDataRow { - pub label: &'static str, - pub data: Cow<'static, str>, -} - -impl JobInfoTable { - pub fn update(&mut self, info: &DashboardJobInfo) { - let rows = create_rows(info); - self.table.set_items(rows); - } - - pub fn draw(&mut self, rect: Rect, frame: &mut DashboardFrame) { - self.table.draw( - rect, - frame, - TableColumnHeaders { - title: "Job Details", - table_headers: None, - column_widths: vec![Constraint::Percentage(30), Constraint::Percentage(70)], - }, - |data| Row::new(vec![Cell::from(data.label), Cell::from(data.data.as_ref())]), - table_style_deselected(), - ); - } -} -fn create_rows(params: &DashboardJobInfo) -> Vec { - let creation_time: DateTime = params.job_creation_time.into(); - let completion_time: Option> = params.completion_date.map(|time| { - let end_time: DateTime = time.into(); - end_time.format("%b %e, %T").to_string().into() - }); - // let log_path = params - // .job_info - // .job_desc - // .log - // .as_ref() - // .map(|log_path| log_path.to_string_lossy().to_string()) - // .unwrap_or_default(); - - vec![ - JobInfoDataRow { - label: "Job Type: ", - data: todo!(), // match params.job_info.job_desc.task_desc { - // JobTaskDescription::Array { .. } => "Array".into(), - // JobTaskDescription::Graph { .. } => "Graph".into(), - // }, - }, - JobInfoDataRow { - label: "Creation Time: ", - data: creation_time.format("%b %e, %T").to_string().into(), - }, - JobInfoDataRow { - label: "Completion Time: ", - data: completion_time.unwrap_or_default(), - }, - JobInfoDataRow { - label: "Num Tasks: ", - data: todo!(), /*params.job_info.task_ids.len().to_string().into(),*/ - }, - JobInfoDataRow { - label: "Log Path: ", - data: todo!(), //log_path.into(), - }, - JobInfoDataRow { - label: "Max Fails: ", - data: todo!(), // params - // .job_info - // .job_desc - // .max_fails - // .map(|fails| fails.to_string()) - // .unwrap_or_default() - // .into(), - }, - ] -} diff --git a/crates/hyperqueue/src/dashboard/ui/fragments/job/mod.rs b/crates/hyperqueue/src/dashboard/ui/fragments/job/mod.rs deleted file mode 100644 index 41cae480a..000000000 --- a/crates/hyperqueue/src/dashboard/ui/fragments/job/mod.rs +++ /dev/null @@ -1,4 +0,0 @@ -pub mod fragment; -mod job_info_display; -mod job_tasks_chart; -mod jobs_table; diff --git a/crates/hyperqueue/src/dashboard/ui/fragments/mod.rs b/crates/hyperqueue/src/dashboard/ui/fragments/mod.rs index d8c8f42d0..90c55b4cf 100644 --- a/crates/hyperqueue/src/dashboard/ui/fragments/mod.rs +++ b/crates/hyperqueue/src/dashboard/ui/fragments/mod.rs @@ -1,2 +1 @@ pub mod auto_allocator; -pub mod job; diff --git a/crates/hyperqueue/src/dashboard/ui/screens/cluster_overview/mod.rs b/crates/hyperqueue/src/dashboard/ui/screens/cluster/mod.rs similarity index 100% rename from crates/hyperqueue/src/dashboard/ui/screens/cluster_overview/mod.rs rename to crates/hyperqueue/src/dashboard/ui/screens/cluster/mod.rs diff --git a/crates/hyperqueue/src/dashboard/ui/screens/cluster_overview/overview/mod.rs b/crates/hyperqueue/src/dashboard/ui/screens/cluster/overview/mod.rs similarity index 92% rename from crates/hyperqueue/src/dashboard/ui/screens/cluster_overview/overview/mod.rs rename to crates/hyperqueue/src/dashboard/ui/screens/cluster/overview/mod.rs index 1b7b71b95..9290c9c38 100644 --- a/crates/hyperqueue/src/dashboard/ui/screens/cluster_overview/overview/mod.rs +++ b/crates/hyperqueue/src/dashboard/ui/screens/cluster/overview/mod.rs @@ -1,6 +1,6 @@ use crate::dashboard::data::DashboardData; -use crate::dashboard::ui::screens::cluster_overview::overview::worker_count_chart::WorkerCountChart; -use crate::dashboard::ui::screens::cluster_overview::overview::worker_table::WorkerTable; +use crate::dashboard::ui::screens::cluster::overview::worker_count_chart::WorkerCountChart; +use crate::dashboard::ui::screens::cluster::overview::worker_table::WorkerTable; use crate::dashboard::ui::styles::style_footer; use crate::dashboard::ui::terminal::DashboardFrame; use crate::dashboard::ui::widgets::text::draw_text; diff --git a/crates/hyperqueue/src/dashboard/ui/screens/cluster_overview/overview/worker_count_chart.rs b/crates/hyperqueue/src/dashboard/ui/screens/cluster/overview/worker_count_chart.rs similarity index 100% rename from crates/hyperqueue/src/dashboard/ui/screens/cluster_overview/overview/worker_count_chart.rs rename to crates/hyperqueue/src/dashboard/ui/screens/cluster/overview/worker_count_chart.rs diff --git a/crates/hyperqueue/src/dashboard/ui/screens/cluster_overview/overview/worker_table.rs b/crates/hyperqueue/src/dashboard/ui/screens/cluster/overview/worker_table.rs similarity index 100% rename from crates/hyperqueue/src/dashboard/ui/screens/cluster_overview/overview/worker_table.rs rename to crates/hyperqueue/src/dashboard/ui/screens/cluster/overview/worker_table.rs diff --git a/crates/hyperqueue/src/dashboard/ui/screens/cluster_overview/worker/cpu_util_table.rs b/crates/hyperqueue/src/dashboard/ui/screens/cluster/worker/cpu_util_table.rs similarity index 100% rename from crates/hyperqueue/src/dashboard/ui/screens/cluster_overview/worker/cpu_util_table.rs rename to crates/hyperqueue/src/dashboard/ui/screens/cluster/worker/cpu_util_table.rs diff --git a/crates/hyperqueue/src/dashboard/ui/screens/cluster_overview/worker/mod.rs b/crates/hyperqueue/src/dashboard/ui/screens/cluster/worker/mod.rs similarity index 94% rename from crates/hyperqueue/src/dashboard/ui/screens/cluster_overview/worker/mod.rs rename to crates/hyperqueue/src/dashboard/ui/screens/cluster/worker/mod.rs index 4a4c4ed85..e5179a83e 100644 --- a/crates/hyperqueue/src/dashboard/ui/screens/cluster_overview/worker/mod.rs +++ b/crates/hyperqueue/src/dashboard/ui/screens/cluster/worker/mod.rs @@ -1,8 +1,8 @@ use crate::dashboard::data::timelines::job_timeline::TaskInfo; use crate::dashboard::data::DashboardData; -use crate::dashboard::ui::screens::cluster_overview::worker::cpu_util_table::render_cpu_util_table; -use crate::dashboard::ui::screens::cluster_overview::worker::worker_config_table::WorkerConfigTable; -use crate::dashboard::ui::screens::cluster_overview::worker::worker_utilization_chart::WorkerUtilizationChart; +use crate::dashboard::ui::screens::cluster::worker::cpu_util_table::render_cpu_util_table; +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, }; diff --git a/crates/hyperqueue/src/dashboard/ui/screens/cluster_overview/worker/worker_config_table.rs b/crates/hyperqueue/src/dashboard/ui/screens/cluster/worker/worker_config_table.rs similarity index 100% rename from crates/hyperqueue/src/dashboard/ui/screens/cluster_overview/worker/worker_config_table.rs rename to crates/hyperqueue/src/dashboard/ui/screens/cluster/worker/worker_config_table.rs diff --git a/crates/hyperqueue/src/dashboard/ui/screens/cluster_overview/worker/worker_utilization_chart.rs b/crates/hyperqueue/src/dashboard/ui/screens/cluster/worker/worker_utilization_chart.rs similarity index 97% rename from crates/hyperqueue/src/dashboard/ui/screens/cluster_overview/worker/worker_utilization_chart.rs rename to crates/hyperqueue/src/dashboard/ui/screens/cluster/worker/worker_utilization_chart.rs index f48579481..8c01f103a 100644 --- a/crates/hyperqueue/src/dashboard/ui/screens/cluster_overview/worker/worker_utilization_chart.rs +++ b/crates/hyperqueue/src/dashboard/ui/screens/cluster/worker/worker_utilization_chart.rs @@ -1,9 +1,7 @@ use ratatui::layout::{Constraint, Rect}; use ratatui::style::{Color, Modifier, Style}; use ratatui::text::Span; -use ratatui::widgets::{ - Block, Borders, Chart, Dataset, GraphType, LegendPosition, -}; +use ratatui::widgets::{Block, Borders, Chart, Dataset, GraphType, LegendPosition}; use tako::hwstats::WorkerHwStateMessage; use tako::worker::WorkerOverview; diff --git a/crates/hyperqueue/src/dashboard/ui/screens/job_screen.rs b/crates/hyperqueue/src/dashboard/ui/screens/job_screen.rs deleted file mode 100644 index 6ee91169d..000000000 --- a/crates/hyperqueue/src/dashboard/ui/screens/job_screen.rs +++ /dev/null @@ -1,66 +0,0 @@ -use crate::dashboard::data::DashboardData; -use crate::dashboard::ui::fragments::job::fragment::JobFragment; -use crate::dashboard::ui::screen::Screen; -use crate::dashboard::ui::screens::cluster_overview::worker::WorkerDetail; -use crate::dashboard::ui::terminal::DashboardFrame; -use crossterm::event::{KeyCode, KeyEvent}; -use ratatui::layout::Rect; - -pub struct JobScreen { - job_overview_fragment: JobFragment, - task_runner_worker: WorkerDetail, - - active_fragment: ScreenState, -} - -enum ScreenState { - JobInfo, - TaskWorkerDetail, -} - -impl Screen for JobScreen { - fn draw(&mut self, in_area: Rect, frame: &mut DashboardFrame) { - match self.active_fragment { - ScreenState::JobInfo => self.job_overview_fragment.draw(in_area, frame), - ScreenState::TaskWorkerDetail => self.task_runner_worker.draw(in_area, frame), - } - } - - fn update(&mut self, data: &DashboardData) { - match self.active_fragment { - ScreenState::JobInfo => self.job_overview_fragment.update(data), - ScreenState::TaskWorkerDetail => self.task_runner_worker.update(data), - } - } - - fn handle_key(&mut self, key: KeyEvent) { - match self.active_fragment { - ScreenState::JobInfo => self.job_overview_fragment.handle_key(key), - ScreenState::TaskWorkerDetail => self.task_runner_worker.handle_key(key), - } - - match key.code { - KeyCode::Char('i') => { - if let Some((_, selected_worker)) = self.job_overview_fragment.get_selected_task() { - self.task_runner_worker.set_worker_id(selected_worker); - self.active_fragment = ScreenState::TaskWorkerDetail; - } - } - KeyCode::Backspace => { - self.task_runner_worker.clear_worker_id(); - self.active_fragment = ScreenState::JobInfo - } - _ => {} - } - } -} - -impl Default for JobScreen { - fn default() -> Self { - JobScreen { - job_overview_fragment: Default::default(), - task_runner_worker: Default::default(), - active_fragment: ScreenState::JobInfo, - } - } -} diff --git a/crates/hyperqueue/src/dashboard/ui/screens/jobs/job_info_display.rs b/crates/hyperqueue/src/dashboard/ui/screens/jobs/job_info_display.rs new file mode 100644 index 000000000..ce3be97c4 --- /dev/null +++ b/crates/hyperqueue/src/dashboard/ui/screens/jobs/job_info_display.rs @@ -0,0 +1,136 @@ +use crate::common::format::human_duration; +use crate::dashboard::data::timelines::job_timeline::DashboardJobInfo; +use crate::dashboard::ui::styles::table_style_deselected; +use crate::dashboard::ui::terminal::DashboardFrame; +use crate::dashboard::ui::widgets::table::{StatefulTable, TableColumnHeaders}; +use crate::transfer::messages::{JobTaskDescription, TaskKind}; +use chrono::{DateTime, Local}; +use itertools::Itertools; +use ratatui::layout::{Constraint, Rect}; +use ratatui::text::Text; +use ratatui::widgets::{Cell, Paragraph, Row}; +use std::borrow::Cow; +use tako::gateway::ResourceRequestVariants; + +#[derive(Default)] +pub struct JobInfoTable { + table: StatefulTable, +} + +#[derive(Default, Debug)] +struct JobInfoDataRow { + label: &'static str, + data: Cell<'static>, +} + +impl JobInfoTable { + pub fn update(&mut self, info: &DashboardJobInfo) { + let rows = create_rows(info); + self.table.set_items(rows); + } + + pub fn draw(&mut self, rect: Rect, frame: &mut DashboardFrame) { + self.table.draw( + rect, + frame, + TableColumnHeaders { + title: "Job Details", + table_headers: None, + column_widths: vec![Constraint::Percentage(30), Constraint::Percentage(70)], + }, + |data| Row::new(vec![Cell::from(data.label), data.data.clone()]), + table_style_deselected(), + ); + } +} +fn create_rows(info: &DashboardJobInfo) -> Vec { + let creation_time: DateTime = info.job_creation_time.into(); + let completion_time: Option> = info.completion_date.map(|time| { + let end_time: DateTime = time.into(); + end_time.format("%d.%m. %H:%M:%S").to_string().into() + }); + + let mut rows = vec![JobInfoDataRow { + label: "Job Type: ", + data: match info.submit_data.task_desc { + JobTaskDescription::Array { .. } => "Array".into(), + JobTaskDescription::Graph { .. } => "Graph".into(), + }, + }]; + if let JobTaskDescription::Array { task_desc, .. } = &info.submit_data.task_desc { + match &task_desc.kind { + TaskKind::ExternalProgram(program) => { + // TODO: wrap text + rows.push(JobInfoDataRow { + label: "Cmdline", + data: Cell::from(Text::from( + program + .program + .args + .iter() + .map(|arg| arg.to_string()) + .join(" "), + )), + }); + // TODO: print more information about the job + rows.push(JobInfoDataRow { + label: "Workdir", + data: program.program.cwd.to_string_lossy().to_string().into(), + }); + rows.push(JobInfoDataRow { + label: "Pin mode", + data: program.pin_mode.to_str().into(), + }) + } + }; + rows.push(JobInfoDataRow { + label: "Resources", + data: format_resources(&task_desc.resources).into(), + }); + if let Some(time_limit) = task_desc.time_limit { + rows.push(JobInfoDataRow { + label: "Foo", + data: human_duration(chrono::Duration::from_std(time_limit).unwrap()).into(), + }); + } + } + + rows.extend([ + JobInfoDataRow { + label: "Creation Time: ", + data: creation_time.format("%d.%m. %H:%M:%S").to_string().into(), + }, + JobInfoDataRow { + label: "Completion Time: ", + data: completion_time.unwrap_or_default().into(), + }, + JobInfoDataRow { + label: "Num Tasks: ", + data: info.submit_data.task_desc.task_count().to_string().into(), + }, + JobInfoDataRow { + label: "Max Fails: ", + data: info + .job + .max_fails + .map(|fails| fails.to_string()) + .unwrap_or_default() + .into(), + }, + ]); + rows +} + +fn format_resources(resources: &ResourceRequestVariants) -> String { + resources + .variants + .iter() + .map(|request| { + request + .resources + .iter() + .map(|entry| format!("{}={}", entry.resource, entry.policy)) + .join(", ") + }) + .join(" OR ") +} diff --git a/crates/hyperqueue/src/dashboard/ui/fragments/job/job_tasks_chart.rs b/crates/hyperqueue/src/dashboard/ui/screens/jobs/job_tasks_chart.rs similarity index 100% rename from crates/hyperqueue/src/dashboard/ui/fragments/job/job_tasks_chart.rs rename to crates/hyperqueue/src/dashboard/ui/screens/jobs/job_tasks_chart.rs diff --git a/crates/hyperqueue/src/dashboard/ui/fragments/job/jobs_table.rs b/crates/hyperqueue/src/dashboard/ui/screens/jobs/jobs_table.rs similarity index 77% rename from crates/hyperqueue/src/dashboard/ui/fragments/job/jobs_table.rs rename to crates/hyperqueue/src/dashboard/ui/screens/jobs/jobs_table.rs index 67a0e318b..44cc5690d 100644 --- a/crates/hyperqueue/src/dashboard/ui/fragments/job/jobs_table.rs +++ b/crates/hyperqueue/src/dashboard/ui/screens/jobs/jobs_table.rs @@ -7,10 +7,10 @@ use crate::dashboard::ui::widgets::table::{StatefulTable, TableColumnHeaders}; use crate::JobId; use crossterm::event::{KeyCode, KeyEvent}; -use ratatui::layout::{Constraint, Rect}; +use ratatui::layout::{Alignment, Constraint, Rect}; use ratatui::style::Style; +use ratatui::text::Text; use ratatui::widgets::{Cell, Row}; -use std::time::SystemTime; #[derive(Default)] pub struct JobsTable { @@ -19,17 +19,18 @@ pub struct JobsTable { impl JobsTable { pub fn update(&mut self, data: &DashboardData) { - let jobs: Vec<(&JobId, &DashboardJobInfo)> = - data.query_jobs_created_before(SystemTime::now()).collect(); + let jobs: Vec<(JobId, &DashboardJobInfo)> = data + .query_jobs_created_before(data.current_time()) + .collect(); let rows = create_rows(jobs); self.table.set_items(rows); } - pub fn select_next_job(&mut self) { + fn select_next_job(&mut self) { self.table.select_next_wrap(); } - pub fn select_previous_job(&mut self) { + fn select_previous_job(&mut self) { self.table.select_previous_wrap(); } @@ -57,7 +58,7 @@ impl JobsTable { }, |data| { Row::new(vec![ - Cell::from(data.id.to_string()), + Cell::from(Text::from(data.id.to_string()).alignment(Alignment::Right)), Cell::from(data.name.as_str()), ]) }, @@ -71,12 +72,12 @@ struct JobInfoRow { name: String, } -fn create_rows(job_infos: Vec<(&JobId, &DashboardJobInfo)>) -> Vec { +fn create_rows(job_infos: Vec<(JobId, &DashboardJobInfo)>) -> Vec { job_infos .iter() .map(|(job_id, info)| JobInfoRow { - id: **job_id, - name: todo!(), //info.job_info.job_desc.name.clone(), + id: *job_id, + name: info.job.name.clone(), }) .collect() } diff --git a/crates/hyperqueue/src/dashboard/ui/screens/jobs/mod.rs b/crates/hyperqueue/src/dashboard/ui/screens/jobs/mod.rs new file mode 100644 index 000000000..a87bfd92c --- /dev/null +++ b/crates/hyperqueue/src/dashboard/ui/screens/jobs/mod.rs @@ -0,0 +1,64 @@ +use crate::dashboard::data::DashboardData; +use crate::dashboard::ui::screen::Screen; +use crate::dashboard::ui::screens::cluster::worker::WorkerDetail; +use crate::dashboard::ui::terminal::DashboardFrame; +use crossterm::event::{KeyCode, KeyEvent}; +use overview::JobOverview; +use ratatui::layout::Rect; + +mod job_info_display; +mod job_tasks_chart; +mod jobs_table; +mod overview; + +#[derive(Default)] +pub struct JobScreen { + job_overview: JobOverview, + task_worker_detail: WorkerDetail, + + active_fragment: ScreenState, +} + +#[derive(Default)] +enum ScreenState { + #[default] + JobInfo, + TaskWorkerDetail, +} + +impl Screen for JobScreen { + fn draw(&mut self, in_area: Rect, frame: &mut DashboardFrame) { + match self.active_fragment { + ScreenState::JobInfo => self.job_overview.draw(in_area, frame), + ScreenState::TaskWorkerDetail => self.task_worker_detail.draw(in_area, frame), + } + } + + fn update(&mut self, data: &DashboardData) { + match self.active_fragment { + ScreenState::JobInfo => self.job_overview.update(data), + ScreenState::TaskWorkerDetail => self.task_worker_detail.update(data), + } + } + + fn handle_key(&mut self, key: KeyEvent) { + match self.active_fragment { + ScreenState::JobInfo => self.job_overview.handle_key(key), + ScreenState::TaskWorkerDetail => self.task_worker_detail.handle_key(key), + } + + match key.code { + KeyCode::Char('i') => { + if let Some((_, selected_worker)) = self.job_overview.get_selected_task() { + self.task_worker_detail.set_worker_id(selected_worker); + self.active_fragment = ScreenState::TaskWorkerDetail; + } + } + KeyCode::Backspace => { + self.task_worker_detail.clear_worker_id(); + self.active_fragment = ScreenState::JobInfo + } + _ => {} + } + } +} diff --git a/crates/hyperqueue/src/dashboard/ui/fragments/job/fragment.rs b/crates/hyperqueue/src/dashboard/ui/screens/jobs/overview.rs similarity index 72% rename from crates/hyperqueue/src/dashboard/ui/fragments/job/fragment.rs rename to crates/hyperqueue/src/dashboard/ui/screens/jobs/overview.rs index a3ed78bfc..a2c50a882 100644 --- a/crates/hyperqueue/src/dashboard/ui/fragments/job/fragment.rs +++ b/crates/hyperqueue/src/dashboard/ui/screens/jobs/overview.rs @@ -1,6 +1,4 @@ -use crate::dashboard::ui::styles::{ - style_footer, style_header_text, table_style_deselected, table_style_selected, -}; +use crate::dashboard::ui::styles::{style_footer, table_style_deselected, table_style_selected}; use crate::dashboard::ui::terminal::DashboardFrame; use crate::dashboard::ui::widgets::text::draw_text; use crossterm::event::{KeyCode, KeyEvent}; @@ -10,30 +8,30 @@ use std::time::SystemTime; use crate::dashboard::data::timelines::job_timeline::TaskInfo; use crate::dashboard::data::DashboardData; -use crate::dashboard::ui::fragments::job::jobs_table::JobsTable; +use crate::dashboard::ui::screens::jobs::jobs_table::JobsTable; use crate::dashboard::ui::widgets::tasks_table::TasksTable; -use crate::dashboard::ui::fragments::job::job_info_display::JobInfoTable; -use crate::dashboard::ui::fragments::job::job_tasks_chart::JobTaskChart; +use crate::dashboard::ui::screens::jobs::job_info_display::JobInfoTable; +use crate::dashboard::ui::screens::jobs::job_tasks_chart::JobTaskChart; use crate::JobTaskId; use ratatui::layout::{Constraint, Direction, Layout, Rect}; use tako::WorkerId; -pub struct JobFragment { +pub struct JobOverview { job_task_chart: JobTaskChart, - jobs_list: JobsTable, + job_list: JobsTable, job_info_table: JobInfoTable, job_tasks_table: TasksTable, component_in_focus: FocusedComponent, } -impl Default for JobFragment { +impl Default for JobOverview { fn default() -> Self { Self { job_task_chart: Default::default(), - jobs_list: Default::default(), + job_list: Default::default(), job_info_table: Default::default(), job_tasks_table: TasksTable::interactive(), component_in_focus: Default::default(), @@ -48,10 +46,9 @@ enum FocusedComponent { JobTasksTable, } -impl JobFragment { +impl JobOverview { pub fn draw(&mut self, in_area: Rect, frame: &mut DashboardFrame) { - let layout = JobFragmentLayout::new(&in_area); - draw_text("Job Info", layout.header_chunk, frame, style_header_text()); + let layout = JobOverviewLayout::new(&in_area); let (jobs_table_style, tasks_table_style) = match self.component_in_focus { FocusedComponent::JobsTable => (table_style_selected(), table_style_deselected()), @@ -60,7 +57,7 @@ impl JobFragment { self.job_info_table.draw(layout.job_info_chunk, frame); self.job_task_chart.draw(layout.chart_chunk, frame); - self.jobs_list + self.job_list .draw(layout.job_list_chunk, frame, tasks_table_style); self.job_tasks_table.draw( "Tasks <2>", @@ -71,7 +68,7 @@ impl JobFragment { ); draw_text( - "<\u{21F5}> select, <1> Jobs, <2> Started Tasks, worker details for selected task", + "<\u{21F5}> select, <1> Jobs, <2> Tasks, worker details for selected task", layout.footer_chunk, frame, style_footer(), @@ -79,9 +76,9 @@ impl JobFragment { } pub fn update(&mut self, data: &DashboardData) { - self.jobs_list.update(data); + self.job_list.update(data); - if let Some(job_id) = self.jobs_list.get_selected_item() { + if let Some(job_id) = self.job_list.get_selected_item() { let task_infos: Vec<(JobTaskId, &TaskInfo)> = data .query_task_history_for_job(job_id, SystemTime::now()) .collect(); @@ -91,7 +88,7 @@ impl JobFragment { self.job_task_chart.clear_chart(); } if let Some(job_info) = self - .jobs_list + .job_list .get_selected_item() .and_then(|job_id| data.query_job_info_for_job(job_id)) { @@ -112,7 +109,7 @@ impl JobFragment { _ => {} } match self.component_in_focus { - FocusedComponent::JobsTable => self.jobs_list.handle_key(key), + FocusedComponent::JobsTable => self.job_list.handle_key(key), FocusedComponent::JobTasksTable => self.job_tasks_table.handle_key(key), }; } @@ -122,16 +119,14 @@ impl JobFragment { } } -/** -* _________________________ - |--------Header---------| - | Chart | - |-----------------------| - | j_info | j_tasks | - |________Footer_________| - **/ -struct JobFragmentLayout { - header_chunk: Rect, +/// _____________________________ +/// | Job details | Task chart | +/// |---------------------------| +/// | Job list | Task list | +/// |---------------------------| +/// | Footer | +/// |---------------------------| +struct JobOverviewLayout { chart_chunk: Rect, job_info_chunk: Rect, job_list_chunk: Rect, @@ -139,12 +134,11 @@ struct JobFragmentLayout { footer_chunk: Rect, } -impl JobFragmentLayout { +impl JobOverviewLayout { fn new(rect: &Rect) -> Self { - let job_screen_chunks = ratatui::layout::Layout::default() + let job_screen_chunks = Layout::default() .constraints(vec![ - Constraint::Percentage(5), - Constraint::Percentage(40), + Constraint::Percentage(45), Constraint::Percentage(50), Constraint::Percentage(5), ]) @@ -155,21 +149,20 @@ impl JobFragmentLayout { .constraints(vec![Constraint::Percentage(35), Constraint::Percentage(65)]) .direction(Direction::Horizontal) .margin(0) - .split(job_screen_chunks[1]); + .split(job_screen_chunks[0]); let table_area = Layout::default() - .constraints(vec![Constraint::Percentage(20), Constraint::Percentage(80)]) + .constraints(vec![Constraint::Percentage(30), Constraint::Percentage(80)]) .direction(Direction::Horizontal) .margin(0) - .split(job_screen_chunks[2]); + .split(job_screen_chunks[1]); Self { - header_chunk: job_screen_chunks[0], job_info_chunk: graph_and_details_area[0], chart_chunk: graph_and_details_area[1], job_list_chunk: table_area[0], job_tasks_chunk: table_area[1], - footer_chunk: job_screen_chunks[3], + footer_chunk: job_screen_chunks[2], } } } diff --git a/crates/hyperqueue/src/dashboard/ui/screens/mod.rs b/crates/hyperqueue/src/dashboard/ui/screens/mod.rs index 363b47dd1..789705d7a 100644 --- a/crates/hyperqueue/src/dashboard/ui/screens/mod.rs +++ b/crates/hyperqueue/src/dashboard/ui/screens/mod.rs @@ -1,4 +1,4 @@ pub mod autoalloc_screen; -pub mod cluster_overview; -pub mod job_screen; +pub mod cluster; +pub mod jobs; pub mod root_screen; diff --git a/crates/hyperqueue/src/dashboard/ui/screens/root_screen.rs b/crates/hyperqueue/src/dashboard/ui/screens/root_screen.rs index 4b0b5a7fd..dd6f101db 100644 --- a/crates/hyperqueue/src/dashboard/ui/screens/root_screen.rs +++ b/crates/hyperqueue/src/dashboard/ui/screens/root_screen.rs @@ -1,8 +1,8 @@ use crate::dashboard::data::{DashboardData, TimeRange}; use crate::dashboard::ui::screen::Screen; use crate::dashboard::ui::screens::autoalloc_screen::AutoAllocScreen; -use crate::dashboard::ui::screens::cluster_overview::WorkerOverviewScreen; -use crate::dashboard::ui::screens::job_screen::JobScreen; +use crate::dashboard::ui::screens::cluster::WorkerOverviewScreen; +use crate::dashboard::ui::screens::jobs::JobScreen; use crate::dashboard::ui::terminal::{DashboardFrame, DashboardTerminal}; use crate::dashboard::DEFAULT_LIVE_DURATION; use chrono::Local; @@ -41,8 +41,8 @@ struct RootChunks { #[derive(Clone, Copy, Default)] enum SelectedScreen { - JobOverview, #[default] + JobOverview, WorkerOverview, AutoAllocator, } From 58c7fbb9b742eb94a9a3fafde7b4f786da421fa7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Sat, 19 Oct 2024 15:08:28 +0200 Subject: [PATCH 25/31] Improve chart abstractions --- .../cluster/overview/worker_count_chart.rs | 68 ++--- .../worker/worker_utilization_chart.rs | 103 +++---- .../ui/screens/jobs/job_info_display.rs | 2 +- .../ui/screens/jobs/job_tasks_chart.rs | 168 +++++------ .../src/dashboard/ui/screens/jobs/overview.rs | 2 +- .../src/dashboard/ui/widgets/chart.rs | 272 ++++++------------ 6 files changed, 216 insertions(+), 399 deletions(-) diff --git a/crates/hyperqueue/src/dashboard/ui/screens/cluster/overview/worker_count_chart.rs b/crates/hyperqueue/src/dashboard/ui/screens/cluster/overview/worker_count_chart.rs index 733f27863..6554376d8 100644 --- a/crates/hyperqueue/src/dashboard/ui/screens/cluster/overview/worker_count_chart.rs +++ b/crates/hyperqueue/src/dashboard/ui/screens/cluster/overview/worker_count_chart.rs @@ -1,39 +1,29 @@ -use std::time::SystemTime; - use ratatui::layout::Rect; -use ratatui::style::{Color, Modifier, Style}; -use ratatui::symbols; -use ratatui::text::Span; -use ratatui::widgets::{Block, Borders, Chart, Dataset, GraphType}; +use ratatui::style::Color; use crate::dashboard::data::{DashboardData, TimeRange}; -use crate::dashboard::ui::styles::chart_style_deselected; use crate::dashboard::ui::terminal::DashboardFrame; -use crate::dashboard::ui::widgets::chart::{get_time_as_secs, x_axis_time_chart, y_axis_steps}; - -struct WorkerCountRecord { - time: SystemTime, - count: usize, -} +use crate::dashboard::ui::widgets::chart::{ + create_chart, create_dataset, get_time_as_secs, y_axis_steps, RangeSteps, +}; #[derive(Default)] pub struct WorkerCountChart { /// Worker count records that should be currently displayed. - worker_records: Vec, + worker_counts: Vec<(f64, f64)>, range: TimeRange, } -const TIME_STEPS: u32 = 25; - impl WorkerCountChart { pub fn update(&mut self, data: &DashboardData) { let range = data.current_time_range(); - let interval = range.end().duration_since(range.start()).unwrap() / TIME_STEPS; - self.worker_records = (0..TIME_STEPS) - .map(|step| range.start() + interval * step) - .map(|time| WorkerCountRecord { - time, - count: data.workers().query_connected_worker_ids_at(time).count(), + let steps = RangeSteps::new(range, 25); + self.worker_counts = steps + .map(|time| { + ( + get_time_as_secs(time), + data.workers().query_connected_worker_ids_at(time).count() as f64, + ) }) .collect(); self.range = range; @@ -41,39 +31,21 @@ impl WorkerCountChart { pub fn draw(&mut self, rect: Rect, frame: &mut DashboardFrame) { let max_workers_in_view = self - .worker_records + .worker_counts .iter() - .map(|record| record.count) + .map(|record| record.1 as u64) .max() .unwrap_or(0) .max(4) as f64; - let worker_counts: Vec<(f64, f64)> = self - .worker_records - .iter() - .map(|record| (get_time_as_secs(record.time), record.count as f64)) - .collect(); - let datasets = vec![Dataset::default() - .name("Connected workers") - .marker(symbols::Marker::Dot) - .graph_type(GraphType::Line) - .style(Style::default().fg(Color::White)) - .data(&worker_counts)]; + let datasets = vec![create_dataset( + &self.worker_counts, + "Connected Workers", + Color::White, + )]; - let chart = Chart::new(datasets) - .style(chart_style_deselected()) + let chart = create_chart(datasets, "Running Worker Count", self.range) .legend_position(None) - .block( - Block::default() - .title(Span::styled( - "Running worker count", - Style::default() - .fg(Color::White) - .add_modifier(Modifier::BOLD), - )) - .borders(Borders::ALL), - ) - .x_axis(x_axis_time_chart(self.range)) .y_axis(y_axis_steps(0.0, max_workers_in_view, 4)); frame.render_widget(chart, rect); } diff --git a/crates/hyperqueue/src/dashboard/ui/screens/cluster/worker/worker_utilization_chart.rs b/crates/hyperqueue/src/dashboard/ui/screens/cluster/worker/worker_utilization_chart.rs index 8c01f103a..204e3e7ae 100644 --- a/crates/hyperqueue/src/dashboard/ui/screens/cluster/worker/worker_utilization_chart.rs +++ b/crates/hyperqueue/src/dashboard/ui/screens/cluster/worker/worker_utilization_chart.rs @@ -1,17 +1,16 @@ -use ratatui::layout::{Constraint, Rect}; -use ratatui::style::{Color, Modifier, Style}; -use ratatui::text::Span; -use ratatui::widgets::{Block, Borders, Chart, Dataset, GraphType, LegendPosition}; +use ratatui::layout::Rect; +use ratatui::style::Color; -use tako::hwstats::WorkerHwStateMessage; use tako::worker::WorkerOverview; use tako::WorkerId; use crate::dashboard::data::DashboardData; use crate::dashboard::data::{ItemWithTime, TimeRange}; -use crate::dashboard::ui::styles::chart_style_deselected; use crate::dashboard::ui::terminal::DashboardFrame; -use crate::dashboard::ui::widgets::chart::{get_time_as_secs, x_axis_time_chart, y_axis_steps}; +use crate::dashboard::ui::widgets::chart::y_axis_steps; +use crate::dashboard::ui::widgets::chart::{ + create_chart, create_dataset, generate_dataset_entries, +}; use crate::dashboard::utils::{get_average_cpu_usage_for_worker, get_memory_usage_pct}; #[derive(Default)] @@ -22,84 +21,50 @@ pub struct WorkerUtilizationChart { impl WorkerUtilizationChart { pub fn draw(&mut self, rect: Rect, frame: &mut DashboardFrame) { - fn create_data f64>( - overviews: &[ItemWithTime], - get_value: F, - ) -> Vec<(f64, f64)> { - overviews - .iter() - .map(|record| { - ( - get_time_as_secs(record.time), - record.item.hw_state.as_ref().map(&get_value).unwrap_or(0.0), - ) - }) - .collect::>() - } - fn create_dataset<'a>(items: &'a [(f64, f64)], name: &'a str, color: Color) -> Dataset<'a> { - Dataset::default() - .name(name) - .graph_type(GraphType::Line) - .style(Style::default().fg(color)) - .data(items) - } - - let cpu_usage = create_data(&self.overviews, |state| { - get_average_cpu_usage_for_worker(state) - }); - let mem_usage = create_data(&self.overviews, |state| { - get_memory_usage_pct(&state.state.memory_usage) as f64 + let cpu_usage = generate_dataset_entries(&self.overviews, |overview| { + overview + .hw_state + .as_ref() + .map(|s| get_average_cpu_usage_for_worker(s)) + .unwrap_or(0.0) }); - - let has_gpus = self.overviews.iter().any(|overview| { + let mem_usage = generate_dataset_entries(&self.overviews, |overview| { overview - .item .hw_state .as_ref() - .and_then(|state| { - state - .state - .nvidia_gpus - .as_ref() - .or(state.state.amd_gpus.as_ref()) - }) - .map(|stats| !stats.gpus.is_empty()) - .unwrap_or(false) + .map(|s| get_memory_usage_pct(&s.state.memory_usage) as f64) + .unwrap_or(0.0) }); - let gpu_usage = create_data(&self.overviews, |state| { - let gpu_state = state - .state + + let gpu_usage = generate_dataset_entries(&self.overviews, |overview| { + let Some(hw_state) = overview.hw_state.as_ref().map(|s| &s.state) else { + return 0.0; + }; + let gpu_usages: Vec = hw_state .nvidia_gpus .as_ref() - .or(state.state.amd_gpus.as_ref()); - gpu_state - .map(|state| state.gpus[0].processor_usage as f64) - .unwrap_or(0.0) + .into_iter() + .chain(hw_state.amd_gpus.as_ref()) + .flat_map(|stats| &stats.gpus) + .map(|gpu| gpu.processor_usage as f64) + .collect(); + if !gpu_usages.is_empty() { + gpu_usages.iter().sum::() / gpu_usages.len() as f64 + } else { + 0.0 + } }); + let has_gpus = gpu_usage.iter().any(|(_, usage)| *usage > 0.0); let mut datasets = vec![ create_dataset(&cpu_usage, "CPU (avg) (%)", Color::Green), create_dataset(&mem_usage, "Mem (%)", Color::Blue), ]; if has_gpus { - datasets.push(create_dataset(&gpu_usage, "GPU 0 (%)", Color::Red)); + datasets.push(create_dataset(&gpu_usage, "GPU (avg) (%)", Color::Red)); } - let chart = Chart::new(datasets) - .style(chart_style_deselected()) - .legend_position(Some(LegendPosition::TopLeft)) - .hidden_legend_constraints((Constraint::Ratio(1, 1), Constraint::Ratio(1, 1))) - .block( - Block::default() - .title(Span::styled( - "Utilization History", - Style::default() - .fg(Color::White) - .add_modifier(Modifier::BOLD), - )) - .borders(Borders::ALL), - ) - .x_axis(x_axis_time_chart(self.range)) + let chart = create_chart(datasets, "Utilization History", self.range) .y_axis(y_axis_steps(0.0, 100.0, 5)); frame.render_widget(chart, rect); } diff --git a/crates/hyperqueue/src/dashboard/ui/screens/jobs/job_info_display.rs b/crates/hyperqueue/src/dashboard/ui/screens/jobs/job_info_display.rs index ce3be97c4..dc21977b6 100644 --- a/crates/hyperqueue/src/dashboard/ui/screens/jobs/job_info_display.rs +++ b/crates/hyperqueue/src/dashboard/ui/screens/jobs/job_info_display.rs @@ -8,7 +8,7 @@ use chrono::{DateTime, Local}; use itertools::Itertools; use ratatui::layout::{Constraint, Rect}; use ratatui::text::Text; -use ratatui::widgets::{Cell, Paragraph, Row}; +use ratatui::widgets::{Cell, Row}; use std::borrow::Cow; use tako::gateway::ResourceRequestVariants; diff --git a/crates/hyperqueue/src/dashboard/ui/screens/jobs/job_tasks_chart.rs b/crates/hyperqueue/src/dashboard/ui/screens/jobs/job_tasks_chart.rs index dc9533d6c..e9cb191d1 100644 --- a/crates/hyperqueue/src/dashboard/ui/screens/jobs/job_tasks_chart.rs +++ b/crates/hyperqueue/src/dashboard/ui/screens/jobs/job_tasks_chart.rs @@ -1,142 +1,118 @@ use crate::dashboard::data::timelines::job_timeline::DashboardTaskState; -use crate::dashboard::data::DashboardData; +use crate::dashboard::data::{DashboardData, ItemWithTime, TimeRange}; use crate::dashboard::ui::terminal::DashboardFrame; use crate::dashboard::ui::widgets::chart::{ - get_time_as_secs, ChartPlotter, DashboardChart, PlotStyle, + create_chart, create_dataset, generate_dataset_entries, generate_time_data, RangeSteps, }; use crate::JobId; use ratatui::layout::Rect; use ratatui::style::Color; -use ratatui::symbols::Marker; use std::time::SystemTime; -use tako::Map; - -const RUNNING_TAG: &str = "Running"; -const FINISHED_TAG: &str = "Finished"; -const FAILED_TAG: &str = "Failed"; #[derive(Default)] pub struct JobTaskChart { - /// The Job for which we want to see the tasks for. + /// The job for which we want to see the tasks chart. job_id: Option, - /// The chart. - chart: DashboardChart, + stats: Option>>, + range: TimeRange, } impl JobTaskChart { /// Sets a new job id for the chart to plot data for. pub fn set_job_id(&mut self, job_id: JobId) { - match self.job_id { - Some(chart_job_id) if chart_job_id == job_id => (), - _ => { - self.job_id = Some(job_id); - self.chart = Default::default(); - let tasks_count_plotter: Box = - Box::new(TaskCountsPlotter(job_id)); - self.chart.add_chart_plotter(tasks_count_plotter); - } - } + self.job_id = Some(job_id); } - pub fn clear_chart(&mut self) { + pub fn unset_job_id(&mut self) { self.job_id = None; - self.chart = Default::default(); + self.stats = None; } } impl JobTaskChart { pub fn update(&mut self, data: &DashboardData) { + self.range = data.current_time_range(); if let Some(job_id) = self.job_id { - let (num_running_tasks, num_failed_tasks, num_finished_tasks) = - get_task_counts_for_job(job_id, data, SystemTime::now()); - self.chart.set_chart_name( - format!("Running: {num_running_tasks}, Finished: {num_finished_tasks}, Failed: {num_failed_tasks}").as_str(), - ); + let steps = RangeSteps::new(self.range, 25); + self.stats = Some(generate_time_data(steps, |time| { + get_task_counts_for_job(job_id, data, time) + })); } else { - self.chart - .set_chart_name("Please select a Job to show Task counts"); + self.stats = None; } - self.chart.update(data); } pub fn draw(&mut self, rect: Rect, frame: &mut DashboardFrame) { - self.chart.draw(rect, frame); - } -} + match &self.stats { + Some(entries) => { + let Some(stats) = entries.last() else { + return; + }; + let title = format!( + "Running: {}, Finished: {}, Failed: {}", + stats.item.running, stats.item.finished, stats.item.failed + ); -struct TaskCountsPlotter(JobId); + let running = generate_dataset_entries(entries, |stats| stats.running as f64); + let finished = generate_dataset_entries(entries, |stats| stats.finished as f64); + let failed = generate_dataset_entries(entries, |stats| stats.failed as f64); -impl ChartPlotter for TaskCountsPlotter { - fn get_charts(&self) -> Map { - let mut plots_and_styles: Map = Default::default(); - - let running_points_style = PlotStyle { - color: Color::Yellow, - marker: Marker::Braille, - }; - let finished_points_style = PlotStyle { - color: Color::Green, - marker: Marker::Braille, - }; - let failed_points_style = PlotStyle { - color: Color::Red, - marker: Marker::Braille, + let datasets = vec![ + create_dataset(&running, "Running", Color::Yellow), + create_dataset(&finished, "Finished", Color::Green), + create_dataset(&failed, "Failed", Color::Red), + ]; + let chart = create_chart(datasets, &title, self.range); + frame.render_widget(chart, rect); + } + None => { + let chart = create_chart( + vec![], + "Please select a Job to show Task counts", + self.range, + ); + frame.render_widget(chart, rect); + } }; - - plots_and_styles.insert(RUNNING_TAG.to_string(), running_points_style); - plots_and_styles.insert(FINISHED_TAG.to_string(), finished_points_style); - plots_and_styles.insert(FAILED_TAG.to_string(), failed_points_style); - - plots_and_styles } +} - /// Fetches the number of Running/Finished/Failed tasks at a given time and makes it available to the chart. - fn fetch_data_points( - &self, - data: &DashboardData, - at_time: SystemTime, - ) -> Map { - let mut data_points: Map = Default::default(); +#[derive(Copy, Clone, Debug, Default)] +struct TaskStats { + running: u64, + failed: u64, + finished: u64, +} - let (num_running_tasks, num_failed_tasks, num_finished_tasks) = - get_task_counts_for_job(self.0, data, at_time); - if num_running_tasks != 0 { - data_points.insert( - RUNNING_TAG.to_string(), - (get_time_as_secs(at_time), num_running_tasks as f64), - ); - } - if num_finished_tasks != 0 { - data_points.insert( - FINISHED_TAG.to_string(), - (get_time_as_secs(at_time), num_finished_tasks as f64), - ); +impl TaskStats { + fn update(self, state: DashboardTaskState) -> Self { + let TaskStats { + mut running, + mut failed, + mut finished, + } = self; + match state { + DashboardTaskState::Running => { + running += 1; + } + DashboardTaskState::Finished => { + finished += 1; + } + DashboardTaskState::Failed => { + failed += 1; + } } - if num_failed_tasks != 0 { - data_points.insert( - FAILED_TAG.to_string(), - (get_time_as_secs(at_time), num_failed_tasks as f64), - ); + Self { + running, + failed, + finished, } - - data_points } } /// Fetches the count of tasks for a `job` as (`running_tasks`, `failed_tasks`, `finished_tasks`) -fn get_task_counts_for_job( - job_id: JobId, - data: &DashboardData, - at_time: SystemTime, -) -> (u32, u32, u32) { +fn get_task_counts_for_job(job_id: JobId, data: &DashboardData, at_time: SystemTime) -> TaskStats { data.query_task_history_for_job(job_id, at_time) .filter_map(|(_, info)| info.get_task_state_at(at_time)) - .fold( - (0, 0, 0), - |(running, failed, finished), state| match state { - DashboardTaskState::Running => (running + 1, failed, finished), - DashboardTaskState::Failed => (running, failed + 1, finished), - DashboardTaskState::Finished => (running, failed, finished + 1), - }, - ) + .fold(TaskStats::default(), |acc, state| acc.update(state)) } diff --git a/crates/hyperqueue/src/dashboard/ui/screens/jobs/overview.rs b/crates/hyperqueue/src/dashboard/ui/screens/jobs/overview.rs index a2c50a882..44f6e4f6d 100644 --- a/crates/hyperqueue/src/dashboard/ui/screens/jobs/overview.rs +++ b/crates/hyperqueue/src/dashboard/ui/screens/jobs/overview.rs @@ -85,7 +85,7 @@ impl JobOverview { self.job_tasks_table.update(task_infos); self.job_task_chart.set_job_id(job_id); } else { - self.job_task_chart.clear_chart(); + self.job_task_chart.unset_job_id(); } if let Some(job_info) = self .job_list diff --git a/crates/hyperqueue/src/dashboard/ui/widgets/chart.rs b/crates/hyperqueue/src/dashboard/ui/widgets/chart.rs index c8916594e..cfe192297 100644 --- a/crates/hyperqueue/src/dashboard/ui/widgets/chart.rs +++ b/crates/hyperqueue/src/dashboard/ui/widgets/chart.rs @@ -1,192 +1,13 @@ -use chrono::{DateTime, Local}; +use chrono::Local; use std::time::{Duration, SystemTime, UNIX_EPOCH}; -use ratatui::layout::{Alignment, Rect}; +use ratatui::layout::{Alignment, Constraint}; use ratatui::style::{Color, Modifier, Style}; -use ratatui::symbols::Marker; -use ratatui::text::{Line, Span}; -use ratatui::widgets::{Axis, Block, Borders, Chart, Dataset}; -use tako::Map; +use ratatui::text::Span; +use ratatui::widgets::{Axis, Block, Borders, Chart, Dataset, GraphType, LegendPosition}; -use crate::dashboard::data::{DashboardData, TimeRange}; +use crate::dashboard::data::{ItemWithTime, TimeRange}; use crate::dashboard::ui::styles::chart_style_deselected; -use crate::dashboard::ui::terminal::DashboardFrame; - -/// Generic chart that shows graphs described by different `ChartPlotter`. -pub struct DashboardChart { - /// The Chart's name - label: String, - /// The charts shown on the `DashboardChart`. - chart_plotters: Vec>, - /// Chart's data, for each of the different plots on the chart. - datasets: Map>, - /// The style of a plot on the chart. - dataset_styles: Map, - /// The end time of the data displayed on the chart, - end_time: SystemTime, - /// The duration for which the data is plotted for. - view_size: Duration, - /// if true, the `end_time` is always updated to current time. - is_live: bool, -} - -#[derive(Copy, Clone)] -pub struct PlotStyle { - pub color: Color, - pub marker: Marker, -} - -/// Declares a group of charts plotted on the `DashboardChart` with `get_charts` -/// and defines how data will be fetched for each chart with `fetch_data_points` -/// Multiple `ChartPlotters` can be used to add different charts to `DashboardChart` -/// with each of the charts fetching their own data. -pub trait ChartPlotter { - /// Declares the labels of the charts that will be plotted by this plotter and their style. - fn get_charts(&self) -> Map; - - /// Fetches one set of points for each of the keys returned by `get_charts` - /// at given `time` - fn fetch_data_points(&self, data: &DashboardData, time: SystemTime) -> Map; -} - -impl DashboardChart { - /// Sets a label for the chart - pub fn set_chart_name(&mut self, chart_name: &str) { - self.label = chart_name.to_string(); - } - - /// Adds a chart to be plotted by `DashboardChart` - pub fn add_chart_plotter(&mut self, chart_plotter: Box) { - self.chart_plotters.push(chart_plotter); - } - - pub fn update(&mut self, data: &DashboardData) { - if self.is_live { - self.end_time = SystemTime::now(); - } - // Clears the old data, TODO: load only the new data points - self.datasets.clear(); - let mut start = self.end_time - self.view_size; - let mut times = vec![]; - while start <= self.end_time { - times.push(start); - start += Duration::from_secs(1); - } - - let styles: Map<_, _> = self - .chart_plotters - .iter() - .flat_map(move |fetcher| fetcher.get_charts()) - .collect(); - - let data_points: Vec<(_, _)> = times - .iter() - .flat_map(|time| { - self.chart_plotters - .iter() - .flat_map(|fetcher| fetcher.fetch_data_points(data, *time)) - }) - .collect(); - - for (key, data) in data_points.iter() { - if !self.datasets.contains_key(key) { - let style = styles.get(key).copied(); - self.add_chart(key.to_string(), style); - } - self.datasets.get_mut(key).unwrap().push(*data); - } - } - - /// Adds a chart with empty data and input `PlotStyle`, if `plt_style` is None, it's set to default. - fn add_chart(&mut self, chart_label: String, plt_style: Option) { - self.datasets.insert(chart_label.clone(), vec![]); - match plt_style { - Some(plot_style) => self.dataset_styles.insert(chart_label, plot_style), - None => self.dataset_styles.insert(chart_label, Default::default()), - }; - } - - pub fn draw(&mut self, rect: Rect, frame: &mut DashboardFrame) { - let mut y_max: f64 = 0.0; - let datasets: Vec = self - .datasets - .iter() - .map(|(label, dataset)| { - let style = self.dataset_styles.get(label).unwrap(); - y_max = dataset - .iter() - .map(|(_, y)| if *y > y_max { *y } else { y_max }) - .fold(y_max, f64::max); - Dataset::default() - .name(Line::from(label.as_str())) - .marker(style.marker) - .style(Style::default().fg(style.color)) - .data(dataset) - }) - .collect(); - - let start_time_label: DateTime = (self.end_time - self.view_size).into(); - let end_time_label: DateTime = self.end_time.into(); - let chart = Chart::new(datasets) - .style(chart_style_deselected()) - .block( - Block::default() - .title(Span::styled( - &self.label, - Style::default() - .fg(Color::White) - .add_modifier(Modifier::BOLD), - )) - .borders(Borders::ALL), - ) - .x_axis( - Axis::default() - .title("t->") - .style(Style::default().fg(Color::Gray)) - .bounds([ - get_time_as_secs(self.end_time - self.view_size), - get_time_as_secs(self.end_time), - ]) - .labels(vec![ - Span::from(start_time_label.format("%H:%M").to_string()), - Span::from(end_time_label.format("%H:%M").to_string()), - ]), - ) - .y_axis( - Axis::default() - .style(Style::default().fg(Color::Gray)) - .bounds([0.00, y_max]) - .labels(vec![ - Span::from(0u8.to_string()), - Span::from(y_max.to_string()), - ]), - ); - frame.render_widget(chart, rect); - } -} - -impl Default for DashboardChart { - fn default() -> Self { - Self { - label: "Chart".to_string(), - chart_plotters: vec![], - datasets: Default::default(), - dataset_styles: Default::default(), - end_time: SystemTime::now(), - view_size: Duration::from_secs(300), - is_live: true, - } - } -} - -impl Default for PlotStyle { - fn default() -> Self { - PlotStyle { - color: Color::Magenta, - marker: Marker::Dot, - } - } -} pub fn get_time_as_secs(time: SystemTime) -> f64 { time.duration_since(UNIX_EPOCH).unwrap().as_secs() as f64 @@ -230,3 +51,86 @@ pub fn y_axis_steps(min: f64, max: f64, step_count: u32) -> Axis<'static> { ) .labels_alignment(Alignment::Right) } + +pub fn create_chart<'a>(datasets: Vec>, title: &'a str, range: TimeRange) -> Chart<'a> { + Chart::new(datasets) + .style(chart_style_deselected()) + .legend_position(Some(LegendPosition::TopLeft)) + .hidden_legend_constraints((Constraint::Ratio(1, 1), Constraint::Ratio(1, 1))) + .block( + Block::default() + .title(Span::styled( + title, + Style::default() + .fg(Color::White) + .add_modifier(Modifier::BOLD), + )) + .borders(Borders::ALL), + ) + .x_axis(x_axis_time_chart(range)) +} + +pub fn create_dataset<'a>(items: &'a [(f64, f64)], name: &'a str, color: Color) -> Dataset<'a> { + Dataset::default() + .name(name) + .graph_type(GraphType::Line) + .style(Style::default().fg(color)) + .data(items) +} + +/// Iterator through individual steps within a time range. +pub struct RangeSteps { + range: TimeRange, + interval: Duration, + step: usize, +} + +impl RangeSteps { + pub fn new(range: TimeRange, steps: u32) -> Self { + let interval = range.end().duration_since(range.start()).unwrap() / steps; + Self { + range, + interval, + step: 0, + } + } +} + +impl Iterator for RangeSteps { + type Item = SystemTime; + + fn next(&mut self) -> Option { + let time = self.range.start() + self.interval * self.step as u32; + if time <= self.range.end() { + self.step += 1; + Some(time) + } else { + None + } + } +} + +/// Generate a series of values from timestamps generated by the given +/// `RangeSteps` iterator. +pub fn generate_time_data(steps: RangeSteps, f: F) -> Vec> +where + F: Fn(SystemTime) -> T, +{ + steps + .map(|time| ItemWithTime { + time, + item: f(time), + }) + .collect() +} + +/// Generate a vector of f64 tuples that can be used to create a dataset. +pub fn generate_dataset_entries(items: &[ItemWithTime], f: F) -> Vec<(f64, f64)> +where + F: Fn(&T) -> f64, +{ + items + .iter() + .map(|item| (get_time_as_secs(item.time), f(&item.item))) + .collect() +} From e34dcc9c9c20db229a88d792d2ac8fbcfa22ddc0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Sat, 19 Oct 2024 15:12:05 +0200 Subject: [PATCH 26/31] Fix style applied to job overview tables --- crates/hyperqueue/src/dashboard/ui/screens/jobs/overview.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/hyperqueue/src/dashboard/ui/screens/jobs/overview.rs b/crates/hyperqueue/src/dashboard/ui/screens/jobs/overview.rs index 44f6e4f6d..26f1b7469 100644 --- a/crates/hyperqueue/src/dashboard/ui/screens/jobs/overview.rs +++ b/crates/hyperqueue/src/dashboard/ui/screens/jobs/overview.rs @@ -58,13 +58,13 @@ impl JobOverview { self.job_info_table.draw(layout.job_info_chunk, frame); self.job_task_chart.draw(layout.chart_chunk, frame); self.job_list - .draw(layout.job_list_chunk, frame, tasks_table_style); + .draw(layout.job_list_chunk, frame, jobs_table_style); self.job_tasks_table.draw( "Tasks <2>", layout.job_tasks_chunk, frame, true, - jobs_table_style, + tasks_table_style, ); draw_text( From 011f65f3c216566f02b3221b2e201db4d5a7a27d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Mon, 21 Oct 2024 08:52:27 +0200 Subject: [PATCH 27/31] Move autoalloc screen to a separate module --- .../ui/screens/{autoalloc_screen.rs => autoalloc/mod.rs} | 0 crates/hyperqueue/src/dashboard/ui/screens/mod.rs | 2 +- crates/hyperqueue/src/dashboard/ui/screens/root_screen.rs | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) rename crates/hyperqueue/src/dashboard/ui/screens/{autoalloc_screen.rs => autoalloc/mod.rs} (100%) diff --git a/crates/hyperqueue/src/dashboard/ui/screens/autoalloc_screen.rs b/crates/hyperqueue/src/dashboard/ui/screens/autoalloc/mod.rs similarity index 100% rename from crates/hyperqueue/src/dashboard/ui/screens/autoalloc_screen.rs rename to crates/hyperqueue/src/dashboard/ui/screens/autoalloc/mod.rs diff --git a/crates/hyperqueue/src/dashboard/ui/screens/mod.rs b/crates/hyperqueue/src/dashboard/ui/screens/mod.rs index 789705d7a..2834d2c5c 100644 --- a/crates/hyperqueue/src/dashboard/ui/screens/mod.rs +++ b/crates/hyperqueue/src/dashboard/ui/screens/mod.rs @@ -1,4 +1,4 @@ -pub mod autoalloc_screen; +pub mod autoalloc; pub mod cluster; pub mod jobs; pub mod root_screen; diff --git a/crates/hyperqueue/src/dashboard/ui/screens/root_screen.rs b/crates/hyperqueue/src/dashboard/ui/screens/root_screen.rs index dd6f101db..7959b0b7c 100644 --- a/crates/hyperqueue/src/dashboard/ui/screens/root_screen.rs +++ b/crates/hyperqueue/src/dashboard/ui/screens/root_screen.rs @@ -1,6 +1,6 @@ use crate::dashboard::data::{DashboardData, TimeRange}; use crate::dashboard::ui::screen::Screen; -use crate::dashboard::ui::screens::autoalloc_screen::AutoAllocScreen; +use crate::dashboard::ui::screens::autoalloc::AutoAllocScreen; use crate::dashboard::ui::screens::cluster::WorkerOverviewScreen; use crate::dashboard::ui::screens::jobs::JobScreen; use crate::dashboard::ui::terminal::{DashboardFrame, DashboardTerminal}; From 9603057282ae811c1488448bb37332aa3e88f268 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Mon, 21 Oct 2024 08:54:52 +0200 Subject: [PATCH 28/31] Move autoalloc fragments to its screen module --- .../src/dashboard/ui/fragments/auto_allocator/mod.rs | 5 ----- crates/hyperqueue/src/dashboard/ui/fragments/mod.rs | 1 - crates/hyperqueue/src/dashboard/ui/mod.rs | 1 - .../autoalloc}/alloc_timeline_chart.rs | 0 .../autoalloc}/allocations_info_table.rs | 0 .../auto_allocator => screens/autoalloc}/fragment.rs | 10 +++++----- .../src/dashboard/ui/screens/autoalloc/mod.rs | 8 +++++++- .../autoalloc}/queue_info_table.rs | 0 .../autoalloc}/queue_params_display.rs | 0 9 files changed, 12 insertions(+), 13 deletions(-) delete mode 100644 crates/hyperqueue/src/dashboard/ui/fragments/auto_allocator/mod.rs delete mode 100644 crates/hyperqueue/src/dashboard/ui/fragments/mod.rs rename crates/hyperqueue/src/dashboard/ui/{fragments/auto_allocator => screens/autoalloc}/alloc_timeline_chart.rs (100%) rename crates/hyperqueue/src/dashboard/ui/{fragments/auto_allocator => screens/autoalloc}/allocations_info_table.rs (100%) rename crates/hyperqueue/src/dashboard/ui/{fragments/auto_allocator => screens/autoalloc}/fragment.rs (93%) rename crates/hyperqueue/src/dashboard/ui/{fragments/auto_allocator => screens/autoalloc}/queue_info_table.rs (100%) rename crates/hyperqueue/src/dashboard/ui/{fragments/auto_allocator => screens/autoalloc}/queue_params_display.rs (100%) diff --git a/crates/hyperqueue/src/dashboard/ui/fragments/auto_allocator/mod.rs b/crates/hyperqueue/src/dashboard/ui/fragments/auto_allocator/mod.rs deleted file mode 100644 index 9ccadb1ba..000000000 --- a/crates/hyperqueue/src/dashboard/ui/fragments/auto_allocator/mod.rs +++ /dev/null @@ -1,5 +0,0 @@ -pub mod alloc_timeline_chart; -pub mod allocations_info_table; -pub mod fragment; -pub mod queue_info_table; -pub mod queue_params_display; diff --git a/crates/hyperqueue/src/dashboard/ui/fragments/mod.rs b/crates/hyperqueue/src/dashboard/ui/fragments/mod.rs deleted file mode 100644 index 90c55b4cf..000000000 --- a/crates/hyperqueue/src/dashboard/ui/fragments/mod.rs +++ /dev/null @@ -1 +0,0 @@ -pub mod auto_allocator; diff --git a/crates/hyperqueue/src/dashboard/ui/mod.rs b/crates/hyperqueue/src/dashboard/ui/mod.rs index a4346725c..09c46c7bf 100644 --- a/crates/hyperqueue/src/dashboard/ui/mod.rs +++ b/crates/hyperqueue/src/dashboard/ui/mod.rs @@ -1,4 +1,3 @@ -pub mod fragments; pub mod screen; pub mod screens; pub mod styles; diff --git a/crates/hyperqueue/src/dashboard/ui/fragments/auto_allocator/alloc_timeline_chart.rs b/crates/hyperqueue/src/dashboard/ui/screens/autoalloc/alloc_timeline_chart.rs similarity index 100% rename from crates/hyperqueue/src/dashboard/ui/fragments/auto_allocator/alloc_timeline_chart.rs rename to crates/hyperqueue/src/dashboard/ui/screens/autoalloc/alloc_timeline_chart.rs diff --git a/crates/hyperqueue/src/dashboard/ui/fragments/auto_allocator/allocations_info_table.rs b/crates/hyperqueue/src/dashboard/ui/screens/autoalloc/allocations_info_table.rs similarity index 100% rename from crates/hyperqueue/src/dashboard/ui/fragments/auto_allocator/allocations_info_table.rs rename to crates/hyperqueue/src/dashboard/ui/screens/autoalloc/allocations_info_table.rs diff --git a/crates/hyperqueue/src/dashboard/ui/fragments/auto_allocator/fragment.rs b/crates/hyperqueue/src/dashboard/ui/screens/autoalloc/fragment.rs similarity index 93% rename from crates/hyperqueue/src/dashboard/ui/fragments/auto_allocator/fragment.rs rename to crates/hyperqueue/src/dashboard/ui/screens/autoalloc/fragment.rs index 92ea337ac..d1a222c57 100644 --- a/crates/hyperqueue/src/dashboard/ui/fragments/auto_allocator/fragment.rs +++ b/crates/hyperqueue/src/dashboard/ui/screens/autoalloc/fragment.rs @@ -9,10 +9,10 @@ use std::time::SystemTime; use crate::dashboard::data::timelines::alloc_timeline::AllocationQueueInfo; use crate::dashboard::data::DashboardData; -use crate::dashboard::ui::fragments::auto_allocator::alloc_timeline_chart::AllocationsChart; -use crate::dashboard::ui::fragments::auto_allocator::allocations_info_table::AllocationInfoTable; -use crate::dashboard::ui::fragments::auto_allocator::queue_info_table::AllocationQueueInfoTable; -use crate::dashboard::ui::fragments::auto_allocator::queue_params_display::QueueParamsTable; +use crate::dashboard::ui::screens::autoalloc::alloc_timeline_chart::AllocationsChart; +use crate::dashboard::ui::screens::autoalloc::allocations_info_table::AllocationInfoTable; +use crate::dashboard::ui::screens::autoalloc::queue_info_table::AllocationQueueInfoTable; +use crate::dashboard::ui::screens::autoalloc::queue_params_display::QueueParamsTable; use crate::server::autoalloc::QueueId; use ratatui::layout::{Constraint, Direction, Layout, Rect}; @@ -104,7 +104,7 @@ impl AutoAllocatorFragment { FocusedComponent::AllocationInfoTable => self.allocations_info_table.handle_key(key), }; - match key.code{ + match key.code { KeyCode::Char('1') => { self.component_in_focus = FocusedComponent::QueueParamsTable; self.allocations_info_table.clear_selection(); diff --git a/crates/hyperqueue/src/dashboard/ui/screens/autoalloc/mod.rs b/crates/hyperqueue/src/dashboard/ui/screens/autoalloc/mod.rs index 3a632f4cd..4e29d7fba 100644 --- a/crates/hyperqueue/src/dashboard/ui/screens/autoalloc/mod.rs +++ b/crates/hyperqueue/src/dashboard/ui/screens/autoalloc/mod.rs @@ -1,10 +1,16 @@ use crate::dashboard::data::DashboardData; -use crate::dashboard::ui::fragments::auto_allocator::fragment::AutoAllocatorFragment; use crate::dashboard::ui::screen::Screen; use crate::dashboard::ui::terminal::DashboardFrame; use crossterm::event::KeyEvent; +use fragment::AutoAllocatorFragment; use ratatui::layout::Rect; +mod alloc_timeline_chart; +mod allocations_info_table; +mod fragment; +mod queue_info_table; +mod queue_params_display; + #[derive(Default)] pub struct AutoAllocScreen { auto_allocator_fragment: AutoAllocatorFragment, diff --git a/crates/hyperqueue/src/dashboard/ui/fragments/auto_allocator/queue_info_table.rs b/crates/hyperqueue/src/dashboard/ui/screens/autoalloc/queue_info_table.rs similarity index 100% rename from crates/hyperqueue/src/dashboard/ui/fragments/auto_allocator/queue_info_table.rs rename to crates/hyperqueue/src/dashboard/ui/screens/autoalloc/queue_info_table.rs diff --git a/crates/hyperqueue/src/dashboard/ui/fragments/auto_allocator/queue_params_display.rs b/crates/hyperqueue/src/dashboard/ui/screens/autoalloc/queue_params_display.rs similarity index 100% rename from crates/hyperqueue/src/dashboard/ui/fragments/auto_allocator/queue_params_display.rs rename to crates/hyperqueue/src/dashboard/ui/screens/autoalloc/queue_params_display.rs From 200a4cacd431bee0a3d2ed85ac89c8296005b8b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Mon, 21 Oct 2024 08:56:08 +0200 Subject: [PATCH 29/31] Inline autoalloc fragment into its screen --- .../ui/screens/autoalloc/fragment.rs | 169 ------------------ .../src/dashboard/ui/screens/autoalloc/mod.rs | 157 +++++++++++++++- 2 files changed, 149 insertions(+), 177 deletions(-) delete mode 100644 crates/hyperqueue/src/dashboard/ui/screens/autoalloc/fragment.rs diff --git a/crates/hyperqueue/src/dashboard/ui/screens/autoalloc/fragment.rs b/crates/hyperqueue/src/dashboard/ui/screens/autoalloc/fragment.rs deleted file mode 100644 index d1a222c57..000000000 --- a/crates/hyperqueue/src/dashboard/ui/screens/autoalloc/fragment.rs +++ /dev/null @@ -1,169 +0,0 @@ -use crate::dashboard::ui::styles::{ - style_footer, style_header_text, table_style_deselected, table_style_selected, -}; -use crate::dashboard::ui::terminal::DashboardFrame; -use crate::dashboard::ui::widgets::text::draw_text; -use crossterm::event::{KeyCode, KeyEvent}; -use std::default::Default; -use std::time::SystemTime; - -use crate::dashboard::data::timelines::alloc_timeline::AllocationQueueInfo; -use crate::dashboard::data::DashboardData; -use crate::dashboard::ui::screens::autoalloc::alloc_timeline_chart::AllocationsChart; -use crate::dashboard::ui::screens::autoalloc::allocations_info_table::AllocationInfoTable; -use crate::dashboard::ui::screens::autoalloc::queue_info_table::AllocationQueueInfoTable; -use crate::dashboard::ui::screens::autoalloc::queue_params_display::QueueParamsTable; -use crate::server::autoalloc::QueueId; -use ratatui::layout::{Constraint, Direction, Layout, Rect}; - -#[derive(Default)] -pub struct AutoAllocatorFragment { - queue_info_table: AllocationQueueInfoTable, - queue_params_table: QueueParamsTable, - allocations_info_table: AllocationInfoTable, - allocations_chart: AllocationsChart, - - component_in_focus: FocusedComponent, -} - -#[derive(Default)] -enum FocusedComponent { - #[default] - QueueParamsTable, - AllocationInfoTable, -} - -impl AutoAllocatorFragment { - pub fn draw(&mut self, in_area: Rect, frame: &mut DashboardFrame) { - let layout = AutoAllocFragmentLayout::new(&in_area); - draw_text( - "AutoAlloc Info", - layout.header_chunk, - frame, - style_header_text(), - ); - - let (queues_table_style, allocations_table_style) = match self.component_in_focus { - FocusedComponent::QueueParamsTable => { - (table_style_selected(), table_style_deselected()) - } - FocusedComponent::AllocationInfoTable => { - (table_style_deselected(), table_style_selected()) - } - }; - - self.allocations_chart.draw(layout.chart_chunk, frame); - self.queue_info_table - .draw(layout.queue_info_chunk, frame, queues_table_style); - self.allocations_info_table.draw( - layout.allocation_info_chunk, - frame, - allocations_table_style, - ); - self.queue_params_table - .draw(layout.allocation_queue_params_chunk, frame); - - draw_text( - "<1>: Allocation Queues, <2>: Allocations", - layout.footer_chunk, - frame, - style_footer(), - ); - } - - pub fn update(&mut self, data: &DashboardData) { - let queue_infos: Vec<(&QueueId, &AllocationQueueInfo)> = - data.query_allocation_queues_at(SystemTime::now()).collect(); - self.queue_info_table.update(queue_infos); - - if let Some(descriptor) = self.queue_info_table.get_selected_queue_descriptor() { - self.allocations_chart.update(data, descriptor); - } - - if let Some(queue_params) = self - .queue_info_table - .get_selected_queue_descriptor() - .and_then(|queue_id| data.query_allocation_params(queue_id)) - { - self.queue_params_table.update(queue_params) - } - - if let Some(allocations_map) = self - .queue_info_table - .get_selected_queue_descriptor() - .and_then(|queue_id| data.query_allocations_info_at(queue_id, SystemTime::now())) - { - self.allocations_info_table.update(allocations_map); - } - } - - /// Handles key presses for the components of the screen - pub fn handle_key(&mut self, key: KeyEvent) { - match self.component_in_focus { - FocusedComponent::QueueParamsTable => self.queue_info_table.handle_key(key), - FocusedComponent::AllocationInfoTable => self.allocations_info_table.handle_key(key), - }; - - match key.code { - KeyCode::Char('1') => { - self.component_in_focus = FocusedComponent::QueueParamsTable; - self.allocations_info_table.clear_selection(); - } - KeyCode::Char('2') => self.component_in_focus = FocusedComponent::AllocationInfoTable, - _ => {} - } - } -} - -/** -* __________________________ - |--------Header---------| - | Chart | - |-----------------------| - | queues | | - |----------| alloc_info | - | q_params | | - |________Footer_________| - **/ -struct AutoAllocFragmentLayout { - chart_chunk: Rect, - allocation_queue_params_chunk: Rect, - header_chunk: Rect, - queue_info_chunk: Rect, - allocation_info_chunk: Rect, - footer_chunk: Rect, -} - -impl AutoAllocFragmentLayout { - fn new(rect: &Rect) -> Self { - let auto_alloc_screen_chunks = ratatui::layout::Layout::default() - .constraints(vec![ - Constraint::Percentage(5), - Constraint::Percentage(40), - Constraint::Percentage(50), - Constraint::Percentage(5), - ]) - .direction(Direction::Vertical) - .split(*rect); - - let component_area = Layout::default() - .constraints(vec![Constraint::Percentage(50), Constraint::Percentage(50)]) - .direction(Direction::Horizontal) - .margin(0) - .split(auto_alloc_screen_chunks[2]); - - let queue_info_area = Layout::default() - .constraints(vec![Constraint::Percentage(50), Constraint::Percentage(50)]) - .direction(Direction::Vertical) - .split(component_area[0]); - - Self { - chart_chunk: auto_alloc_screen_chunks[1], - header_chunk: auto_alloc_screen_chunks[0], - queue_info_chunk: queue_info_area[0], - allocation_queue_params_chunk: queue_info_area[1], - allocation_info_chunk: component_area[1], - footer_chunk: auto_alloc_screen_chunks[3], - } - } -} diff --git a/crates/hyperqueue/src/dashboard/ui/screens/autoalloc/mod.rs b/crates/hyperqueue/src/dashboard/ui/screens/autoalloc/mod.rs index 4e29d7fba..0b5b4f444 100644 --- a/crates/hyperqueue/src/dashboard/ui/screens/autoalloc/mod.rs +++ b/crates/hyperqueue/src/dashboard/ui/screens/autoalloc/mod.rs @@ -1,31 +1,172 @@ +use crate::dashboard::data::timelines::alloc_timeline::AllocationQueueInfo; use crate::dashboard::data::DashboardData; use crate::dashboard::ui::screen::Screen; +use crate::dashboard::ui::screens::autoalloc::alloc_timeline_chart::AllocationsChart; +use crate::dashboard::ui::screens::autoalloc::allocations_info_table::AllocationInfoTable; +use crate::dashboard::ui::screens::autoalloc::queue_info_table::AllocationQueueInfoTable; +use crate::dashboard::ui::screens::autoalloc::queue_params_display::QueueParamsTable; +use crate::dashboard::ui::styles::{ + style_footer, style_header_text, table_style_deselected, table_style_selected, +}; use crate::dashboard::ui::terminal::DashboardFrame; -use crossterm::event::KeyEvent; -use fragment::AutoAllocatorFragment; -use ratatui::layout::Rect; +use crate::dashboard::ui::widgets::text::draw_text; +use crate::server::autoalloc::QueueId; +use crossterm::event::{KeyCode, KeyEvent}; +use ratatui::layout::{Constraint, Direction, Layout, Rect}; +use std::time::SystemTime; mod alloc_timeline_chart; mod allocations_info_table; -mod fragment; mod queue_info_table; mod queue_params_display; #[derive(Default)] pub struct AutoAllocScreen { - auto_allocator_fragment: AutoAllocatorFragment, + queue_info_table: AllocationQueueInfoTable, + queue_params_table: QueueParamsTable, + allocations_info_table: AllocationInfoTable, + allocations_chart: AllocationsChart, + + component_in_focus: FocusedComponent, +} + +#[derive(Default)] +enum FocusedComponent { + #[default] + QueueParamsTable, + AllocationInfoTable, } impl Screen for AutoAllocScreen { fn draw(&mut self, in_area: Rect, frame: &mut DashboardFrame) { - self.auto_allocator_fragment.draw(in_area, frame); + let layout = AutoAllocFragmentLayout::new(&in_area); + draw_text( + "AutoAlloc Info", + layout.header_chunk, + frame, + style_header_text(), + ); + + let (queues_table_style, allocations_table_style) = match self.component_in_focus { + FocusedComponent::QueueParamsTable => { + (table_style_selected(), table_style_deselected()) + } + FocusedComponent::AllocationInfoTable => { + (table_style_deselected(), table_style_selected()) + } + }; + + self.allocations_chart.draw(layout.chart_chunk, frame); + self.queue_info_table + .draw(layout.queue_info_chunk, frame, queues_table_style); + self.allocations_info_table.draw( + layout.allocation_info_chunk, + frame, + allocations_table_style, + ); + self.queue_params_table + .draw(layout.allocation_queue_params_chunk, frame); + + draw_text( + "<1>: Allocation Queues, <2>: Allocations", + layout.footer_chunk, + frame, + style_footer(), + ); } fn update(&mut self, data: &DashboardData) { - self.auto_allocator_fragment.update(data); + let queue_infos: Vec<(&QueueId, &AllocationQueueInfo)> = + data.query_allocation_queues_at(SystemTime::now()).collect(); + self.queue_info_table.update(queue_infos); + + if let Some(descriptor) = self.queue_info_table.get_selected_queue_descriptor() { + self.allocations_chart.update(data, descriptor); + } + + if let Some(queue_params) = self + .queue_info_table + .get_selected_queue_descriptor() + .and_then(|queue_id| data.query_allocation_params(queue_id)) + { + self.queue_params_table.update(queue_params) + } + + if let Some(allocations_map) = self + .queue_info_table + .get_selected_queue_descriptor() + .and_then(|queue_id| data.query_allocations_info_at(queue_id, SystemTime::now())) + { + self.allocations_info_table.update(allocations_map); + } } fn handle_key(&mut self, key: KeyEvent) { - self.auto_allocator_fragment.handle_key(key); + match self.component_in_focus { + FocusedComponent::QueueParamsTable => self.queue_info_table.handle_key(key), + FocusedComponent::AllocationInfoTable => self.allocations_info_table.handle_key(key), + }; + + match key.code { + KeyCode::Char('1') => { + self.component_in_focus = FocusedComponent::QueueParamsTable; + self.allocations_info_table.clear_selection(); + } + KeyCode::Char('2') => self.component_in_focus = FocusedComponent::AllocationInfoTable, + _ => {} + } + } +} + +/** +* __________________________ + |--------Header---------| + | Chart | + |-----------------------| + | queues | | + |----------| alloc_info | + | q_params | | + |________Footer_________| + **/ +struct AutoAllocFragmentLayout { + chart_chunk: Rect, + allocation_queue_params_chunk: Rect, + header_chunk: Rect, + queue_info_chunk: Rect, + allocation_info_chunk: Rect, + footer_chunk: Rect, +} + +impl AutoAllocFragmentLayout { + fn new(rect: &Rect) -> Self { + let auto_alloc_screen_chunks = ratatui::layout::Layout::default() + .constraints(vec![ + Constraint::Percentage(5), + Constraint::Percentage(40), + Constraint::Percentage(50), + Constraint::Percentage(5), + ]) + .direction(Direction::Vertical) + .split(*rect); + + let component_area = Layout::default() + .constraints(vec![Constraint::Percentage(50), Constraint::Percentage(50)]) + .direction(Direction::Horizontal) + .margin(0) + .split(auto_alloc_screen_chunks[2]); + + let queue_info_area = Layout::default() + .constraints(vec![Constraint::Percentage(50), Constraint::Percentage(50)]) + .direction(Direction::Vertical) + .split(component_area[0]); + + Self { + chart_chunk: auto_alloc_screen_chunks[1], + header_chunk: auto_alloc_screen_chunks[0], + queue_info_chunk: queue_info_area[0], + allocation_queue_params_chunk: queue_info_area[1], + allocation_info_chunk: component_area[1], + footer_chunk: auto_alloc_screen_chunks[3], + } } } From ae316a480fba20291a9a78c0657a54277478b997 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Mon, 21 Oct 2024 10:39:37 +0200 Subject: [PATCH 30/31] Resolve warnings --- crates/hyperqueue/src/bin/hq.rs | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/crates/hyperqueue/src/bin/hq.rs b/crates/hyperqueue/src/bin/hq.rs index 25b446fda..4a931835c 100644 --- a/crates/hyperqueue/src/bin/hq.rs +++ b/crates/hyperqueue/src/bin/hq.rs @@ -37,16 +37,14 @@ use hyperqueue::client::task::{ TaskListOpts, TaskOpts, }; use hyperqueue::common::cli::{ - get_task_id_selector, get_task_selector, ColorPolicy, CommonOpts, DashboardCommand, - DashboardOpts, GenerateCompletionOpts, HwDetectOpts, JobCommand, JobOpts, JobProgressOpts, - JobWaitOpts, OptsWithMatches, RootOptions, SubCommand, WorkerAddressOpts, WorkerCommand, - WorkerInfoOpts, WorkerListOpts, WorkerOpts, WorkerStopOpts, WorkerWaitOpts, + get_task_id_selector, get_task_selector, ColorPolicy, CommonOpts, GenerateCompletionOpts, + HwDetectOpts, JobCommand, JobOpts, JobProgressOpts, JobWaitOpts, OptsWithMatches, RootOptions, + SubCommand, WorkerAddressOpts, WorkerCommand, WorkerInfoOpts, WorkerListOpts, WorkerOpts, + WorkerStopOpts, WorkerWaitOpts, }; use hyperqueue::common::setup::setup_logging; use hyperqueue::common::utils::fs::absolute_path; use hyperqueue::server::bootstrap::get_client_session; -use hyperqueue::server::event::log::JournalReader; -use hyperqueue::server::event::Event; use hyperqueue::transfer::messages::{ FromClientMessage, IdSelector, JobInfoRequest, ToClientMessage, }; @@ -295,12 +293,15 @@ async fn command_worker_address( } #[cfg(feature = "dashboard")] -///Starts the hq Dashboard +/// Starts the hq dashboard async fn command_dashboard_start( gsettings: &GlobalSettings, - opts: DashboardOpts, + opts: hyperqueue::common::cli::DashboardOpts, ) -> anyhow::Result<()> { + use hyperqueue::common::cli::DashboardCommand; use hyperqueue::dashboard::start_ui_loop; + use hyperqueue::server::event::log::JournalReader; + use hyperqueue::server::event::Event; match opts.subcmd.unwrap_or_default() { DashboardCommand::Replay { journal, stream } => { From f9dc2e64a73c6316e79d6dc1b50c0f8be9f3da14 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Sat, 26 Oct 2024 17:24:42 +0200 Subject: [PATCH 31/31] Remove event sort --- crates/hyperqueue/src/dashboard/data/data.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/crates/hyperqueue/src/dashboard/data/data.rs b/crates/hyperqueue/src/dashboard/data/data.rs index 849b0e503..28412d1f3 100644 --- a/crates/hyperqueue/src/dashboard/data/data.rs +++ b/crates/hyperqueue/src/dashboard/data/data.rs @@ -37,8 +37,6 @@ impl DashboardData { } pub fn push_new_events(&mut self, mut events: Vec) { - events.sort_unstable_by_key(|e| e.time); - // Update data views self.worker_timeline.handle_new_events(&events); self.job_timeline.handle_new_events(&events);