diff --git a/substrate/core/cli/src/informant.rs b/substrate/core/cli/src/informant.rs index d6bbf4831d..ca4be4bd1b 100644 --- a/substrate/core/cli/src/informant.rs +++ b/substrate/core/cli/src/informant.rs @@ -16,19 +16,16 @@ //! Console informant. Prints sync progress and block events. Runs on the calling thread. -use ansi_term::Colour; -use std::fmt; -use std::time; +use client::{backend::Backend, BlockchainEvents}; use futures::{Future, Stream}; use futures03::{StreamExt as _, TryStreamExt as _}; +use log::{info, warn}; +use runtime_primitives::generic::BlockId; +use runtime_primitives::traits::Header; use service::{Service, Components}; use tokio::runtime::TaskExecutor; -use network::SyncState; -use client::{backend::Backend, BlockchainEvents}; -use log::{info, warn}; -use runtime_primitives::generic::BlockId; -use runtime_primitives::traits::{Header, SaturatedConversion}; +mod display; /// Spawn informant on the event loop #[deprecated(note = "Please use informant::build instead, and then create the task manually")] @@ -42,37 +39,12 @@ pub fn start(service: &Service, exit: ::exit_future::Exit, handle: TaskExe pub fn build(service: &Service) -> impl Future where C: Components { let client = service.client(); - let mut last_number = None; - let mut last_update = time::Instant::now(); + + let mut display = display::InformantDisplay::new(); let display_notifications = service.network_status().for_each(move |(net_status, _)| { - let info = client.info(); - let best_number = info.chain.best_number.saturated_into::(); - let best_hash = info.chain.best_hash; - let speed = move || speed(best_number, last_number, last_update); - last_update = time::Instant::now(); - let (status, target) = match (net_status.sync_state, net_status.best_seen_block) { - (SyncState::Idle, _) => ("Idle".into(), "".into()), - (SyncState::Downloading, None) => (format!("Syncing{}", speed()), "".into()), - (SyncState::Downloading, Some(n)) => (format!("Syncing{}", speed()), format!(", target=#{}", n)), - }; - last_number = Some(best_number); - let finalized_number: u64 = info.chain.finalized_number.saturated_into::(); - info!( - target: "substrate", - "{}{} ({} peers), best: #{} ({}), finalized #{} ({}), ⬇ {} ⬆ {}", - Colour::White.bold().paint(&status), - target, - Colour::White.bold().paint(format!("{}", net_status.num_connected_peers)), - Colour::White.paint(format!("{}", best_number)), - best_hash, - Colour::White.paint(format!("{}", finalized_number)), - info.chain.finalized_hash, - TransferRateFormat(net_status.average_download_per_sec), - TransferRateFormat(net_status.average_upload_per_sec), - ); - + display.display(&info, net_status); Ok(()) }); @@ -115,42 +87,3 @@ where C: Components { display_notifications.join(display_block_import) .map(|((), ())| ()) } - -fn speed(best_number: u64, last_number: Option, last_update: time::Instant) -> String { - let since_last_millis = last_update.elapsed().as_secs() * 1000; - let since_last_subsec_millis = last_update.elapsed().subsec_millis() as u64; - let speed = last_number - .and_then(|num| - (best_number.saturating_sub(num) * 10_000).checked_div(since_last_millis + since_last_subsec_millis)) - .map_or(0.0, |s| s as f64); - - if speed < 1.0 { - "".into() - } else { - format!(" {:4.1} bps", speed / 10.0) - } -} - -/// Contains a number of bytes per second. Implements `fmt::Display` and shows this number of bytes -/// per second in a nice way. -struct TransferRateFormat(u64); -impl fmt::Display for TransferRateFormat { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - // Special case 0. - if self.0 == 0 { - return write!(f, "0") - } - - // Under 0.1 kiB, display plain bytes. - if self.0 < 100 { - return write!(f, "{} B/s", self.0) - } - - // Under 1.0 MiB/sec, display the value in kiB/sec. - if self.0 < 1024 * 1024 { - return write!(f, "{:.1}kiB/s", self.0 as f64 / 1024.0) - } - - write!(f, "{:.1}MiB/s", self.0 as f64 / (1024.0 * 1024.0)) - } -} diff --git a/substrate/core/cli/src/informant/display.rs b/substrate/core/cli/src/informant/display.rs new file mode 100644 index 0000000000..481bff0cde --- /dev/null +++ b/substrate/core/cli/src/informant/display.rs @@ -0,0 +1,149 @@ +// Copyright 2017-2019 Parity Technologies (UK) Ltd. +// This file is part of Substrate. + +// Substrate is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Substrate is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Substrate. If not, see . + +use ansi_term::Colour; +use client::ClientInfo; +use log::info; +use network::SyncState; +use runtime_primitives::traits::{Block as BlockT, SaturatedConversion, CheckedDiv, NumberFor, One, Zero, Saturating}; +use service::NetworkStatus; +use std::{convert::TryInto, fmt, time}; + +/// State of the informant display system. +/// +/// This is the system that handles the line that gets regularly printed and that looks something +/// like: +/// +/// > Syncing 5.4 bps, target=#531028 (4 peers), best: #90683 (0x4ca8…51b8), +/// > finalized #360 (0x6f24…a38b), ⬇ 5.5kiB/s ⬆ 0.9kiB/s +/// +/// # Usage +/// +/// Call `InformantDisplay::new` to initialize the state, then regularly call `display` with the +/// information to display. +/// +pub struct InformantDisplay { + /// Head of chain block number from the last time `display` has been called. + /// `None` if `display` has never been called. + last_number: Option>, + /// The last time `display` or `new` has been called. + last_update: time::Instant, +} + +impl InformantDisplay { + /// Builds a new informant display system. + pub fn new() -> InformantDisplay { + InformantDisplay { + last_number: None, + last_update: time::Instant::now(), + } + } + + /// Displays the informant by calling `info!`. + pub fn display(&mut self, info: &ClientInfo, net_status: NetworkStatus) { + let best_number = info.chain.best_number; + let best_hash = info.chain.best_hash; + let speed = speed::(best_number, self.last_number, self.last_update); + self.last_update = time::Instant::now(); + self.last_number = Some(best_number); + + let (status, target) = match (net_status.sync_state, net_status.best_seen_block) { + (SyncState::Idle, _) => ("Idle".into(), "".into()), + (SyncState::Downloading, None) => (format!("Syncing{}", speed), "".into()), + (SyncState::Downloading, Some(n)) => (format!("Syncing{}", speed), format!(", target=#{}", n)), + }; + + let finalized_number: u64 = info.chain.finalized_number.saturated_into::(); + + info!( + target: "substrate", + "{}{} ({} peers), best: #{} ({}), finalized #{} ({}), ⬇ {} ⬆ {}", + Colour::White.bold().paint(&status), + target, + Colour::White.bold().paint(format!("{}", net_status.num_connected_peers)), + Colour::White.paint(format!("{}", best_number)), + best_hash, + Colour::White.paint(format!("{}", finalized_number)), + info.chain.finalized_hash, + TransferRateFormat(net_status.average_download_per_sec), + TransferRateFormat(net_status.average_upload_per_sec), + ); + } +} + +/// Calculates `(best_number - last_number) / (now - last_update)` and returns a `String` +/// representing the speed of import. +fn speed( + best_number: NumberFor, + last_number: Option>, + last_update: time::Instant +) -> String { + let elapsed = last_update.elapsed(); + let since_last_millis = elapsed.as_secs() * 1000; + let since_last_subsec_millis = elapsed.subsec_millis() as u64; + let elapsed_ms = since_last_millis + since_last_subsec_millis; + + // Number of blocks that have been imported since last time. + let diff = match last_number { + None => return String::new(), + Some(n) => best_number.saturating_sub(n) + }; + + if let Ok(diff) = TryInto::::try_into(diff) { + // If the number of blocks can be converted to a regular integer, then it's easy: just + // do the math and turn it into a `f64`. + let speed = diff.saturating_mul(10_000).checked_div(u128::from(elapsed_ms)) + .map_or(0.0, |s| s as f64) / 10.0; + format!(" {:4.1} bps", speed) + + } else { + // If the number of blocks can't be converted to a regular integer, then we need a more + // algebraic approach and we stay within the realm of integers. + let ten = (0..10) + .fold( as Zero>::zero(), |a, _| a.saturating_add(One::one())); + let one_thousand = ten * ten * ten; + let elapsed = (0..elapsed_ms) + .fold( as Zero>::zero(), |a, _| a.saturating_add(One::one())); + + let speed = diff.saturating_mul(one_thousand).checked_div(&elapsed) + .unwrap_or_else(Zero::zero); + format!(" {} bps", speed) + } +} + +/// Contains a number of bytes per second. Implements `fmt::Display` and shows this number of bytes +/// per second in a nice way. +struct TransferRateFormat(u64); +impl fmt::Display for TransferRateFormat { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + // Special case 0. + if self.0 == 0 { + return write!(f, "0") + } + + // Under 0.1 kiB, display plain bytes. + if self.0 < 100 { + return write!(f, "{} B/s", self.0) + } + + // Under 1.0 MiB/sec, display the value in kiB/sec. + if self.0 < 1024 * 1024 { + return write!(f, "{:.1}kiB/s", self.0 as f64 / 1024.0) + } + + write!(f, "{:.1}MiB/s", self.0 as f64 / (1024.0 * 1024.0)) + } +}