Coverage Report

Created: 2024-05-16 12:16

/__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
}