diff --git a/src/command/feed_logs/runner.rs b/src/command/feed_logs/runner.rs index 385081d..16f8563 100644 --- a/src/command/feed_logs/runner.rs +++ b/src/command/feed_logs/runner.rs @@ -18,8 +18,8 @@ use crate::nix_util::RunningUpdate; pub(crate) async fn feed_logs(args: FeedLogArgs) -> Result<()> { let db_handle = DbHandle::new::(None).await?; - let mut running_build = RunningUpdate::new()?; - // let mut running_build = RunningBuild::new(&db_handle)?; + // let mut running_build = RunningUpdate::new()?; + let mut running_build = RunningBuild::new(&db_handle)?; let file_stream = FileStream::new(args.input).await?; let mut nix_output_stream = NixOutputStream::new(file_stream); while let Some(message) = nix_output_stream.next().await? { diff --git a/src/nix_util/activity.rs b/src/nix_util/activity.rs index 775c260..f7b6857 100644 --- a/src/nix_util/activity.rs +++ b/src/nix_util/activity.rs @@ -126,26 +126,6 @@ impl Activity { } } - pub(crate) fn is_transparent(&self) -> bool { - match self { - Activity::Root(_activity_root) => true, - Activity::Unknown(_activity_unknown) => false, - Activity::CopyPath(_activity_copy_path) => false, - Activity::FileTransfer(_activity_file_transfer) => false, - Activity::Realize(_activity_realize) => true, - Activity::CopyPaths(_activity_copy_paths) => true, - Activity::Builds(_activity_builds) => true, - Activity::Build(_activity_build) => false, - 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, - } - } - pub(crate) fn display_name(&self) -> Option> { match self { Activity::Root(_activity_root) => { @@ -204,63 +184,43 @@ impl Activity { pub(crate) fn get_progress_text(&self) -> Option> { match self { - Activity::Root(_activity_root) => { - // TODO - panic!("Attempted to get_progress_text of a root activity."); - } + Activity::Root(_activity_root) => None, Activity::Unknown(_activity_unknown) => None, - Activity::CopyPath(activity_copy_path) => Some(Cow::Owned(format!( - "[{}/{}]", - activity_copy_path.done, activity_copy_path.expected - ))), - Activity::FileTransfer(activity_file_transfer) => Some(Cow::Owned(format!( - "[{}/{}]", - activity_file_transfer.done, activity_file_transfer.expected - ))), - Activity::Realize(_activity_realize) => { - // TODO - panic!("Attempted to get_progress_text of a realize activity."); + Activity::CopyPath(activity_copy_path) => { + get_progress_bar(activity_copy_path.done, activity_copy_path.expected).or_else( + || get_progress_text(activity_copy_path.done, activity_copy_path.expected), + ) + } + Activity::FileTransfer(activity_file_transfer) => { + get_progress_bar(activity_file_transfer.done, activity_file_transfer.expected) + .or_else(|| { + get_progress_text( + activity_file_transfer.done, + activity_file_transfer.expected, + ) + }) + } + Activity::Realize(_activity_realize) => None, + Activity::CopyPaths(activity_copy_paths) => { + get_progress_bar(activity_copy_paths.done, activity_copy_paths.expected).or_else( + || get_progress_text(activity_copy_paths.done, activity_copy_paths.expected), + ) + } + Activity::Builds(activity_builds) => { + get_progress_bar(activity_builds.done, activity_builds.expected) + .or_else(|| get_progress_text(activity_builds.done, activity_builds.expected)) } - Activity::CopyPaths(activity_copy_paths) => Some(Cow::Owned(format!( - "[{}/{}]", - activity_copy_paths.done, activity_copy_paths.expected - ))), - Activity::Builds(activity_builds) => Some(Cow::Owned(format!( - "[{}/{}]", - activity_builds.done, activity_builds.expected - ))), Activity::Build(activity_build) => activity_build .phase .as_ref() .map(|phase| Cow::Owned(format!("[{}]", phase))), - Activity::OptimizeStore(_activity_optimize_store) => { - // TODO - panic!("Attempted to get_progress_text of a optimize store activity."); - } - Activity::VerifyPaths(_activity_verify_paths) => { - // TODO - panic!("Attempted to get_progress_text of a verify paths activity."); - } - Activity::Substitute(_activity_substitute) => { - // TODO - panic!("Attempted to get_progress_text of a substitute activity."); - } - Activity::QueryPathInfo(_activity_query_path_info) => { - // TODO - panic!("Attempted to get_progress_text of a query path info activity."); - } - Activity::PostBuildHook(_activity_post_build_hook) => { - // TODO - panic!("Attempted to get_progress_text of a post build hook activity."); - } - Activity::BuildWaiting(_activity_build_waiting) => { - // TODO - panic!("Attempted to get_progress_text of a build waiting activity."); - } - Activity::FetchTree(_activity_fetch_tree) => { - // TODO - panic!("Attempted to get_progress_text of a fetch tree activity."); - } + Activity::OptimizeStore(_activity_optimize_store) => None, + Activity::VerifyPaths(_activity_verify_paths) => None, + Activity::Substitute(_activity_substitute) => None, + Activity::QueryPathInfo(_activity_query_path_info) => None, + Activity::PostBuildHook(_activity_post_build_hook) => None, + Activity::BuildWaiting(_activity_build_waiting) => None, + Activity::FetchTree(_activity_fetch_tree) => None, } } @@ -531,3 +491,35 @@ impl Default for ActivityState { } } } + +fn get_progress_bar(done: u64, expected: u64) -> Option> { + if expected == 0 { + return None; + } + let percent = done as f32 / expected as f32; + // ○◔◑◕● + // ○◎◉● + // ▁▂▃▄▅▆▇█ + // ▏▎▍▌▋▊▉█ + // ░▒▓█ + // + // 🮷 download + // 🮸 upload + // 🮵 + // 🮶 + if percent < 0.33 { + Some(Cow::Borrowed("░")) + } else if percent < 0.66 { + Some(Cow::Borrowed("▒")) + } else if percent < 1.0 { + Some(Cow::Borrowed("▓")) + } else if percent >= 1.0 { + Some(Cow::Borrowed("█")) + } else { + None + } +} + +fn get_progress_text(done: u64, expected: u64) -> Option> { + Some(Cow::Owned(format!("[{}/{}]", done, expected))) +} diff --git a/src/nix_util/activity_tree.rs b/src/nix_util/activity_tree.rs index 2f1b909..cd8d534 100644 --- a/src/nix_util/activity_tree.rs +++ b/src/nix_util/activity_tree.rs @@ -6,7 +6,7 @@ use std::ops::Deref; use super::activity::Activity; use super::activity::ActivityRoot; -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq)] pub(crate) struct ActivityId { id: u64, } diff --git a/src/nix_util/mod.rs b/src/nix_util/mod.rs index e249b12..8073d73 100644 --- a/src/nix_util/mod.rs +++ b/src/nix_util/mod.rs @@ -6,7 +6,6 @@ mod nix_output_stream; mod output_stream; mod running_build; mod running_update; -mod transparent_iter; mod tree_iter; pub(crate) use activity_tree::ActivityIdAlreadyInTreeError; pub(crate) use activity_tree::ActivityIdNotInTreeError; diff --git a/src/nix_util/running_build.rs b/src/nix_util/running_build.rs index 3e41081..f35aa46 100644 --- a/src/nix_util/running_build.rs +++ b/src/nix_util/running_build.rs @@ -12,13 +12,16 @@ use crate::database::db_handle::DbHandle; use crate::nix_util::nix_output_stream::NixAction; use crate::nix_util::nix_output_stream::NixOutputStream; use crate::nix_util::output_stream::OutputStream; -use crate::nix_util::transparent_iter::TransparentIter; -use super::activity_tree::ActivityId; +use super::activity::Activity; +use super::activity_tree::ActivityTree; use super::activity_tree::ActivityTreeEntry; use super::activity_tree_stream::ActivityTreeStream; use super::nix_output_stream::ActivityResultMessage; use super::nix_output_stream::NixMessage; +use super::tree_iter::DrawDagEntry; +use super::tree_iter::ReverseTreeIter; +use super::tree_iter::get_draw_order; pub(crate) struct RunningBuild<'db> { db_handle: &'db DbHandle, @@ -159,143 +162,83 @@ impl<'db> RunningBuild<'db> { } fn print_current_status(&mut 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_tree().get(&dag_entry.activity_id); - let display_name = activity - .get_activity() - .display_name() - .expect("Currently we always return a display name."); - let progress_text = activity.get_activity().get_progress_text(); - let (progress, progress_sep) = match progress_text { - Some(text) => (text, " "), - None => (Cow::Borrowed(""), ""), - }; - tree += &format!("{leading_bars}{branch}𜸟 {progress}{progress_sep}{display_name}\n"); - } - if tree.is_empty() { - println!("No active activities."); - } else { - print!("\n{}\n", tree); - } + // let draw_order = get_draw_order(self.activity_tree.get_tree()); + let draw_order = ReverseTreeIter::new( + self.activity_tree.get_tree(), + None, + is_match_predicate, + is_transparent_predicate, + is_alive_predicate, + ) + .get_draw_order(); + + print_dag(self.activity_tree.get_tree(), draw_order); self.last_announce = Some(Instant::now()); } - - fn get_draw_order(&self) -> Vec { - let mut draw_order: Vec = Vec::new(); - let mut stack: Vec = self - .get_children_in_order(self.activity_tree.get_tree().get_root_id()) - .filter_map(|child| { - if child.get_activity().is_active() { - Some(DrawStackEntry::HasNotVisitedChildren(DrawDagEntry { - activity_id: child.get_activity_id().clone(), - 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.clone(); - 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) - .filter_map(|child| { - if child.get_activity().is_active() { - Some(child.get_activity_id().clone()) - } else { - None - } - }) - .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: ActivityId, - ) -> impl Iterator { - let parent = self.activity_tree.get_tree().get(&parent_id); - parent - .get_child_ids() - .iter() - .map(|child_id| self.activity_tree.get_tree().get(child_id)) - .flat_map(|child| TransparentIter::new(self.activity_tree.get_tree(), child)) - } } -enum DrawStackEntry { - HasNotVisitedChildren(DrawDagEntry), - VisitedChildren(DrawDagEntry), -} - -struct DrawDagEntry { - activity_id: ActivityId, - 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 +pub(crate) fn print_dag(activity_tree: &ActivityTree, draw_order: Vec) -> () { + let mut tree = String::new(); + 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 = activity_tree.get(&dag_entry.activity_id); + let display_name = activity + .get_activity() + .display_name() + .expect("Currently we always return a display name."); + let progress_text = activity.get_activity().get_progress_text(); + let (progress, progress_sep) = match progress_text { + Some(text) => (text, " "), + None => (Cow::Borrowed(""), ""), + }; + tree += &format!("{leading_bars}{branch}𜸟 {progress}{progress_sep}{display_name}\n"); + } + if tree.is_empty() { + println!("No active activities."); + } else { + print!("\n{}\n", tree); } } + +fn is_match_predicate(_entry: &ActivityTreeEntry) -> bool { + true +} + +pub(crate) fn is_transparent_predicate(entry: &ActivityTreeEntry) -> bool { + match entry.get_activity() { + Activity::Root(_activity_root) => true, + Activity::Unknown(_activity_unknown) => false, + Activity::CopyPath(_activity_copy_path) => false, + Activity::FileTransfer(_activity_file_transfer) => false, + Activity::Realize(_activity_realize) => true, + Activity::CopyPaths(_activity_copy_paths) => true, + Activity::Builds(_activity_builds) => true, + Activity::Build(_activity_build) => false, + 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 is_alive_predicate(entry: &ActivityTreeEntry) -> bool { + entry.get_activity().is_active() +} diff --git a/src/nix_util/running_update.rs b/src/nix_util/running_update.rs index 03d0b78..0b0711c 100644 --- a/src/nix_util/running_update.rs +++ b/src/nix_util/running_update.rs @@ -1,4 +1,7 @@ use std::borrow::Cow; +use std::ops::Deref; +use std::time::Duration; +use std::time::Instant; use tokio::process::Child; use tracing::error; @@ -9,21 +12,22 @@ use crate::nix_util::nix_output_stream::NixAction; use crate::nix_util::output_stream::OutputStream; use crate::nix_util::tree_iter::ForwardTreeIter; -use super::activity_tree::ActivityId; +use super::activity::Activity; use super::activity_tree::ActivityTreeEntry; use super::activity_tree_stream::ActivityTreeStream; use super::nix_output_stream::ActivityResultMessage; use super::nix_output_stream::NixMessage; -use super::transparent_iter::TransparentIter; pub(crate) struct RunningUpdate { activity_tree: ActivityTreeStream, + last_announce: Option, } impl RunningUpdate { pub(crate) fn new() -> Result { Ok(RunningUpdate { activity_tree: ActivityTreeStream::new(), + last_announce: None, }) } @@ -71,11 +75,11 @@ impl RunningUpdate { // } } NixAction::Start(activity_start_message) => { - println!("START: {}", serde_json::to_string(&activity_start_message)?); + // println!("START: {}", serde_json::to_string(&activity_start_message)?); self.print_current_status(); } NixAction::Stop(stop_message) => { - println!("STOP: {}", serde_json::to_string(&stop_message)?); + // println!("STOP: {}", serde_json::to_string(&stop_message)?); self.print_current_status(); } NixAction::Result(activity_result_message) => { @@ -87,19 +91,19 @@ impl RunningUpdate { ActivityResultMessage::SetPhase(_activity_result_set_phase) => {} ActivityResultMessage::Progress(activity_result_progress) => { // if activity_result_progress.expected != 0 { - println!( - "PROGRESS: {}", - serde_json::to_string(&activity_result_progress)? - ); + // println!( + // "PROGRESS: {}", + // serde_json::to_string(&activity_result_progress)? + // ); // } self.maybe_print_current_status(); } ActivityResultMessage::SetExpected(activity_result_set_expected) => { // if activity_result_set_expected.expected != 0 { - println!( - "EXPECTED: {}", - serde_json::to_string(&activity_result_set_expected)? - ); + // println!( + // "EXPECTED: {}", + // serde_json::to_string(&activity_result_set_expected)? + // ); // } self.maybe_print_current_status(); } @@ -113,24 +117,81 @@ impl RunningUpdate { Ok(()) } - fn maybe_print_current_status(&mut self) -> () {} + fn maybe_print_current_status(&mut self) -> () { + let last_announce = match self.last_announce { + Some(instant) => instant, + None => { + // If we haven't announced before, always announce. + return self.print_current_status(); + } + }; + + let now = Instant::now(); + let time_since_last_announce = now.duration_since(last_announce); + if time_since_last_announce > Duration::new(5, 0) { + return self.print_current_status(); + } + } fn print_current_status(&mut self) -> () { let nodes = ForwardTreeIter::new( self.activity_tree.get_tree(), None, - |entry| true, - |entry| false, - |entry| true, + is_match_predicate, + is_transparent_predicate, + is_alive_predicate, ); - println!("\n\n\nvvvvvv\n\n"); - for (depth, is_match, is_transparent, node) in nodes { + let mut out = Vec::new(); + for (depth, _is_match, _is_transparent, node) in nodes { + let progress_text = node + .get_activity() + .get_progress_text() + .unwrap_or(Cow::Borrowed("")); let name = node .get_activity() .display_name() .unwrap_or(Cow::Borrowed("null")); - println!("{depth}\t{is_match}\t{is_transparent}\t{name}"); + out.push(format!("{depth}\t{progress_text}\t{name}")) } - println!("\n\n\n^^^^^^\n\n"); + + if out.is_empty() { + println!("No active activities."); + } else { + println!("\n"); + for l in out { + println!("{l}\n"); + } + println!("\n"); + } + self.last_announce = Some(Instant::now()); } } + +fn is_match_predicate(entry: &ActivityTreeEntry) -> bool { + entry.get_activity().get_progress_text().is_some() +} + +pub(crate) fn is_transparent_predicate(entry: &ActivityTreeEntry) -> bool { + return false; + // match entry.get_activity() { + // Activity::Root(_activity_root) => true, + // Activity::Unknown(_activity_unknown) => false, + // Activity::CopyPath(_activity_copy_path) => false, + // Activity::FileTransfer(_activity_file_transfer) => false, + // Activity::Realize(_activity_realize) => true, + // Activity::CopyPaths(_activity_copy_paths) => true, + // Activity::Builds(_activity_builds) => true, + // Activity::Build(_activity_build) => false, + // 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 is_alive_predicate(entry: &ActivityTreeEntry) -> bool { + entry.get_activity().is_active() +} diff --git a/src/nix_util/tree_iter/forward_tree_iter/benchmarks.rs b/src/nix_util/tree_iter/forward_tree_iter/benchmarks.rs index 970a92f..6ff93b5 100644 --- a/src/nix_util/tree_iter/forward_tree_iter/benchmarks.rs +++ b/src/nix_util/tree_iter/forward_tree_iter/benchmarks.rs @@ -8,22 +8,22 @@ use test::Bencher; use super::*; -/// Build an ActivityTree matching the description at the top of the file. fn build_example_tree( num_levels: u64, num_children_per_node: u64, ) -> Result> { let mut activity_tree = ActivityTree::new(); let mut next_index = 1; - let mut previous_level: Vec = Vec::new(); + let mut previous_level: Vec = vec![0]; for _ in 0..num_levels { - if previous_level.is_empty() { - // first level, add num_children + let mut current_parents = Vec::new(); + std::mem::swap(&mut previous_level, &mut current_parents); + for parent in current_parents { for _ in 0..num_children_per_node { activity_tree.add_activity( next_index, - 0, + parent, Activity::Unknown(ActivityUnknown { state: ActivityState::Stopped, text: format!("N{next_index}"), @@ -32,24 +32,6 @@ fn build_example_tree( previous_level.push(next_index); next_index += 1; } - } else { - // Subsequent levels, add num_children to each node of the previous level. - let mut current_parents = Vec::new(); - std::mem::swap(&mut previous_level, &mut current_parents); - for parent in current_parents { - for _ in 0..num_children_per_node { - activity_tree.add_activity( - next_index, - parent, - Activity::Unknown(ActivityUnknown { - state: ActivityState::Stopped, - text: format!("N{next_index}"), - }), - )?; - previous_level.push(next_index); - next_index += 1; - } - } } } diff --git a/src/nix_util/tree_iter/forward_tree_iter/mod.rs b/src/nix_util/tree_iter/forward_tree_iter/mod.rs index 14abf22..cac79fb 100644 --- a/src/nix_util/tree_iter/forward_tree_iter/mod.rs +++ b/src/nix_util/tree_iter/forward_tree_iter/mod.rs @@ -43,13 +43,13 @@ use super::stack_entry::StackEntry; /// you would get the following output: /// /// ```text -/// 1 False False A -/// 2 True False B -/// 3 False True D -/// 3 True False E -/// 2 False False H -/// 3 False False I -/// 4 True False J +/// 0 False False A +/// 1 True False B +/// 2 False True D +/// 2 True False E +/// 1 False False H +/// 2 False False I +/// 3 True False J /// ``` pub(crate) struct ForwardTreeIter<'tree, MP, TP, AP> { activity_tree: &'tree ActivityTree, @@ -91,10 +91,15 @@ where fn get_next_announce<'iter>( &'iter mut self, ) -> Option<(usize, bool, bool, &'tree ActivityTreeEntry)> { + let origin_activity_id = *self.origin.get_activity_id().deref(); let (activity, is_match, is_transparent) = match self .stack .iter_mut() - .filter(|entry| entry.should_announce() && !entry.is_announced()) + .filter(|entry| { + entry.should_announce() + && !entry.is_announced() + && *entry.get_node().get_activity_id().deref() != origin_activity_id + }) .next() { Some(entry) => { @@ -110,7 +115,10 @@ where let depth = self .walk_up_stack(activity) - .filter(|node| !node.is_transparent()) + .filter(|node| { + !node.is_transparent() + && *node.get_node().get_activity_id().deref() != origin_activity_id + }) .count(); Some((depth, is_match, is_transparent, activity)) diff --git a/src/nix_util/tree_iter/forward_tree_iter/tests.rs b/src/nix_util/tree_iter/forward_tree_iter/tests.rs index 9cda1fa..e2477b3 100644 --- a/src/nix_util/tree_iter/forward_tree_iter/tests.rs +++ b/src/nix_util/tree_iter/forward_tree_iter/tests.rs @@ -1,114 +1,19 @@ -use crate::nix_util::activity::Activity; -use crate::nix_util::activity::ActivityState; -use crate::nix_util::activity::ActivityUnknown; +use crate::nix_util::running_build::is_transparent_predicate; +use crate::nix_util::tree_iter::test_util::build_example_tree; +use crate::nix_util::tree_iter::test_util::is_alive_predicate; +use crate::nix_util::tree_iter::test_util::is_match_predicate; use super::*; -/// Build an ActivityTree matching the description at the top of the file. -fn build_example_tree() -> Result> { - let mut activity_tree = ActivityTree::new(); - activity_tree.add_activity( - 1, - 0, - Activity::Unknown(ActivityUnknown { - state: ActivityState::Stopped, - text: "A".to_owned(), - }), - )?; - activity_tree.add_activity( - 2, - 1, - Activity::Unknown(ActivityUnknown { - state: ActivityState::Stopped, - text: "B".to_owned(), - }), - )?; - activity_tree.add_activity( - 3, - 2, - Activity::Unknown(ActivityUnknown { - state: ActivityState::Stopped, - text: "C".to_owned(), - }), - )?; - activity_tree.add_activity( - 4, - 2, - Activity::Unknown(ActivityUnknown { - state: ActivityState::Stopped, - text: "D".to_owned(), - }), - )?; - activity_tree.add_activity( - 5, - 4, - Activity::Unknown(ActivityUnknown { - state: ActivityState::Stopped, - text: "E".to_owned(), - }), - )?; - activity_tree.add_activity( - 6, - 4, - Activity::Unknown(ActivityUnknown { - state: ActivityState::Stopped, - text: "F".to_owned(), - }), - )?; - activity_tree.add_activity( - 7, - 1, - Activity::Unknown(ActivityUnknown { - state: ActivityState::Stopped, - text: "G".to_owned(), - }), - )?; - activity_tree.add_activity( - 8, - 1, - Activity::Unknown(ActivityUnknown { - state: ActivityState::Stopped, - text: "H".to_owned(), - }), - )?; - activity_tree.add_activity( - 9, - 8, - Activity::Unknown(ActivityUnknown { - state: ActivityState::Stopped, - text: "I".to_owned(), - }), - )?; - activity_tree.add_activity( - 10, - 9, - Activity::Unknown(ActivityUnknown { - state: ActivityState::Stopped, - text: "J".to_owned(), - }), - )?; - - Ok(activity_tree) -} - #[test] fn forward_tree_order() -> Result<(), Box> { let activity_tree = build_example_tree()?; let nodes = ForwardTreeIter::new( &activity_tree, None, - |entry| match *entry.get_activity_id().deref() { - 2 | 5 | 10 => true, - _ => false, - }, - |entry| match *entry.get_activity_id().deref() { - 4 => true, - _ => false, - }, - |entry| match *entry.get_activity_id().deref() { - 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 => true, - _ => false, - }, + is_match_predicate, + is_transparent_predicate, + is_alive_predicate, ); let output: Vec<_> = nodes @@ -124,13 +29,20 @@ fn forward_tree_order() -> Result<(), Box> { }) .collect(); let expected = vec![ - (1, false, false, "Unknown(A)".into()), - (2, true, false, "Unknown(B)".into()), - (3, false, true, "Unknown(D)".into()), - (3, true, false, "Unknown(E)".into()), - (2, false, false, "Unknown(H)".into()), - (3, false, false, "Unknown(I)".into()), - (4, true, false, "Unknown(J)".into()), + (0, false, false, "Unknown(N1)".into()), + (1, true, false, "Unknown(N2)".into()), + (2, false, true, "Realize".into()), // N4 + (2, true, false, "Unknown(N5)".into()), + (1, false, false, "Unknown(N8)".into()), + (2, false, false, "Unknown(N9)".into()), + (3, true, false, "Unknown(N10)".into()), + (0, false, false, "Unknown(N11)".into()), + (1, true, false, "Unknown(N12)".into()), + (2, false, true, "Realize".into()), // N14 + (2, true, false, "Unknown(N15)".into()), + (1, false, false, "Unknown(N18)".into()), + (2, false, false, "Unknown(N19)".into()), + (3, true, false, "Unknown(N20)".into()), ]; assert_eq!(output, expected); @@ -144,18 +56,9 @@ fn start_from_a() -> Result<(), Box> { let nodes = ForwardTreeIter::new( &activity_tree, Some(activity_tree.get(&activity_tree.get_activity_id(1)?)), - |entry| match *entry.get_activity_id().deref() { - 2 | 5 | 10 => true, - _ => false, - }, - |entry| match *entry.get_activity_id().deref() { - 4 => true, - _ => false, - }, - |entry| match *entry.get_activity_id().deref() { - 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 => true, - _ => false, - }, + is_match_predicate, + is_transparent_predicate, + is_alive_predicate, ); let output: Vec<_> = nodes @@ -171,12 +74,12 @@ fn start_from_a() -> Result<(), Box> { }) .collect(); let expected = vec![ - (1, true, false, "Unknown(B)".into()), - (2, false, true, "Unknown(D)".into()), - (2, true, false, "Unknown(E)".into()), - (1, false, false, "Unknown(H)".into()), - (2, false, false, "Unknown(I)".into()), - (3, true, false, "Unknown(J)".into()), + (0, true, false, "Unknown(N2)".into()), + (1, false, true, "Realize".into()), // N4 + (1, true, false, "Unknown(N5)".into()), + (0, false, false, "Unknown(N8)".into()), + (1, false, false, "Unknown(N9)".into()), + (2, true, false, "Unknown(N10)".into()), ]; assert_eq!(output, expected); @@ -190,18 +93,9 @@ fn start_from_h() -> Result<(), Box> { let nodes = ForwardTreeIter::new( &activity_tree, Some(activity_tree.get(&activity_tree.get_activity_id(8)?)), - |entry| match *entry.get_activity_id().deref() { - 2 | 5 | 10 => true, - _ => false, - }, - |entry| match *entry.get_activity_id().deref() { - 4 => true, - _ => false, - }, - |entry| match *entry.get_activity_id().deref() { - 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 => true, - _ => false, - }, + is_match_predicate, + is_transparent_predicate, + is_alive_predicate, ); let output: Vec<_> = nodes @@ -217,8 +111,8 @@ fn start_from_h() -> Result<(), Box> { }) .collect(); let expected = vec![ - (1, false, false, "Unknown(I)".into()), - (2, true, false, "Unknown(J)".into()), + (0, false, false, "Unknown(N9)".into()), + (1, true, false, "Unknown(N10)".into()), ]; assert_eq!(output, expected); @@ -232,18 +126,9 @@ fn start_from_c() -> Result<(), Box> { let nodes = ForwardTreeIter::new( &activity_tree, Some(activity_tree.get(&activity_tree.get_activity_id(3)?)), - |entry| match *entry.get_activity_id().deref() { - 2 | 5 | 10 => true, - _ => false, - }, - |entry| match *entry.get_activity_id().deref() { - 4 => true, - _ => false, - }, - |entry| match *entry.get_activity_id().deref() { - 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 => true, - _ => false, - }, + is_match_predicate, + is_transparent_predicate, + is_alive_predicate, ); let output: Vec<_> = nodes diff --git a/src/nix_util/tree_iter/mod.rs b/src/nix_util/tree_iter/mod.rs index b1aa94f..5eeafc3 100644 --- a/src/nix_util/tree_iter/mod.rs +++ b/src/nix_util/tree_iter/mod.rs @@ -1,6 +1,14 @@ +#[cfg(test)] +mod test_util; +#[cfg(test)] +mod tests; + mod forward_tree_iter; +mod original_implementation; mod reverse_tree_iter; mod stack_entry; pub(crate) use forward_tree_iter::ForwardTreeIter; +pub(crate) use original_implementation::DrawDagEntry; +pub(crate) use original_implementation::get_draw_order; pub(crate) use reverse_tree_iter::ReverseTreeIter; diff --git a/src/nix_util/tree_iter/original_implementation/benchmarks.rs b/src/nix_util/tree_iter/original_implementation/benchmarks.rs new file mode 100644 index 0000000..37e493f --- /dev/null +++ b/src/nix_util/tree_iter/original_implementation/benchmarks.rs @@ -0,0 +1,85 @@ +use test::Bencher; +use test::black_box; + +use crate::nix_util::activity::Activity; +use crate::nix_util::activity::ActivityRealize; +use crate::nix_util::activity::ActivityState; +use crate::nix_util::activity::ActivityUnknown; +use crate::nix_util::activity_tree::ActivityTree; + +use super::get_draw_order; + +fn build_example_tree( + num_levels: u64, + num_children_per_node: u64, +) -> Result> { + let mut activity_tree = ActivityTree::new(); + let mut next_index = 1; + let mut previous_level: Vec = vec![0]; + + for _ in 0..num_levels { + let mut current_parents = Vec::new(); + std::mem::swap(&mut previous_level, &mut current_parents); + for parent in current_parents { + for _ in 0..num_children_per_node { + // transparent = prime + let activity = if primal::is_prime(next_index as u64) { + Activity::Realize(ActivityRealize { + state: ActivityState::Started { + progress_numerator: 0, + progress_denominator: 0, + progress_running: 0, + progress_failed: 0, + }, + }) + } else { + Activity::Unknown(ActivityUnknown { + state: ActivityState::Started { + progress_numerator: 0, + progress_denominator: 0, + progress_running: 0, + progress_failed: 0, + }, + text: format!("N{next_index}"), + }) + }; + // if next_index is prime {} else {} + activity_tree.add_activity(next_index, parent, activity)?; + previous_level.push(next_index); + next_index += 1; + } + } + } + + Ok(activity_tree) +} + +/// Test a tree with few levels but many nodes per level. +#[bench] +fn bench_shallow_wide_tree(b: &mut Bencher) -> Result<(), Box> { + let num_levels: u64 = 3; + let num_children_per_node: u64 = 27; + + let activity_tree = build_example_tree(num_levels, num_children_per_node)?; + + b.iter(|| { + let _ = black_box(get_draw_order(&activity_tree)); + }); + + Ok(()) +} + +/// Test a tree with many levels but few nodes per level. +#[bench] +fn bench_deep_narrow_tree(b: &mut Bencher) -> Result<(), Box> { + let num_levels: u64 = 9; + let num_children_per_node: u64 = 3; + + let activity_tree = build_example_tree(num_levels, num_children_per_node)?; + + b.iter(|| { + let _ = black_box(get_draw_order(&activity_tree)); + }); + + Ok(()) +} diff --git a/src/nix_util/tree_iter/original_implementation/mod.rs b/src/nix_util/tree_iter/original_implementation/mod.rs new file mode 100644 index 0000000..d2b411b --- /dev/null +++ b/src/nix_util/tree_iter/original_implementation/mod.rs @@ -0,0 +1,112 @@ +#[cfg(test)] +mod benchmarks; +#[cfg(test)] +mod tests; +mod transparent_iter; + +use crate::nix_util::activity_tree::ActivityId; +use crate::nix_util::activity_tree::ActivityTree; +use crate::nix_util::activity_tree::ActivityTreeEntry; + +use self::transparent_iter::TransparentIter; + +pub(crate) fn get_draw_order(activity_tree: &ActivityTree) -> Vec { + let mut draw_order: Vec = Vec::new(); + let mut stack: Vec = + get_children_in_order(activity_tree, activity_tree.get_root_id()) + .filter_map(|child| { + if child.get_activity().is_active() { + Some(DrawStackEntry::HasNotVisitedChildren(DrawDagEntry { + activity_id: child.get_activity_id().clone(), + 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.clone(); + 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 = get_children_in_order(activity_tree, current_id) + .filter_map(|child| { + if child.get_activity().is_active() { + Some(child.get_activity_id().clone()) + } else { + None + } + }) + .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( + activity_tree: &ActivityTree, + parent_id: ActivityId, +) -> impl Iterator { + let parent = activity_tree.get(&parent_id); + parent + .get_child_ids() + .iter() + .map(|child_id| activity_tree.get(child_id)) + .flat_map(|child| TransparentIter::new(activity_tree, child)) +} + +enum DrawStackEntry { + HasNotVisitedChildren(DrawDagEntry), + VisitedChildren(DrawDagEntry), +} + +#[derive(Debug, PartialEq)] +pub(crate) struct DrawDagEntry { + pub(crate) activity_id: ActivityId, + pub(crate) depth: Vec, + pub(crate) 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 + } + }; + } +} diff --git a/src/nix_util/tree_iter/original_implementation/tests.rs b/src/nix_util/tree_iter/original_implementation/tests.rs new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/src/nix_util/tree_iter/original_implementation/tests.rs @@ -0,0 +1 @@ + diff --git a/src/nix_util/transparent_iter.rs b/src/nix_util/tree_iter/original_implementation/transparent_iter.rs similarity index 90% rename from src/nix_util/transparent_iter.rs rename to src/nix_util/tree_iter/original_implementation/transparent_iter.rs index f52ab62..07da449 100644 --- a/src/nix_util/transparent_iter.rs +++ b/src/nix_util/tree_iter/original_implementation/transparent_iter.rs @@ -1,5 +1,6 @@ -use super::activity_tree::ActivityTree; -use super::activity_tree::ActivityTreeEntry; +use crate::nix_util::activity_tree::ActivityTree; +use crate::nix_util::activity_tree::ActivityTreeEntry; +use crate::nix_util::running_build::is_transparent_predicate; /// An iterator that returns either the original activity if it is not transparent, or if it is transparent, the earliest non-transparent children. pub(crate) struct TransparentIter<'tree> { @@ -19,7 +20,7 @@ impl<'tree> Iterator for TransparentIter<'tree> { } }; - if !origin.get_activity().is_transparent() { + if !is_transparent_predicate(origin) { let origin = self .origin .take() @@ -34,7 +35,7 @@ impl<'tree> Iterator for TransparentIter<'tree> { loop { let current_entry = self.get_current_entry(); if let Some(child) = current_entry { - if child.get_activity().is_transparent() { + if !is_transparent_predicate(child) { self.child_index.push(0); } else { let last_index = self.child_index.last_mut().expect("Stack cannot be empty."); diff --git a/src/nix_util/tree_iter/reverse_tree_iter/benchmarks.rs b/src/nix_util/tree_iter/reverse_tree_iter/benchmarks.rs index b757e73..043d0d9 100644 --- a/src/nix_util/tree_iter/reverse_tree_iter/benchmarks.rs +++ b/src/nix_util/tree_iter/reverse_tree_iter/benchmarks.rs @@ -5,25 +5,27 @@ use crate::nix_util::activity::ActivityState; use crate::nix_util::activity::ActivityUnknown; use test::Bencher; +use test::black_box; use super::*; -/// Build an ActivityTree matching the description at the top of the file. fn build_example_tree( num_levels: u64, num_children_per_node: u64, ) -> Result> { let mut activity_tree = ActivityTree::new(); let mut next_index = 1; - let mut previous_level: Vec = Vec::new(); + let mut previous_level: Vec = vec![0]; for _ in 0..num_levels { - if previous_level.is_empty() { - // first level, add num_children + // Subsequent levels, add num_children to each node of the previous level. + let mut current_parents = Vec::new(); + std::mem::swap(&mut previous_level, &mut current_parents); + for parent in current_parents { for _ in 0..num_children_per_node { activity_tree.add_activity( next_index, - 0, + parent, Activity::Unknown(ActivityUnknown { state: ActivityState::Stopped, text: format!("N{next_index}"), @@ -32,24 +34,6 @@ fn build_example_tree( previous_level.push(next_index); next_index += 1; } - } else { - // Subsequent levels, add num_children to each node of the previous level. - let mut current_parents = Vec::new(); - std::mem::swap(&mut previous_level, &mut current_parents); - for parent in current_parents { - for _ in 0..num_children_per_node { - activity_tree.add_activity( - next_index, - parent, - Activity::Unknown(ActivityUnknown { - state: ActivityState::Stopped, - text: format!("N{next_index}"), - }), - )?; - previous_level.push(next_index); - next_index += 1; - } - } } } @@ -90,9 +74,36 @@ fn bench_shallow_wide_tree(b: &mut Bencher) -> Result<(), Box Result<(), Box> { + let num_levels: u64 = 3; + let num_children_per_node: u64 = 27; + let total_nodes = num_children_per_node.pow(num_levels as u32); + + let primes = get_primes(total_nodes); + // match = power of 2 + // transparent = prime + let activity_tree = build_example_tree(num_levels, num_children_per_node)?; + + b.iter(|| { + let nodes = ReverseTreeIter::new( + &activity_tree, + None, + |entry| (*entry.get_activity_id().deref()).count_ones() == 1, + |entry| primes.contains(entry.get_activity_id().deref()), + |_entry| true, + ); + + let _ = black_box(nodes.get_draw_order()); + }); + + Ok(()) +} + /// Test a tree with many levels but few nodes per level. #[bench] -fn bench_deep_narrow_tree(b: &mut Bencher) -> Result<(), Box> { +fn bench_deep_narrow_tree_draw(b: &mut Bencher) -> Result<(), Box> { let num_levels: u64 = 9; let num_children_per_node: u64 = 3; let total_nodes = num_children_per_node.pow(num_levels as u32); @@ -111,7 +122,7 @@ fn bench_deep_narrow_tree(b: &mut Bencher) -> Result<(), Box { activity_tree: &'tree ActivityTree, @@ -115,6 +116,36 @@ where .skip(1) .filter(|node| node.is_visited()) } + + pub(crate) fn get_draw_order(self) -> Vec { + let mut order = Vec::new(); + let mut walk_depth: Vec = Vec::new(); + + for (inp_depth, _is_match, _is_transparent, entry) in + self.filter(|(_inp_depth, _is_match, is_transparent, _entry)| !*is_transparent) + { + let mut depth = vec![false; inp_depth]; + let mut high_water_mark = inp_depth; + for preceding_depth in walk_depth.iter().rev() { + if *preceding_depth < high_water_mark { + depth[*preceding_depth] = true; + high_water_mark = *preceding_depth; + } + } + order.push(DrawDagEntry { + activity_id: entry.get_activity_id().clone(), + depth, + has_later_siblings: walk_depth + .iter() + .rev() + .take_while(|wd| **wd >= inp_depth) + .any(|wd| *wd == inp_depth), + }); + walk_depth.push(inp_depth); + } + + order + } } impl<'tree, MP, TP, AP> Iterator for ReverseTreeIter<'tree, MP, TP, AP> @@ -126,6 +157,8 @@ where type Item = (usize, bool, bool, &'tree ActivityTreeEntry); fn next(&mut self) -> Option { + let origin_activity_id = *self.origin.get_activity_id().deref(); + // Walk through stack loop { let mut should_check_for_output = false; @@ -144,8 +177,8 @@ where let is_transparent = stack_entry.is_transparent(); // If we've already processed the children, remove it from the stack. - if stack_entry.is_visited() { - if stack_entry.should_announce() { + if is_visited { + if should_announce && *node.get_activity_id().deref() != origin_activity_id { let output = Some(self.announce(node, is_match, is_transparent)); self.stack.pop(); return output; @@ -158,7 +191,7 @@ where }; if is_visited { - if should_announce { + if should_announce && *node.get_activity_id().deref() != origin_activity_id { let output = Some(self.announce(node, is_match, is_transparent)); self.stack.pop(); return output; diff --git a/src/nix_util/tree_iter/reverse_tree_iter/tests.rs b/src/nix_util/tree_iter/reverse_tree_iter/tests.rs index 23d4d02..ec8dffd 100644 --- a/src/nix_util/tree_iter/reverse_tree_iter/tests.rs +++ b/src/nix_util/tree_iter/reverse_tree_iter/tests.rs @@ -1,114 +1,19 @@ -use crate::nix_util::activity::Activity; -use crate::nix_util::activity::ActivityState; -use crate::nix_util::activity::ActivityUnknown; +use crate::nix_util::running_build::is_transparent_predicate; +use crate::nix_util::tree_iter::test_util::build_example_tree; +use crate::nix_util::tree_iter::test_util::is_alive_predicate; +use crate::nix_util::tree_iter::test_util::is_match_predicate; use super::*; -/// Build an ActivityTree matching the description at the top of the file. -fn build_example_tree() -> Result> { - let mut activity_tree = ActivityTree::new(); - activity_tree.add_activity( - 1, - 0, - Activity::Unknown(ActivityUnknown { - state: ActivityState::Stopped, - text: "A".to_owned(), - }), - )?; - activity_tree.add_activity( - 2, - 1, - Activity::Unknown(ActivityUnknown { - state: ActivityState::Stopped, - text: "B".to_owned(), - }), - )?; - activity_tree.add_activity( - 3, - 2, - Activity::Unknown(ActivityUnknown { - state: ActivityState::Stopped, - text: "C".to_owned(), - }), - )?; - activity_tree.add_activity( - 4, - 2, - Activity::Unknown(ActivityUnknown { - state: ActivityState::Stopped, - text: "D".to_owned(), - }), - )?; - activity_tree.add_activity( - 5, - 4, - Activity::Unknown(ActivityUnknown { - state: ActivityState::Stopped, - text: "E".to_owned(), - }), - )?; - activity_tree.add_activity( - 6, - 4, - Activity::Unknown(ActivityUnknown { - state: ActivityState::Stopped, - text: "F".to_owned(), - }), - )?; - activity_tree.add_activity( - 7, - 1, - Activity::Unknown(ActivityUnknown { - state: ActivityState::Stopped, - text: "G".to_owned(), - }), - )?; - activity_tree.add_activity( - 8, - 1, - Activity::Unknown(ActivityUnknown { - state: ActivityState::Stopped, - text: "H".to_owned(), - }), - )?; - activity_tree.add_activity( - 9, - 8, - Activity::Unknown(ActivityUnknown { - state: ActivityState::Stopped, - text: "I".to_owned(), - }), - )?; - activity_tree.add_activity( - 10, - 9, - Activity::Unknown(ActivityUnknown { - state: ActivityState::Stopped, - text: "J".to_owned(), - }), - )?; - - Ok(activity_tree) -} - #[test] fn reverse_tree_order() -> Result<(), Box> { let activity_tree = build_example_tree()?; let nodes = ReverseTreeIter::new( &activity_tree, None, - |entry| match *entry.get_activity_id().deref() { - 2 | 5 | 10 => true, - _ => false, - }, - |entry| match *entry.get_activity_id().deref() { - 4 => true, - _ => false, - }, - |entry| match *entry.get_activity_id().deref() { - 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 => true, - _ => false, - }, + is_match_predicate, + is_transparent_predicate, + is_alive_predicate, ); let output: Vec<_> = nodes @@ -124,13 +29,20 @@ fn reverse_tree_order() -> Result<(), Box> { }) .collect(); let expected = vec![ - (4, true, false, "Unknown(J)".into()), - (3, false, false, "Unknown(I)".into()), - (2, false, false, "Unknown(H)".into()), - (3, true, false, "Unknown(E)".into()), - (3, false, true, "Unknown(D)".into()), - (2, true, false, "Unknown(B)".into()), - (1, false, false, "Unknown(A)".into()), + (3, true, false, "Unknown(N20)".into()), + (2, false, false, "Unknown(N19)".into()), + (1, false, false, "Unknown(N18)".into()), + (2, true, false, "Unknown(N15)".into()), + (2, false, true, "Realize".into()), + (1, true, false, "Unknown(N12)".into()), + (0, false, false, "Unknown(N11)".into()), + (3, true, false, "Unknown(N10)".into()), + (2, false, false, "Unknown(N9)".into()), + (1, false, false, "Unknown(N8)".into()), + (2, true, false, "Unknown(N5)".into()), + (2, false, true, "Realize".into()), + (1, true, false, "Unknown(N2)".into()), + (0, false, false, "Unknown(N1)".into()), ]; assert_eq!(output, expected); @@ -139,23 +51,14 @@ fn reverse_tree_order() -> Result<(), Box> { } #[test] -fn start_from_a() -> Result<(), Box> { +fn start_from_1() -> Result<(), Box> { let activity_tree = build_example_tree()?; let nodes = ReverseTreeIter::new( &activity_tree, Some(activity_tree.get(&activity_tree.get_activity_id(1)?)), - |entry| match *entry.get_activity_id().deref() { - 2 | 5 | 10 => true, - _ => false, - }, - |entry| match *entry.get_activity_id().deref() { - 4 => true, - _ => false, - }, - |entry| match *entry.get_activity_id().deref() { - 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 => true, - _ => false, - }, + is_match_predicate, + is_transparent_predicate, + is_alive_predicate, ); let output: Vec<_> = nodes @@ -171,12 +74,12 @@ fn start_from_a() -> Result<(), Box> { }) .collect(); let expected = vec![ - (3, true, false, "Unknown(J)".into()), - (2, false, false, "Unknown(I)".into()), - (1, false, false, "Unknown(H)".into()), - (2, true, false, "Unknown(E)".into()), - (2, false, true, "Unknown(D)".into()), - (1, true, false, "Unknown(B)".into()), + (3, true, false, "Unknown(N10)".into()), + (2, false, false, "Unknown(N9)".into()), + (1, false, false, "Unknown(N8)".into()), + (2, true, false, "Unknown(N5)".into()), + (2, false, true, "Realize".into()), // N4 + (1, true, false, "Unknown(N2)".into()), ]; assert_eq!(output, expected); @@ -185,23 +88,14 @@ fn start_from_a() -> Result<(), Box> { } #[test] -fn start_from_h() -> Result<(), Box> { +fn start_from_8() -> Result<(), Box> { let activity_tree = build_example_tree()?; let nodes = ReverseTreeIter::new( &activity_tree, Some(activity_tree.get(&activity_tree.get_activity_id(8)?)), - |entry| match *entry.get_activity_id().deref() { - 2 | 5 | 10 => true, - _ => false, - }, - |entry| match *entry.get_activity_id().deref() { - 4 => true, - _ => false, - }, - |entry| match *entry.get_activity_id().deref() { - 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 => true, - _ => false, - }, + is_match_predicate, + is_transparent_predicate, + is_alive_predicate, ); let output: Vec<_> = nodes @@ -217,8 +111,8 @@ fn start_from_h() -> Result<(), Box> { }) .collect(); let expected = vec![ - (2, true, false, "Unknown(J)".into()), - (1, false, false, "Unknown(I)".into()), + (2, true, false, "Unknown(N10)".into()), + (1, false, false, "Unknown(N9)".into()), ]; assert_eq!(output, expected); @@ -227,23 +121,14 @@ fn start_from_h() -> Result<(), Box> { } #[test] -fn start_from_c() -> Result<(), Box> { +fn start_from_3() -> Result<(), Box> { let activity_tree = build_example_tree()?; let nodes = ReverseTreeIter::new( &activity_tree, Some(activity_tree.get(&activity_tree.get_activity_id(3)?)), - |entry| match *entry.get_activity_id().deref() { - 2 | 5 | 10 => true, - _ => false, - }, - |entry| match *entry.get_activity_id().deref() { - 4 => true, - _ => false, - }, - |entry| match *entry.get_activity_id().deref() { - 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 => true, - _ => false, - }, + is_match_predicate, + is_transparent_predicate, + is_alive_predicate, ); let output: Vec<_> = nodes @@ -264,3 +149,82 @@ fn start_from_c() -> Result<(), Box> { Ok(()) } + +#[test] +fn reverse_tree_draw_order() -> Result<(), Box> { + let activity_tree = build_example_tree()?; + let expected = vec![ + DrawDagEntry { + activity_id: activity_tree.get_activity_id(20).unwrap(), + depth: vec![false, false, false], + has_later_siblings: false, + }, + DrawDagEntry { + activity_id: activity_tree.get_activity_id(19).unwrap(), + depth: vec![false, false], + has_later_siblings: false, + }, + DrawDagEntry { + activity_id: activity_tree.get_activity_id(18).unwrap(), + depth: vec![false], + has_later_siblings: false, + }, + DrawDagEntry { + activity_id: activity_tree.get_activity_id(15).unwrap(), + depth: vec![false, true], + has_later_siblings: false, + }, + DrawDagEntry { + activity_id: activity_tree.get_activity_id(12).unwrap(), + depth: vec![false], + has_later_siblings: true, + }, + DrawDagEntry { + activity_id: activity_tree.get_activity_id(11).unwrap(), + depth: vec![], + has_later_siblings: false, + }, + DrawDagEntry { + activity_id: activity_tree.get_activity_id(10).unwrap(), + depth: vec![true, false, false], + has_later_siblings: false, + }, + DrawDagEntry { + activity_id: activity_tree.get_activity_id(9).unwrap(), + depth: vec![true, false], + has_later_siblings: false, + }, + DrawDagEntry { + activity_id: activity_tree.get_activity_id(8).unwrap(), + depth: vec![true], + has_later_siblings: false, + }, + DrawDagEntry { + activity_id: activity_tree.get_activity_id(5).unwrap(), + depth: vec![true, true], + has_later_siblings: false, + }, + DrawDagEntry { + activity_id: activity_tree.get_activity_id(2).unwrap(), + depth: vec![true], + has_later_siblings: true, + }, + DrawDagEntry { + activity_id: activity_tree.get_activity_id(1).unwrap(), + depth: vec![], + has_later_siblings: true, + }, + ]; + + let reverse_draw_order = ReverseTreeIter::new( + &activity_tree, + None, + is_match_predicate, + is_transparent_predicate, + is_alive_predicate, + ) + .get_draw_order(); + + assert_eq!(reverse_draw_order, expected); + Ok(()) +} diff --git a/src/nix_util/tree_iter/test_util.rs b/src/nix_util/tree_iter/test_util.rs new file mode 100644 index 0000000..ff1e7f2 --- /dev/null +++ b/src/nix_util/tree_iter/test_util.rs @@ -0,0 +1,392 @@ +use std::ops::Deref; + +use crate::nix_util::activity::Activity; +use crate::nix_util::activity::ActivityRealize; +use crate::nix_util::activity::ActivityState; +use crate::nix_util::activity::ActivityUnknown; +use crate::nix_util::activity_tree::ActivityTree; +use crate::nix_util::activity_tree::ActivityTreeEntry; + +impl Activity { + pub(crate) fn start(&mut self) -> () { + match self { + Activity::Root(_activity_root) => { + panic!("Attempted to start root activity."); + } + Activity::Unknown(activity_unknown) => { + activity_unknown.state = ActivityState::Started { + progress_numerator: 0, + progress_denominator: 0, + progress_running: 0, + progress_failed: 0, + }; + } + Activity::CopyPath(activity_copy_path) => { + activity_copy_path.state = ActivityState::Started { + progress_numerator: 0, + progress_denominator: 0, + progress_running: 0, + progress_failed: 0, + }; + } + Activity::FileTransfer(activity_file_transfer) => { + activity_file_transfer.state = ActivityState::Started { + progress_numerator: 0, + progress_denominator: 0, + progress_running: 0, + progress_failed: 0, + }; + } + Activity::Realize(activity_realize) => { + activity_realize.state = ActivityState::Started { + progress_numerator: 0, + progress_denominator: 0, + progress_running: 0, + progress_failed: 0, + }; + } + Activity::CopyPaths(activity_copy_paths) => { + activity_copy_paths.state = ActivityState::Started { + progress_numerator: 0, + progress_denominator: 0, + progress_running: 0, + progress_failed: 0, + }; + } + Activity::Builds(activity_builds) => { + activity_builds.state = ActivityState::Started { + progress_numerator: 0, + progress_denominator: 0, + progress_running: 0, + progress_failed: 0, + }; + } + Activity::Build(activity_build) => { + activity_build.state = ActivityState::Started { + progress_numerator: 0, + progress_denominator: 0, + progress_running: 0, + progress_failed: 0, + }; + } + Activity::OptimizeStore(activity_optimize_store) => { + activity_optimize_store.state = ActivityState::Started { + progress_numerator: 0, + progress_denominator: 0, + progress_running: 0, + progress_failed: 0, + }; + } + Activity::VerifyPaths(activity_verify_paths) => { + activity_verify_paths.state = ActivityState::Started { + progress_numerator: 0, + progress_denominator: 0, + progress_running: 0, + progress_failed: 0, + }; + } + Activity::Substitute(activity_substitute) => { + activity_substitute.state = ActivityState::Started { + progress_numerator: 0, + progress_denominator: 0, + progress_running: 0, + progress_failed: 0, + }; + } + Activity::QueryPathInfo(activity_query_path_info) => { + activity_query_path_info.state = ActivityState::Started { + progress_numerator: 0, + progress_denominator: 0, + progress_running: 0, + progress_failed: 0, + }; + } + Activity::PostBuildHook(activity_post_build_hook) => { + activity_post_build_hook.state = ActivityState::Started { + progress_numerator: 0, + progress_denominator: 0, + progress_running: 0, + progress_failed: 0, + }; + } + Activity::BuildWaiting(activity_build_waiting) => { + activity_build_waiting.state = ActivityState::Started { + progress_numerator: 0, + progress_denominator: 0, + progress_running: 0, + progress_failed: 0, + }; + } + Activity::FetchTree(activity_fetch_tree) => { + activity_fetch_tree.state = ActivityState::Started { + progress_numerator: 0, + progress_denominator: 0, + progress_running: 0, + progress_failed: 0, + }; + } + } + } +} + +/// Build the following ActivityTree: +/// +/// 𜸛𜸟 Unknown(N20) +/// 𜸛𜸟 Unknown(N19) +/// 𜸛𜸟 Unknown(N18) +/// 𜹈 𜸛𜸟 Unknown(N15) +/// 𜸨𜸟 Unknown(N12) +/// 𜸛𜸟 Unknown(N11) +/// 𜹈 𜸛𜸟 Unknown(N10) +/// 𜹈 𜸛𜸟 Unknown(N9) +/// 𜹈 𜸛𜸟 Unknown(N8) +/// 𜹈 𜹈 𜸛𜸟 Unknown(N5) +/// 𜹈 𜸨𜸟 Unknown(N2) +/// 𜸨𜸟 Unknown(N1) +pub(crate) fn build_example_tree() -> Result> { + let instructions = vec![ + BuildInstruction { + activity_id: 1, + parent_id: 0, + is_match: false, + is_transparent: false, + }, + BuildInstruction { + activity_id: 2, + parent_id: 1, + is_match: true, + is_transparent: false, + }, + BuildInstruction { + activity_id: 3, + parent_id: 2, + is_match: false, + is_transparent: false, + }, + BuildInstruction { + activity_id: 4, + parent_id: 2, + is_match: false, + is_transparent: true, + }, + BuildInstruction { + activity_id: 5, + parent_id: 4, + is_match: true, + is_transparent: false, + }, + BuildInstruction { + activity_id: 6, + parent_id: 4, + is_match: false, + is_transparent: false, + }, + BuildInstruction { + activity_id: 7, + parent_id: 1, + is_match: false, + is_transparent: false, + }, + BuildInstruction { + activity_id: 8, + parent_id: 1, + is_match: false, + is_transparent: false, + }, + BuildInstruction { + activity_id: 9, + parent_id: 8, + is_match: false, + is_transparent: false, + }, + BuildInstruction { + activity_id: 10, + parent_id: 9, + is_match: true, + is_transparent: false, + }, + BuildInstruction { + activity_id: 11, + parent_id: 0, + is_match: false, + is_transparent: false, + }, + BuildInstruction { + activity_id: 12, + parent_id: 11, + is_match: true, + is_transparent: false, + }, + BuildInstruction { + activity_id: 13, + parent_id: 12, + is_match: false, + is_transparent: false, + }, + BuildInstruction { + activity_id: 14, + parent_id: 12, + is_match: false, + is_transparent: true, + }, + BuildInstruction { + activity_id: 15, + parent_id: 14, + is_match: true, + is_transparent: false, + }, + BuildInstruction { + activity_id: 16, + parent_id: 14, + is_match: false, + is_transparent: false, + }, + BuildInstruction { + activity_id: 17, + parent_id: 11, + is_match: false, + is_transparent: false, + }, + BuildInstruction { + activity_id: 18, + parent_id: 11, + is_match: false, + is_transparent: false, + }, + BuildInstruction { + activity_id: 19, + parent_id: 18, + is_match: false, + is_transparent: false, + }, + BuildInstruction { + activity_id: 20, + parent_id: 19, + is_match: true, + is_transparent: false, + }, + ]; + + let activity_tree = build_activity_tree_from_instructions(&instructions, false)?; + + Ok(activity_tree) +} + +#[derive(Debug, Clone)] +pub(crate) struct BuildInstruction { + activity_id: u64, + parent_id: u64, + is_transparent: bool, + is_match: bool, +} + +pub(crate) fn get_every_possible_addition<'old, 'new>( + old_state: &'old Vec, +) -> Vec> { + let mut out = Vec::new(); + for is_transparent in [true, false] { + for is_match in [true, false] { + let next_activity_id = old_state.last().map(|bi| bi.activity_id).unwrap_or(0) + 1; + for parent_id in 0..next_activity_id { + let mut new_state = old_state.clone(); + new_state.push(BuildInstruction { + activity_id: next_activity_id, + parent_id, + is_transparent, + is_match, + }); + out.push(new_state); + } + } + } + out +} + +pub(crate) fn build_activity_tree_from_instructions( + instructions: &Vec, + start_parents: bool, +) -> Result> { + let mut activity_tree = ActivityTree::new(); + for instruct in instructions { + match (instruct.is_transparent, instruct.is_match) { + (true, true) => { + activity_tree.add_activity( + instruct.activity_id, + instruct.parent_id, + Activity::Realize(ActivityRealize { + state: ActivityState::Started { + progress_numerator: 0, + progress_denominator: 0, + progress_running: 0, + progress_failed: 0, + }, + }), + )?; + // Walk up tree marking parents as match + if start_parents { + let mut parent_id = instruct.parent_id; + while parent_id > 0 { + let parent = + activity_tree.get_mut(&activity_tree.get_activity_id(parent_id)?); + parent.get_mut_activity().start(); + parent_id = *parent.get_parent_id().deref(); + } + } + } + (true, false) => { + activity_tree.add_activity( + instruct.activity_id, + instruct.parent_id, + Activity::Realize(ActivityRealize { + state: ActivityState::Stopped, + }), + )?; + } + (false, true) => { + activity_tree.add_activity( + instruct.activity_id, + instruct.parent_id, + Activity::Unknown(ActivityUnknown { + state: ActivityState::Started { + progress_numerator: 0, + progress_denominator: 0, + progress_running: 0, + progress_failed: 0, + }, + text: format!("N{}", instruct.activity_id), + }), + )?; + // Walk up tree marking parents as match + if start_parents { + let mut parent_id = instruct.parent_id; + while parent_id > 0 { + let parent = + activity_tree.get_mut(&activity_tree.get_activity_id(parent_id)?); + parent.get_mut_activity().start(); + parent_id = *parent.get_parent_id().deref(); + } + } + } + (false, false) => { + activity_tree.add_activity( + instruct.activity_id, + instruct.parent_id, + Activity::Unknown(ActivityUnknown { + state: ActivityState::Stopped, + text: format!("N{}", instruct.activity_id), + }), + )?; + } + } + } + + Ok(activity_tree) +} + +pub(crate) fn is_match_predicate(entry: &ActivityTreeEntry) -> bool { + entry.get_activity().is_active() +} + +pub(crate) fn is_alive_predicate(_entry: &ActivityTreeEntry) -> bool { + true +} diff --git a/src/nix_util/tree_iter/tests.rs b/src/nix_util/tree_iter/tests.rs new file mode 100644 index 0000000..70c0e03 --- /dev/null +++ b/src/nix_util/tree_iter/tests.rs @@ -0,0 +1,55 @@ +use crate::nix_util::running_build::is_transparent_predicate; +use crate::nix_util::running_build::print_dag; + +use super::ReverseTreeIter; +use super::get_draw_order; +use super::test_util::BuildInstruction; +use super::test_util::build_activity_tree_from_instructions; +use super::test_util::get_every_possible_addition; + +fn compare_single_tree( + instructions: &Vec, +) -> Result<(), Box> { + let activity_tree = build_activity_tree_from_instructions(instructions, true)?; + let original_draw_order = get_draw_order(&activity_tree); + let reverse_draw_order = ReverseTreeIter::new( + &activity_tree, + None, + |entry| entry.get_activity().is_active(), + is_transparent_predicate, + |_entry| true, + ) + .get_draw_order(); + + if original_draw_order != reverse_draw_order { + println!("Draw order mismatch!"); + println!("Instructions: {:?}", instructions); + println!("Original: {:?}", original_draw_order); + println!("Reverse: {:?}", reverse_draw_order); + print_dag(&activity_tree, original_draw_order); + print_dag(&activity_tree, reverse_draw_order); + return Err("mismatch".into()); + } + + Ok(()) +} + +#[test] +#[ignore] +fn reverse_tree_order_draw_order_iterative() -> Result<(), Box> { + let mut prev_generation: Vec> = Vec::new(); + let mut current_generation: Vec> = vec![vec![]]; + + // Iterate through every possible tree with N nodes. + for _ in 0..7 { + std::mem::swap(&mut prev_generation, &mut current_generation); + for old_state in prev_generation.drain(..) { + for new_state in get_every_possible_addition(&old_state) { + compare_single_tree(&new_state)?; + current_generation.push(new_state); + } + } + } + + Ok(()) +}