diff --git a/console/src/tasks.rs b/console/src/tasks.rs index 87cf390fd..36bcf814f 100644 --- a/console/src/tasks.rs +++ b/console/src/tasks.rs @@ -4,19 +4,21 @@ use std::{ cell::RefCell, collections::HashMap, convert::TryFrom, - fmt::Write, + fmt, rc::{Rc, Weak}, + sync::Arc, time::{Duration, SystemTime}, }; use tui::{ layout, - style::{self, Style}, - text, + style::{self, Modifier, Style}, + text::{self, Span, Spans}, widgets::{Block, Cell, Row, Table, TableState}, }; #[derive(Debug)] pub(crate) struct State { tasks: HashMap>>, + metas: HashMap, sorted_tasks: Vec>>, sort_by: SortBy, table_state: TableState, @@ -39,12 +41,19 @@ enum SortBy { pub(crate) struct Task { id: u64, id_hex: String, - fields: String, + fields: Vec, + formatted_fields: Vec>, kind: &'static str, stats: Stats, completed_for: usize, } +#[derive(Debug)] +pub(crate) struct Metadata { + field_names: Vec>, + //TODO: add more metadata as needed +} + #[derive(Debug)] struct Stats { polls: u64, @@ -54,6 +63,21 @@ struct Stats { total: Option, } +#[derive(Debug)] +pub(crate) struct Field { + pub(crate) name: Arc, + pub(crate) value: FieldValue, +} + +#[derive(Debug)] +pub(crate) enum FieldValue { + Bool(bool), + Str(String), + U64(u64), + I64(i64), + Debug(String), +} + impl State { // How many updates to retain completed tasks for const RETAIN_COMPLETED_FOR: usize = 6; @@ -114,9 +138,20 @@ impl State { if let Some(now) = update.now { self.last_updated_at = Some(now.into()); } + + if let Some(new_metadata) = update.new_metadata { + let metas = new_metadata.metadata.into_iter().filter_map(|meta| { + let id = meta.id?.id; + let metadata = meta.metadata?; + Some((id, metadata.into())) + }); + self.metas.extend(metas); + } + let mut stats_update = update.stats_update; let sorted = &mut self.sorted_tasks; - let new_tasks = update.new_tasks.into_iter().filter_map(|task| { + let metas = &mut self.metas; + let new_tasks = update.new_tasks.into_iter().filter_map(|mut task| { if task.id.is_none() { tracing::warn!(?task, "skipping task with no id"); } @@ -124,21 +159,46 @@ impl State { proto::tasks::task::Kind::Spawn => "T", proto::tasks::task::Kind::Blocking => "B", }; - let fields = task + + let fields: Vec = task .fields - .iter() - .fold(String::new(), |mut res, f| { - write!(&mut res, "{} ", f).unwrap(); - res + .drain(..) + .filter_map(|f| { + let field_name = f.name.as_ref()?; + let name: Option> = match field_name { + proto::field::Name::StrName(n) => Some(n.clone().into()), + proto::field::Name::NameIdx(idx) => { + let meta_id = f.metadata_id.as_ref()?; + metas + .get(&meta_id.id) + .and_then(|meta| meta.field_names.get(*idx as usize)) + .cloned() + } + }; + let value = f.value.as_ref().expect("no value").clone().into(); + name.map(|name| Field { name, value }) }) - .trim_end() - .into(); + .collect(); + + let formatted_fields = fields.iter().fold(Vec::default(), |mut acc, f| { + acc.extend(vec![ + Span::styled( + f.name.to_string(), + Style::default().add_modifier(Modifier::BOLD), + ), + Span::from("="), + Span::from(format!("{} ", f.value)), + ]); + acc + }); + let id = task.id?.id; let stats = stats_update.remove(&id)?.into(); let mut task = Task { id, id_hex: format!("{:x}", id), fields, + formatted_fields, kind, stats, completed_for: 0, @@ -182,6 +242,7 @@ impl State { let rows = self.sorted_tasks.iter().filter_map(|task| { let task = task.upgrade()?; let task = task.borrow(); + let mut row = Row::new(vec![ Cell::from(task.id_hex.to_string()), // TODO(eliza): is there a way to write a `fmt::Debug` impl @@ -206,7 +267,7 @@ impl State { prec = DUR_PRECISION, )), Cell::from(format!("{:>width$}", task.stats.polls, width = POLLS_LEN)), - Cell::from(task.fields.to_string()), + Cell::from(Spans::from(task.formatted_fields.clone())), ]); if task.completed_for > 0 { row = row.style(Style::default().add_modifier(style::Modifier::DIM)); @@ -330,6 +391,7 @@ impl Default for State { fn default() -> Self { Self { tasks: Default::default(), + metas: Default::default(), sorted_tasks: Default::default(), sort_by: Default::default(), selected_column: SortBy::default() as usize, @@ -345,8 +407,8 @@ impl Task { &self.id_hex } - pub(crate) fn fields(&self) -> &str { - &self.fields + pub(crate) fn formatted_fields(&self) -> &Vec> { + &self.formatted_fields } pub(crate) fn total(&self, since: SystemTime) -> Duration { @@ -397,6 +459,26 @@ impl From for Stats { } } +impl From for Metadata { + fn from(pb: proto::Metadata) -> Self { + Self { + field_names: pb.field_names.into_iter().map(|n| n.into()).collect(), + } + } +} + +impl From for FieldValue { + fn from(pb: proto::field::Value) -> Self { + match pb { + proto::field::Value::BoolVal(v) => Self::Bool(v), + proto::field::Value::StrVal(v) => Self::Str(v), + proto::field::Value::I64Val(v) => Self::I64(v), + proto::field::Value::U64Val(v) => Self::U64(v), + proto::field::Value::DebugVal(v) => Self::Debug(v), + } + } +} + impl Default for SortBy { fn default() -> Self { Self::Total @@ -437,3 +519,17 @@ impl TryFrom for SortBy { } } } + +impl fmt::Display for FieldValue { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + FieldValue::Bool(v) => fmt::Display::fmt(v, f)?, + FieldValue::Str(v) => fmt::Display::fmt(v, f)?, + FieldValue::U64(v) => fmt::Display::fmt(v, f)?, + FieldValue::Debug(v) => fmt::Display::fmt(v, f)?, + FieldValue::I64(v) => fmt::Display::fmt(v, f)?, + } + + Ok(()) + } +} diff --git a/console/src/view/task.rs b/console/src/view/task.rs index 75806afec..afc6b1893 100644 --- a/console/src/view/task.rs +++ b/console/src/view/task.rs @@ -35,7 +35,7 @@ impl TaskView { let task = &*self.task.borrow(); const DUR_PRECISION: usize = 4; - let attrs = Spans::from(vec![ + let mut attrs = vec![ Span::styled("ID: ", Style::default().add_modifier(style::Modifier::BOLD)), Span::raw(task.id_hex()), Span::raw(", "), @@ -43,8 +43,10 @@ impl TaskView { "Fields: ", Style::default().add_modifier(style::Modifier::BOLD), ), - Span::raw(task.fields()), - ]); + ]; + + attrs.extend(task.formatted_fields().clone()); + let atributes = Spans::from(attrs); let metrics = Spans::from(vec![ Span::styled( @@ -66,7 +68,7 @@ impl TaskView { Span::from(format!("{:.prec$?}", task.idle(now), prec = DUR_PRECISION,)), ]); - let lines = vec![attrs, metrics]; + let lines = vec![atributes, metrics]; let block = Block::default().borders(Borders::ALL).title("Task"); let paragraph = Paragraph::new(lines).block(block); frame.render_widget(paragraph, area);