diff --git a/Cargo.lock b/Cargo.lock index 32fb656298e9..d082cc1ae98e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4969,9 +4969,7 @@ name = "perf-metrics" version = "0.1.0" dependencies = [ "ctor 0.2.6", - "libc", "minstant", - "reth-mdbx-sys", "revm", "revm-utils", "tokio", @@ -6250,7 +6248,6 @@ dependencies = [ "libc", "libffi", "parking_lot 0.12.1", - "perf-metrics", "pprof", "rand 0.8.5", "rand_xorshift", diff --git a/crates/perf-metrics/Cargo.toml b/crates/perf-metrics/Cargo.toml index 1a2cd76774d0..a5b18d25e295 100644 --- a/crates/perf-metrics/Cargo.toml +++ b/crates/perf-metrics/Cargo.toml @@ -12,9 +12,6 @@ tokio = { workspace = true, features = ["sync"] } revm-utils = { workspace = true, optional = true } revm = { workspace = true, optional = true } -ffi = { package = "reth-mdbx-sys", path = "../storage/libmdbx-rs/mdbx-sys" } -libc = "0.2" - [features] enable_opcode_metrics = [ "revm-utils", diff --git a/crates/perf-metrics/src/dashboard/cache.rs b/crates/perf-metrics/src/dashboard/cache.rs index a683890bf3b7..e3dfa66800aa 100644 --- a/crates/perf-metrics/src/dashboard/cache.rs +++ b/crates/perf-metrics/src/dashboard/cache.rs @@ -38,37 +38,14 @@ impl Default for CacheStats { } } -impl Print for CacheStats { - fn print_title(&self) { - println!("================================================ Metric of State ==========================================="); - println!( - "{: COL_WIDTH_MIDDLE$}{:>COL_WIDTH_MIDDLE$}{:>COL_WIDTH_BIG$}{:>COL_WIDTH_BIG$}{:>COL_WIDTH_BIG$}", - "State functions", "Hits", "Misses", "Miss ratio (%)","Penalty time(s)", "Avg penalty (us)" - ); - } - - fn print_content(&self) { - self.print_item("blockhash", Function::BlockHash as usize); - self.print_item("code_by_hash", Function::CodeByHash as usize); - self.print_item("load_account/basic", Function::LoadCacheAccount as usize); - self.print_item("storage", Function::Storage as usize); - self.print_item("total", CACHE_STATS_LEN - 1); - } -} - -trait StatsAndPrint { - fn stats(&self) -> CacheStats; - fn print_penalty_distribution(&self); -} - -impl StatsAndPrint for CacheDbRecord { - fn stats(&self) -> CacheStats { +impl From<&CacheDbRecord> for CacheStats { + fn from(record: &CacheDbRecord) -> Self { let mut cache_stats = CacheStats::default(); - let total_stats = self.access_count(); - let hit_stats = self.hit_stats(); - let miss_stats = self.miss_stats(); - let penalty_stats = self.penalty_stats(); + let total_stats = record.access_count(); + let hit_stats = record.hit_stats(); + let miss_stats = record.miss_stats(); + let penalty_stats = record.penalty_stats(); for index in 0..total_stats.function.len() { cache_stats.functions[index].hits = hit_stats.function[index]; @@ -94,8 +71,32 @@ impl StatsAndPrint for CacheDbRecord { cache_stats } +} + +impl Print for CacheStats { + fn print_title(&self) { + println!("================================================ Metric of State ==========================================="); + println!( + "{: COL_WIDTH_MIDDLE$}{:>COL_WIDTH_MIDDLE$}{:>COL_WIDTH_BIG$}{:>COL_WIDTH_BIG$}{:>COL_WIDTH_BIG$}", + "State functions", "Hits", "Misses", "Miss ratio (%)","Penalty time(s)", "Avg penalty (us)" + ); + } + + fn print_content(&self) { + self.print_item("blockhash", Function::BlockHash as usize); + self.print_item("code_by_hash", Function::CodeByHash as usize); + self.print_item("load_account/basic", Function::LoadCacheAccount as usize); + self.print_item("storage", Function::Storage as usize); + self.print_item("total", CACHE_STATS_LEN - 1); + } +} + +trait PrintPenalty { + fn print_penalty(&self); +} - fn print_penalty_distribution(&self) { +impl PrintPenalty for CacheDbRecord { + fn print_penalty(&self) { println!(); println!("================Penalty percentile============="); self.penalty_stats().percentile.print_content(); @@ -105,8 +106,8 @@ impl StatsAndPrint for CacheDbRecord { impl Print for CacheDbRecord { fn print(&self, _block_number: u64) { - self.stats().print(_block_number); - self.print_penalty_distribution(); + Into::::into(self).print(_block_number); + self.print_penalty(); } } diff --git a/crates/perf-metrics/src/dashboard/opcode.rs b/crates/perf-metrics/src/dashboard/opcode.rs index bea051f65784..62efdc393de0 100644 --- a/crates/perf-metrics/src/dashboard/opcode.rs +++ b/crates/perf-metrics/src/dashboard/opcode.rs @@ -1,20 +1,24 @@ //! This module is used to support the display of opcode statistics metrics. +use super::commons::*; use revm::revm_opcode::*; -use revm_utils::metrics::types::OpcodeRecord; +use revm_utils::{metrics::types::OpcodeRecord, time_utils::convert_cycles_to_ns_f64}; +use std::collections::BTreeMap; -pub(crate) const OPCODE_NUMBER: usize = 256; +const MGAS_TO_GAS: u64 = 1_000_000u64; +const COL_WIDTH: usize = 15; +const OPCODE_NUMBER: usize = 256; #[derive(Debug, Clone, Copy, Default)] -pub(crate) struct OpcodeInfo { +struct OpcodeInfo { /// opcode category - pub(crate) category: &'static str, + category: &'static str, /// gas fee - pub(crate) gas: u64, + gas: u64, /// opcode cost a fixed gas fee? - pub(crate) static_gas: bool, + static_gas: bool, } -pub(crate) const MERGE_MAP: [Option<(u8, OpcodeInfo)>; OPCODE_NUMBER] = [ +const MERGE_MAP: [Option<(u8, OpcodeInfo)>; OPCODE_NUMBER] = [ Some((STOP, OpcodeInfo { category: "stop", gas: 0, static_gas: true })), //0x00 Some((ADD, OpcodeInfo { category: "arithmetic", gas: 3, static_gas: true })), //0x01 Some((MUL, OpcodeInfo { category: "arithmetic", gas: 5, static_gas: true })), //0x02 @@ -273,12 +277,6 @@ pub(crate) const MERGE_MAP: [Option<(u8, OpcodeInfo)>; OPCODE_NUMBER] = [ Some((SELFDESTRUCT, OpcodeInfo { category: "host", gas: 5000, static_gas: false })), //0xff ]; -use super::commons::*; -use revm_utils::time_utils::convert_cycles_to_ns_f64; -use std::collections::BTreeMap; -const MGAS_TO_GAS: u64 = 1_000_000u64; - -const COL_WIDTH: usize = 15; #[derive(Default, Debug)] struct OpcodeMergeRecord { count: u64, @@ -338,92 +336,6 @@ impl OpcodeStat { } } -#[derive(Debug)] -struct OpcodeStats { - overall: OpcodeStat, - opcode: [Option; OPCODE_NUMBER], - merge_records: BTreeMap<&'static str, OpcodeMergeRecord>, -} - -const ARRAY_REPEAT_VALUE: std::option::Option = None; -impl Default for OpcodeStats { - fn default() -> Self { - Self { - overall: OpcodeStat::default(), - opcode: [ARRAY_REPEAT_VALUE; OPCODE_NUMBER], - merge_records: BTreeMap::new(), - } - } -} - -impl OpcodeStats { - fn print_opcode_title(&self) { - println!("===========================================================================Metric of instruction======================================================================"); - println!( - "{: COL_WIDTH$}{:>COL_WIDTH$}{:>COL_WIDTH$}{:>COL_WIDTH$} \ - {:>COL_WIDTH$}{:>COL_WIDTH$}{:>COL_WIDTH$}{:>COL_WIDTH$}{:>COL_WIDTH$}{:>COL_WIDTH$}", - "Opcode", - "Count", - "Count (%)", - "Time (s)", - "Time (%)", - "Cost (ns)", - "Total Mgas", - "Gas (%)", - "Static gas", - "Dyn. gas", - "Category" - ); - } - - fn print_opcodes(&self) { - println!(); - self.print_opcode_title(); - self.overall.print("overall"); - for i in 0..OPCODE_NUMBER { - let name = OpCode::new(i as u8); - if name.is_none() { - continue - } - self.opcode[i] - .as_ref() - .expect("opcode record should not empty") - .print(name.unwrap().as_str()); - } - println!(); - } - - fn print_category(&self) { - println!("\n"); - println!("=========================================================================================="); - println!( - "{:COL_WIDTH$}{:>COL_WIDTH$}{:>COL_WIDTH$}{:>COL_WIDTH$}{:>COL_WIDTH$}", - "Opcode Cat.", "Count", "Count (%)", "Time (s)", "Time (%)", "Cost (ns)", - ); - - for (k, v) in self.merge_records.iter() { - if *k == "" { - continue - } - println!( - "{:COL_WIDTH$}{:>COL_WIDTH$.2}{:>COL_WIDTH$.1}{:>COL_WIDTH$.3}{:>COL_WIDTH$.3}", - *k, - v.count, - v.count_pct * 100.0, - cycles_as_secs(v.time), - v.time_pct * 100.0, - v.avg_cost, - ); - } - } -} - -trait SupportPrint { - fn stats(&self) -> OpcodeStats; - fn print_addition_count(&self); - fn print_sload_percentile(&self); -} - // Return (total_gas, static_gas, dyn_gas). fn caculate_gas(opcode: u8, count: u64, total_gas: i128) -> (f64, u64, f64) { let (base_gas, is_static) = match MERGE_MAP[opcode as usize] { @@ -449,11 +361,29 @@ fn category_name(opcode: u8) -> Option<&'static str> { Some(MERGE_MAP[opcode as usize]?.1.category) } -impl SupportPrint for OpcodeRecord { - fn stats(&self) -> OpcodeStats { +#[derive(Debug)] +struct OpcodeStats { + overall: OpcodeStat, + opcode: [Option; OPCODE_NUMBER], + merge_records: BTreeMap<&'static str, OpcodeMergeRecord>, +} + +const ARRAY_REPEAT_VALUE: std::option::Option = None; +impl Default for OpcodeStats { + fn default() -> Self { + Self { + overall: OpcodeStat::default(), + opcode: [ARRAY_REPEAT_VALUE; OPCODE_NUMBER], + merge_records: BTreeMap::new(), + } + } +} + +impl From<&OpcodeRecord> for OpcodeStats { + fn from(record: &OpcodeRecord) -> Self { let mut opcode_stats = OpcodeStats::default(); // induction - for (i, v) in self.opcode_record.iter().enumerate() { + for (i, v) in record.opcode_record.iter().enumerate() { // opcode let op = i as u8; let mut opcode_stat = OpcodeStat::default(); @@ -497,7 +427,7 @@ impl SupportPrint for OpcodeRecord { } // calculate opcode pct - for (i, _v) in self.opcode_record.iter().enumerate() { + for (i, _v) in record.opcode_record.iter().enumerate() { opcode_stats.opcode[i].as_mut().expect("empty").count_pct = opcode_stats.opcode[i].as_mut().expect("empty").count as f64 / opcode_stats.overall.count as f64; @@ -523,7 +453,76 @@ impl SupportPrint for OpcodeRecord { opcode_stats } +} + +impl OpcodeStats { + fn print_opcode_title(&self) { + println!("===========================================================================Metric of instruction======================================================================"); + println!( + "{: COL_WIDTH$}{:>COL_WIDTH$}{:>COL_WIDTH$}{:>COL_WIDTH$} \ + {:>COL_WIDTH$}{:>COL_WIDTH$}{:>COL_WIDTH$}{:>COL_WIDTH$}{:>COL_WIDTH$}{:>COL_WIDTH$}", + "Opcode", + "Count", + "Count (%)", + "Time (s)", + "Time (%)", + "Cost (ns)", + "Total Mgas", + "Gas (%)", + "Static gas", + "Dyn. gas", + "Category" + ); + } + + fn print_opcodes(&self) { + println!(); + self.print_opcode_title(); + self.overall.print("overall"); + for i in 0..OPCODE_NUMBER { + let name = OpCode::new(i as u8); + if name.is_none() { + continue + } + self.opcode[i] + .as_ref() + .expect("opcode record should not empty") + .print(name.unwrap().as_str()); + } + println!(); + } + + fn print_category(&self) { + println!("\n"); + println!("=========================================================================================="); + println!( + "{:COL_WIDTH$}{:>COL_WIDTH$}{:>COL_WIDTH$}{:>COL_WIDTH$}{:>COL_WIDTH$}", + "Opcode Cat.", "Count", "Count (%)", "Time (s)", "Time (%)", "Cost (ns)", + ); + + for (k, v) in self.merge_records.iter() { + if *k == "" { + continue + } + println!( + "{:COL_WIDTH$}{:>COL_WIDTH$.2}{:>COL_WIDTH$.1}{:>COL_WIDTH$.3}{:>COL_WIDTH$.3}", + *k, + v.count, + v.count_pct * 100.0, + cycles_as_secs(v.time), + v.time_pct * 100.0, + v.avg_cost, + ); + } + } +} + +trait ExtraPrint { + fn print_addition_count(&self); + fn print_sload_percentile(&self); +} +impl ExtraPrint for OpcodeRecord { fn print_addition_count(&self) { println!(); println!("call additional rdtsc count: {}", self.additional_count[0]); @@ -541,7 +540,7 @@ impl SupportPrint for OpcodeRecord { impl Print for OpcodeRecord { fn print(&self, _block_number: u64) { - let opcode_stats = self.stats(); + let opcode_stats: OpcodeStats = self.into(); opcode_stats.print_opcodes(); opcode_stats.print_category(); self.print_addition_count(); diff --git a/crates/perf-metrics/src/dashboard/tps_gas.rs b/crates/perf-metrics/src/dashboard/tps_gas.rs index 9e1b6bcadc2e..c6a2bc8751fc 100644 --- a/crates/perf-metrics/src/dashboard/tps_gas.rs +++ b/crates/perf-metrics/src/dashboard/tps_gas.rs @@ -1,9 +1,10 @@ +//! This module is used to support the display of tps and mgas/s. use crate::metrics::TpsAndGasMessage; use revm_utils::time_utils::instant::Instant; use std::ops::{Div, Mul}; #[derive(Debug, Default)] -pub(crate) struct TpsAndGasDisplayer { +pub(super) struct TpsAndGasDisplayer { pre_txs: u128, pre_gas: u128, last_txs: u128, @@ -50,7 +51,7 @@ impl TpsAndGasDisplayer { println!("block_number: {:?}, MGas: {:.3}\n", block_number, mgas_ps); } - pub fn print(&mut self, block_number: u64, message: TpsAndGasMessage) { + pub(super) fn print(&mut self, block_number: u64, message: TpsAndGasMessage) { match message { TpsAndGasMessage::Record(record) => { self.update_tps_and_gas(record.block_number, record.txs, record.gas) diff --git a/crates/perf-metrics/src/metrics/duration.rs b/crates/perf-metrics/src/metrics/duration.rs index 3c1607d1a834..c8172f5fb2f9 100644 --- a/crates/perf-metrics/src/metrics/duration.rs +++ b/crates/perf-metrics/src/metrics/duration.rs @@ -6,19 +6,19 @@ use revm_utils::{metrics::types::TransactTime, time_utils::instant::Instant}; #[derive(Debug, Clone, Copy, Default)] pub struct ExecutionDurationRecord { // Total time recorder. - pub total_recorder: Instant, + pub(crate) total_recorder: Instant, // General time recorder. - pub time_recorder: Instant, + pub(crate) time_recorder: Instant, // Time of execute inner. - pub total: u64, + pub(crate) total: u64, // Time of get_block_td. - pub block_td: u64, + pub(crate) block_td: u64, // Time of block_with_senders. - pub block_with_senders: u64, + pub(crate) block_with_senders: u64, // Record of txs execution(execute_and_verify_receipt). - pub execution: ExecuteTxsRecord, + pub(crate) execution: ExecuteTxsRecord, // Record of write to db - pub write_to_db: WriteToDbRecord, + pub(crate) write_to_db: WriteToDbRecord, } // The following functions are used to record overhead. @@ -43,23 +43,23 @@ pub struct ExecuteTxsRecord { /// Record the start time of each subfunction. sub_record: Instant, /// Time of execute_and_verify_receipt. - pub total: u64, + pub(crate) total: u64, /// Time of transact. - pub transact: u64, + pub(crate) transact: u64, /// Time of revm's transact. - pub revm_transact: TransactTime, + pub(crate) revm_transact: TransactTime, /// Time of commit changes. - pub commit_changes: u64, + pub(crate) commit_changes: u64, /// Time of add receipt. - pub add_receipt: u64, + pub(crate) add_receipt: u64, /// Time of apply_post_execution_state_change. - pub apply_post_execution_state_change: u64, + pub(crate) apply_post_execution_state_change: u64, /// Time of merge_transactions. - pub merge_transactions: u64, + pub(crate) merge_transactions: u64, /// Time of verify_receipt. - pub verify_receipt: u64, + pub(crate) verify_receipt: u64, /// Time of save_receipts. - pub save_receipts: u64, + pub(crate) save_receipts: u64, } impl ExecuteTxsRecord { @@ -118,50 +118,50 @@ pub struct WriteToDbRecord { write_start_record: Instant, /// Time of write_to_db. - pub total: u64, + pub(crate) total: u64, /// Time of write storage changes in StateReverts. - pub revert_storage_time: u64, + pub(crate) revert_storage_time: u64, /// Data size of write storage changes in StateReverts. - pub revert_storage_size: usize, + pub(crate) revert_storage_size: usize, /// Time of append_dup when write storage changes in StateReverts. - pub revert_storage_append_time: u64, + pub(crate) revert_storage_append_time: u64, /// Time of write account changes in StateReverts. - pub revert_account_time: u64, + pub(crate) revert_account_time: u64, /// Data size of write account changes in StateReverts. - pub revert_account_size: usize, + pub(crate) revert_account_size: usize, /// Time of append_dup when write account changes in StateReverts. - pub revert_account_append_time: u64, + pub(crate) revert_account_append_time: u64, /// Time of write receipts. - pub write_receipts_time: u64, + pub(crate) write_receipts_time: u64, /// Data size of write receipts. - pub write_receipts_size: usize, + pub(crate) write_receipts_size: usize, /// Time of append when write receipts. - pub receipts_append_time: u64, + pub(crate) receipts_append_time: u64, /// Time of sort in StateChanges's write_to_db. - pub sort_time: u64, + pub(crate) sort_time: u64, /// Time of write account in StateChanges. - pub state_account_time: u64, + pub(crate) state_account_time: u64, /// Data size of write account in StateChanges. - pub state_account_size: usize, + pub(crate) state_account_size: usize, /// Time of upsert when write account changes in StateChanges. - pub state_account_upsert_time: u64, + pub(crate) state_account_upsert_time: u64, /// Time of write bytecode in StateChanges. - pub state_bytecode_time: u64, + pub(crate) state_bytecode_time: u64, /// Data size of write bytecode in StateChanges. - pub state_bytecode_size: usize, + pub(crate) state_bytecode_size: usize, /// Time of upsert when write bytecode in StateChanges. - pub state_bytecode_upsert_time: u64, + pub(crate) state_bytecode_upsert_time: u64, /// Time of write storage in StateChanges. - pub state_storage_time: u64, + pub(crate) state_storage_time: u64, /// Data size of write storage in StateChanges. - pub state_storage_size: usize, + pub(crate) state_storage_size: usize, /// Time of upsert when write storage in StateChanges. - pub state_storage_upsert_time: u64, + pub(crate) state_storage_upsert_time: u64, } impl WriteToDbRecord { diff --git a/crates/perf-metrics/src/metrics/execute_measure.rs b/crates/perf-metrics/src/metrics/execute_measure.rs new file mode 100644 index 000000000000..748c04d851ac --- /dev/null +++ b/crates/perf-metrics/src/metrics/execute_measure.rs @@ -0,0 +1,363 @@ +//! The functions defined in this file are mainly used to measure the execute stage. + +// The functions in execute_inner module should be called in the execute_inner function of +// execution stage. +pub mod execute_inner { + use crate::metrics::metric::*; + pub fn start_record() { + #[cfg(feature = "enable_execution_duration_record")] + recorder().duration_record.start_total_record(); + } + + pub fn record_before_loop() { + #[cfg(feature = "enable_tps_gas_record")] + let _ = + recorder().events_tx.as_mut().expect("No sender").send(MetricEvent::BlockTpsAndGas { + block_number: recorder().block_number, + record: TpsAndGasMessage::Switch(true), + }); + } + + pub fn record_before_td(block_number: u64) { + #[cfg(feature = "enable_execution_duration_record")] + recorder().duration_record.start_time_record(); + + recorder().block_number = block_number; + } + + pub fn record_after_td() { + #[cfg(feature = "enable_execution_duration_record")] + { + recorder().duration_record.add_block_td_duration(); + recorder().duration_record.start_time_record(); + } + } + + pub fn record_after_block_with_senders() { + #[cfg(feature = "enable_execution_duration_record")] + { + recorder().duration_record.add_block_with_senders_duration(); + recorder().duration_record.start_time_record(); + } + } + + pub fn record_after_get_tps(_block_number: u64, _txs: u64, _gas: u64) { + #[cfg(feature = "enable_tps_gas_record")] + { + recorder().tps_gas_record.record(_block_number, _txs as u128, _gas as u128); + let _ = recorder().events_tx.as_mut().expect("No sender").send( + MetricEvent::BlockTpsAndGas { + block_number: recorder().block_number, + record: TpsAndGasMessage::Record(recorder().tps_gas_record), + }, + ); + } + } + + pub fn record_after_take_output_state() { + #[cfg(feature = "enable_tps_gas_record")] + let _ = + recorder().events_tx.as_mut().expect("No sender").send(MetricEvent::BlockTpsAndGas { + block_number: recorder().block_number, + record: TpsAndGasMessage::Switch(false), + }); + + #[cfg(feature = "enable_execution_duration_record")] + recorder().duration_record.start_time_record(); + } + + pub fn record_at_end(_cachedb_size: usize) { + #[cfg(feature = "enable_execution_duration_record")] + { + recorder().duration_record.add_total_duration(); + let _ = recorder().events_tx.as_mut().expect("No sender").send( + MetricEvent::ExecutionStageTime { + block_number: recorder().block_number, + record: recorder().duration_record, + }, + ); + } + + #[cfg(feature = "enable_cache_record")] + { + let cachedb_record = revm_utils::metrics::get_cache_record(); + recorder().cachedb_record.update(&cachedb_record); + let _ = + recorder().events_tx.as_mut().expect("No sender").send(MetricEvent::CacheDbInfo { + block_number: recorder().block_number, + size: _cachedb_size, + record: recorder().cachedb_record, + }); + } + + #[cfg(feature = "enable_opcode_metrics")] + let _ = recorder().events_tx.as_mut().expect("No sender").send(MetricEvent::OpcodeInfo { + block_number: recorder().block_number, + record: recorder().op_record, + }); + } +} + +// The functions in this module should be called in executor. +#[cfg(feature = "enable_opcode_metrics")] +pub mod revm_measure { + /// After each transaction is executed, the execution status of instructions is counted and + /// then updated to the global metric recorder. This function will be called in + /// executor. + pub fn record_opcode() { + let mut op_record = revm_utils::metrics::get_op_record(); + if op_record.not_empty() { + crate::recorder().op_record.update(&mut op_record); + } + } +} + +// The functions in this module should be called in executor. +#[cfg(feature = "enable_execution_duration_record")] +pub mod execute_txs { + use crate::metrics::metric::*; + + /// start execute_tx record. + pub fn start_execute_tx_record() { + recorder().duration_record.execution.start_record(); + } + + /// start execute_tx sub record. + pub fn start_execute_tx_sub_record() { + recorder().duration_record.execution.start_sub_record(); + } + + /// transact record + pub fn transact_record() { + recorder().duration_record.execution.transact_record(); + } + + /// commit_changes_record + pub fn commit_changes_record() { + recorder().duration_record.execution.commit_changes_record(); + } + + /// add_receipt_record + pub fn add_receipt_record() { + recorder().duration_record.execution.add_receipt_record(); + } + + /// apply_post_execution_state_change_record + pub fn apply_post_execution_state_change_record() { + recorder().duration_record.execution.apply_post_execution_state_change_record(); + } + + /// merge_transactions_record + pub fn merge_transactions_record() { + recorder().duration_record.execution.merge_transactions_record(); + } + + /// verify_receipt_record + pub fn verify_receipt_record() { + recorder().duration_record.execution.verify_receipt_record(); + } + + /// save_receipts_record + pub fn save_receipts_record() { + recorder().duration_record.execution.save_receipts_record(); + } + + /// get_execute_tx_record + pub fn get_execute_tx_record() -> crate::metrics::ExecuteTxsRecord { + recorder().duration_record.execution + } + + /// Record for verfity_and_save_receipts + pub struct VerifyAndSaveReceiptsRecord; + + impl VerifyAndSaveReceiptsRecord { + /// Return VerifyAndSaveReceiptsRecord + pub fn new() -> Self { + verify_receipt_record(); + VerifyAndSaveReceiptsRecord + } + } + + impl Drop for VerifyAndSaveReceiptsRecord { + fn drop(&mut self) { + save_receipts_record(); + } + } +} + +// The functions in the module will be used to measure write_to_db and will be called in +// write_to_db. +#[cfg(feature = "enable_execution_duration_record")] +pub mod write_to_db { + use crate::metrics::metric::*; + + /// start write_to_db record. + pub fn start_write_to_db_record() { + recorder().duration_record.write_to_db.start_record(); + } + + /// start write_to_db sub record. + pub fn start_write_to_db_sub_record() { + recorder().duration_record.write_to_db.start_sub_record(); + } + + /// start write_to_db write record. + fn start_write_to_db_write_record() { + recorder().duration_record.write_to_db.start_write_record(); + } + + /// Record data size of write storage changes in StateReverts's write_to_db. + fn record_revert_storage_size(size: usize) { + recorder().duration_record.write_to_db.record_revert_storage_size(size); + } + + /// Record time of write storage append time in StateReverts's write_to_db. + fn record_revert_storage_append_time() { + recorder().duration_record.write_to_db.record_revert_storage_append_time(); + } + + // Encapsulate this structure to record write_storage in revert state in a RAII manner. + impl_write_macro!( + RevertsStorageWrite, + start_write_to_db_write_record, + record_revert_storage_append_time, + record_revert_storage_size, + record_receipts_append_time, + record_write_receipts_size + ); + + /// Record time of write storage changes in StateReverts's write_to_db. + pub fn record_revert_storage_time() { + recorder().duration_record.write_to_db.record_revert_storage_time(); + } + + /// Record data size of write account changes in StateReverts's write_to_db. + fn record_revert_account_size(size: usize) { + recorder().duration_record.write_to_db.record_revert_account_size(size); + } + + /// Record time of write account append time in StateReverts's write_to_db. + fn record_revert_account_append_time() { + recorder().duration_record.write_to_db.record_revert_account_append_time(); + } + + // Encapsulate this structure to record write_account in revert state in a RAII manner. + impl_write_macro!( + RevertsAccountWrite, + start_write_to_db_write_record, + record_revert_account_append_time, + record_revert_account_size, + record_receipts_append_time, + record_write_receipts_size + ); + + /// Record time of write account changes in StateReverts's write_to_db. + pub fn record_revert_account_time() { + recorder().duration_record.write_to_db.record_revert_account_time(); + } + + /// Record data size of write receipts in BundleStateWithReceipts's write_to_db. + fn record_write_receipts_size(size: usize) { + recorder().duration_record.write_to_db.record_write_receipts_size(size); + } + + /// Record time of write receipts append in BundleStateWithReceipts's write_to_db. + fn record_receipts_append_time() { + recorder().duration_record.write_to_db.record_receipts_append_time(); + } + + // Encapsulate this structure to record write receipts in a RAII manner. + impl_write_macro!( + ReceiptsWrite, + start_write_to_db_write_record, + record_receipts_append_time, + record_write_receipts_size, + record_receipts_append_time, + record_write_receipts_size + ); + + /// Record time of write receipts in BundleStateWithReceipts's write_to_db. + pub fn record_write_receipts_time() { + recorder().duration_record.write_to_db.record_write_receipts_time(); + } + + /// Record time of sort in StateChanges's write_to_db. + pub fn record_sort_time() { + recorder().duration_record.write_to_db.record_sort_time(); + } + + /// Record data size of write account in StateChanges's write_to_db. + fn record_state_account_size(size: usize) { + recorder().duration_record.write_to_db.record_state_account_size(size); + } + + /// Record time of write account upsert in StateChanges's write_to_db. + fn record_state_account_upsert_time() { + recorder().duration_record.write_to_db.record_state_account_upsert_time(); + } + + // Encapsulate this structure to record write_account in state changes in a RAII manner. + impl_write_macro!( + StateAccountWrite, + start_write_to_db_write_record, + record_state_account_upsert_time, + record_state_account_size, + record_receipts_append_time, + record_write_receipts_size + ); + + /// Record time of write account in StateChanges's write_to_db. + pub fn record_state_account_time() { + recorder().duration_record.write_to_db.record_state_account_time(); + } + + /// Record data size of write bytecode in StateChanges's write_to_db. + fn record_state_bytecode_size(size: usize) { + recorder().duration_record.write_to_db.record_state_bytecode_size(size); + } + + /// Record time of write bytecode upsert in StateChanges's write_to_db. + fn record_state_bytecode_upsert_time() { + recorder().duration_record.write_to_db.record_state_bytecode_upsert_time(); + } + + // Encapsulate this structure to record write_bytecode in state changes in a RAII manner. + impl_write_macro!( + StateBytecodeWrite, + start_write_to_db_write_record, + record_state_bytecode_upsert_time, + record_state_bytecode_size, + record_receipts_append_time, + record_write_receipts_size + ); + + /// Record time of write bytecode in StateChanges's write_to_db. + pub fn record_state_bytecode_time() { + recorder().duration_record.write_to_db.record_state_bytecode_time(); + } + + /// Record data size of write storage in StateChanges's write_to_db. + fn record_state_storage_size(size: usize) { + recorder().duration_record.write_to_db.record_state_storage_size(size); + } + + /// Record time of write storage upsert in StateChanges's write_to_db. + fn record_state_storage_upsert_time() { + recorder().duration_record.write_to_db.record_state_storage_upsert_time(); + } + + // Encapsulate this structure to record write_storage in state changes in a RAII manner. + impl_write_macro!( + StateStorageWrite, + start_write_to_db_write_record, + record_state_storage_upsert_time, + record_state_storage_size, + record_receipts_append_time, + record_write_receipts_size + ); + + /// Record time of write storage in StateChanges's write_to_db. + pub fn record_state_storage_time() { + recorder().duration_record.write_to_db.record_state_storage_time(); + } +} diff --git a/crates/perf-metrics/src/metrics/metric.rs b/crates/perf-metrics/src/metrics/metric.rs index 530d5052ebc8..bb0e419fc270 100644 --- a/crates/perf-metrics/src/metrics/metric.rs +++ b/crates/perf-metrics/src/metrics/metric.rs @@ -1,15 +1,24 @@ //! This module provides a metric to measure reth. +// #[cfg(feature = "enable_execution_duration_record")] +// pub use super::duration::ExecuteTxsRecord; #[cfg(feature = "enable_execution_duration_record")] -use super::duration::{ExecuteTxsRecord, ExecutionDurationRecord}; +use super::duration::ExecutionDurationRecord; #[cfg(feature = "enable_tps_gas_record")] -use super::tps_gas::{TpsAndGasMessage, TpsGasRecord}; +pub use super::tps_gas::TpsAndGasMessage; +#[cfg(feature = "enable_tps_gas_record")] +use super::tps_gas::TpsGasRecord; #[cfg(feature = "enable_cache_record")] use revm_utils::metrics::types::CacheDbRecord; #[cfg(feature = "enable_opcode_metrics")] use revm_utils::metrics::types::OpcodeRecord; - use tokio::sync::mpsc::UnboundedSender; +pub use super::execute_measure::execute_inner::*; +#[cfg(feature = "enable_opcode_metrics")] +pub use super::execute_measure::revm_measure::*; +#[cfg(feature = "enable_execution_duration_record")] +pub use super::execute_measure::{execute_txs::*, write_to_db::*}; + /// Alias type for metric producers to use. pub type MetricEventsSender = UnboundedSender; @@ -54,25 +63,25 @@ pub enum MetricEvent { /// This structure is used to facilitate all metric operations in reth's performance test. #[derive(Default)] -struct PerfMetric { +pub struct PerfMetric { /// Record the time consumption of each function in execution stage. #[cfg(feature = "enable_execution_duration_record")] - duration_record: ExecutionDurationRecord, + pub(crate) duration_record: ExecutionDurationRecord, /// Record tps and gas. #[cfg(feature = "enable_tps_gas_record")] - tps_gas_record: TpsGasRecord, + pub(crate) tps_gas_record: TpsGasRecord, /// Record cache hits, number of accesses, and memory usage. #[cfg(feature = "enable_cache_record")] - cachedb_record: CacheDbRecord, + pub(crate) cachedb_record: CacheDbRecord, /// Record information on instruction execution. #[cfg(feature = "enable_opcode_metrics")] - op_record: OpcodeRecord, + pub(crate) op_record: OpcodeRecord, /// A channel for sending recorded indicator information to the dashboard for display. - events_tx: Option, + pub(crate) events_tx: Option, /// Used to record the current block_number. - block_number: u64, + pub(crate) block_number: u64, } static mut METRIC_RECORDER: Option = None; @@ -91,410 +100,6 @@ pub fn set_metric_event_sender(events_tx: MetricEventsSender) { } } -fn recorder<'a>() -> &'a mut PerfMetric { +pub(crate) fn recorder<'a>() -> &'a mut PerfMetric { unsafe { METRIC_RECORDER.as_mut().expect("Metric recorder should not empty!") } } - -// ************************************************************************************************* -// -// The functions in the following range should be called in the execute_inner function of execution -// stage. -// -// ************************************************************************************************* -pub fn start_record() { - #[cfg(feature = "enable_execution_duration_record")] - recorder().duration_record.start_total_record(); -} - -pub fn record_before_loop() { - #[cfg(feature = "enable_tps_gas_record")] - let _ = recorder().events_tx.as_mut().expect("No sender").send(MetricEvent::BlockTpsAndGas { - block_number: recorder().block_number, - record: TpsAndGasMessage::Switch(true), - }); -} - -pub fn record_before_td(block_number: u64) { - #[cfg(feature = "enable_execution_duration_record")] - recorder().duration_record.start_time_record(); - - recorder().block_number = block_number; -} - -pub fn record_after_td() { - #[cfg(feature = "enable_execution_duration_record")] - { - recorder().duration_record.add_block_td_duration(); - recorder().duration_record.start_time_record(); - } -} - -pub fn record_after_block_with_senders() { - #[cfg(feature = "enable_execution_duration_record")] - { - recorder().duration_record.add_block_with_senders_duration(); - recorder().duration_record.start_time_record(); - } -} - -pub fn record_after_get_tps(_block_number: u64, _txs: u64, _gas: u64) { - #[cfg(feature = "enable_tps_gas_record")] - { - recorder().tps_gas_record.record(_block_number, _txs as u128, _gas as u128); - let _ = - recorder().events_tx.as_mut().expect("No sender").send(MetricEvent::BlockTpsAndGas { - block_number: recorder().block_number, - record: TpsAndGasMessage::Record(recorder().tps_gas_record), - }); - } -} - -pub fn record_after_take_output_state() { - #[cfg(feature = "enable_tps_gas_record")] - let _ = recorder().events_tx.as_mut().expect("No sender").send(MetricEvent::BlockTpsAndGas { - block_number: recorder().block_number, - record: TpsAndGasMessage::Switch(false), - }); - - #[cfg(feature = "enable_execution_duration_record")] - recorder().duration_record.start_time_record(); -} - -pub fn record_at_end(_cachedb_size: usize) { - #[cfg(feature = "enable_execution_duration_record")] - { - recorder().duration_record.add_total_duration(); - let _ = recorder().events_tx.as_mut().expect("No sender").send( - MetricEvent::ExecutionStageTime { - block_number: recorder().block_number, - record: recorder().duration_record, - }, - ); - } - - #[cfg(feature = "enable_cache_record")] - { - let cachedb_record = revm_utils::metrics::get_cache_record(); - recorder().cachedb_record.update(&cachedb_record); - let _ = recorder().events_tx.as_mut().expect("No sender").send(MetricEvent::CacheDbInfo { - block_number: recorder().block_number, - size: _cachedb_size, - record: recorder().cachedb_record, - }); - } - - #[cfg(feature = "enable_opcode_metrics")] - let _ = recorder().events_tx.as_mut().expect("No sender").send(MetricEvent::OpcodeInfo { - block_number: recorder().block_number, - record: recorder().op_record, - }); -} -// ************************************************************************************************* -// functions called by execute_inner end -// ************************************************************************************************* - -// ************************************************************************************************* -// -// The functions in the following range should be called in executor. -// -// ************************************************************************************************* - -/// After each transaction is executed, the execution status of instructions is counted and then -/// updated to the global metric recorder. This function will be called in executor. -#[cfg(feature = "enable_opcode_metrics")] -pub fn record_opcode() { - let mut op_record = revm_utils::metrics::get_op_record(); - if op_record.not_empty() { - recorder().op_record.update(&mut op_record); - } -} - -/// start execute_tx record. -#[cfg(feature = "enable_execution_duration_record")] -pub fn start_execute_tx_record() { - recorder().duration_record.execution.start_record(); -} - -/// start execute_tx sub record. -#[cfg(feature = "enable_execution_duration_record")] -pub fn start_execute_tx_sub_record() { - recorder().duration_record.execution.start_sub_record(); -} - -/// transact record -#[cfg(feature = "enable_execution_duration_record")] -pub fn transact_record() { - recorder().duration_record.execution.transact_record(); -} - -/// commit_changes_record -#[cfg(feature = "enable_execution_duration_record")] -pub fn commit_changes_record() { - recorder().duration_record.execution.commit_changes_record(); -} - -/// add_receipt_record -#[cfg(feature = "enable_execution_duration_record")] -pub fn add_receipt_record() { - recorder().duration_record.execution.add_receipt_record(); -} - -/// apply_post_execution_state_change_record -#[cfg(feature = "enable_execution_duration_record")] -pub fn apply_post_execution_state_change_record() { - recorder().duration_record.execution.apply_post_execution_state_change_record(); -} - -/// merge_transactions_record -#[cfg(feature = "enable_execution_duration_record")] -pub fn merge_transactions_record() { - recorder().duration_record.execution.merge_transactions_record(); -} - -/// verify_receipt_record -#[cfg(feature = "enable_execution_duration_record")] -pub fn verify_receipt_record() { - recorder().duration_record.execution.verify_receipt_record(); -} - -/// save_receipts_record -#[cfg(feature = "enable_execution_duration_record")] -pub fn save_receipts_record() { - recorder().duration_record.execution.save_receipts_record(); -} - -/// get_execute_tx_record -#[cfg(feature = "enable_execution_duration_record")] -pub fn get_execute_tx_record() -> ExecuteTxsRecord { - recorder().duration_record.execution -} - -/// Record for verfity_and_save_receipts -#[cfg(feature = "enable_execution_duration_record")] -pub struct VerifyAndSaveReceiptsRecord; - -#[cfg(feature = "enable_execution_duration_record")] -impl VerifyAndSaveReceiptsRecord { - /// Return VerifyAndSaveReceiptsRecord - pub fn new() -> Self { - verify_receipt_record(); - VerifyAndSaveReceiptsRecord - } -} - -#[cfg(feature = "enable_execution_duration_record")] -impl Drop for VerifyAndSaveReceiptsRecord { - fn drop(&mut self) { - save_receipts_record(); - } -} -// ************************************************************************************************* -// functions called by executor end -// ************************************************************************************************* - -// ************************************************************************************************* -// -// The function within this range will be used to measure write_to_db and will be called in -// write_to_db. -// -// ************************************************************************************************* -/// start write_to_db record. -#[cfg(feature = "enable_execution_duration_record")] -pub fn start_write_to_db_record() { - recorder().duration_record.write_to_db.start_record(); -} - -/// start write_to_db sub record. -#[cfg(feature = "enable_execution_duration_record")] -pub fn start_write_to_db_sub_record() { - recorder().duration_record.write_to_db.start_sub_record(); -} - -/// start write_to_db write record. -#[cfg(feature = "enable_execution_duration_record")] -fn start_write_to_db_write_record() { - recorder().duration_record.write_to_db.start_write_record(); -} - -/// Record data size of write storage changes in StateReverts's write_to_db. -#[cfg(feature = "enable_execution_duration_record")] -fn record_revert_storage_size(size: usize) { - recorder().duration_record.write_to_db.record_revert_storage_size(size); -} - -/// Record time of write storage append time in StateReverts's write_to_db. -#[cfg(feature = "enable_execution_duration_record")] -fn record_revert_storage_append_time() { - recorder().duration_record.write_to_db.record_revert_storage_append_time(); -} - -// Encapsulate this structure to record write_storage in revert state in a RAII manner. -#[cfg(feature = "enable_execution_duration_record")] -impl_write_macro!( - RevertsStorageWrite, - start_write_to_db_write_record, - record_revert_storage_append_time, - record_revert_storage_size, - record_receipts_append_time, - record_write_receipts_size -); - -/// Record time of write storage changes in StateReverts's write_to_db. -#[cfg(feature = "enable_execution_duration_record")] -pub fn record_revert_storage_time() { - recorder().duration_record.write_to_db.record_revert_storage_time(); -} - -/// Record data size of write account changes in StateReverts's write_to_db. -#[cfg(feature = "enable_execution_duration_record")] -fn record_revert_account_size(size: usize) { - recorder().duration_record.write_to_db.record_revert_account_size(size); -} - -/// Record time of write account append time in StateReverts's write_to_db. -#[cfg(feature = "enable_execution_duration_record")] -fn record_revert_account_append_time() { - recorder().duration_record.write_to_db.record_revert_account_append_time(); -} - -// Encapsulate this structure to record write_account in revert state in a RAII manner. -#[cfg(feature = "enable_execution_duration_record")] -impl_write_macro!( - RevertsAccountWrite, - start_write_to_db_write_record, - record_revert_account_append_time, - record_revert_account_size, - record_receipts_append_time, - record_write_receipts_size -); - -/// Record time of write account changes in StateReverts's write_to_db. -#[cfg(feature = "enable_execution_duration_record")] -pub fn record_revert_account_time() { - recorder().duration_record.write_to_db.record_revert_account_time(); -} - -/// Record data size of write receipts in BundleStateWithReceipts's write_to_db. -#[cfg(feature = "enable_execution_duration_record")] -fn record_write_receipts_size(size: usize) { - recorder().duration_record.write_to_db.record_write_receipts_size(size); -} - -/// Record time of write receipts append in BundleStateWithReceipts's write_to_db. -#[cfg(feature = "enable_execution_duration_record")] -fn record_receipts_append_time() { - recorder().duration_record.write_to_db.record_receipts_append_time(); -} - -// Encapsulate this structure to record write receipts in a RAII manner. -#[cfg(feature = "enable_execution_duration_record")] -impl_write_macro!( - ReceiptsWrite, - start_write_to_db_write_record, - record_receipts_append_time, - record_write_receipts_size, - record_receipts_append_time, - record_write_receipts_size -); - -/// Record time of write receipts in BundleStateWithReceipts's write_to_db. -#[cfg(feature = "enable_execution_duration_record")] -pub fn record_write_receipts_time() { - recorder().duration_record.write_to_db.record_write_receipts_time(); -} - -/// Record time of sort in StateChanges's write_to_db. -#[cfg(feature = "enable_execution_duration_record")] -pub fn record_sort_time() { - recorder().duration_record.write_to_db.record_sort_time(); -} - -/// Record data size of write account in StateChanges's write_to_db. -#[cfg(feature = "enable_execution_duration_record")] -fn record_state_account_size(size: usize) { - recorder().duration_record.write_to_db.record_state_account_size(size); -} - -/// Record time of write account upsert in StateChanges's write_to_db. -#[cfg(feature = "enable_execution_duration_record")] -fn record_state_account_upsert_time() { - recorder().duration_record.write_to_db.record_state_account_upsert_time(); -} - -// Encapsulate this structure to record write_account in state changes in a RAII manner. -#[cfg(feature = "enable_execution_duration_record")] -impl_write_macro!( - StateAccountWrite, - start_write_to_db_write_record, - record_state_account_upsert_time, - record_state_account_size, - record_receipts_append_time, - record_write_receipts_size -); - -/// Record time of write account in StateChanges's write_to_db. -#[cfg(feature = "enable_execution_duration_record")] -pub fn record_state_account_time() { - recorder().duration_record.write_to_db.record_state_account_time(); -} - -/// Record data size of write bytecode in StateChanges's write_to_db. -#[cfg(feature = "enable_execution_duration_record")] -fn record_state_bytecode_size(size: usize) { - recorder().duration_record.write_to_db.record_state_bytecode_size(size); -} - -/// Record time of write bytecode upsert in StateChanges's write_to_db. -#[cfg(feature = "enable_execution_duration_record")] -fn record_state_bytecode_upsert_time() { - recorder().duration_record.write_to_db.record_state_bytecode_upsert_time(); -} - -// Encapsulate this structure to record write_bytecode in state changes in a RAII manner. -#[cfg(feature = "enable_execution_duration_record")] -impl_write_macro!( - StateBytecodeWrite, - start_write_to_db_write_record, - record_state_bytecode_upsert_time, - record_state_bytecode_size, - record_receipts_append_time, - record_write_receipts_size -); - -/// Record time of write bytecode in StateChanges's write_to_db. -#[cfg(feature = "enable_execution_duration_record")] -pub fn record_state_bytecode_time() { - recorder().duration_record.write_to_db.record_state_bytecode_time(); -} - -/// Record data size of write storage in StateChanges's write_to_db. -#[cfg(feature = "enable_execution_duration_record")] -fn record_state_storage_size(size: usize) { - recorder().duration_record.write_to_db.record_state_storage_size(size); -} - -/// Record time of write storage upsert in StateChanges's write_to_db. -#[cfg(feature = "enable_execution_duration_record")] -fn record_state_storage_upsert_time() { - recorder().duration_record.write_to_db.record_state_storage_upsert_time(); -} - -// Encapsulate this structure to record write_storage in state changes in a RAII manner. -#[cfg(feature = "enable_execution_duration_record")] -impl_write_macro!( - StateStorageWrite, - start_write_to_db_write_record, - record_state_storage_upsert_time, - record_state_storage_size, - record_receipts_append_time, - record_write_receipts_size -); - -/// Record time of write storage in StateChanges's write_to_db. -#[cfg(feature = "enable_execution_duration_record")] -pub fn record_state_storage_time() { - recorder().duration_record.write_to_db.record_state_storage_time(); -} -// ************************************************************************************************* -// functions called by write_to_db end -// ************************************************************************************************* diff --git a/crates/perf-metrics/src/metrics/mod.rs b/crates/perf-metrics/src/metrics/mod.rs index adec4f5e8117..1320860d0b09 100644 --- a/crates/perf-metrics/src/metrics/mod.rs +++ b/crates/perf-metrics/src/metrics/mod.rs @@ -2,13 +2,14 @@ mod macros; #[cfg(feature = "enable_execution_duration_record")] mod duration; +mod execute_measure; #[cfg(feature = "enable_tps_gas_record")] mod tps_gas; pub mod metric; #[cfg(feature = "enable_execution_duration_record")] -pub use duration::{ExecuteTxsRecord, ExecutionDurationRecord, WriteToDbRecord}; +pub(crate) use duration::{ExecuteTxsRecord, ExecutionDurationRecord, WriteToDbRecord}; #[cfg(feature = "enable_tps_gas_record")] pub use tps_gas::{TpsAndGasMessage, TpsGasRecord}; diff --git a/crates/perf-metrics/src/metrics/tps_gas.rs b/crates/perf-metrics/src/metrics/tps_gas.rs index e47fe0699d56..90d169e44871 100644 --- a/crates/perf-metrics/src/metrics/tps_gas.rs +++ b/crates/perf-metrics/src/metrics/tps_gas.rs @@ -2,9 +2,9 @@ //! and total gas consumed so far. #[derive(Debug, Default, Copy, Clone)] pub struct TpsGasRecord { - pub block_number: u64, - pub txs: u128, - pub gas: u128, + pub(crate) block_number: u64, + pub(crate) txs: u128, + pub(crate) gas: u128, } impl TpsGasRecord { diff --git a/crates/storage/libmdbx-rs/Cargo.toml b/crates/storage/libmdbx-rs/Cargo.toml index 7ade3ae51a76..e06eb089acc0 100644 --- a/crates/storage/libmdbx-rs/Cargo.toml +++ b/crates/storage/libmdbx-rs/Cargo.toml @@ -26,7 +26,6 @@ dashmap = { version = "5.5.3", features = ["inline"], optional = true } tracing = { workspace = true, optional = true } ffi = { package = "reth-mdbx-sys", path = "./mdbx-sys" } -perf-metrics = { workspace = true, optional = true } [target.'cfg(not(windows))'.dependencies] libffi = "3.2.0"