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 serde::Serialize;
32use sp_runtime::generic::BlockId;
33use std::{fmt::Debug, path::PathBuf, sync::Arc};
34
35use super::{
36 keys_selection::{select_entries, EmptyStorage as SelectEntriesEmptyStorage},
37 template::TemplateData,
38};
39use crate::shared::{HostInfoParams, WeightParams};
40
41#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Serialize, ValueEnum)]
43pub enum StorageBenchmarkMode {
44 #[default]
46 ImportBlock,
47 ValidateBlock,
49}
50
51#[derive(Debug, Parser)]
53pub struct StorageCmd {
54 #[allow(missing_docs)]
55 #[clap(flatten)]
56 pub shared_params: SharedParams,
57
58 #[allow(missing_docs)]
59 #[clap(flatten)]
60 pub database_params: DatabaseParams,
61
62 #[allow(missing_docs)]
63 #[clap(flatten)]
64 pub pruning_params: PruningParams,
65
66 #[allow(missing_docs)]
67 #[clap(flatten)]
68 pub params: StorageParams,
69}
70
71#[derive(Debug, Default, Serialize, Clone, PartialEq, Args)]
73pub struct StorageParams {
74 #[allow(missing_docs)]
75 #[clap(flatten)]
76 pub weight_params: WeightParams,
77
78 #[allow(missing_docs)]
79 #[clap(flatten)]
80 pub hostinfo: HostInfoParams,
81
82 #[arg(long)]
84 pub skip_read: bool,
85
86 #[arg(long)]
88 pub skip_write: bool,
89
90 #[arg(long)]
92 pub template_path: Option<PathBuf>,
93
94 #[arg(long, value_name = "PATH")]
98 pub header: Option<PathBuf>,
99
100 #[arg(long)]
102 pub json_read_path: Option<PathBuf>,
103
104 #[arg(long)]
106 pub json_write_path: Option<PathBuf>,
107
108 #[arg(long, default_value_t = 1)]
110 pub warmups: u32,
111
112 #[arg(long, value_parser = clap::value_parser!(u8).range(0..=1))]
115 pub state_version: u8,
116
117 #[arg(long, value_name = "Bytes", default_value_t = 67108864)]
121 pub trie_cache_size: usize,
122
123 #[arg(long)]
127 pub enable_trie_cache: bool,
128
129 #[arg(long)]
131 pub include_child_trees: bool,
132
133 #[arg(long, default_value = "false")]
142 pub disable_pov_recorder: bool,
143
144 #[arg(long, default_value_t = 100_000)]
150 pub batch_size: usize,
151
152 #[arg(long, value_enum, default_value_t = StorageBenchmarkMode::ImportBlock)]
156 pub mode: StorageBenchmarkMode,
157
158 #[arg(long, default_value_t = 20)]
163 pub validate_block_rounds: u32,
164
165 #[arg(long)]
174 pub keys_limit: Option<usize>,
175
176 #[arg(long)]
181 pub child_keys_limit: Option<usize>,
182
183 #[arg(long)]
186 pub random_seed: Option<u64>,
187}
188
189impl StorageParams {
190 pub fn is_import_block_mode(&self) -> bool {
191 matches!(self.mode, StorageBenchmarkMode::ImportBlock)
192 }
193
194 pub fn is_validate_block_mode(&self) -> bool {
195 matches!(self.mode, StorageBenchmarkMode::ValidateBlock)
196 }
197}
198
199impl StorageCmd {
200 pub fn run<Block, BA, C>(
203 &self,
204 cfg: Configuration,
205 client: Arc<C>,
206 db: (Arc<dyn Database<DbHash>>, ColumnId),
207 storage: Arc<dyn Storage<HashingFor<Block>>>,
208 shared_trie_cache: Option<sp_trie::cache::SharedTrieCache<HashingFor<Block>>>,
209 ) -> Result<()>
210 where
211 BA: ClientBackend<Block>,
212 Block: BlockT<Hash = DbHash>,
213 C: UsageProvider<Block>
214 + StorageProvider<Block, BA>
215 + HeaderBackend<Block>
216 + CallApiAt<Block>,
217 {
218 let mut template = TemplateData::new(&cfg, &self.params)?;
219
220 let block_id = BlockId::<Block>::Number(client.usage_info().chain.best_number);
221 template.set_block_number(block_id.to_string());
222
223 if !self.params.skip_read {
224 self.bench_warmup(&client)?;
225 let record = self.bench_read(client.clone(), shared_trie_cache.clone())?;
226 if let Some(path) = &self.params.json_read_path {
227 record.save_json(&cfg, path, "read")?;
228 }
229 let stats = record.calculate_stats()?;
230 info!("Time summary [ns]:\n{:?}\nValue size summary:\n{:?}", stats.0, stats.1);
231 template.set_stats(Some(stats), None)?;
232 }
233
234 if !self.params.skip_write {
235 self.bench_warmup(&client)?;
236 let record = self.bench_write(client, db, storage, shared_trie_cache)?;
237 if let Some(path) = &self.params.json_write_path {
238 record.save_json(&cfg, path, "write")?;
239 }
240 let stats = record.calculate_stats()?;
241 info!("Time summary [ns]:\n{:?}\nValue size summary:\n{:?}", stats.0, stats.1);
242 template.set_stats(None, Some(stats))?;
243 }
244
245 template.write(&self.params.weight_params.weight_path, &self.params.template_path)
246 }
247
248 pub(crate) fn state_version(&self) -> StateVersion {
250 match self.params.state_version {
251 0 => StateVersion::V0,
252 1 => StateVersion::V1,
253 _ => unreachable!("Clap set to only allow 0 and 1"),
254 }
255 }
256
257 pub(crate) fn is_child_key(&self, key: Vec<u8>) -> Option<ChildInfo> {
259 if let Some((ChildType::ParentKeyId, storage_key)) =
260 ChildType::from_prefixed_key(&PrefixedStorageKey::new(key))
261 {
262 return Some(ChildInfo::new_default(storage_key));
263 }
264 None
265 }
266
267 fn bench_warmup<B, BA, C>(&self, client: &Arc<C>) -> Result<()>
270 where
271 C: UsageProvider<B> + StorageProvider<B, BA>,
272 B: BlockT + Debug,
273 BA: ClientBackend<B>,
274 {
275 let hash = client.usage_info().chain.best_hash;
276 let (keys, _) = select_entries(
277 self.params.keys_limit,
278 self.params.random_seed,
279 |first_key_ref| {
280 let fk = first_key_ref.map(|b| sp_storage::StorageKey(b.to_vec()));
281 Ok(client.storage_keys(hash, None, fk.as_ref())?)
282 },
283 || Ok(client.storage_keys(hash, None, None)?),
284 |k: &sp_storage::StorageKey| k.0.as_slice(),
285 )?;
286
287 for i in 0..self.params.warmups {
288 info!("Warmup round {}/{}", i + 1, self.params.warmups);
289 let mut child_nodes = Vec::new();
290
291 for key in keys.as_slice() {
292 let _ = client
293 .storage(hash, &key)
294 .expect("Checked above to exist")
295 .ok_or("Value unexpectedly empty");
296
297 if let Some(info) = self
298 .params
299 .include_child_trees
300 .then(|| self.is_child_key(key.clone().0))
301 .flatten()
302 {
303 match select_entries(
305 self.params.child_keys_limit,
306 self.params.random_seed,
307 |first_key_ref| {
308 let fk = first_key_ref.map(|b| sp_storage::StorageKey(b.to_vec()));
309 Ok(client
310 .child_storage_keys(hash, info.clone(), None, fk.as_ref())?
311 .map(|ck| (ck, info.clone())))
312 },
313 || {
314 Ok(client
315 .child_storage_keys(hash, info.clone(), None, None)?
316 .map(|ck| (ck, info.clone())))
317 },
318 |(k, _): &(sp_storage::StorageKey, sp_storage::ChildInfo)| k.0.as_slice(),
319 ) {
320 Ok((entries, _)) => child_nodes.extend(entries),
321 Err(SelectEntriesEmptyStorage::Input(_)) => {},
322 Err(e) => return Err(e),
323 }
324 }
325 }
326 for (key, info) in child_nodes.as_slice() {
327 client
328 .child_storage(hash, info, key)
329 .expect("Checked above to exist")
330 .ok_or("Value unexpectedly empty")?;
331 }
332 }
333
334 Ok(())
335 }
336}
337
338impl CliConfiguration for StorageCmd {
340 fn shared_params(&self) -> &SharedParams {
341 &self.shared_params
342 }
343
344 fn database_params(&self) -> Option<&DatabaseParams> {
345 Some(&self.database_params)
346 }
347
348 fn pruning_params(&self) -> Option<&PruningParams> {
349 Some(&self.pruning_params)
350 }
351
352 fn trie_cache_maximum_size(&self) -> Result<Option<usize>> {
353 if self.params.enable_trie_cache && self.params.trie_cache_size > 0 {
354 Ok(Some(self.params.trie_cache_size))
355 } else {
356 Ok(None)
357 }
358 }
359}