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

            
19
//! This crate contains the code necessary to gather basic hardware
20
//! and software telemetry information about the node on which we're running.
21

            
22
use futures::prelude::*;
23
use std::time::Duration;
24

            
25
mod sysinfo;
26
#[cfg(target_os = "linux")]
27
mod sysinfo_linux;
28

            
29
pub use sysinfo::{
30
	benchmark_cpu, benchmark_disk_random_writes, benchmark_disk_sequential_writes,
31
	benchmark_memory, benchmark_sr25519_verify, gather_hwbench, gather_sysinfo,
32
	serialize_throughput, serialize_throughput_option, Metric, Requirement, Requirements,
33
	Throughput,
34
};
35

            
36
/// The operating system part of the current target triplet.
37
pub const TARGET_OS: &str = include_str!(concat!(env!("OUT_DIR"), "/target_os.txt"));
38

            
39
/// The CPU ISA architecture part of the current target triplet.
40
pub const TARGET_ARCH: &str = include_str!(concat!(env!("OUT_DIR"), "/target_arch.txt"));
41

            
42
/// The environment part of the current target triplet.
43
pub const TARGET_ENV: &str = include_str!(concat!(env!("OUT_DIR"), "/target_env.txt"));
44

            
45
/// Hardware benchmark results for the node.
46
#[derive(Clone, Debug, serde::Serialize)]
47
pub struct HwBench {
48
	/// The CPU speed, as measured in how many MB/s it can hash using the BLAKE2b-256 hash.
49
	#[serde(serialize_with = "serialize_throughput")]
50
	pub cpu_hashrate_score: Throughput,
51
	/// Memory bandwidth in MB/s, calculated by measuring the throughput of `memcpy`.
52
	#[serde(serialize_with = "serialize_throughput")]
53
	pub memory_memcpy_score: Throughput,
54
	/// Sequential disk write speed in MB/s.
55
	#[serde(
56
		serialize_with = "serialize_throughput_option",
57
		skip_serializing_if = "Option::is_none"
58
	)]
59
	pub disk_sequential_write_score: Option<Throughput>,
60
	/// Random disk write speed in MB/s.
61
	#[serde(
62
		serialize_with = "serialize_throughput_option",
63
		skip_serializing_if = "Option::is_none"
64
	)]
65
	pub disk_random_write_score: Option<Throughput>,
66
}
67

            
68
/// Limit the execution time of a benchmark.
69
pub enum ExecutionLimit {
70
	/// Limit by the maximal duration.
71
	MaxDuration(Duration),
72

            
73
	/// Limit by the maximal number of iterations.
74
	MaxIterations(usize),
75

            
76
	/// Limit by the maximal duration and maximal number of iterations.
77
	Both { max_iterations: usize, max_duration: Duration },
78
}
79

            
80
impl ExecutionLimit {
81
	/// Creates a new execution limit with the passed seconds as duration limit.
82
	pub fn from_secs_f32(secs: f32) -> Self {
83
		Self::MaxDuration(Duration::from_secs_f32(secs))
84
	}
85

            
86
	/// Returns the duration limit or `MAX` if none is present.
87
	pub fn max_duration(&self) -> Duration {
88
		match self {
89
			Self::MaxDuration(d) => *d,
90
			Self::Both { max_duration, .. } => *max_duration,
91
			_ => Duration::from_secs(u64::MAX),
92
		}
93
	}
94

            
95
	/// Returns the iterations limit or `MAX` if none is present.
96
	pub fn max_iterations(&self) -> usize {
97
		match self {
98
			Self::MaxIterations(d) => *d,
99
			Self::Both { max_iterations, .. } => *max_iterations,
100
			_ => usize::MAX,
101
		}
102
	}
103
}
104

            
105
/// Prints out the system software/hardware information in the logs.
106
pub fn print_sysinfo(sysinfo: &sc_telemetry::SysInfo) {
107
	log::info!("💻 Operating system: {}", TARGET_OS);
108
	log::info!("💻 CPU architecture: {}", TARGET_ARCH);
109
	if !TARGET_ENV.is_empty() {
110
		log::info!("💻 Target environment: {}", TARGET_ENV);
111
	}
112

            
113
	if let Some(ref cpu) = sysinfo.cpu {
114
		log::info!("💻 CPU: {}", cpu);
115
	}
116
	if let Some(core_count) = sysinfo.core_count {
117
		log::info!("💻 CPU cores: {}", core_count);
118
	}
119
	if let Some(memory) = sysinfo.memory {
120
		log::info!("💻 Memory: {}MB", memory / (1024 * 1024));
121
	}
122
	if let Some(ref linux_kernel) = sysinfo.linux_kernel {
123
		log::info!("💻 Kernel: {}", linux_kernel);
124
	}
125
	if let Some(ref linux_distro) = sysinfo.linux_distro {
126
		log::info!("💻 Linux distribution: {}", linux_distro);
127
	}
128
	if let Some(is_virtual_machine) = sysinfo.is_virtual_machine {
129
		log::info!("💻 Virtual machine: {}", if is_virtual_machine { "yes" } else { "no" });
130
	}
131
}
132

            
133
/// Prints out the results of the hardware benchmarks in the logs.
134
pub fn print_hwbench(hwbench: &HwBench) {
135
	log::info!("🏁 CPU score: {}", hwbench.cpu_hashrate_score);
136
	log::info!("🏁 Memory score: {}", hwbench.memory_memcpy_score);
137

            
138
	if let Some(score) = hwbench.disk_sequential_write_score {
139
		log::info!("🏁 Disk score (seq. writes): {}", score);
140
	}
141
	if let Some(score) = hwbench.disk_random_write_score {
142
		log::info!("🏁 Disk score (rand. writes): {}", score);
143
	}
144
}
145

            
146
/// Initializes the hardware benchmarks telemetry.
147
pub fn initialize_hwbench_telemetry(
148
	telemetry_handle: sc_telemetry::TelemetryHandle,
149
	hwbench: HwBench,
150
) -> impl std::future::Future<Output = ()> {
151
	let mut connect_stream = telemetry_handle.on_connect_stream();
152
	async move {
153
		let payload = serde_json::to_value(&hwbench)
154
			.expect("the `HwBench` can always be serialized into a JSON object; qed");
155
		let mut payload = match payload {
156
			serde_json::Value::Object(map) => map,
157
			_ => unreachable!("the `HwBench` always serializes into a JSON object; qed"),
158
		};
159
		payload.insert("msg".into(), "sysinfo.hwbench".into());
160
		while connect_stream.next().await.is_some() {
161
			telemetry_handle.send_telemetry(sc_telemetry::SUBSTRATE_INFO, payload.clone());
162
		}
163
	}
164
}