diff --git a/Cargo.lock b/Cargo.lock index f5e02cd..5a14e92 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8,6 +8,21 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aae1277d39aeec15cb388266ecc24b11c80469deae6067e17a1a7aa9e5c1f234" +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + [[package]] name = "anstream" version = "0.6.4" @@ -220,6 +235,12 @@ dependencies = [ "tracing", ] +[[package]] +name = "bumpalo" +version = "3.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" + [[package]] name = "byteorder" version = "1.5.0" @@ -232,12 +253,36 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53" +[[package]] +name = "cc" +version = "1.0.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" +dependencies = [ + "libc", +] + [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "chrono" +version = "0.4.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f2c685bad3eb3d45a01354cedb7d5faa66194d1d58ba6e267a8de788f79db38" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "js-sys", + "num-traits", + "serde", + "wasm-bindgen", + "windows-targets", +] + [[package]] name = "clap" version = "4.4.6" @@ -293,6 +338,12 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "core-foundation-sys" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" + [[package]] name = "crc32fast" version = "1.3.2" @@ -428,6 +479,7 @@ name = "erldash" version = "0.1.5" dependencies = [ "anyhow", + "chrono", "clap", "crossterm 0.27.0", "dirs", @@ -611,6 +663,29 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7" +[[package]] +name = "iana-time-zone" +version = "0.1.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8326b86b6cff230b97d0d312a6c40a60726df3332e721f72a1b035f451663b20" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + [[package]] name = "instant" version = "0.1.12" @@ -637,6 +712,15 @@ version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" +[[package]] +name = "js-sys" +version = "0.3.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5f195fe497f702db0f318b07fdd68edb16955aed830df8363d837542f8f935a" +dependencies = [ + "wasm-bindgen", +] + [[package]] name = "libc" version = "0.2.149" @@ -800,6 +884,12 @@ dependencies = [ "libc", ] +[[package]] +name = "once_cell" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" + [[package]] name = "option-ext" version = "0.2.0" @@ -1273,6 +1363,60 @@ version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +[[package]] +name = "wasm-bindgen" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7706a72ab36d8cb1f80ffbf0e071533974a60d0a308d01a5d0375bf60499a342" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ef2b6d3c510e9625e5fe6f509ab07d66a760f0885d858736483c32ed7809abd" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dee495e55982a3bd48105a7b947fd2a9b4a8ae3010041b9e0faab3f9cd028f1d" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1" + [[package]] name = "winapi" version = "0.3.9" @@ -1304,6 +1448,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows-core" +version = "0.51.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1f8cf84f35d2db49a46868f947758c7a1138116f7fac3bc844f43ade1292e64" +dependencies = [ + "windows-targets", +] + [[package]] name = "windows-sys" version = "0.48.0" diff --git a/Cargo.toml b/Cargo.toml index 6b55c2b..888de79 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,6 +13,7 @@ readme = "README.md" [dependencies] anyhow = "1" +chrono = { version = "0.4.31", features = ["serde"] } clap = { version = "4", features = ["derive"] } crossterm = "0.27" dirs = "5" diff --git a/src/metrics.rs b/src/metrics.rs index 1e2d829..ff6154a 100644 --- a/src/metrics.rs +++ b/src/metrics.rs @@ -253,10 +253,10 @@ impl MetricsPoller { matches!(self, Self::Replay(_)) } - pub fn system_version(&self) -> &SystemVersion { + pub fn header(&self) -> &Header { match self { - Self::Realtime(poller) => &poller.system_version, - Self::Replay(poller) => &poller.system_version, + Self::Realtime(poller) => &poller.header, + Self::Replay(poller) => &poller.header, } } @@ -295,9 +295,15 @@ impl MetricsPoller { } } +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct Header { + pub system_version: SystemVersion, + pub start_time: chrono::DateTime, +} + #[derive(Debug)] pub struct ReplayMetricsPoller { - system_version: SystemVersion, + header: Header, metrics_log: Vec, } @@ -311,12 +317,12 @@ impl ReplayMetricsPoller { })?; let reader = std::io::BufReader::new(file); - let mut system_version = None; + let mut header = None; let mut metrics_log = Vec::new(); for (i, line) in reader.lines().enumerate() { let line = line?; if i == 0 { - system_version = Some( + header = Some( serde_json::from_str(&line) .with_context(|| format!("failed to parse record file: line={}", i + 1))?, ); @@ -326,10 +332,9 @@ impl ReplayMetricsPoller { .with_context(|| format!("failed to parse record file: line={}", i + 1))?; metrics_log.push(metrics); } - let system_version = - system_version.ok_or_else(|| anyhow::anyhow!("record file is empty"))?; + let header = header.ok_or_else(|| anyhow::anyhow!("record file is empty"))?; Ok(Self { - system_version, + header, metrics_log, }) } @@ -338,7 +343,7 @@ impl ReplayMetricsPoller { #[derive(Debug)] pub struct RealtimeMetricsPoller { rx: MetricsReceiver, - system_version: SystemVersion, + header: Header, rpc_client: RpcClient, old_microstate_accounting_flag: bool, } @@ -371,7 +376,7 @@ struct MetricsPollerThread { tx: MetricsSender, prev_metrics: Metrics, start: Instant, - system_version: SystemVersion, + header: Header, record_file: Option, } @@ -391,9 +396,13 @@ impl MetricsPollerThread { "enabled microstate accounting (old flag state is {old_microstate_accounting_flag})" ); + let header = Header { + system_version: system_version.clone(), + start_time: chrono::Local::now(), + }; let poller = RealtimeMetricsPoller { rx, - system_version: system_version.clone(), + header: header.clone(), rpc_client: rpc_client.clone(), old_microstate_accounting_flag, }; @@ -414,7 +423,7 @@ impl MetricsPollerThread { tx, prev_metrics: Metrics::new(start), start, - system_version, + header, record_file, } .run() @@ -436,7 +445,7 @@ impl MetricsPollerThread { let interval = Duration::from_secs(self.options.polling_interval.get() as u64); let mut next_time = Duration::from_secs(0); smol::block_on(async { - if let Err(e) = self.write_json_line(&self.system_version.clone()).await { + if let Err(e) = self.write_json_line(&self.header.clone()).await { log::error!("faild to write record file: {e}"); return; } diff --git a/src/ui.rs b/src/ui.rs index 938bfeb..5fc4133 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -32,11 +32,12 @@ impl App { log::debug!("setup terminal"); let replay_mode = poller.is_replay(); - let system_version = poller.system_version().clone(); + let system_version = poller.header().system_version.clone(); + let start_time = poller.header().start_time; Ok(Self { terminal, poller, - ui: UiState::new(system_version, replay_mode), + ui: UiState::new(system_version, start_time, replay_mode), replay_cursor_time: Duration::default(), }) } @@ -92,6 +93,7 @@ impl App { } log::debug!("remove old metrics"); } + self.ui.elapsed = self.ui.start.elapsed(); self.render_ui()?; } } @@ -186,18 +188,13 @@ impl App { let time = self.replay_cursor_time; self.ui.history.clear(); + self.ui.averages.clear(); for metrics in self .poller .get_metrics_range(time, time + Duration::from_secs(CHART_DURATION))? { self.ui.history.push_back(metrics.clone()); - } - self.ui.averages.clear(); - for metrics in self.poller.get_metrics_range( - time.saturating_sub(Duration::from_secs(CHART_DURATION)), - time, - )? { for (name, item) in &metrics.items { if let Some(avg) = self.ui.averages.get_mut(name) { avg.add(item.clone()); @@ -209,6 +206,13 @@ impl App { } } + self.ui.elapsed = self + .ui + .history + .back() + .map(|x| x.timestamp) + .unwrap_or_default(); + self.render_ui()?; Ok(()) } @@ -247,6 +251,8 @@ impl Drop for App { struct UiState { start: Instant, system_version: SystemVersion, + start_time: chrono::DateTime, + elapsed: Duration, pause: bool, history: VecDeque, averages: BTreeMap, @@ -257,10 +263,16 @@ struct UiState { } impl UiState { - fn new(system_version: SystemVersion, replay_mode: bool) -> Self { + fn new( + system_version: SystemVersion, + start_time: chrono::DateTime, + replay_mode: bool, + ) -> Self { Self { start: Instant::now(), system_version, + start_time, + elapsed: Duration::default(), pause: false, history: VecDeque::new(), averages: BTreeMap::new(), @@ -282,10 +294,23 @@ impl UiState { } fn render_header(&mut self, f: &mut Frame, area: Rect) { + let chunks = Layout::default() + .direction(Direction::Horizontal) + .constraints([Constraint::Percentage(75), Constraint::Percentage(25)].as_ref()) + .split(area); + let paragraph = Paragraph::new(vec![Spans::from(self.system_version.get())]) .block(self.make_block("System Version")) .alignment(Alignment::Left); - f.render_widget(paragraph, area); + f.render_widget(paragraph, chunks[0]); + + let now = self.start_time + self.elapsed; + let paragraph = Paragraph::new(vec![Spans::from( + now.to_rfc3339_opts(chrono::SecondsFormat::Secs, true), + )]) + .block(self.make_block("Time")) + .alignment(Alignment::Left); + f.render_widget(paragraph, chunks[1]); } fn render_body(&mut self, f: &mut Frame, area: Rect) { @@ -322,7 +347,7 @@ impl UiState { let header = Row::new(header_cells).bottom_margin(1); let items = self.latest_metrics().root_items().collect::>(); - let is_avg_available = self.start.elapsed().as_secs() >= ONE_MINUTE; + let is_avg_available = self.elapsed.as_secs() >= (ONE_MINUTE - 1); let mut value_width = 0; let mut avg_width = 0; let mut row_items = Vec::with_capacity(items.len());