Skip to main content

soil_service/sysinfo/
mod.rs

1// This file is part of Soil.
2
3// Copyright (C) Soil contributors.
4// Copyright (C) Parity Technologies (UK) Ltd.
5// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
6
7//! This crate contains the code necessary to gather basic hardware
8//! and software telemetry information about the node on which we're running.
9
10use futures::prelude::*;
11use std::time::Duration;
12
13mod sysinfo;
14#[cfg(target_os = "freebsd")]
15mod sysinfo_freebsd;
16#[cfg(target_os = "linux")]
17mod sysinfo_linux;
18
19pub use sysinfo::{
20	benchmark_cpu, benchmark_cpu_parallelism, benchmark_disk_random_writes,
21	benchmark_disk_sequential_writes, benchmark_memory, benchmark_sr25519_verify, gather_hwbench,
22	gather_sysinfo, serialize_throughput, serialize_throughput_option, Metric, Requirement,
23	Requirements, Throughput, SUBSTRATE_REFERENCE_HARDWARE,
24};
25
26/// The operating system part of the current target triplet.
27pub const TARGET_OS: &str = include_str!(concat!(env!("OUT_DIR"), "/target_os.txt"));
28
29/// The CPU ISA architecture part of the current target triplet.
30pub const TARGET_ARCH: &str = include_str!(concat!(env!("OUT_DIR"), "/target_arch.txt"));
31
32/// The environment part of the current target triplet.
33pub const TARGET_ENV: &str = include_str!(concat!(env!("OUT_DIR"), "/target_env.txt"));
34
35/// Hardware benchmark results for the node.
36#[derive(Clone, Debug, serde::Serialize)]
37pub struct HwBench {
38	/// The CPU speed, as measured in how many MB/s it can hash using the BLAKE2b-256 hash.
39	#[serde(serialize_with = "serialize_throughput")]
40	pub cpu_hashrate_score: Throughput,
41	/// The parallel CPU speed, as measured in how many MB/s it can hash in parallel using the
42	/// BLAKE2b-256 hash.
43	#[serde(serialize_with = "serialize_throughput")]
44	pub parallel_cpu_hashrate_score: Throughput,
45	/// The number of expected cores used for computing the parallel CPU speed.
46	pub parallel_cpu_cores: usize,
47	/// Memory bandwidth in MB/s, calculated by measuring the throughput of `memcpy`.
48	#[serde(serialize_with = "serialize_throughput")]
49	pub memory_memcpy_score: Throughput,
50	/// Sequential disk write speed in MB/s.
51	#[serde(
52		serialize_with = "serialize_throughput_option",
53		skip_serializing_if = "Option::is_none"
54	)]
55	pub disk_sequential_write_score: Option<Throughput>,
56	/// Random disk write speed in MB/s.
57	#[serde(
58		serialize_with = "serialize_throughput_option",
59		skip_serializing_if = "Option::is_none"
60	)]
61	pub disk_random_write_score: Option<Throughput>,
62}
63
64#[derive(Copy, Clone, Debug)]
65/// Limit the execution time of a benchmark.
66pub enum ExecutionLimit {
67	/// Limit by the maximal duration.
68	MaxDuration(Duration),
69
70	/// Limit by the maximal number of iterations.
71	MaxIterations(usize),
72
73	/// Limit by the maximal duration and maximal number of iterations.
74	Both { max_iterations: usize, max_duration: Duration },
75}
76
77impl ExecutionLimit {
78	/// Creates a new execution limit with the passed seconds as duration limit.
79	pub fn from_secs_f32(secs: f32) -> Self {
80		Self::MaxDuration(Duration::from_secs_f32(secs))
81	}
82
83	/// Returns the duration limit or `MAX` if none is present.
84	pub fn max_duration(&self) -> Duration {
85		match self {
86			Self::MaxDuration(d) => *d,
87			Self::Both { max_duration, .. } => *max_duration,
88			_ => Duration::from_secs(u64::MAX),
89		}
90	}
91
92	/// Returns the iterations limit or `MAX` if none is present.
93	pub fn max_iterations(&self) -> usize {
94		match self {
95			Self::MaxIterations(d) => *d,
96			Self::Both { max_iterations, .. } => *max_iterations,
97			_ => usize::MAX,
98		}
99	}
100}
101
102/// Prints out the system software/hardware information in the logs.
103pub fn print_sysinfo(sysinfo: &soil_telemetry::SysInfo) {
104	log::info!("💻 Operating system: {}", TARGET_OS);
105	log::info!("💻 CPU architecture: {}", TARGET_ARCH);
106	if !TARGET_ENV.is_empty() {
107		log::info!("💻 Target environment: {}", TARGET_ENV);
108	}
109
110	if let Some(ref cpu) = sysinfo.cpu {
111		log::info!("💻 CPU: {}", cpu);
112	}
113	if let Some(core_count) = sysinfo.core_count {
114		log::info!("💻 CPU cores: {}", core_count);
115	}
116	if let Some(memory) = sysinfo.memory {
117		log::info!("💻 Memory: {}MB", memory / (1024 * 1024));
118	}
119	if let Some(ref linux_kernel) = sysinfo.linux_kernel {
120		log::info!("💻 Kernel: {}", linux_kernel);
121	}
122	if let Some(ref linux_distro) = sysinfo.linux_distro {
123		log::info!("💻 Linux distribution: {}", linux_distro);
124	}
125	if let Some(is_virtual_machine) = sysinfo.is_virtual_machine {
126		log::info!("💻 Virtual machine: {}", if is_virtual_machine { "yes" } else { "no" });
127	}
128}
129
130/// Prints out the results of the hardware benchmarks in the logs.
131pub fn print_hwbench(hwbench: &HwBench) {
132	log::info!(
133		"🏁 CPU single core score: {}, parallelism score: {} with expected cores: {}",
134		hwbench.cpu_hashrate_score,
135		hwbench.parallel_cpu_hashrate_score,
136		hwbench.parallel_cpu_cores,
137	);
138	log::info!("🏁 Memory score: {}", hwbench.memory_memcpy_score);
139
140	if let Some(score) = hwbench.disk_sequential_write_score {
141		log::info!("🏁 Disk score (seq. writes): {}", score);
142	}
143	if let Some(score) = hwbench.disk_random_write_score {
144		log::info!("🏁 Disk score (rand. writes): {}", score);
145	}
146}
147
148/// Initializes the hardware benchmarks telemetry.
149pub fn initialize_hwbench_telemetry(
150	telemetry_handle: soil_telemetry::TelemetryHandle,
151	hwbench: HwBench,
152) -> impl std::future::Future<Output = ()> {
153	let mut connect_stream = telemetry_handle.on_connect_stream();
154	async move {
155		let payload = serde_json::to_value(&hwbench)
156			.expect("the `HwBench` can always be serialized into a JSON object; qed");
157		let mut payload = match payload {
158			serde_json::Value::Object(map) => map,
159			_ => unreachable!("the `HwBench` always serializes into a JSON object; qed"),
160		};
161		payload.insert("msg".into(), "sysinfo.hwbench".into());
162		while connect_stream.next().await.is_some() {
163			telemetry_handle.send_telemetry(soil_telemetry::SUBSTRATE_INFO, payload.clone());
164		}
165	}
166}