Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion cumulus/polkadot-parachain/src/command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -560,7 +560,10 @@ pub fn run() -> Result<()> {
let hwbench = (!cli.no_hardware_benchmarks)
.then_some(config.database.path().map(|database_path| {
let _ = std::fs::create_dir_all(database_path);
sc_sysinfo::gather_hwbench(Some(database_path))
sc_sysinfo::gather_hwbench(
Some(database_path),
&SUBSTRATE_REFERENCE_HARDWARE,
)
}))
.flatten();

Expand Down
4 changes: 3 additions & 1 deletion cumulus/polkadot-parachain/src/service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -852,7 +852,9 @@ where
fn warn_if_slow_hardware(hwbench: &sc_sysinfo::HwBench) {
// Polkadot para-chains should generally use these requirements to ensure that the relay-chain
// will not take longer than expected to import its blocks.
if let Err(err) = frame_benchmarking_cli::SUBSTRATE_REFERENCE_HARDWARE.check_hardware(hwbench) {
if let Err(err) =
frame_benchmarking_cli::SUBSTRATE_REFERENCE_HARDWARE.check_hardware(hwbench, false)
{
log::warn!(
"⚠️ The hardware does not meet the minimal requirements {} for role 'Authority' find out more at:\n\
https://wiki.polkadot.network/docs/maintain-guides-how-to-validate-polkadot#reference-hardware",
Expand Down
2 changes: 1 addition & 1 deletion polkadot/cli/src/command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -230,7 +230,7 @@ where
let hwbench = (!cli.run.no_hardware_benchmarks)
.then_some(config.database.path().map(|database_path| {
let _ = std::fs::create_dir_all(&database_path);
sc_sysinfo::gather_hwbench(Some(database_path))
sc_sysinfo::gather_hwbench(Some(database_path), &SUBSTRATE_REFERENCE_HARDWARE)
}))
.flatten();

Expand Down
31 changes: 25 additions & 6 deletions polkadot/node/service/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -765,6 +765,7 @@ pub fn new_full<
use polkadot_availability_recovery::FETCH_CHUNKS_THRESHOLD;
use polkadot_node_network_protocol::request_response::IncomingRequest;
use sc_network_sync::WarpSyncParams;
use sc_sysinfo::Metric;

let is_offchain_indexing_enabled = config.offchain_worker.indexing_enabled;
let role = config.role.clone();
Expand Down Expand Up @@ -1082,13 +1083,31 @@ pub fn new_full<

if let Some(hwbench) = hwbench {
sc_sysinfo::print_hwbench(&hwbench);
match SUBSTRATE_REFERENCE_HARDWARE.check_hardware(&hwbench) {
match SUBSTRATE_REFERENCE_HARDWARE.check_hardware(&hwbench, role.is_authority()) {
Err(err) if role.is_authority() => {
log::warn!(
"⚠️ The hardware does not meet the minimal requirements {} for role 'Authority' find out more at:\n\
https://wiki.polkadot.network/docs/maintain-guides-how-to-validate-polkadot#reference-hardware",
err
);
if err
.0
.iter()
.any(|failure| matches!(failure.metric, Metric::Blake2256Parallel { .. }))
{
log::info!(
"⚠️ Starting January 2025 the hardware will fail the minimal physical CPU cores requirements {} for role 'Authority',\n\
find out more when this will become mandatory at:\n\
https://wiki.polkadot.network/docs/maintain-guides-how-to-validate-polkadot#reference-hardware",
err
);
}
if err
.0
.iter()
.any(|failure| !matches!(failure.metric, Metric::Blake2256Parallel { .. }))
{
log::warn!(
"⚠️ The hardware does not meet the minimal requirements {} for role 'Authority' find out more at:\n\
https://wiki.polkadot.network/docs/maintain-guides-how-to-validate-polkadot#reference-hardware",
err
);
}
},
_ => {},
}
Expand Down
4 changes: 2 additions & 2 deletions substrate/bin/node/cli/src/service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -418,7 +418,7 @@ pub fn new_full_base<N: NetworkBackend<Block, <Block as BlockT>::Hash>>(
let hwbench = (!disable_hardware_benchmarks)
.then_some(config.database.path().map(|database_path| {
let _ = std::fs::create_dir_all(&database_path);
sc_sysinfo::gather_hwbench(Some(database_path))
sc_sysinfo::gather_hwbench(Some(database_path), &SUBSTRATE_REFERENCE_HARDWARE)
}))
.flatten();

Expand Down Expand Up @@ -555,7 +555,7 @@ pub fn new_full_base<N: NetworkBackend<Block, <Block as BlockT>::Hash>>(

if let Some(hwbench) = hwbench {
sc_sysinfo::print_hwbench(&hwbench);
match SUBSTRATE_REFERENCE_HARDWARE.check_hardware(&hwbench) {
match SUBSTRATE_REFERENCE_HARDWARE.check_hardware(&hwbench, false) {
Err(err) if role.is_authority() => {
log::warn!(
"⚠️ The hardware does not meet the minimal requirements {} for role 'Authority'.",
Expand Down
14 changes: 10 additions & 4 deletions substrate/client/sysinfo/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,10 @@ mod sysinfo;
mod sysinfo_linux;

pub use sysinfo::{
benchmark_cpu, benchmark_disk_random_writes, benchmark_disk_sequential_writes,
benchmark_memory, benchmark_sr25519_verify, gather_hwbench, gather_sysinfo,
serialize_throughput, serialize_throughput_option, Metric, Requirement, Requirements,
Throughput,
benchmark_cpu, benchmark_cpu_parallelism, benchmark_disk_random_writes,
benchmark_disk_sequential_writes, benchmark_memory, benchmark_sr25519_verify, gather_hwbench,
gather_sysinfo, serialize_throughput, serialize_throughput_option, Metric, Requirement,
Requirements, Throughput,
};

/// The operating system part of the current target triplet.
Expand All @@ -48,6 +48,10 @@ pub struct HwBench {
/// The CPU speed, as measured in how many MB/s it can hash using the BLAKE2b-256 hash.
#[serde(serialize_with = "serialize_throughput")]
pub cpu_hashrate_score: Throughput,
/// The parallel CPU speed, as measured in how many MB/s it can hash in parallel using the
/// BLAKE2b-256 hash.
#[serde(serialize_with = "serialize_throughput")]
pub parallel_cpu_hashrate_score: Throughput,
/// Memory bandwidth in MB/s, calculated by measuring the throughput of `memcpy`.
#[serde(serialize_with = "serialize_throughput")]
pub memory_memcpy_score: Throughput,
Expand All @@ -65,6 +69,7 @@ pub struct HwBench {
pub disk_random_write_score: Option<Throughput>,
}

#[derive(Copy, Clone, Debug)]
/// Limit the execution time of a benchmark.
pub enum ExecutionLimit {
/// Limit by the maximal duration.
Expand Down Expand Up @@ -133,6 +138,7 @@ pub fn print_sysinfo(sysinfo: &sc_telemetry::SysInfo) {
/// Prints out the results of the hardware benchmarks in the logs.
pub fn print_hwbench(hwbench: &HwBench) {
log::info!("🏁 CPU score: {}", hwbench.cpu_hashrate_score);
log::info!("🏁 CPU parallelism score: {}", hwbench.parallel_cpu_hashrate_score);
log::info!("🏁 Memory score: {}", hwbench.memory_memcpy_score);

if let Some(score) = hwbench.disk_sequential_write_score {
Expand Down
104 changes: 98 additions & 6 deletions substrate/client/sysinfo/src/sysinfo.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,16 +22,17 @@ use sc_telemetry::SysInfo;
use sp_core::{sr25519, Pair};
use sp_io::crypto::sr25519_verify;

use core::f64;
use derive_more::From;
use rand::{seq::SliceRandom, Rng, RngCore};
use serde::{de::Visitor, Deserialize, Deserializer, Serialize, Serializer};
use std::{
fmt,
fmt::{Display, Formatter},
fmt::{self, Display, Formatter},
fs::File,
io::{Seek, SeekFrom, Write},
ops::{Deref, DerefMut},
path::{Path, PathBuf},
sync::{Arc, Barrier},
time::{Duration, Instant},
};

Expand All @@ -42,6 +43,8 @@ pub enum Metric {
Sr25519Verify,
/// Blake2-256 hashing algorithm.
Blake2256,
/// Blake2-256 hashing algorithm executed in parallel
Blake2256Parallel { num_cores: usize },
/// Copying data in RAM.
MemCopy,
/// Disk sequential write.
Expand Down Expand Up @@ -85,7 +88,7 @@ impl Metric {
/// The category of the metric.
pub fn category(&self) -> &'static str {
match self {
Self::Sr25519Verify | Self::Blake2256 => "CPU",
Self::Sr25519Verify | Self::Blake2256 | Self::Blake2256Parallel { .. } => "CPU",
Self::MemCopy => "Memory",
Self::DiskSeqWrite | Self::DiskRndWrite => "Disk",
}
Expand All @@ -96,6 +99,8 @@ impl Metric {
match self {
Self::Sr25519Verify => "SR25519-Verify",
Self::Blake2256 => "BLAKE2-256",
Self::Blake2256Parallel { num_cores } =>
format!("BLAKE2-256-Parallel-{}", num_cores).leak(),
Self::MemCopy => "Copy",
Self::DiskSeqWrite => "Seq Write",
Self::DiskRndWrite => "Rnd Write",
Expand Down Expand Up @@ -253,6 +258,14 @@ pub struct Requirement {
deserialize_with = "deserialize_throughput"
)]
pub minimum: Throughput,
/// Check this requirement only for relay chain authority nodes.
#[serde(default)]
#[serde(skip_serializing_if = "is_false")]
pub check_on_rc_authority: bool,
}

fn is_false(value: &bool) -> bool {
!value
}

#[inline(always)]
Expand Down Expand Up @@ -375,6 +388,48 @@ pub fn benchmark_cpu(limit: ExecutionLimit) -> Throughput {
.expect("benchmark cannot fail; qed")
}

// This benchmarks the entire CPU speed as measured by calculating BLAKE2b-256 hashes, in bytes per
// second. It spawns multiple threads to measure the throughput of the entire CPU and averages the
// score obtained by each thread. If we have at least `refhw_num_cores` available then the
// average throughput should be relatively close to the single core performance as measured by
// `benchmark_cpu`.
pub fn benchmark_cpu_parallelism(limit: ExecutionLimit, refhw_num_cores: usize) -> Throughput {
const SIZE: usize = 32 * 1024;

let ready_to_run_benchmark = Arc::new(Barrier::new(refhw_num_cores));
let mut benchmark_threads = Vec::new();

// Spawn a thread for each expected core and average the throughput for each of them.
for _ in 0..refhw_num_cores {
let ready_to_run_benchmark = ready_to_run_benchmark.clone();

let handle = std::thread::spawn(move || {
let mut buffer = Vec::new();
buffer.resize(SIZE, 0x66);
let mut hash = Default::default();

let run = || -> Result<(), ()> {
clobber_slice(&mut buffer);
hash = sp_crypto_hashing::blake2_256(&buffer);
clobber_slice(&mut hash);

Ok(())
};
ready_to_run_benchmark.wait();
benchmark("CPU score", SIZE, limit.max_iterations(), limit.max_duration(), run)
.expect("benchmark cannot fail; qed")
});
benchmark_threads.push(handle);
}

let average_score = benchmark_threads
.into_iter()
.map(|thread| thread.join().map(|throughput| throughput.as_kibs()).unwrap_or(f64::MIN))
.sum::<f64>() /
refhw_num_cores as f64;
Throughput::from_kibs(average_score)
}

/// A default [`ExecutionLimit`] that can be used to call [`benchmark_memory`].
pub const DEFAULT_MEMORY_EXECUTION_LIMIT: ExecutionLimit =
ExecutionLimit::Both { max_iterations: 32, max_duration: Duration::from_millis(100) };
Expand Down Expand Up @@ -624,10 +679,23 @@ pub fn benchmark_sr25519_verify(limit: ExecutionLimit) -> Throughput {
/// Optionally accepts a path to a `scratch_directory` to use to benchmark the
/// disk. Also accepts the `requirements` for the hardware benchmark and a
/// boolean to specify if the node is an authority.
pub fn gather_hwbench(scratch_directory: Option<&Path>) -> HwBench {
pub fn gather_hwbench(scratch_directory: Option<&Path>, requirements: &Requirements) -> HwBench {
let parallel_num_cores = requirements
.0
.iter()
.filter_map(|requirement| match requirement.metric {
Metric::Blake2256Parallel { num_cores } => Some(num_cores),
_ => None,
})
.next()
.unwrap_or(1);
#[allow(unused_mut)]
let mut hwbench = HwBench {
cpu_hashrate_score: benchmark_cpu(DEFAULT_CPU_EXECUTION_LIMIT),
parallel_cpu_hashrate_score: benchmark_cpu_parallelism(
DEFAULT_CPU_EXECUTION_LIMIT,
parallel_num_cores,
),
memory_memcpy_score: benchmark_memory(DEFAULT_MEMORY_EXECUTION_LIMIT),
disk_sequential_write_score: None,
disk_random_write_score: None,
Expand Down Expand Up @@ -659,9 +727,17 @@ pub fn gather_hwbench(scratch_directory: Option<&Path>) -> HwBench {

impl Requirements {
/// Whether the hardware requirements are met by the provided benchmark results.
pub fn check_hardware(&self, hwbench: &HwBench) -> Result<(), CheckFailures> {
pub fn check_hardware(
&self,
hwbench: &HwBench,
is_rc_authority: bool,
) -> Result<(), CheckFailures> {
let mut failures = Vec::new();
for requirement in self.0.iter() {
if requirement.check_on_rc_authority && !is_rc_authority {
continue
}

match requirement.metric {
Metric::Blake2256 =>
if requirement.minimum > hwbench.cpu_hashrate_score {
Expand All @@ -671,6 +747,14 @@ impl Requirements {
found: hwbench.cpu_hashrate_score,
});
},
Metric::Blake2256Parallel { .. } =>
if requirement.minimum > hwbench.parallel_cpu_hashrate_score {
failures.push(CheckFailure {
metric: requirement.metric,
expected: requirement.minimum,
found: hwbench.parallel_cpu_hashrate_score,
});
},
Metric::MemCopy =>
if requirement.minimum > hwbench.memory_memcpy_score {
failures.push(CheckFailure {
Expand Down Expand Up @@ -732,6 +816,13 @@ mod tests {
assert!(benchmark_cpu(DEFAULT_CPU_EXECUTION_LIMIT) > Throughput::from_mibs(0.0));
}

#[test]
fn test_benchmark_parallel_cpu() {
assert!(
benchmark_cpu_parallelism(DEFAULT_CPU_EXECUTION_LIMIT, 8) > Throughput::from_mibs(0.0)
);
}

#[test]
fn test_benchmark_memory() {
assert!(benchmark_memory(DEFAULT_MEMORY_EXECUTION_LIMIT) > Throughput::from_mibs(0.0));
Expand Down Expand Up @@ -781,13 +872,14 @@ mod tests {
fn hwbench_serialize_works() {
let hwbench = HwBench {
cpu_hashrate_score: Throughput::from_gibs(1.32),
parallel_cpu_hashrate_score: Throughput::from_gibs(1.32),
memory_memcpy_score: Throughput::from_kibs(9342.432),
disk_sequential_write_score: Some(Throughput::from_kibs(4332.12)),
disk_random_write_score: None,
};

let serialized = serde_json::to_string(&hwbench).unwrap();
// Throughput from all of the benchmarks should be converted to MiBs.
assert_eq!(serialized, "{\"cpu_hashrate_score\":1351,\"memory_memcpy_score\":9,\"disk_sequential_write_score\":4}");
assert_eq!(serialized, "{\"cpu_hashrate_score\":1351,\"parallel_cpu_hashrate_score\":1351,\"memory_memcpy_score\":9,\"disk_sequential_write_score\":4}");
}
}
25 changes: 22 additions & 3 deletions substrate/utils/frame/benchmarking-cli/src/machine/hardware.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,17 +51,36 @@ mod tests {
assert_eq!(
*SUBSTRATE_REFERENCE_HARDWARE,
Requirements(vec![
Requirement { metric: Metric::Blake2256, minimum: Throughput::from_mibs(1000.00) },
Requirement {
metric: Metric::Blake2256,
minimum: Throughput::from_mibs(1000.00),
check_on_rc_authority: false
},
Requirement {
metric: Metric::Blake2256Parallel { num_cores: 8 },
minimum: Throughput::from_mibs(1000.00),
check_on_rc_authority: true,
},
Requirement {
metric: Metric::Sr25519Verify,
minimum: Throughput::from_kibs(637.619999744),
check_on_rc_authority: false
},
Requirement {
metric: Metric::MemCopy,
minimum: Throughput::from_gibs(11.4925205078125003),
check_on_rc_authority: false,
},
Requirement {
metric: Metric::DiskSeqWrite,
minimum: Throughput::from_mibs(950.0),
check_on_rc_authority: false,
},
Requirement {
metric: Metric::DiskRndWrite,
minimum: Throughput::from_mibs(420.0),
check_on_rc_authority: false
},
Requirement { metric: Metric::DiskSeqWrite, minimum: Throughput::from_mibs(950.0) },
Requirement { metric: Metric::DiskRndWrite, minimum: Throughput::from_mibs(420.0) },
])
);
}
Expand Down
8 changes: 5 additions & 3 deletions substrate/utils/frame/benchmarking-cli/src/machine/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,9 @@ use log::{error, info, warn};
use sc_cli::{CliConfiguration, Result, SharedParams};
use sc_service::Configuration;
use sc_sysinfo::{
benchmark_cpu, benchmark_disk_random_writes, benchmark_disk_sequential_writes,
benchmark_memory, benchmark_sr25519_verify, ExecutionLimit, Metric, Requirement, Requirements,
Throughput,
benchmark_cpu, benchmark_cpu_parallelism, benchmark_disk_random_writes,
benchmark_disk_sequential_writes, benchmark_memory, benchmark_sr25519_verify, ExecutionLimit,
Metric, Requirement, Requirements, Throughput,
};

use crate::shared::check_build_profile;
Expand Down Expand Up @@ -150,6 +150,8 @@ impl MachineCmd {

let score = match metric {
Metric::Blake2256 => benchmark_cpu(hash_limit),
Metric::Blake2256Parallel { num_cores } =>
benchmark_cpu_parallelism(hash_limit, *num_cores),
Metric::Sr25519Verify => benchmark_sr25519_verify(verify_limit),
Metric::MemCopy => benchmark_memory(memory_limit),
Metric::DiskSeqWrite => benchmark_disk_sequential_writes(disk_limit, dir)?,
Expand Down
Loading