frame_benchmarking_cli/storage/
cmd.rs1use sc_cli::{CliConfiguration, DatabaseParams, PruningParams, Result, SharedParams};
19use sc_client_api::{Backend as ClientBackend, StorageProvider, UsageProvider};
20use sc_client_db::DbHash;
21use sc_service::Configuration;
22use sp_api::CallApiAt;
23use sp_blockchain::HeaderBackend;
24use sp_database::{ColumnId, Database};
25use sp_runtime::traits::{Block as BlockT, HashingFor};
26use sp_state_machine::Storage;
27use sp_storage::{ChildInfo, ChildType, PrefixedStorageKey, StateVersion};
28
29use clap::{Args, Parser, ValueEnum};
30use log::info;
31use rand::prelude::*;
32use serde::Serialize;
33use sp_runtime::generic::BlockId;
34use std::{fmt::Debug, path::PathBuf, sync::Arc};
35
36use super::template::TemplateData;
37use crate::shared::{new_rng, HostInfoParams, WeightParams};
38
39#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Serialize, ValueEnum)]
41pub enum StorageBenchmarkMode {
42 #[default]
44 ImportBlock,
45 ValidateBlock,
47}
48
49#[derive(Debug, Parser)]
51pub struct StorageCmd {
52 #[allow(missing_docs)]
53 #[clap(flatten)]
54 pub shared_params: SharedParams,
55
56 #[allow(missing_docs)]
57 #[clap(flatten)]
58 pub database_params: DatabaseParams,
59
60 #[allow(missing_docs)]
61 #[clap(flatten)]
62 pub pruning_params: PruningParams,
63
64 #[allow(missing_docs)]
65 #[clap(flatten)]
66 pub params: StorageParams,
67}
68
69#[derive(Debug, Default, Serialize, Clone, PartialEq, Args)]
71pub struct StorageParams {
72 #[allow(missing_docs)]
73 #[clap(flatten)]
74 pub weight_params: WeightParams,
75
76 #[allow(missing_docs)]
77 #[clap(flatten)]
78 pub hostinfo: HostInfoParams,
79
80 #[arg(long)]
82 pub skip_read: bool,
83
84 #[arg(long)]
86 pub skip_write: bool,
87
88 #[arg(long)]
90 pub template_path: Option<PathBuf>,
91
92 #[arg(long, value_name = "PATH")]
96 pub header: Option<PathBuf>,
97
98 #[arg(long)]
100 pub json_read_path: Option<PathBuf>,
101
102 #[arg(long)]
104 pub json_write_path: Option<PathBuf>,
105
106 #[arg(long, default_value_t = 1)]
108 pub warmups: u32,
109
110 #[arg(long, value_parser = clap::value_parser!(u8).range(0..=1))]
113 pub state_version: u8,
114
115 #[arg(long, value_name = "Bytes", default_value_t = 67108864)]
119 pub trie_cache_size: usize,
120
121 #[arg(long)]
125 pub enable_trie_cache: bool,
126
127 #[arg(long)]
129 pub include_child_trees: bool,
130
131 #[arg(long, default_value = "false")]
140 pub disable_pov_recorder: bool,
141
142 #[arg(long, default_value_t = 100_000)]
148 pub batch_size: usize,
149
150 #[arg(long, value_enum, default_value_t = StorageBenchmarkMode::ImportBlock)]
154 pub mode: StorageBenchmarkMode,
155
156 #[arg(long, default_value_t = 20)]
161 pub validate_block_rounds: u32,
162}
163
164impl StorageParams {
165 pub fn is_import_block_mode(&self) -> bool {
166 matches!(self.mode, StorageBenchmarkMode::ImportBlock)
167 }
168
169 pub fn is_validate_block_mode(&self) -> bool {
170 matches!(self.mode, StorageBenchmarkMode::ValidateBlock)
171 }
172}
173
174impl StorageCmd {
175 pub fn run<Block, BA, C>(
178 &self,
179 cfg: Configuration,
180 client: Arc<C>,
181 db: (Arc<dyn Database<DbHash>>, ColumnId),
182 storage: Arc<dyn Storage<HashingFor<Block>>>,
183 shared_trie_cache: Option<sp_trie::cache::SharedTrieCache<HashingFor<Block>>>,
184 ) -> Result<()>
185 where
186 BA: ClientBackend<Block>,
187 Block: BlockT<Hash = DbHash>,
188 C: UsageProvider<Block>
189 + StorageProvider<Block, BA>
190 + HeaderBackend<Block>
191 + CallApiAt<Block>,
192 {
193 let mut template = TemplateData::new(&cfg, &self.params)?;
194
195 let block_id = BlockId::<Block>::Number(client.usage_info().chain.best_number);
196 template.set_block_number(block_id.to_string());
197
198 if !self.params.skip_read {
199 self.bench_warmup(&client)?;
200 let record = self.bench_read(client.clone(), shared_trie_cache.clone())?;
201 if let Some(path) = &self.params.json_read_path {
202 record.save_json(&cfg, path, "read")?;
203 }
204 let stats = record.calculate_stats()?;
205 info!("Time summary [ns]:\n{:?}\nValue size summary:\n{:?}", stats.0, stats.1);
206 template.set_stats(Some(stats), None)?;
207 }
208
209 if !self.params.skip_write {
210 self.bench_warmup(&client)?;
211 let record = self.bench_write(client, db, storage, shared_trie_cache)?;
212 if let Some(path) = &self.params.json_write_path {
213 record.save_json(&cfg, path, "write")?;
214 }
215 let stats = record.calculate_stats()?;
216 info!("Time summary [ns]:\n{:?}\nValue size summary:\n{:?}", stats.0, stats.1);
217 template.set_stats(None, Some(stats))?;
218 }
219
220 template.write(&self.params.weight_params.weight_path, &self.params.template_path)
221 }
222
223 pub(crate) fn state_version(&self) -> StateVersion {
225 match self.params.state_version {
226 0 => StateVersion::V0,
227 1 => StateVersion::V1,
228 _ => unreachable!("Clap set to only allow 0 and 1"),
229 }
230 }
231
232 pub(crate) fn is_child_key(&self, key: Vec<u8>) -> Option<ChildInfo> {
234 if let Some((ChildType::ParentKeyId, storage_key)) =
235 ChildType::from_prefixed_key(&PrefixedStorageKey::new(key))
236 {
237 return Some(ChildInfo::new_default(storage_key))
238 }
239 None
240 }
241
242 fn bench_warmup<B, BA, C>(&self, client: &Arc<C>) -> Result<()>
245 where
246 C: UsageProvider<B> + StorageProvider<B, BA>,
247 B: BlockT + Debug,
248 BA: ClientBackend<B>,
249 {
250 let hash = client.usage_info().chain.best_hash;
251 let mut keys: Vec<_> = client.storage_keys(hash, None, None)?.collect();
252 let (mut rng, _) = new_rng(None);
253 keys.shuffle(&mut rng);
254
255 for i in 0..self.params.warmups {
256 info!("Warmup round {}/{}", i + 1, self.params.warmups);
257 let mut child_nodes = Vec::new();
258
259 for key in keys.as_slice() {
260 let _ = client
261 .storage(hash, &key)
262 .expect("Checked above to exist")
263 .ok_or("Value unexpectedly empty");
264
265 if let Some(info) = self
266 .params
267 .include_child_trees
268 .then(|| self.is_child_key(key.clone().0))
269 .flatten()
270 {
271 for ck in client.child_storage_keys(hash, info.clone(), None, None)? {
273 child_nodes.push((ck.clone(), info.clone()));
274 }
275 }
276 }
277 for (key, info) in child_nodes.as_slice() {
278 client
279 .child_storage(hash, info, key)
280 .expect("Checked above to exist")
281 .ok_or("Value unexpectedly empty")?;
282 }
283 }
284
285 Ok(())
286 }
287}
288
289impl CliConfiguration for StorageCmd {
291 fn shared_params(&self) -> &SharedParams {
292 &self.shared_params
293 }
294
295 fn database_params(&self) -> Option<&DatabaseParams> {
296 Some(&self.database_params)
297 }
298
299 fn pruning_params(&self) -> Option<&PruningParams> {
300 Some(&self.pruning_params)
301 }
302
303 fn trie_cache_maximum_size(&self) -> Result<Option<usize>> {
304 if self.params.enable_trie_cache && self.params.trie_cache_size > 0 {
305 Ok(Some(self.params.trie_cache_size))
306 } else {
307 Ok(None)
308 }
309 }
310}