Skip to content
This repository has been archived by the owner on Nov 15, 2023. It is now read-only.

cli: introduce host-perf-check command #4342

Merged
merged 31 commits into from
Dec 9, 2021
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
d21db4e
cli: introduce host-perf-check command
slumber Nov 21, 2021
5937ce6
fix build
slumber Nov 22, 2021
ff6d3a1
Review fixes
slumber Nov 22, 2021
71b19d3
Extract perf check into new module
slumber Nov 23, 2021
6770d63
Explicit log level
slumber Nov 23, 2021
e142424
Use a build script for cfg build
slumber Nov 23, 2021
9414d2f
Save dummy file in HOME
slumber Nov 23, 2021
c6a72a8
Refactor
slumber Nov 24, 2021
a4f75a2
Error on any non-release build
slumber Nov 24, 2021
65701ad
Improve naming
slumber Nov 24, 2021
30e225f
Use the base path for caching
slumber Nov 24, 2021
7bb37a2
Reuse wasm binary from kusama_runtime
slumber Nov 24, 2021
11f1a16
Update cli/src/command.rs
slumber Nov 25, 2021
a2b0a72
Add an explanation to gethostname
slumber Nov 25, 2021
2aba480
Make cache path configurable
slumber Nov 30, 2021
2891597
Add erasure-coding check
slumber Dec 1, 2021
a39de72
Green threshold for perf tests
slumber Dec 1, 2021
f89e6b5
Write hostname without zero bytes
slumber Dec 2, 2021
4dd4e89
Add force flag
slumber Dec 2, 2021
de88f4c
Extract performance test into a separate crate
slumber Dec 3, 2021
95bf838
Implement constants generation
slumber Dec 3, 2021
633088d
Add logs
slumber Dec 3, 2021
28ecac3
Simplify fs read/write
slumber Dec 4, 2021
ab46e65
Propagate tracing error
slumber Dec 4, 2021
04d311e
Expect instead of unwrap
slumber Dec 4, 2021
f9ae107
Update headers
slumber Dec 4, 2021
f8739ae
Rename cache_path to base_path
slumber Dec 5, 2021
9bdbddd
Drop caching
slumber Dec 5, 2021
f36888d
Apply suggestions from code review
bkchr Dec 5, 2021
e1e42e4
Merge remote-tracking branch 'origin/master' into slumber-hostperfche…
slumber Dec 9, 2021
66dfa6d
Decrease the number of warm up runs
slumber Dec 9, 2021
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
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ log = "0.4.13"
thiserror = "1.0.30"
structopt = { version = "0.3.25", optional = true }
futures = "0.3.17"
nix = "0.20.0"

service = { package = "polkadot-service", path = "../node/service", default-features = false, optional = true }
polkadot-node-core-pvf = { path = "../node/core/pvf", optional = true }
Expand Down
3 changes: 3 additions & 0 deletions cli/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,8 @@
// along with Polkadot. If not, see <http://www.gnu.org/licenses/>.

