referrerpolicy=no-referrer-when-downgrade

sc_informant/
display.rs

1// This file is part of Substrate.
2
3// Copyright (C) Parity Technologies (UK) Ltd.
4// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
5
6// This program is free software: you can redistribute it and/or modify
7// it under the terms of the GNU General Public License as published by
8// the Free Software Foundation, either version 3 of the License, or
9// (at your option) any later version.
10
11// This program is distributed in the hope that it will be useful,
12// but WITHOUT ANY WARRANTY; without even the implied warranty of
13// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14// GNU General Public License for more details.
15
16// You should have received a copy of the GNU General Public License
17// along with this program. If not, see <https://www.gnu.org/licenses/>.
18
19use console::style;
20use log::info;
21use sc_client_api::ClientInfo;
22use sc_network::NetworkStatus;
23use sc_network_sync::{SyncState, SyncStatus, WarpSyncPhase, WarpSyncProgress};
24use sp_runtime::traits::{Block as BlockT, CheckedDiv, NumberFor, Saturating, Zero};
25use std::{fmt, time::Instant};
26
27use crate::PrintFullHashOnDebugLogging;
28
29/// State of the informant display system.
30///
31/// This is the system that handles the line that gets regularly printed and that looks something
32/// like:
33///
34/// > Syncing  5.4 bps, target=#531028 (4 peers), best: #90683 (0x4ca8…51b8),
35/// > finalized #360 (0x6f24…a38b), ⬇ 5.5kiB/s ⬆ 0.9kiB/s
36///
37/// # Usage
38///
39/// Call `InformantDisplay::new` to initialize the state, then regularly call `display` with the
40/// information to display.
41pub struct InformantDisplay<B: BlockT> {
42	/// Head of chain block number from the last time `display` has been called.
43	/// `None` if `display` has never been called.
44	last_number: Option<NumberFor<B>>,
45	/// The last time `display` or `new` has been called.
46	last_update: Instant,
47	/// The last seen total of bytes received.
48	last_total_bytes_inbound: u64,
49	/// The last seen total of bytes sent.
50	last_total_bytes_outbound: u64,
51}
52
53impl<B: BlockT> InformantDisplay<B> {
54	/// Builds a new informant display system.
55	pub fn new() -> InformantDisplay<B> {
56		InformantDisplay {
57			last_number: None,
58			last_update: Instant::now(),
59			last_total_bytes_inbound: 0,
60			last_total_bytes_outbound: 0,
61		}
62	}
63
64	/// Displays the informant by calling `info!`.
65	pub fn display(
66		&mut self,
67		info: &ClientInfo<B>,
68		net_status: NetworkStatus,
69		sync_status: SyncStatus<B>,
70		num_connected_peers: usize,
71	) {
72		let best_number = info.chain.best_number;
73		let best_hash = info.chain.best_hash;
74		let finalized_number = info.chain.finalized_number;
75		let speed = speed::<B>(best_number, self.last_number, self.last_update);
76		let total_bytes_inbound = net_status.total_bytes_inbound;
77		let total_bytes_outbound = net_status.total_bytes_outbound;
78
79		let now = Instant::now();
80		let elapsed = (now - self.last_update).as_secs();
81		self.last_update = now;
82		self.last_number = Some(best_number);
83
84		let diff_bytes_inbound = total_bytes_inbound - self.last_total_bytes_inbound;
85		let diff_bytes_outbound = total_bytes_outbound - self.last_total_bytes_outbound;
86		let (avg_bytes_per_sec_inbound, avg_bytes_per_sec_outbound) = if elapsed > 0 {
87			self.last_total_bytes_inbound = total_bytes_inbound;
88			self.last_total_bytes_outbound = total_bytes_outbound;
89			(diff_bytes_inbound / elapsed, diff_bytes_outbound / elapsed)
90		} else {
91			(diff_bytes_inbound, diff_bytes_outbound)
92		};
93
94		let (level, status, target) =
95			match (sync_status.state, sync_status.state_sync, sync_status.warp_sync) {
96				// Do not set status to "Block history" when we are doing a major sync.
97				//
98				// A node could for example have been warp synced to the tip of the chain and
99				// shutdown. At the next start we still need to download the block history, but
100				// first will sync to the tip of the chain.
101				(
102					sync_status,
103					_,
104					Some(WarpSyncProgress { phase: WarpSyncPhase::DownloadingBlocks(n), .. }),
105				) if !sync_status.is_major_syncing() => ("⏩", "Block history".into(), format!(", #{}", n)),
106				// Handle all phases besides the two phases we already handle above.
107				(_, _, Some(warp))
108					if !matches!(warp.phase, WarpSyncPhase::DownloadingBlocks(_)) =>
109					(
110						"⏩",
111						"Warping".into(),
112						format!(
113							", {}, {:.2} Mib",
114							warp.phase,
115							(warp.total_bytes as f32) / (1024f32 * 1024f32)
116						),
117					),
118				(_, Some(state), _) => (
119					"⚙️ ",
120					"State sync".into(),
121					format!(
122						", {}, {}%, {:.2} Mib",
123						state.phase,
124						state.percentage,
125						(state.size as f32) / (1024f32 * 1024f32)
126					),
127				),
128				(SyncState::Idle, _, _) => ("💤", "Idle".into(), "".into()),
129				(SyncState::Downloading { target }, _, _) =>
130					("⚙️ ", format!("Syncing{}", speed), format!(", target=#{target}")),
131				(SyncState::Importing { target }, _, _) =>
132					("⚙️ ", format!("Preparing{}", speed), format!(", target=#{target}")),
133			};
134
135		info!(
136			target: "substrate",
137			"{} {}{} ({} peers), best: #{} ({}), finalized #{} ({}), ⬇ {} ⬆ {}",
138			level,
139			style(&status).white().bold(),
140			target,
141			style(num_connected_peers).white().bold(),
142			style(best_number).white().bold(),
143			PrintFullHashOnDebugLogging(&best_hash),
144			style(finalized_number).white().bold(),
145			PrintFullHashOnDebugLogging(&info.chain.finalized_hash),
146			style(TransferRateFormat(avg_bytes_per_sec_inbound)).green(),
147			style(TransferRateFormat(avg_bytes_per_sec_outbound)).red(),
148		)
149	}
150}
151
152/// Calculates `(best_number - last_number) / (now - last_update)` and returns a `String`
153/// representing the speed of import.
154fn speed<B: BlockT>(
155	best_number: NumberFor<B>,
156	last_number: Option<NumberFor<B>>,
157	last_update: Instant,
158) -> String {
159	// Number of milliseconds elapsed since last time.
160	let elapsed_ms = {
161		let elapsed = last_update.elapsed();
162		let since_last_millis = elapsed.as_secs() * 1000;
163		let since_last_subsec_millis = elapsed.subsec_millis() as u64;
164		since_last_millis + since_last_subsec_millis
165	};
166
167	// Number of blocks that have been imported since last time.
168	let diff = match last_number {
169		None => return String::new(),
170		Some(n) => best_number.saturating_sub(n),
171	};
172
173	if let Ok(diff) = TryInto::<u128>::try_into(diff) {
174		// If the number of blocks can be converted to a regular integer, then it's easy: just
175		// do the math and turn it into a `f64`.
176		let speed = diff
177			.saturating_mul(10_000)
178			.checked_div(u128::from(elapsed_ms))
179			.map_or(0.0, |s| s as f64) /
180			10.0;
181		format!(" {:4.1} bps", speed)
182	} else {
183		// If the number of blocks can't be converted to a regular integer, then we need a more
184		// algebraic approach and we stay within the realm of integers.
185		let one_thousand = NumberFor::<B>::from(1_000u32);
186		let elapsed =
187			NumberFor::<B>::from(<u32 as TryFrom<_>>::try_from(elapsed_ms).unwrap_or(u32::MAX));
188
189		let speed = diff
190			.saturating_mul(one_thousand)
191			.checked_div(&elapsed)
192			.unwrap_or_else(Zero::zero);
193		format!(" {} bps", speed)
194	}
195}
196
197/// Contains a number of bytes per second. Implements `fmt::Display` and shows this number of bytes
198/// per second in a nice way.
199struct TransferRateFormat(u64);
200impl fmt::Display for TransferRateFormat {
201	fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
202		// Special case 0.
203		if self.0 == 0 {
204			return write!(f, "0")
205		}
206
207		// Under 0.1 kiB, display plain bytes.
208		if self.0 < 100 {
209			return write!(f, "{} B/s", self.0)
210		}
211
212		// Under 1.0 MiB/sec, display the value in kiB/sec.
213		if self.0 < 1024 * 1024 {
214			return write!(f, "{:.1}kiB/s", self.0 as f64 / 1024.0)
215		}
216
217		write!(f, "{:.1}MiB/s", self.0 as f64 / (1024.0 * 1024.0))
218	}
219}