diff --git a/draw_tree.py b/draw_tree.py new file mode 100644 index 0000000..aaeea32 --- /dev/null +++ b/draw_tree.py @@ -0,0 +1,106 @@ +from __future__ import annotations + +from dataclasses import dataclass + + +def main(): + activity_tree = ActivityTree() + activity_tree.activities[1] = Activity(id=1, parent=0, text="foo") + activity_tree.activities[2] = Activity(id=2, parent=0, text="bar") + activity_tree.activities[3] = Activity(id=3, parent=0, text="baz") + activity_tree.activities[4] = Activity(id=4, parent=2, text="lorem") + # activity_tree.activities[5] = Activity(id=5, parent=2, text="ipsum") + # activity_tree.activities[6] = Activity(id=6, parent=2, text="dolar") + activity_tree.activities[7] = Activity(id=7, parent=4, text="north") + activity_tree.activities[8] = Activity(id=8, parent=4, text="east") + activity_tree.activities[9] = Activity(id=9, parent=4, text="south") + activity_tree.activities[10] = Activity(id=10, parent=4, text="west") + + # For each line we need to know: + # - the depth + # - whether there are later siblings + # - whether there are earlier siblings + # + # Draw order: + # - Find all leaves + # - Start with last child of root + # - If it has children, keep going down until you hit leaves + + for entry in activity_tree.get_draw_order(): + text = activity_tree.activities[entry.id].text + leading_bars = "".join("𜹈 " if depth else " " for depth in entry.depth) + branch = "𜸨" if entry.has_later_siblings else "𜸛" + print(f"{leading_bars}{branch}𜸟 {text}") + + +class ActivityTree: + activities: dict[int, Activity] + + def __init__(self) -> None: + self.activities = {} + + def get_draw_order(self) -> list[StackEntry]: + draw_order = [] + stack = [ + StackEntry( + id=child_id, + depth=[], + has_later_siblings=True, + have_pulled_children=False, + ) + for child_id in self.get_children_in_order(0) + ] + stack[-1].has_later_siblings = False + while len(stack) > 0: + current_entry = stack.pop() + if current_entry.have_pulled_children: + if current_entry.id != 0: + draw_order.append(current_entry) + continue + + current_entry.have_pulled_children = True + stack.append(current_entry) + all_children = self.get_children_in_order(current_entry.id) + for child_id in all_children: + stack.append( + StackEntry( + id=child_id, + depth=current_entry.depth + [current_entry.has_later_siblings], + has_later_siblings=True, + have_pulled_children=False, + ) + ) + if len(all_children) > 0: + stack[-1].has_later_siblings = False + return draw_order + + def get_children_in_order(self, parent_id: int) -> list[int]: + return sorted( + child_id + for child_id, child in self.activities.items() + if child.parent == parent_id + ) + + def has_children(self, parent_id: int) -> bool: + return any( + child.parent == parent_id for _child_id, child in self.activities.items() + ) + + +@dataclass +class StackEntry: + id: int + depth: list[bool] + has_later_siblings: bool + have_pulled_children: bool # Only used when building the draw order + + +@dataclass +class Activity: + id: int + parent: int + text: str + + +if __name__ == "__main__": + main() diff --git a/src/nix_util/running_build.rs b/src/nix_util/running_build.rs index 65a7865..ec50b37 100644 --- a/src/nix_util/running_build.rs +++ b/src/nix_util/running_build.rs @@ -1,9 +1,11 @@ use std::borrow::Cow; use std::collections::BTreeMap; +use std::collections::BTreeSet; use sqlx::Row; use tokio::process::Child; use tracing::error; +use tracing::warn; use crate::Result; use crate::database::db_handle::DbHandle; @@ -110,8 +112,6 @@ impl<'db> RunningBuild<'db> { id: activity_start_unknown.id, parent: activity_start_unknown.parent, state: ActivityState::default(), - level: activity_start_unknown.level, - text: activity_start_unknown.text, })); } ActivityStartMessage::CopyPath(activity_start_copy_path) => { @@ -126,7 +126,6 @@ impl<'db> RunningBuild<'db> { id: activity_start_file_transfer.id, parent: activity_start_file_transfer.parent, state: ActivityState::default(), - text: activity_start_file_transfer.text, url: activity_start_file_transfer.url, })); } @@ -142,8 +141,6 @@ impl<'db> RunningBuild<'db> { id: activity_start_copy_paths.id, parent: activity_start_copy_paths.parent, state: ActivityState::default(), - level: activity_start_copy_paths.level, - text: activity_start_copy_paths.text, })); } ActivityStartMessage::Builds(activity_start_builds) => { @@ -158,8 +155,12 @@ impl<'db> RunningBuild<'db> { id: activity_start_build.id, parent: activity_start_build.parent, state: ActivityState::default(), - text: activity_start_build.text, drv_path: activity_start_build.drv_path, + machine_name: if activity_start_build.machine_name.len() > 0 { + Some(activity_start_build.machine_name) + } else { + None + }, })); } ActivityStartMessage::OptimizeStore(activity_start_optimize_store) => { @@ -282,16 +283,120 @@ impl<'db> RunningBuild<'db> { } fn print_current_status(&self) -> () { - let in_progress = self + let mut tree = String::new(); + let draw_order = self.get_draw_order(); + for dag_entry in draw_order { + let leading_bars = { + let mut leading_bars = String::with_capacity(3 * dag_entry.depth.len()); + for leading_bar in dag_entry + .depth + .iter() + .map(|depth| if *depth { "𜹈 " } else { " " }) + { + leading_bars.push_str(leading_bar); + } + leading_bars + }; + let branch = if dag_entry.has_later_siblings { + "𜸨" + } else { + "𜸛" + }; + let activity = self + .activity_tree + .get(&dag_entry.activity_id) + .expect("Activity should exist in the tree."); + let display_name = activity + .display_name() + .expect("Currently we always return a display name."); + let activity_id = activity.get_activity_id(); + let parent_id = activity.get_parent_id(); + tree += &format!("{leading_bars}{branch}𜸟 {display_name} {parent_id} {activity_id}\n"); + } + if tree.is_empty() { + println!("No active activities."); + } else { + print!("\n{}\n", tree); + } + } + + fn get_draw_order(&self) -> Vec { + let active_activity_ids: BTreeSet = self .activity_tree .iter() - .filter(|activity: &(&u64, &Activity)| activity.1.is_active()); - let names: Vec> = in_progress - .filter_map(|(id, activity)| activity.display_name()) + .filter(|(activity_id, activity)| activity.is_active()) + .flat_map(|(activity_id, activity)| { + PathToRootIter::new(&self.activity_tree, *activity_id) + }) .collect(); - let name_list = names.join(", "); - // TODO: Make a meaningful current status. - // println!("In progress: {name_list}"); + + let mut draw_order: Vec = Vec::new(); + let mut stack: Vec = self + .get_children_in_order(0) + .filter_map(|child_id| { + if active_activity_ids.contains(&child_id) { + Some(DrawStackEntry::HasNotVisitedChildren(DrawDagEntry { + activity_id: child_id, + depth: Vec::new(), + has_later_siblings: true, + })) + } else { + None + } + }) + .collect(); + if stack.len() == 0 { + return draw_order; + } + stack + .last_mut() + .expect("If-statement ensured this is Some()") + .set_no_later_siblings(); + + while !stack.is_empty() { + let current_entry = stack.pop().expect("While-loop ensured this is Some."); + match current_entry { + DrawStackEntry::HasNotVisitedChildren(draw_dag_entry) => { + let current_id = draw_dag_entry.activity_id; + let mut current_depth = draw_dag_entry.depth.clone(); + current_depth.push(draw_dag_entry.has_later_siblings); + stack.push(DrawStackEntry::VisitedChildren(draw_dag_entry)); + let children: Vec = self + .get_children_in_order(current_id) + .into_iter() + .filter(|child_id| active_activity_ids.contains(child_id)) + .collect(); + let has_children = !children.is_empty(); + stack.extend(children.into_iter().map(|child_id| { + DrawStackEntry::HasNotVisitedChildren(DrawDagEntry { + activity_id: child_id, + depth: current_depth.clone(), + has_later_siblings: true, + }) + })); + if has_children { + stack + .last_mut() + .expect("If-statement ensured this is Some()") + .set_no_later_siblings(); + } + } + DrawStackEntry::VisitedChildren(draw_dag_entry) => { + draw_order.push(draw_dag_entry); + } + }; + } + draw_order + } + + fn get_children_in_order(&self, parent_id: u64) -> impl Iterator { + let child_ids = self + .activity_tree + .iter() + .filter(move |(_child_id, activity)| activity.get_parent_id() == parent_id) + .flat_map(|(_child_id, activity)| TransparentIter::new(&self.activity_tree, activity)) + .map(|activity| activity.get_activity_id()); + child_ids } } @@ -316,6 +421,112 @@ impl Default for ActivityState { } } +enum DrawStackEntry { + HasNotVisitedChildren(DrawDagEntry), + VisitedChildren(DrawDagEntry), +} + +struct DrawDagEntry { + activity_id: u64, + depth: Vec, + has_later_siblings: bool, +} + +impl DrawStackEntry { + fn set_no_later_siblings(&mut self) -> () { + match self { + DrawStackEntry::HasNotVisitedChildren(draw_dag_entry) => { + draw_dag_entry.has_later_siblings = false + } + DrawStackEntry::VisitedChildren(draw_dag_entry) => { + draw_dag_entry.has_later_siblings = false + } + }; + } +} + +struct PathToRootIter<'tree> { + activity_tree: &'tree BTreeMap, + next_activity_id: u64, +} + +impl<'tree> PathToRootIter<'tree> { + fn new( + activity_tree: &'tree BTreeMap, + next_activity_id: u64, + ) -> PathToRootIter<'tree> { + PathToRootIter { + activity_tree, + next_activity_id, + } + } +} + +impl<'tree> Iterator for PathToRootIter<'tree> { + type Item = u64; + + fn next(&mut self) -> Option { + if self.next_activity_id == 0 { + return None; + } + let next_activity = self.activity_tree.get(&self.next_activity_id).expect( + "There shouldn't be a reference to an activity that does not exist in the tree.", + ); + self.next_activity_id = next_activity.get_parent_id(); + Some(next_activity.get_activity_id()) + } +} + +/// An iterator that returns either the original activity if it is not transparent, or if it is transparent, the earliest non-transparent children. +struct TransparentIter<'tree> { + activity_tree: &'tree BTreeMap, + parent: &'tree Activity, + child_index: usize, +} + +impl<'tree> TransparentIter<'tree> { + fn new( + activity_tree: &'tree BTreeMap, + parent: &'tree Activity, + ) -> TransparentIter<'tree> { + TransparentIter { + activity_tree, + parent, + child_index: 0, + } + } +} + +impl<'tree> Iterator for TransparentIter<'tree> { + type Item = &'tree Activity; + + fn next(&mut self) -> Option { + if self.child_index == usize::max_value() { + return None; + } + if !self.parent.is_transparent() { + self.child_index = usize::max_value(); + return Some(self.parent); + } + + let mut visible_children_remaining = self.child_index; + for (_child_id, child) in self.activity_tree.iter() { + if child.get_parent_id() != self.parent.get_activity_id() { + continue; + } + for visible_child in TransparentIter::new(self.activity_tree, child) { + if visible_children_remaining > 0 { + visible_children_remaining -= 1; + } else { + self.child_index += 1; + return Some(visible_child); + } + } + } + return None; + } +} + #[repr(u8)] enum Activity { Unknown(ActivityUnknown), @@ -435,61 +646,97 @@ impl Activity { } } + fn is_transparent(&self) -> bool { + match self { + Activity::Unknown(_activity_unknown) => false, + Activity::CopyPath(_activity_copy_path) => false, + Activity::FileTransfer(_activity_file_transfer) => false, + Activity::Realize(_activity_realize) => false, + Activity::CopyPaths(_activity_copy_paths) => false, + Activity::Builds(_activity_builds) => false, + Activity::Build(_activity_build) => true, + Activity::OptimizeStore(_activity_optimize_store) => false, + Activity::VerifyPaths(_activity_verify_paths) => false, + Activity::Substitute(_activity_substitute) => false, + Activity::QueryPathInfo(_activity_query_path_info) => false, + Activity::PostBuildHook(_activity_post_build_hook) => false, + Activity::BuildWaiting(_activity_build_waiting) => true, + Activity::FetchTree(_activity_fetch_tree) => false, + } + } + fn display_name(&self) -> Option> { match self { Activity::Unknown(activity_unknown) => { + warn!("Unimplemented display_name Unknown"); // TODO Some(Cow::Borrowed("Unknown")) } Activity::CopyPath(activity_copy_path) => { + warn!("Unimplemented display_name CopyPath"); // TODO Some(Cow::Borrowed("CopyPath")) } - Activity::FileTransfer(activity_file_transfer) => { - // TODO - Some(Cow::Borrowed("FileTransfer")) - } + Activity::FileTransfer(activity_file_transfer) => Some(Cow::Owned(format!( + "FileTransfer({})", + activity_file_transfer.url + ))), Activity::Realize(activity_realize) => { - // TODO + // TODO what is a good display name for this? Maybe Realize() ? Some(Cow::Borrowed("Realize")) } Activity::CopyPaths(activity_copy_paths) => { + warn!("Unimplemented display_name CopyPaths"); // TODO Some(Cow::Borrowed("CopyPaths")) } Activity::Builds(activity_builds) => { + warn!("Unimplemented display_name Builds"); // TODO Some(Cow::Borrowed("Builds")) } Activity::Build(activity_build) => { - // TODO - Some(Cow::Borrowed("Build")) + if let Some(machine_name) = &activity_build.machine_name { + Some(Cow::Owned(format!( + "Build({}@{machine_name})", + activity_build.drv_path + ))) + } else { + Some(Cow::Owned(format!("Build({})", activity_build.drv_path))) + } } Activity::OptimizeStore(activity_optimize_store) => { + warn!("Unimplemented display_name OptimizeStore"); // TODO Some(Cow::Borrowed("OptimizeStore")) } Activity::VerifyPaths(activity_verify_paths) => { + warn!("Unimplemented display_name VerifyPaths"); // TODO Some(Cow::Borrowed("VerifyPaths")) } Activity::Substitute(activity_substitute) => { + warn!("Unimplemented display_name Substitute"); // TODO Some(Cow::Borrowed("Substitute")) } Activity::QueryPathInfo(activity_query_path_info) => { + warn!("Unimplemented display_name QueryPathInfo"); // TODO Some(Cow::Borrowed("QueryPathInfo")) } Activity::PostBuildHook(activity_post_build_hook) => { + warn!("Unimplemented display_name PostBuildHook"); // TODO Some(Cow::Borrowed("PostBuildHook")) } Activity::BuildWaiting(activity_build_waiting) => { + warn!("Unimplemented display_name BuildWaiting"); // TODO Some(Cow::Borrowed("BuildWaiting")) } Activity::FetchTree(activity_fetch_tree) => { + warn!("Unimplemented display_name FetchTree"); // TODO Some(Cow::Borrowed("FetchTree")) } @@ -501,8 +748,6 @@ struct ActivityUnknown { id: u64, parent: u64, state: ActivityState, - level: u8, - text: String, } struct ActivityCopyPath { id: u64, @@ -513,7 +758,6 @@ struct ActivityFileTransfer { id: u64, parent: u64, state: ActivityState, - text: String, url: String, } struct ActivityRealize { @@ -525,8 +769,6 @@ struct ActivityCopyPaths { id: u64, parent: u64, state: ActivityState, - level: u8, - text: String, } struct ActivityBuilds { id: u64, @@ -537,8 +779,8 @@ struct ActivityBuild { id: u64, parent: u64, state: ActivityState, - text: String, drv_path: String, + machine_name: Option, } struct ActivityOptimizeStore { id: u64, @@ -574,3 +816,197 @@ struct ActivityFetchTree { parent: u64, state: ActivityState, } + +trait ActivityTreeEntry { + fn get_activity_id(&self) -> u64; + + fn get_parent_id(&self) -> u64; +} + +impl ActivityTreeEntry for Activity { + fn get_activity_id(&self) -> u64 { + match self { + Activity::Unknown(activity_unknown) => activity_unknown.get_activity_id(), + Activity::CopyPath(activity_copy_path) => activity_copy_path.get_activity_id(), + Activity::FileTransfer(activity_file_transfer) => { + activity_file_transfer.get_activity_id() + } + Activity::Realize(activity_realize) => activity_realize.get_activity_id(), + Activity::CopyPaths(activity_copy_paths) => activity_copy_paths.get_activity_id(), + Activity::Builds(activity_builds) => activity_builds.get_activity_id(), + Activity::Build(activity_build) => activity_build.get_activity_id(), + Activity::OptimizeStore(activity_optimize_store) => { + activity_optimize_store.get_activity_id() + } + Activity::VerifyPaths(activity_verify_paths) => activity_verify_paths.get_activity_id(), + Activity::Substitute(activity_substitute) => activity_substitute.get_activity_id(), + Activity::QueryPathInfo(activity_query_path_info) => { + activity_query_path_info.get_activity_id() + } + Activity::PostBuildHook(activity_post_build_hook) => { + activity_post_build_hook.get_activity_id() + } + Activity::BuildWaiting(activity_build_waiting) => { + activity_build_waiting.get_activity_id() + } + Activity::FetchTree(activity_fetch_tree) => activity_fetch_tree.get_activity_id(), + } + } + + fn get_parent_id(&self) -> u64 { + match self { + Activity::Unknown(activity_unknown) => activity_unknown.get_parent_id(), + Activity::CopyPath(activity_copy_path) => activity_copy_path.get_parent_id(), + Activity::FileTransfer(activity_file_transfer) => { + activity_file_transfer.get_parent_id() + } + Activity::Realize(activity_realize) => activity_realize.get_parent_id(), + Activity::CopyPaths(activity_copy_paths) => activity_copy_paths.get_parent_id(), + Activity::Builds(activity_builds) => activity_builds.get_parent_id(), + Activity::Build(activity_build) => activity_build.get_parent_id(), + Activity::OptimizeStore(activity_optimize_store) => { + activity_optimize_store.get_parent_id() + } + Activity::VerifyPaths(activity_verify_paths) => activity_verify_paths.get_parent_id(), + Activity::Substitute(activity_substitute) => activity_substitute.get_parent_id(), + Activity::QueryPathInfo(activity_query_path_info) => { + activity_query_path_info.get_parent_id() + } + Activity::PostBuildHook(activity_post_build_hook) => { + activity_post_build_hook.get_parent_id() + } + Activity::BuildWaiting(activity_build_waiting) => { + activity_build_waiting.get_parent_id() + } + Activity::FetchTree(activity_fetch_tree) => activity_fetch_tree.get_parent_id(), + } + } +} + +impl ActivityTreeEntry for ActivityUnknown { + fn get_activity_id(&self) -> u64 { + self.id + } + + fn get_parent_id(&self) -> u64 { + self.parent + } +} +impl ActivityTreeEntry for ActivityCopyPath { + fn get_activity_id(&self) -> u64 { + self.id + } + + fn get_parent_id(&self) -> u64 { + self.parent + } +} +impl ActivityTreeEntry for ActivityFileTransfer { + fn get_activity_id(&self) -> u64 { + self.id + } + + fn get_parent_id(&self) -> u64 { + self.parent + } +} +impl ActivityTreeEntry for ActivityRealize { + fn get_activity_id(&self) -> u64 { + self.id + } + + fn get_parent_id(&self) -> u64 { + self.parent + } +} +impl ActivityTreeEntry for ActivityCopyPaths { + fn get_activity_id(&self) -> u64 { + self.id + } + + fn get_parent_id(&self) -> u64 { + self.parent + } +} +impl ActivityTreeEntry for ActivityBuilds { + fn get_activity_id(&self) -> u64 { + self.id + } + + fn get_parent_id(&self) -> u64 { + self.parent + } +} +impl ActivityTreeEntry for ActivityBuild { + fn get_activity_id(&self) -> u64 { + self.id + } + + fn get_parent_id(&self) -> u64 { + self.parent + } +} +impl ActivityTreeEntry for ActivityOptimizeStore { + fn get_activity_id(&self) -> u64 { + self.id + } + + fn get_parent_id(&self) -> u64 { + self.parent + } +} +impl ActivityTreeEntry for ActivityVerifyPaths { + fn get_activity_id(&self) -> u64 { + self.id + } + + fn get_parent_id(&self) -> u64 { + self.parent + } +} +impl ActivityTreeEntry for ActivitySubstitute { + fn get_activity_id(&self) -> u64 { + self.id + } + + fn get_parent_id(&self) -> u64 { + self.parent + } +} +impl ActivityTreeEntry for ActivityQueryPathInfo { + fn get_activity_id(&self) -> u64 { + self.id + } + + fn get_parent_id(&self) -> u64 { + self.parent + } +} +impl ActivityTreeEntry for ActivityPostBuildHook { + fn get_activity_id(&self) -> u64 { + self.id + } + + fn get_parent_id(&self) -> u64 { + self.parent + } +} +impl ActivityTreeEntry for ActivityBuildWaiting { + fn get_activity_id(&self) -> u64 { + self.id + } + + fn get_parent_id(&self) -> u64 { + // Parent seems to always be zero? + 0 + } +} +impl ActivityTreeEntry for ActivityFetchTree { + fn get_activity_id(&self) -> u64 { + self.id + } + + fn get_parent_id(&self) -> u64 { + self.parent + } +}