fn main() {
if let Ok(profile) = std::env::var("PROFILE") {
println!("cargo:rustc-cfg=build_type=\"{}\"", profile);
}
substrate_build_script_utils::generate_cargo_keys();
}
4 changes: 4 additions & 0 deletions cli/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,10 @@ pub enum Subcommand {
#[structopt(name = "benchmark", about = "Benchmark runtime pallets.")]
Benchmark(frame_benchmarking_cli::BenchmarkCmd),

/// Compiles a sample wasm code in order to measure the machine capabilities
/// of running PVF host.
HostPerfCheck,

/// Try some command against runtime state.
#[cfg(feature = "try-runtime")]
TryRuntime(try_runtime_cli::TryRuntimeCmd),
Expand Down
36 changes: 22 additions & 14 deletions cli/src/command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,20 +21,7 @@ use sc_cli::{Role, RuntimeVersion, SubstrateCli};
use service::{self, IdentifyVariant};
use sp_core::crypto::Ss58AddressFormatRegistry;

#[derive(thiserror::Error, Debug)]
pub enum Error {
#[error(transparent)]
PolkadotService(#[from] service::Error),

#[error(transparent)]
SubstrateCli(#[from] sc_cli::Error),

#[error(transparent)]
SubstrateService(#[from] sc_service::Error),

#[error("Other: {0}")]
Other(String),
}
pub use crate::error::{Error, PerfCheckError};

impl std::convert::From<String> for Error {
fn from(s: String) -> Self {
Expand Down Expand Up @@ -215,6 +202,20 @@ fn ensure_dev(spec: &Box<dyn service::ChainSpec>) -> std::result::Result<(), Str
}
}

/// Runs a performance check via compiling sample wasm code with a timeout.
/// Should only be run in release build since the check would take too much time otherwise.
/// Returns `Ok` immediately if the check has been passed previously.
fn host_perf_check() -> Result<()> {
#[cfg(not(build_type = "release"))]
{
Err(PerfCheckError::WrongBuildType.into())
}
#[cfg(build_type = "release")]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

will this work with custom profiles #4311 (comment) @bkchr?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think so, the profile should be matched with the one used for distributing the binary. If this profile changes to e.g release-lto, it should be also updated here.

{
crate::host_perf_check::host_perf_check().map_err(Into::into)
}
}

/// Launch a node, accepting arguments just like a regular node,
/// accepts an alternative overseer generator, to adjust behavior
/// for integration tests as needed.
Expand Down Expand Up @@ -415,6 +416,13 @@ pub fn run() -> Result<()> {
#[cfg(not(feature = "polkadot-native"))]
panic!("No runtime feature (polkadot, kusama, westend, rococo) is enabled")
},
Some(Subcommand::HostPerfCheck) => {
let mut builder = sc_cli::LoggerBuilder::new("info");
slumber marked this conversation as resolved.
Show resolved Hide resolved
builder.with_colors(true);
let _ = builder.init();
slumber marked this conversation as resolved.
Show resolved Hide resolved

host_perf_check()
},
Some(Subcommand::Key(cmd)) => Ok(cmd.run(&cli)?),
#[cfg(feature = "try-runtime")]
Some(Subcommand::TryRuntime(cmd)) => {
Expand Down
54 changes: 54 additions & 0 deletions cli/src/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// Copyright 2017-2021 Parity Technologies (UK) Ltd.
// This file is part of Polkadot.

// Polkadot is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.

// Polkadot is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with Polkadot. If not, see <http://www.gnu.org/licenses/>.

use polkadot_node_core_pvf::sc_executor_common;
use std::time::Duration;

#[derive(thiserror::Error, Debug)]
pub enum Error {
#[error(transparent)]
PolkadotService(#[from] service::Error),

#[error(transparent)]
SubstrateCli(#[from] sc_cli::Error),

#[error(transparent)]
SubstrateService(#[from] sc_service::Error),

#[error(transparent)]
PerfCheck(#[from] PerfCheckError),

#[error("Other: {0}")]
Other(String),
}

#[allow(missing_docs)]
#[derive(thiserror::Error, Debug)]
pub enum PerfCheckError {
#[error("This subcommand is only available in release mode")]
WrongBuildType,

#[error("Failed to decompress wasm code")]
CodeDecompressionFailed,

#[error(transparent)]
Wasm(#[from] sc_executor_common::error::WasmError),

#[error(
"Performance check not passed: exceeded the {limit:?} time limit, elapsed: {elapsed:?}"
)]
TimeOut { elapsed: Duration, limit: Duration },
}
drahnr marked this conversation as resolved.
Show resolved Hide resolved
115 changes: 115 additions & 0 deletions cli/src/host_perf_check.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
// Copyright 2017-2021 Parity Technologies (UK) Ltd.
// This file is part of Polkadot.

// Polkadot is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.

// Polkadot is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with Polkadot. If not, see <http://www.gnu.org/licenses/>.

use crate::error::PerfCheckError;
use log::info;
use nix::unistd;
use polkadot_node_core_pvf::sp_maybe_compressed_blob;
use std::{
fs::{self, OpenOptions},
io::{self, Read, Write},
path::{Path, PathBuf},
time::{Duration, Instant},
};

fn check_passed_file_path() -> io::Result<PathBuf> {
let home_dir =
std::env::var("HOME").map_err(|err| io::Error::new(io::ErrorKind::Other, err))?;
slumber marked this conversation as resolved.
Show resolved Hide resolved
let path = Path::new(&home_dir).join(".polkadot");

if let Err(err) = fs::create_dir(&path) {
if err.kind() != io::ErrorKind::AlreadyExists {
return Err(err)
}
}

Ok(path.join("perf_check_passed"))
}

fn is_perf_check_done(path: &Path) -> io::Result<bool> {
let host_name_max_len = unistd::SysconfVar::HOST_NAME_MAX as usize;

let mut host_name = vec![0u8; host_name_max_len];
let mut buf = host_name.clone();
unistd::gethostname(&mut host_name).map_err(|err| io::Error::new(io::ErrorKind::Other, err))?;

let file = match fs::File::open(path) {
Ok(file) => file,
Err(err) if err.kind() == io::ErrorKind::NotFound => return Ok(false),
Err(err) => return Err(err),
};
let mut reader = io::BufReader::new(file);

reader.read_exact(&mut buf)?;
slumber marked this conversation as resolved.
Show resolved Hide resolved

Ok(host_name == buf)
}

fn save_check_passed_file(path: &Path) -> io::Result<()> {
let host_name_max_len = unistd::SysconfVar::HOST_NAME_MAX as usize;
let mut host_name = vec![0u8; host_name_max_len];
unistd::gethostname(&mut host_name).map_err(|err| io::Error::new(io::ErrorKind::Other, err))?;
drahnr marked this conversation as resolved.
Show resolved Hide resolved

let mut file = OpenOptions::new().truncate(true).create(true).write(true).open(path)?;

file.write(&host_name)?;

Ok(())
slumber marked this conversation as resolved.
Show resolved Hide resolved
}

pub fn host_perf_check() -> Result<(), PerfCheckError> {
const PERF_CHECK_TIME_LIMIT: Duration = Duration::from_secs(20);
const CODE_SIZE_LIMIT: usize = 1024usize.pow(3);
const WASM_CODE: &[u8] = include_bytes!(
"../../target/release/wbuild/kusama-runtime/kusama_runtime.compact.compressed.wasm"
slumber marked this conversation as resolved.
Show resolved Hide resolved
);

// We will try to save a special file at $HOME/.polkadot/perf_check_passed.
let check_passed_path = check_passed_file_path()
.map_err(|err| {
info!(
"Performance check result is not going to be persisted due to an error: {:?}",
err
)
})
.ok();

if let Some(ref path) = check_passed_path {
if let Ok(true) = is_perf_check_done(path) {
info!("Performance check skipped: already passed");
return Ok(())
}
}

info!("Running the performance check...");
let start = Instant::now();

// Recreate the pipeline from the pvf prepare worker.
let code = sp_maybe_compressed_blob::decompress(WASM_CODE, CODE_SIZE_LIMIT)
.or(Err(PerfCheckError::CodeDecompressionFailed))?;
let blob = polkadot_node_core_pvf::prevalidate(code.as_ref()).map_err(PerfCheckError::from)?;
let _ = polkadot_node_core_pvf::prepare(blob).map_err(PerfCheckError::from)?;

let elapsed = start.elapsed();
if elapsed <= PERF_CHECK_TIME_LIMIT {
info!("Performance check passed, elapsed: {:?}", start.elapsed());
slumber marked this conversation as resolved.
Show resolved Hide resolved
// Persist successful result.
check_passed_path.map(|path| save_check_passed_file(&path));
Ok(())
} else {
Err(PerfCheckError::TimeOut { elapsed, limit: PERF_CHECK_TIME_LIMIT })
}
}
4 changes: 4 additions & 0 deletions cli/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@
mod cli;
#[cfg(feature = "cli")]
mod command;
#[cfg(feature = "cli")]
mod error;
#[cfg(all(feature = "cli", build_type = "release"))]
mod host_perf_check;

#[cfg(feature = "full-node")]
pub use service::RuntimeApiCollection;
Expand Down
5 changes: 5 additions & 0 deletions node/core/pvf/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -102,4 +102,9 @@ pub use metrics::Metrics;
pub use execute::worker_entrypoint as execute_worker_entrypoint;
pub use prepare::worker_entrypoint as prepare_worker_entrypoint;

pub use executor_intf::{prepare, prevalidate};

pub use sc_executor_common;
pub use sp_maybe_compressed_blob;

const LOG_TARGET: &str = "parachain::pvf";