/__w/smoldot/smoldot/repo/lib/src/informant.rs
Line | Count | Source (jump to first uncovered line) |
1 | | // Smoldot |
2 | | // Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. |
3 | | // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 |
4 | | |
5 | | // This program is free software: you can redistribute it and/or modify |
6 | | // it under the terms of the GNU General Public License as published by |
7 | | // the Free Software Foundation, either version 3 of the License, or |
8 | | // (at your option) any later version. |
9 | | |
10 | | // This program is distributed in the hope that it will be useful, |
11 | | // but WITHOUT ANY WARRANTY; without even the implied warranty of |
12 | | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
13 | | // GNU General Public License for more details. |
14 | | |
15 | | // You should have received a copy of the GNU General Public License |
16 | | // along with this program. If not, see <http://www.gnu.org/licenses/>. |
17 | | |
18 | | //! Information string printed out. |
19 | | //! |
20 | | //! The so-called informant is a line of text typically printed at a regular interval on stdout. |
21 | | //! |
22 | | //! You can make the informant overwrite itself by printing a `\r` at the end of it. |
23 | | //! |
24 | | //! This code intentionally doesn't perform any printing and only provides some helping code to |
25 | | //! make the printing straight-forward. |
26 | | //! |
27 | | //! # Usage |
28 | | //! |
29 | | //! The [`InformantLine`] struct implements the [`core::fmt::Display`] trait. |
30 | | //! |
31 | | //! ``` |
32 | | //! use smoldot::informant::InformantLine; |
33 | | //! eprint!("{}\r", InformantLine { |
34 | | //! enable_colors: true, |
35 | | //! chain_name: "My chain", |
36 | | //! relay_chain: None, |
37 | | //! max_line_width: 80, |
38 | | //! num_peers: 8, |
39 | | //! num_network_connections: 12, |
40 | | //! best_number: 220, |
41 | | //! finalized_number: 217, |
42 | | //! best_hash: &[0x12, 0x34, 0x56, 0x76], |
43 | | //! finalized_hash: &[0xaa, 0xbb, 0xcc, 0xdd], |
44 | | //! network_known_best: Some(224), |
45 | | //! }); |
46 | | //! ``` |
47 | | |
48 | | use alloc::format; |
49 | | use core::{cmp, fmt}; |
50 | | |
51 | | /// Values used to build the informant line. Implements the [`core::fmt::Display`] trait. |
52 | | // TODO: some fields here aren't printed; remove them once what is printed is final |
53 | | #[derive(Debug)] |
54 | | pub struct InformantLine<'a> { |
55 | | /// If true, ANSI escape characters will be written out. |
56 | | /// |
57 | | /// > **Note**: Despite its name, this controls *all* styling escape characters, not just |
58 | | /// > colors. |
59 | | pub enable_colors: bool, |
60 | | /// Name of the chain. |
61 | | pub chain_name: &'a str, |
62 | | /// Extra fields related to the relay chain. |
63 | | pub relay_chain: Option<RelayChain<'a>>, |
64 | | /// Maximum number of characters of the informant line. |
65 | | pub max_line_width: u32, |
66 | | /// Number of gossiping substreams open with nodes of the same chain. |
67 | | pub num_peers: u64, |
68 | | /// Number of network connections we are having with the rest of the peer-to-peer network. |
69 | | pub num_network_connections: u64, |
70 | | /// Best block currently being propagated on the peer-to-peer. `None` if unknown. |
71 | | pub network_known_best: Option<u64>, |
72 | | /// Number of the best block that we have locally. |
73 | | pub best_number: u64, |
74 | | /// Hash of the best block that we have locally. |
75 | | pub best_hash: &'a [u8], |
76 | | /// Number of the latest finalized block we have locally. |
77 | | pub finalized_number: u64, |
78 | | /// Hash of the latest finalized block we have locally. |
79 | | pub finalized_hash: &'a [u8], |
80 | | } |
81 | | |
82 | | /// Extra fields if a relay chain exists. |
83 | | #[derive(Debug)] |
84 | | pub struct RelayChain<'a> { |
85 | | /// Name of the chain. |
86 | | pub chain_name: &'a str, |
87 | | /// Number of the best block that we have locally. |
88 | | pub best_number: u64, |
89 | | } |
90 | | |
91 | | impl<'a> fmt::Display for InformantLine<'a> { |
92 | 0 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
93 | | // TODO: lots of allocations in here |
94 | | // TODO: this bar is actually harder to implement than expected; clean up code |
95 | | |
96 | | // Define the escape sequences used below for colouring purposes. |
97 | 0 | let cyan = if self.enable_colors { "\x1b[36m" } else { "" }; |
98 | 0 | let white_bold = if self.enable_colors { "\x1b[1;37m" } else { "" }; |
99 | 0 | let light_gray = if self.enable_colors { "\x1b[90m" } else { "" }; |
100 | 0 | let reset = if self.enable_colors { "\x1b[0m" } else { "" }; |
101 | | |
102 | 0 | let (header, header_len) = if let Some(relay_chain) = &self.relay_chain { |
103 | 0 | let header = format!( |
104 | 0 | " {cyan}{chain_name}{reset} {white_bold}{local_best:<10}{reset} {light_gray}({relay_chain_name} {relay_best}){reset} [", |
105 | 0 | cyan = cyan, |
106 | 0 | reset = reset, |
107 | 0 | white_bold = white_bold, |
108 | 0 | light_gray = light_gray, |
109 | 0 | chain_name = self.chain_name, |
110 | 0 | relay_chain_name = relay_chain.chain_name, |
111 | 0 | local_best = BlockNumberDisplay(self.best_number), |
112 | 0 | relay_best = BlockNumberDisplay(relay_chain.best_number), |
113 | 0 | ); |
114 | 0 |
|
115 | 0 | let header_len = self.chain_name.chars().count() + relay_chain.chain_name.len() + 29; // TODO: ? it's easier to do that than deal with unicode |
116 | 0 | (header, header_len) |
117 | | } else { |
118 | 0 | let header = format!( |
119 | 0 | " {cyan}{chain_name}{reset} {white_bold}{local_best:<10}{reset} [", |
120 | 0 | cyan = cyan, |
121 | 0 | reset = reset, |
122 | 0 | white_bold = white_bold, |
123 | 0 | chain_name = self.chain_name, |
124 | 0 | local_best = BlockNumberDisplay(self.best_number), |
125 | 0 | ); |
126 | 0 |
|
127 | 0 | let header_len = self.chain_name.chars().count() + 19; // TODO: ? it's easier to do that than deal with unicode |
128 | 0 | (header, header_len) |
129 | | }; |
130 | | |
131 | | // TODO: it's a bit of a clusterfuck to properly align because the emoji eats a whitespace |
132 | 0 | let trailer = format!( |
133 | 0 | "] {white_bold}{network_best}{reset} (🔗{white_bold}{peers:>3}{reset}) (🌐{white_bold}{connec:>4}{reset}) ", |
134 | 0 | network_best = self |
135 | 0 | .network_known_best |
136 | 0 | .map(BlockNumberDisplay) |
137 | 0 | .map_or(either::Right("?"), either::Left), |
138 | 0 | peers = self.num_network_connections, |
139 | 0 | connec = self.num_network_connections, |
140 | 0 | white_bold = white_bold, |
141 | 0 | reset = reset, |
142 | 0 | ); |
143 | 0 | let trailer_len = format!( |
144 | 0 | "] {network_best} ( {peers:>3}) ( {connec:>4}) ", |
145 | 0 | network_best = self |
146 | 0 | .network_known_best |
147 | 0 | .map(BlockNumberDisplay) |
148 | 0 | .map(either::Left) |
149 | 0 | .unwrap_or(either::Right("?")), |
150 | 0 | peers = self.num_network_connections, |
151 | 0 | connec = self.num_network_connections, |
152 | 0 | ) |
153 | 0 | .len(); |
154 | 0 |
|
155 | 0 | let bar_width = self |
156 | 0 | .max_line_width |
157 | 0 | .saturating_sub(u32::try_from(header_len).unwrap()) |
158 | 0 | .saturating_sub(u32::try_from(trailer_len).unwrap()); |
159 | 0 |
|
160 | 0 | let actual_network_best = cmp::max(self.network_known_best.unwrap_or(0), self.best_number); |
161 | 0 | assert!(self.best_number <= actual_network_best); |
162 | 0 | let bar_done_width = u128::from(self.best_number) |
163 | 0 | .checked_mul(u128::from(bar_width)) |
164 | 0 | .unwrap() |
165 | 0 | .checked_div(u128::from(actual_network_best)) |
166 | 0 | .unwrap_or(0); // TODO: hack to not panic |
167 | 0 | let bar_done_width = u32::try_from(bar_done_width).unwrap(); |
168 | 0 |
|
169 | 0 | let done_bar1 = "=".repeat(usize::try_from(bar_done_width.saturating_sub(1)).unwrap()); |
170 | 0 | let done_bar2 = if bar_done_width == bar_width { |
171 | 0 | '=' |
172 | | } else { |
173 | 0 | '>' |
174 | | }; |
175 | 0 | let todo_bar = " ".repeat( |
176 | 0 | usize::try_from( |
177 | 0 | bar_width |
178 | 0 | .checked_sub(bar_done_width.saturating_sub(1).saturating_add(1)) |
179 | 0 | .unwrap(), |
180 | 0 | ) |
181 | 0 | .unwrap(), |
182 | 0 | ); |
183 | 0 | assert_eq!( |
184 | 0 | done_bar1.len() + 1 + todo_bar.len(), |
185 | 0 | usize::try_from(bar_width).unwrap() |
186 | 0 | ); |
187 | | |
188 | 0 | write!(f, "{header}{done_bar1}{done_bar2}{todo_bar}{trailer}") |
189 | 0 | } Unexecuted instantiation: _RNvXNtCsN16ciHI6Qf_7smoldot9informantNtB2_13InformantLineNtNtCsaYZPK01V26L_4core3fmt7Display3fmt Unexecuted instantiation: _RNvXNtCseuYC0Zibziv_7smoldot9informantNtB2_13InformantLineNtNtCsaYZPK01V26L_4core3fmt7Display3fmt |
190 | | } |
191 | | |
192 | | /// Implements `fmt::Display` and displays hashes in a nice way. |
193 | | pub struct HashDisplay<'a>(pub &'a [u8]); |
194 | | |
195 | | impl<'a> fmt::Display for HashDisplay<'a> { |
196 | 21 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
197 | 21 | write!(f, "0x")?0 ; |
198 | 21 | if self.0.len() >= 2 { |
199 | 21 | let val = u16::from_be_bytes(<[u8; 2]>::try_from(&self.0[..2]).unwrap()); |
200 | 21 | write!(f, "{val:04x}")?0 ; |
201 | 0 | } |
202 | 21 | if self.0.len() >= 5 { |
203 | 21 | write!(f, "…")?0 ; |
204 | 0 | } |
205 | 21 | if self.0.len() >= 4 { |
206 | 21 | let len = self.0.len(); |
207 | 21 | let val = u16::from_be_bytes(<[u8; 2]>::try_from(&self.0[len - 2..]).unwrap()); |
208 | 21 | write!(f, "{val:04x}")?0 ; |
209 | 0 | } |
210 | 21 | Ok(()) |
211 | 21 | } Unexecuted instantiation: _RNvXs_NtCsN16ciHI6Qf_7smoldot9informantNtB4_11HashDisplayNtNtCsaYZPK01V26L_4core3fmt7Display3fmt _RNvXs_NtCseuYC0Zibziv_7smoldot9informantNtB4_11HashDisplayNtNtCsaYZPK01V26L_4core3fmt7Display3fmt Line | Count | Source | 196 | 21 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | 197 | 21 | write!(f, "0x")?0 ; | 198 | 21 | if self.0.len() >= 2 { | 199 | 21 | let val = u16::from_be_bytes(<[u8; 2]>::try_from(&self.0[..2]).unwrap()); | 200 | 21 | write!(f, "{val:04x}")?0 ; | 201 | 0 | } | 202 | 21 | if self.0.len() >= 5 { | 203 | 21 | write!(f, "…")?0 ; | 204 | 0 | } | 205 | 21 | if self.0.len() >= 4 { | 206 | 21 | let len = self.0.len(); | 207 | 21 | let val = u16::from_be_bytes(<[u8; 2]>::try_from(&self.0[len - 2..]).unwrap()); | 208 | 21 | write!(f, "{val:04x}")?0 ; | 209 | 0 | } | 210 | 21 | Ok(()) | 211 | 21 | } |
|
212 | | } |
213 | | |
214 | | /// Implements `fmt::Display` and displays a number of bytes in a nice way. |
215 | | pub struct BytesDisplay(pub u64); |
216 | | |
217 | | impl fmt::Display for BytesDisplay { |
218 | 0 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
219 | 0 | let mut value = self.0 as f64; |
220 | 0 |
|
221 | 0 | if value < 1000.0 { |
222 | 0 | return write!(f, "{value} B"); |
223 | 0 | } |
224 | 0 | value /= 1024.0; |
225 | 0 |
|
226 | 0 | if value < 100.0 { |
227 | 0 | return write!(f, "{value:.1} kiB"); |
228 | 0 | } |
229 | 0 | if value < 1000.0 { |
230 | 0 | return write!(f, "{value:.0} kiB"); |
231 | 0 | } |
232 | 0 | value /= 1024.0; |
233 | 0 |
|
234 | 0 | if value < 100.0 { |
235 | 0 | return write!(f, "{value:.1} MiB"); |
236 | 0 | } |
237 | 0 | if value < 1000.0 { |
238 | 0 | return write!(f, "{value:.0} MiB"); |
239 | 0 | } |
240 | 0 | value /= 1024.0; |
241 | 0 |
|
242 | 0 | if value < 100.0 { |
243 | 0 | return write!(f, "{value:.1} GiB"); |
244 | 0 | } |
245 | 0 | if value < 1000.0 { |
246 | 0 | return write!(f, "{value:.0} GiB"); |
247 | 0 | } |
248 | 0 | value /= 1024.0; |
249 | 0 |
|
250 | 0 | if value < 100.0 { |
251 | 0 | return write!(f, "{value:.1} TiB"); |
252 | 0 | } |
253 | 0 | if value < 1000.0 { |
254 | 0 | return write!(f, "{value:.0} TiB"); |
255 | 0 | } |
256 | 0 | value /= 1024.0; |
257 | 0 |
|
258 | 0 | write!(f, "{value:.1} PiB") |
259 | | |
260 | | // Hopefully we never have to go above petabytes. |
261 | 0 | } Unexecuted instantiation: _RNvXs0_NtCsN16ciHI6Qf_7smoldot9informantNtB5_12BytesDisplayNtNtCsaYZPK01V26L_4core3fmt7Display3fmt Unexecuted instantiation: _RNvXs0_NtCseuYC0Zibziv_7smoldot9informantNtB5_12BytesDisplayNtNtCsaYZPK01V26L_4core3fmt7Display3fmt |
262 | | } |
263 | | |
264 | | /// Implements `fmt::Display` and displays a block number with a `#` in front. |
265 | | struct BlockNumberDisplay(u64); |
266 | | |
267 | | impl fmt::Display for BlockNumberDisplay { |
268 | 0 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
269 | 0 | write!(f, "#{}", self.0)?; |
270 | 0 | Ok(()) |
271 | 0 | } Unexecuted instantiation: _RNvXs1_NtCsN16ciHI6Qf_7smoldot9informantNtB5_18BlockNumberDisplayNtNtCsaYZPK01V26L_4core3fmt7Display3fmt Unexecuted instantiation: _RNvXs1_NtCseuYC0Zibziv_7smoldot9informantNtB5_18BlockNumberDisplayNtNtCsaYZPK01V26L_4core3fmt7Display3fmt |
272 | | } |