feat: Rebrand Polkadot/Substrate references to PezkuwiChain
This commit systematically rebrands various references from Parity Technologies' Polkadot/Substrate ecosystem to PezkuwiChain within the kurdistan-sdk. Key changes include: - Updated external repository URLs (zombienet-sdk, parity-db, parity-scale-codec, wasm-instrument) to point to pezkuwichain forks. - Modified internal documentation and code comments to reflect PezkuwiChain naming and structure. - Replaced direct references to with or specific paths within the for XCM, Pezkuwi, and other modules. - Cleaned up deprecated issue and PR references in various and files, particularly in and modules. - Adjusted image and logo URLs in documentation to point to PezkuwiChain assets. - Removed or rephrased comments related to external Polkadot/Substrate PRs and issues. This is a significant step towards fully customizing the SDK for the PezkuwiChain ecosystem.
This commit is contained in:
@@ -0,0 +1,19 @@
|
||||
# Shared code
|
||||
|
||||
Contains code that is shared among multiple sub-commands.
|
||||
|
||||
## Arguments
|
||||
|
||||
- `--mul` Multiply the result with a factor. Can be used to manually adjust for future chain growth.
|
||||
- `--add` Add a value to the result. Can be used to manually offset the results.
|
||||
- `--metric` Set the metric to use for calculating the final weight from the raw data. Defaults to `average`.
|
||||
- `--weight-path` Set the file or directory to write the weight files to.
|
||||
- `--db` The database backend to use. This depends on your snapshot.
|
||||
- `--pruning` Set the pruning mode of the node. Some benchmarks require you to set this to `archive`.
|
||||
- `--base-path` The location on the disk that should be used for the benchmarks. You can try this on different disks or
|
||||
even on a mounted RAM-disk. It is important to use the same location that will later-on be used to store the chain
|
||||
data to get the correct results.
|
||||
- `--header` Optional file header which will be prepended to the weight output file. Can be used for adding LICENSE
|
||||
headers.
|
||||
|
||||
License: Apache-2.0
|
||||
@@ -0,0 +1,141 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use crate::overhead::command::TeyrchainExtension;
|
||||
use pezsc_chain_spec::{ChainSpec, GenericChainSpec, GenesisConfigBuilderRuntimeCaller};
|
||||
use pezsc_cli::Result;
|
||||
use serde_json::Value;
|
||||
use pezsp_storage::{well_known_keys::CODE, Storage};
|
||||
use pezsp_wasm_interface::HostFunctions;
|
||||
use std::{borrow::Cow, path::PathBuf};
|
||||
|
||||
/// When the runtime could not build the genesis storage.
|
||||
const ERROR_CANNOT_BUILD_GENESIS: &str = "The runtime returned \
|
||||
an error when trying to build the genesis storage. Please ensure that all pallets \
|
||||
define a genesis config that can be built. This can be tested with: \
|
||||
https://github.com/pezkuwichain/kurdistan-sdk/issues/115";
|
||||
|
||||
/// Warn when using the chain spec to generate the genesis state.
|
||||
pub const WARN_SPEC_GENESIS_CTOR: &'static str = "Using the chain spec instead of the runtime to \
|
||||
generate the genesis state is deprecated. Please remove the `--chain`/`--dev`/`--local` argument, \
|
||||
point `--runtime` to your runtime blob and set `--genesis-builder=runtime`. This warning may \
|
||||
become a hard error any time after December 2024.";
|
||||
|
||||
/// Defines how the chain specification shall be used to build the genesis storage.
|
||||
pub enum SpecGenesisSource {
|
||||
/// Use preset provided by the runtime embedded in the chain specification.
|
||||
Runtime(String),
|
||||
/// Use provided chain-specification JSON file.
|
||||
SpecJson,
|
||||
/// Use default storage.
|
||||
None,
|
||||
}
|
||||
|
||||
/// Defines how the genesis storage shall be built.
|
||||
pub enum GenesisStateHandler {
|
||||
ChainSpec(Box<dyn ChainSpec>, SpecGenesisSource),
|
||||
Runtime(Vec<u8>, Option<String>),
|
||||
}
|
||||
|
||||
impl GenesisStateHandler {
|
||||
/// Populate the genesis storage.
|
||||
///
|
||||
/// If the raw storage is derived from a named genesis preset, `json_patcher` is can be used to
|
||||
/// inject values into the preset.
|
||||
pub fn build_storage<HF: HostFunctions>(
|
||||
&self,
|
||||
json_patcher: Option<Box<dyn FnOnce(Value) -> Value + 'static>>,
|
||||
) -> Result<Storage> {
|
||||
match self {
|
||||
GenesisStateHandler::ChainSpec(chain_spec, source) => match source {
|
||||
SpecGenesisSource::Runtime(preset) => {
|
||||
let mut storage = chain_spec.build_storage()?;
|
||||
let code_bytes = storage
|
||||
.top
|
||||
.remove(CODE)
|
||||
.ok_or("chain spec genesis does not contain code")?;
|
||||
genesis_from_code::<HF>(code_bytes.as_slice(), preset, json_patcher)
|
||||
},
|
||||
SpecGenesisSource::SpecJson => chain_spec
|
||||
.build_storage()
|
||||
.map_err(|e| format!("{ERROR_CANNOT_BUILD_GENESIS}\nError: {e}").into()),
|
||||
SpecGenesisSource::None => Ok(Storage::default()),
|
||||
},
|
||||
GenesisStateHandler::Runtime(code_bytes, Some(preset)) =>
|
||||
genesis_from_code::<HF>(code_bytes.as_slice(), preset, json_patcher),
|
||||
GenesisStateHandler::Runtime(_, None) => Ok(Storage::default()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the runtime code blob.
|
||||
pub fn get_code_bytes(&self) -> Result<Cow<'_, [u8]>> {
|
||||
match self {
|
||||
GenesisStateHandler::ChainSpec(chain_spec, _) => {
|
||||
let mut storage = chain_spec.build_storage()?;
|
||||
storage
|
||||
.top
|
||||
.remove(CODE)
|
||||
.map(|code| Cow::from(code))
|
||||
.ok_or("chain spec genesis does not contain code".into())
|
||||
},
|
||||
GenesisStateHandler::Runtime(code_bytes, _) => Ok(code_bytes.into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn chain_spec_from_path<HF: HostFunctions>(
|
||||
chain: PathBuf,
|
||||
) -> Result<(Box<dyn ChainSpec>, Option<u32>)> {
|
||||
let spec = GenericChainSpec::<TeyrchainExtension, HF>::from_json_file(chain)
|
||||
.map_err(|e| format!("Unable to load chain spec: {:?}", e))?;
|
||||
|
||||
let para_id_from_chain_spec = spec.extensions().para_id;
|
||||
Ok((Box::new(spec), para_id_from_chain_spec))
|
||||
}
|
||||
|
||||
fn genesis_from_code<EHF: HostFunctions>(
|
||||
code: &[u8],
|
||||
genesis_builder_preset: &String,
|
||||
storage_patcher: Option<Box<dyn FnOnce(Value) -> Value>>,
|
||||
) -> Result<Storage> {
|
||||
let genesis_config_caller = GenesisConfigBuilderRuntimeCaller::<(
|
||||
pezsp_io::BizinikiwiHostFunctions,
|
||||
pezframe_benchmarking::benchmarking::HostFunctions,
|
||||
EHF,
|
||||
)>::new(code);
|
||||
|
||||
let mut preset_json = genesis_config_caller.get_named_preset(Some(genesis_builder_preset))?;
|
||||
if let Some(patcher) = storage_patcher {
|
||||
preset_json = patcher(preset_json);
|
||||
}
|
||||
|
||||
let mut storage =
|
||||
genesis_config_caller.get_storage_for_patch(preset_json).inspect_err(|e| {
|
||||
let presets = genesis_config_caller.preset_names().unwrap_or_default();
|
||||
log::error!(
|
||||
"Please pick one of the available presets with \
|
||||
`--genesis-builder-preset=<PRESET>`. Available presets ({}): {:?}. Error: {:?}",
|
||||
presets.len(),
|
||||
presets,
|
||||
e
|
||||
);
|
||||
})?;
|
||||
|
||||
storage.top.insert(CODE.into(), code.into());
|
||||
|
||||
Ok(storage)
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! Code that is shared among all benchmarking sub-commands.
|
||||
|
||||
pub mod genesis_state;
|
||||
pub mod record;
|
||||
pub mod stats;
|
||||
pub mod weight_params;
|
||||
|
||||
pub use record::BenchRecord;
|
||||
pub use stats::{StatSelect, Stats};
|
||||
pub use weight_params::WeightParams;
|
||||
|
||||
use clap::Args;
|
||||
use rand::prelude::*;
|
||||
use pezsc_sysinfo::gather_sysinfo;
|
||||
use serde::Serialize;
|
||||
|
||||
/// A Handlebars helper to add an underscore after every 3rd character,
|
||||
/// i.e. a separator for large numbers.
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct UnderscoreHelper;
|
||||
|
||||
impl handlebars::HelperDef for UnderscoreHelper {
|
||||
fn call<'reg: 'rc, 'rc>(
|
||||
&self,
|
||||
h: &handlebars::Helper,
|
||||
_: &handlebars::Handlebars,
|
||||
_: &handlebars::Context,
|
||||
_rc: &mut handlebars::RenderContext,
|
||||
out: &mut dyn handlebars::Output,
|
||||
) -> handlebars::HelperResult {
|
||||
use handlebars::JsonRender;
|
||||
let param = h.param(0).unwrap();
|
||||
let underscore_param = underscore(param.value().render());
|
||||
out.write(&underscore_param)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Add an underscore after every 3rd character, i.e. a separator for large numbers.
|
||||
fn underscore<Number>(i: Number) -> String
|
||||
where
|
||||
Number: std::string::ToString,
|
||||
{
|
||||
let mut s = String::new();
|
||||
let i_str = i.to_string();
|
||||
let a = i_str.chars().rev().enumerate();
|
||||
for (idx, val) in a {
|
||||
if idx != 0 && idx % 3 == 0 {
|
||||
s.insert(0, '_');
|
||||
}
|
||||
s.insert(0, val);
|
||||
}
|
||||
s
|
||||
}
|
||||
|
||||
/// Returns an rng and the seed that was used to create it.
|
||||
///
|
||||
/// Uses a random seed if none is provided.
|
||||
pub fn new_rng(seed: Option<u64>) -> (impl rand::Rng, u64) {
|
||||
let seed = seed.unwrap_or(rand::thread_rng().gen::<u64>());
|
||||
(rand_pcg::Pcg64::seed_from_u64(seed), seed)
|
||||
}
|
||||
|
||||
/// Returns an error if a debug profile is detected.
|
||||
///
|
||||
/// The rust compiler only exposes the binary information whether
|
||||
/// or not we are in a `debug` build.
|
||||
/// This means that `release` and `production` cannot be told apart.
|
||||
/// This function additionally checks for OPT-LEVEL = 3.
|
||||
pub fn check_build_profile() -> Result<(), String> {
|
||||
if cfg!(build_profile = "debug") {
|
||||
Err("Detected a `debug` profile".into())
|
||||
} else if !cfg!(build_opt_level = "3") {
|
||||
Err("The optimization level is not set to 3".into())
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Parameters to configure how the host info will be determined.
|
||||
#[derive(Debug, Default, Serialize, Clone, PartialEq, Args)]
|
||||
#[command(rename_all = "kebab-case")]
|
||||
pub struct HostInfoParams {
|
||||
/// Manually override the hostname to use.
|
||||
#[arg(long)]
|
||||
pub hostname_override: Option<String>,
|
||||
|
||||
/// Specify a fallback hostname if no-one could be detected automatically.
|
||||
///
|
||||
/// Note: This only exists to make the `hostname` function infallible.
|
||||
#[arg(long, default_value = "<UNKNOWN>")]
|
||||
pub hostname_fallback: String,
|
||||
|
||||
/// Specify a fallback CPU name if no-one could be detected automatically.
|
||||
///
|
||||
/// Note: This only exists to make the `cpuname` function infallible.
|
||||
#[arg(long, default_value = "<UNKNOWN>")]
|
||||
pub cpuname_fallback: String,
|
||||
}
|
||||
|
||||
impl HostInfoParams {
|
||||
/// Returns the hostname of the machine.
|
||||
///
|
||||
/// Can be used to track on which machine a benchmark was run.
|
||||
pub fn hostname(&self) -> String {
|
||||
self.hostname_override
|
||||
.clone()
|
||||
.or(gethostname::gethostname().into_string().ok())
|
||||
.unwrap_or(self.hostname_fallback.clone())
|
||||
}
|
||||
|
||||
/// Returns the CPU name of the current machine.
|
||||
///
|
||||
/// Can be used to track on which machine a benchmark was run.
|
||||
pub fn cpuname(&self) -> String {
|
||||
gather_sysinfo().cpu.unwrap_or(self.cpuname_fallback.clone())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! Defines the [`BenchRecord`] and its facilities for computing [`super::Stats`].
|
||||
|
||||
use pezsc_cli::Result;
|
||||
use pezsc_service::Configuration;
|
||||
|
||||
use log::info;
|
||||
use serde::Serialize;
|
||||
use std::{fs, path::PathBuf, time::Duration};
|
||||
|
||||
use super::Stats;
|
||||
|
||||
/// Raw output of a Storage benchmark.
|
||||
#[derive(Debug, Default, Clone, Serialize)]
|
||||
pub struct BenchRecord {
|
||||
/// Multi-Map of value sizes and the time that it took to access them.
|
||||
ns_per_size: Vec<(u64, u64)>,
|
||||
}
|
||||
|
||||
impl BenchRecord {
|
||||
/// Appends a new record. Uses safe casts.
|
||||
pub fn append(&mut self, size: usize, d: Duration) -> Result<()> {
|
||||
let size: u64 = size.try_into().map_err(|e| format!("Size overflow u64: {}", e))?;
|
||||
let ns: u64 = d
|
||||
.as_nanos()
|
||||
.try_into()
|
||||
.map_err(|e| format!("Nanoseconds overflow u64: {}", e))?;
|
||||
self.ns_per_size.push((size, ns));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Returns the statistics for *time* and *value size*.
|
||||
pub fn calculate_stats(self) -> Result<(Stats, Stats)> {
|
||||
let (size, time): (Vec<_>, Vec<_>) = self.ns_per_size.into_iter().unzip();
|
||||
let size = Stats::new(&size)?;
|
||||
let time = Stats::new(&time)?;
|
||||
Ok((time, size)) // The swap of time/size here is intentional.
|
||||
}
|
||||
|
||||
/// Unless a path is specified, saves the raw results in a json file in the current directory.
|
||||
/// Prefixes it with the DB name and suffixed with `path_suffix`.
|
||||
pub fn save_json(&self, cfg: &Configuration, out_path: &PathBuf, suffix: &str) -> Result<()> {
|
||||
let mut path = PathBuf::from(out_path);
|
||||
if path.is_dir() || path.as_os_str().is_empty() {
|
||||
path.push(&format!("{}_{}", cfg.database, suffix).to_lowercase());
|
||||
path.set_extension("json");
|
||||
}
|
||||
|
||||
let json = serde_json::to_string_pretty(&self)
|
||||
.map_err(|e| format!("Serializing as JSON: {:?}", e))?;
|
||||
|
||||
fs::write(&path, json)?;
|
||||
info!("Raw data written to {:?}", fs::canonicalize(&path)?);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,188 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! Handles statistics that were generated from benchmarking results and
|
||||
//! that can be used to fill out weight templates.
|
||||
|
||||
use pezsc_cli::Result;
|
||||
|
||||
use serde::Serialize;
|
||||
use std::{fmt, result, str::FromStr};
|
||||
|
||||
/// Various statistics that help to gauge the quality of the produced weights.
|
||||
/// Will be written to the weight file and printed to console.
|
||||
#[derive(Serialize, Default, Clone)]
|
||||
pub struct Stats {
|
||||
/// Sum of all values.
|
||||
pub sum: u64,
|
||||
/// Minimal observed value.
|
||||
pub min: u64,
|
||||
/// Maximal observed value.
|
||||
pub max: u64,
|
||||
|
||||
/// Average of all values.
|
||||
pub avg: u64,
|
||||
/// Median of all values.
|
||||
pub median: u64,
|
||||
/// Standard derivation of all values.
|
||||
pub stddev: f64,
|
||||
|
||||
/// 99th percentile. At least 99% of all values are below this threshold.
|
||||
pub p99: u64,
|
||||
/// 95th percentile. At least 95% of all values are below this threshold.
|
||||
pub p95: u64,
|
||||
/// 75th percentile. At least 75% of all values are below this threshold.
|
||||
pub p75: u64,
|
||||
}
|
||||
|
||||
/// Selects a specific field from a [`Stats`] object.
|
||||
/// Not all fields are available.
|
||||
#[derive(Debug, Clone, Copy, Serialize, PartialEq)]
|
||||
pub enum StatSelect {
|
||||
/// Select the maximum.
|
||||
Maximum,
|
||||
/// Select the average.
|
||||
Average,
|
||||
/// Select the median.
|
||||
Median,
|
||||
/// Select the 99th percentile.
|
||||
P99Percentile,
|
||||
/// Select the 95th percentile.
|
||||
P95Percentile,
|
||||
/// Select the 75th percentile.
|
||||
P75Percentile,
|
||||
}
|
||||
|
||||
impl Stats {
|
||||
/// Calculates statistics and returns them.
|
||||
pub fn new(xs: &Vec<u64>) -> Result<Self> {
|
||||
if xs.is_empty() {
|
||||
return Err("Empty input is invalid".into());
|
||||
}
|
||||
let (avg, stddev) = Self::avg_and_stddev(xs);
|
||||
|
||||
Ok(Self {
|
||||
sum: xs.iter().sum(),
|
||||
min: *xs.iter().min().expect("Checked for non-empty above"),
|
||||
max: *xs.iter().max().expect("Checked for non-empty above"),
|
||||
|
||||
avg: avg as u64,
|
||||
median: Self::percentile(xs.clone(), 0.50),
|
||||
stddev: (stddev * 100.0).round() / 100.0, // round to 1/100
|
||||
|
||||
p99: Self::percentile(xs.clone(), 0.99),
|
||||
p95: Self::percentile(xs.clone(), 0.95),
|
||||
p75: Self::percentile(xs.clone(), 0.75),
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns the selected stat.
|
||||
pub fn select(&self, s: StatSelect) -> u64 {
|
||||
match s {
|
||||
StatSelect::Maximum => self.max,
|
||||
StatSelect::Average => self.avg,
|
||||
StatSelect::Median => self.median,
|
||||
StatSelect::P99Percentile => self.p99,
|
||||
StatSelect::P95Percentile => self.p95,
|
||||
StatSelect::P75Percentile => self.p75,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the *average* and the *standard derivation*.
|
||||
fn avg_and_stddev(xs: &Vec<u64>) -> (f64, f64) {
|
||||
let avg = xs.iter().map(|x| *x as f64).sum::<f64>() / xs.len() as f64;
|
||||
let variance = xs.iter().map(|x| (*x as f64 - avg).powi(2)).sum::<f64>() / xs.len() as f64;
|
||||
(avg, variance.sqrt())
|
||||
}
|
||||
|
||||
/// Returns the specified percentile for the given data.
|
||||
/// This is best effort since it ignores the interpolation case.
|
||||
fn percentile(mut xs: Vec<u64>, p: f64) -> u64 {
|
||||
xs.sort();
|
||||
let index = (xs.len() as f64 * p).ceil() as usize - 1;
|
||||
xs[index.clamp(0, xs.len() - 1)]
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for Stats {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
writeln!(f, "Total: {}", self.sum)?;
|
||||
writeln!(f, "Min: {}, Max: {}", self.min, self.max)?;
|
||||
writeln!(f, "Average: {}, Median: {}, Stddev: {}", self.avg, self.median, self.stddev)?;
|
||||
write!(f, "Percentiles 99th, 95th, 75th: {}, {}, {}", self.p99, self.p95, self.p75)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for StatSelect {
|
||||
/// Returns the `Average` selector.
|
||||
fn default() -> Self {
|
||||
Self::Average
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for StatSelect {
|
||||
type Err = &'static str;
|
||||
|
||||
fn from_str(day: &str) -> result::Result<Self, Self::Err> {
|
||||
match day.to_lowercase().as_str() {
|
||||
"max" => Ok(Self::Maximum),
|
||||
"average" => Ok(Self::Average),
|
||||
"median" => Ok(Self::Median),
|
||||
"p99" => Ok(Self::P99Percentile),
|
||||
"p95" => Ok(Self::P95Percentile),
|
||||
"p75" => Ok(Self::P75Percentile),
|
||||
_ => Err("String was not a StatSelect"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test_stats {
|
||||
use super::Stats;
|
||||
use rand::{seq::SliceRandom, thread_rng};
|
||||
|
||||
#[test]
|
||||
fn stats_correct() {
|
||||
let mut data: Vec<u64> = (1..=100).collect();
|
||||
data.shuffle(&mut thread_rng());
|
||||
let stats = Stats::new(&data).unwrap();
|
||||
|
||||
assert_eq!(stats.sum, 5050);
|
||||
assert_eq!(stats.min, 1);
|
||||
assert_eq!(stats.max, 100);
|
||||
|
||||
assert_eq!(stats.avg, 50);
|
||||
assert_eq!(stats.median, 50); // 50.5 to be exact.
|
||||
assert_eq!(stats.stddev, 28.87); // Rounded with 1/100 precision.
|
||||
|
||||
assert_eq!(stats.p99, 99);
|
||||
assert_eq!(stats.p95, 95);
|
||||
assert_eq!(stats.p75, 75);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_panic_short_lengths() {
|
||||
// Empty input does error.
|
||||
assert!(Stats::new(&vec![]).is_err());
|
||||
|
||||
// Different small input lengths are fine.
|
||||
for l in 1..10 {
|
||||
let data = (0..=l).collect();
|
||||
assert!(Stats::new(&data).is_ok());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! Calculates a weight from the [`super::Stats`] of a benchmark result.
|
||||
|
||||
use pezsc_cli::Result;
|
||||
|
||||
use clap::Args;
|
||||
use serde::Serialize;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use super::{StatSelect, Stats};
|
||||
|
||||
/// Configures the weight generation.
|
||||
#[derive(Debug, Default, Serialize, Clone, PartialEq, Args)]
|
||||
pub struct WeightParams {
|
||||
/// File or directory to write the *weight* files to.
|
||||
///
|
||||
/// For Bizinikiwi this should be `frame/support/src/weights`.
|
||||
#[arg(long)]
|
||||
pub weight_path: Option<PathBuf>,
|
||||
|
||||
/// Select a specific metric to calculate the final weight output.
|
||||
#[arg(long = "metric", default_value = "average")]
|
||||
pub weight_metric: StatSelect,
|
||||
|
||||
/// Multiply the resulting weight with the given factor. Must be positive.
|
||||
///
|
||||
/// Is applied before `weight_add`.
|
||||
#[arg(long = "mul", default_value_t = 1.0)]
|
||||
pub weight_mul: f64,
|
||||
|
||||
/// Add the given offset to the resulting weight.
|
||||
///
|
||||
/// Is applied after `weight_mul`.
|
||||
#[arg(long = "add", default_value_t = 0)]
|
||||
pub weight_add: u64,
|
||||
}
|
||||
|
||||
/// Calculates the final weight by multiplying the selected metric with
|
||||
/// `weight_mul` and adding `weight_add`.
|
||||
/// Does not use safe casts and can overflow.
|
||||
impl WeightParams {
|
||||
pub fn calc_weight(&self, stat: &Stats) -> Result<u64> {
|
||||
if self.weight_mul.is_sign_negative() || !self.weight_mul.is_normal() {
|
||||
return Err("invalid floating number for `weight_mul`".into());
|
||||
}
|
||||
let s = stat.select(self.weight_metric) as f64;
|
||||
let w = s.mul_add(self.weight_mul, self.weight_add as f64).ceil();
|
||||
Ok(w as u64) // No safe cast here since there is no `From<f64>` for `u64`.
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test_weight_params {
|
||||
use super::WeightParams;
|
||||
use crate::shared::{StatSelect, Stats};
|
||||
|
||||
#[test]
|
||||
fn calc_weight_works() {
|
||||
let stats = Stats { avg: 113, ..Default::default() };
|
||||
let params = WeightParams {
|
||||
weight_metric: StatSelect::Average,
|
||||
weight_mul: 0.75,
|
||||
weight_add: 3,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let want = (113.0f64 * 0.75 + 3.0).ceil() as u64; // Ceil for overestimation.
|
||||
let got = params.calc_weight(&stats).unwrap();
|
||||
assert_eq!(want, got);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn calc_weight_detects_negative_mul() {
|
||||
let stats = Stats::default();
|
||||
let params = WeightParams { weight_mul: -0.75, ..Default::default() };
|
||||
|
||||
assert!(params.calc_weight(&stats).is_err());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user