forked from NationTech/harmony
fix: improve usage of indicatif for tracking progress (#101)
The multiprogress wasn't used properly and leading to conflicting progress bars (within our own progress bars, as well as the log wrapper). This PR introduce a layer on top of `indicatif::MultiProgress` to properly handle sections of progress bars, where we can dynamically add/update/remove progress bars from any sections. We can see in the demo that new sections + progress bars are added on the fly and that extra logs (e.g. info logs) are appended on top of the progress bars. Progress are also grouped together based on their parent score. Co-authored-by: Ian Letourneau <letourneau.ian@gmail.com> Co-authored-by: johnride <jg@nationtech.io> Reviewed-on: NationTech/harmony#101
This commit is contained in:
@@ -2,18 +2,15 @@ use harmony::{
|
||||
instrumentation::{self, HarmonyEvent},
|
||||
topology::TopologyStatus,
|
||||
};
|
||||
use indicatif::{MultiProgress, ProgressBar};
|
||||
use indicatif::MultiProgress;
|
||||
use indicatif_log_bridge::LogWrapper;
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
sync::{Arc, Mutex},
|
||||
};
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use crate::progress;
|
||||
use crate::progress::{IndicatifProgressTracker, ProgressTracker};
|
||||
|
||||
pub fn init() -> tokio::task::JoinHandle<()> {
|
||||
configure_logger();
|
||||
let handle = tokio::spawn(handle_events());
|
||||
let base_progress = configure_logger();
|
||||
let handle = tokio::spawn(handle_events(base_progress));
|
||||
|
||||
loop {
|
||||
if instrumentation::instrument(HarmonyEvent::HarmonyStarted).is_ok() {
|
||||
@@ -24,32 +21,45 @@ pub fn init() -> tokio::task::JoinHandle<()> {
|
||||
handle
|
||||
}
|
||||
|
||||
fn configure_logger() {
|
||||
fn configure_logger() -> MultiProgress {
|
||||
let logger =
|
||||
env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")).build();
|
||||
let level = logger.filter();
|
||||
let multi = MultiProgress::new();
|
||||
LogWrapper::new(multi.clone(), logger).try_init().unwrap();
|
||||
let progress = MultiProgress::new();
|
||||
|
||||
LogWrapper::new(progress.clone(), logger)
|
||||
.try_init()
|
||||
.unwrap();
|
||||
log::set_max_level(level);
|
||||
|
||||
progress
|
||||
}
|
||||
|
||||
async fn handle_events() {
|
||||
instrumentation::subscribe("Harmony CLI Logger", {
|
||||
let sections: Arc<Mutex<HashMap<String, MultiProgress>>> =
|
||||
Arc::new(Mutex::new(HashMap::new()));
|
||||
let progress_bars: Arc<Mutex<HashMap<String, ProgressBar>>> =
|
||||
Arc::new(Mutex::new(HashMap::new()));
|
||||
async fn handle_events(base_progress: MultiProgress) {
|
||||
let progress_tracker = Arc::new(IndicatifProgressTracker::new(base_progress.clone()));
|
||||
let preparing_topology = Arc::new(Mutex::new(false));
|
||||
let current_score: Arc<Mutex<Option<String>>> = Arc::new(Mutex::new(None));
|
||||
|
||||
instrumentation::subscribe("Harmony CLI Logger", {
|
||||
move |event| {
|
||||
let sections_clone = Arc::clone(§ions);
|
||||
let progress_bars_clone = Arc::clone(&progress_bars);
|
||||
let progress_tracker = Arc::clone(&progress_tracker);
|
||||
let preparing_topology = Arc::clone(&preparing_topology);
|
||||
let current_score = Arc::clone(¤t_score);
|
||||
|
||||
async move {
|
||||
let mut sections = sections_clone.lock().unwrap();
|
||||
let mut progress_bars = progress_bars_clone.lock().unwrap();
|
||||
let mut preparing_topology = preparing_topology.lock().unwrap();
|
||||
let mut current_score = current_score.lock().unwrap();
|
||||
|
||||
match event {
|
||||
HarmonyEvent::HarmonyStarted => {}
|
||||
HarmonyEvent::HarmonyFinished => {
|
||||
progress_tracker.add_section(
|
||||
"harmony-summary",
|
||||
&format!("\n{} Harmony completed\n\n", crate::theme::EMOJI_HARMONY),
|
||||
);
|
||||
progress_tracker.add_section("harmony-finished", "\n\n");
|
||||
return false;
|
||||
}
|
||||
HarmonyEvent::TopologyStateChanged {
|
||||
topology,
|
||||
status,
|
||||
@@ -60,111 +70,95 @@ async fn handle_events() {
|
||||
match status {
|
||||
TopologyStatus::Queued => {}
|
||||
TopologyStatus::Preparing => {
|
||||
let section = progress::new_section(format!(
|
||||
"{} Preparing environment: {topology}...",
|
||||
crate::theme::EMOJI_TOPOLOGY,
|
||||
));
|
||||
(*sections).insert(section_key, section);
|
||||
progress_tracker.add_section(
|
||||
§ion_key,
|
||||
&format!(
|
||||
"\n{} Preparing environment: {topology}...",
|
||||
crate::theme::EMOJI_TOPOLOGY
|
||||
),
|
||||
);
|
||||
(*preparing_topology) = true;
|
||||
}
|
||||
TopologyStatus::Success => {
|
||||
let section = (*sections).get(§ion_key).unwrap();
|
||||
let progress = progress::add_spinner(section, "".into());
|
||||
|
||||
progress::success(
|
||||
section,
|
||||
Some(progress),
|
||||
message.unwrap_or("".into()),
|
||||
);
|
||||
|
||||
(*sections).remove(§ion_key);
|
||||
(*preparing_topology) = false;
|
||||
progress_tracker.add_task(§ion_key, "topology-success", "");
|
||||
progress_tracker
|
||||
.finish_task("topology-success", &message.unwrap_or("".into()));
|
||||
}
|
||||
TopologyStatus::Noop => {
|
||||
let section = (*sections).get(§ion_key).unwrap();
|
||||
let progress = progress::add_spinner(section, "".into());
|
||||
|
||||
progress::skip(
|
||||
section,
|
||||
Some(progress),
|
||||
message.unwrap_or("".into()),
|
||||
);
|
||||
|
||||
(*sections).remove(§ion_key);
|
||||
(*preparing_topology) = false;
|
||||
progress_tracker.add_task(§ion_key, "topology-skip", "");
|
||||
progress_tracker
|
||||
.skip_task("topology-skip", &message.unwrap_or("".into()));
|
||||
}
|
||||
TopologyStatus::Error => {
|
||||
let section = (*sections).get(§ion_key).unwrap();
|
||||
let progress = progress::add_spinner(section, "".into());
|
||||
|
||||
progress::error(
|
||||
section,
|
||||
Some(progress),
|
||||
message.unwrap_or("".into()),
|
||||
);
|
||||
|
||||
(*sections).remove(§ion_key);
|
||||
progress_tracker.add_task(§ion_key, "topology-error", "");
|
||||
(*preparing_topology) = false;
|
||||
progress_tracker
|
||||
.fail_task("topology-error", &message.unwrap_or("".into()));
|
||||
}
|
||||
}
|
||||
}
|
||||
HarmonyEvent::InterpretExecutionStarted {
|
||||
execution_id: task_key,
|
||||
topology,
|
||||
interpret,
|
||||
interpret: _,
|
||||
score,
|
||||
message,
|
||||
} => {
|
||||
let section_key = if (*sections).contains_key(&topology_key(&topology)) {
|
||||
let is_key_topology = (*preparing_topology)
|
||||
&& progress_tracker.contains_section(&topology_key(&topology));
|
||||
let is_key_current_score = current_score.is_some()
|
||||
&& progress_tracker
|
||||
.contains_section(&score_key(¤t_score.clone().unwrap()));
|
||||
let is_key_score = progress_tracker.contains_section(&score_key(&score));
|
||||
|
||||
let section_key = if is_key_topology {
|
||||
topology_key(&topology)
|
||||
} else if (*sections).contains_key(&score_key(&score)) {
|
||||
score_key(&interpret)
|
||||
} else if is_key_current_score {
|
||||
score_key(¤t_score.clone().unwrap())
|
||||
} else if is_key_score {
|
||||
score_key(&score)
|
||||
} else {
|
||||
(*current_score) = Some(score.clone());
|
||||
let key = score_key(&score);
|
||||
let section = progress::new_section(format!(
|
||||
"\n{} Interpreting score: {score}...",
|
||||
crate::theme::EMOJI_SCORE,
|
||||
));
|
||||
(*sections).insert(key.clone(), section);
|
||||
progress_tracker.add_section(
|
||||
&key,
|
||||
&format!(
|
||||
"{} Interpreting score: {score}...",
|
||||
crate::theme::EMOJI_SCORE
|
||||
),
|
||||
);
|
||||
key
|
||||
};
|
||||
let section = (*sections).get(§ion_key).unwrap();
|
||||
let progress_bar = progress::add_spinner(section, message);
|
||||
|
||||
(*progress_bars).insert(interpret_key(&interpret), progress_bar);
|
||||
progress_tracker.add_task(§ion_key, &task_key, &message);
|
||||
}
|
||||
HarmonyEvent::InterpretExecutionFinished {
|
||||
topology,
|
||||
interpret,
|
||||
execution_id: task_key,
|
||||
topology: _,
|
||||
interpret: _,
|
||||
score,
|
||||
outcome,
|
||||
} => {
|
||||
let has_topology = (*sections).contains_key(&topology_key(&topology));
|
||||
let section_key = if has_topology {
|
||||
topology_key(&topology)
|
||||
} else {
|
||||
score_key(&score)
|
||||
};
|
||||
|
||||
let section = (*sections).get(§ion_key).unwrap();
|
||||
let progress_bar =
|
||||
(*progress_bars).get(&interpret_key(&interpret)).cloned();
|
||||
|
||||
let _ = section.clear();
|
||||
if current_score.is_some() && current_score.clone().unwrap() == score {
|
||||
(*current_score) = None;
|
||||
}
|
||||
|
||||
match outcome {
|
||||
Ok(outcome) => match outcome.status {
|
||||
harmony::interpret::InterpretStatus::SUCCESS => {
|
||||
progress::success(section, progress_bar, outcome.message)
|
||||
progress_tracker.finish_task(&task_key, &outcome.message);
|
||||
}
|
||||
harmony::interpret::InterpretStatus::NOOP => {
|
||||
progress::skip(section, progress_bar, outcome.message)
|
||||
progress_tracker.skip_task(&task_key, &outcome.message);
|
||||
}
|
||||
_ => progress::error(section, progress_bar, outcome.message),
|
||||
_ => progress_tracker.fail_task(&task_key, &outcome.message),
|
||||
},
|
||||
Err(err) => {
|
||||
progress::error(section, progress_bar, err.to_string());
|
||||
progress_tracker.fail_task(&task_key, &err.to_string());
|
||||
}
|
||||
}
|
||||
|
||||
if !has_topology {
|
||||
(*progress_bars).remove(§ion_key);
|
||||
}
|
||||
}
|
||||
}
|
||||
true
|
||||
@@ -181,7 +175,3 @@ fn topology_key(topology: &str) -> String {
|
||||
fn score_key(score: &str) -> String {
|
||||
format!("score-{score}")
|
||||
}
|
||||
|
||||
fn interpret_key(interpret: &str) -> String {
|
||||
format!("interpret-{interpret}")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user