1use csv::Writer;
5use eyre::OptionExt;
6use reth_primitives_traits::constants::GIGAGAS;
7use serde::{ser::SerializeStruct, Serialize};
8use std::{fs, path::Path, time::Duration};
9use tracing::info;
10
11pub(crate) const GAS_OUTPUT_SUFFIX: &str = "total_gas.csv";
13
14pub(crate) const COMBINED_OUTPUT_SUFFIX: &str = "combined_latency.csv";
16
17pub(crate) const NEW_PAYLOAD_OUTPUT_SUFFIX: &str = "new_payload_latency.csv";
19
20#[derive(Debug)]
23pub(crate) struct NewPayloadResult {
24 pub(crate) gas_used: u64,
26 pub(crate) latency: Duration,
28 pub(crate) persistence_wait: Option<Duration>,
30 pub(crate) execution_cache_wait: Duration,
32 pub(crate) sparse_trie_wait: Duration,
34}
35
36impl NewPayloadResult {
37 pub(crate) fn gas_per_second(&self) -> f64 {
39 self.gas_used as f64 / self.latency.as_secs_f64()
40 }
41}
42
43impl std::fmt::Display for NewPayloadResult {
44 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
45 write!(
46 f,
47 "New payload processed at {:.4} Ggas/s, used {} total gas. Latency: {:?}",
48 self.gas_per_second() / GIGAGAS as f64,
49 self.gas_used,
50 self.latency
51 )
52 }
53}
54
55impl Serialize for NewPayloadResult {
58 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
59 where
60 S: serde::ser::Serializer,
61 {
62 let time = self.latency.as_micros();
64 let mut state = serializer.serialize_struct("NewPayloadResult", 5)?;
65 state.serialize_field("gas_used", &self.gas_used)?;
66 state.serialize_field("latency", &time)?;
67 state.serialize_field("persistence_wait", &self.persistence_wait.map(|d| d.as_micros()))?;
68 state.serialize_field("execution_cache_wait", &self.execution_cache_wait.as_micros())?;
69 state.serialize_field("sparse_trie_wait", &self.sparse_trie_wait.as_micros())?;
70 state.end()
71 }
72}
73
74#[derive(Debug)]
78pub(crate) struct CombinedResult {
79 pub(crate) block_number: u64,
81 pub(crate) gas_limit: u64,
83 pub(crate) transaction_count: u64,
85 pub(crate) new_payload_result: NewPayloadResult,
87 pub(crate) fcu_latency: Duration,
89 pub(crate) total_latency: Duration,
91}
92
93impl CombinedResult {
94 pub(crate) fn combined_gas_per_second(&self) -> f64 {
96 self.new_payload_result.gas_used as f64 / self.total_latency.as_secs_f64()
97 }
98}
99
100impl std::fmt::Display for CombinedResult {
101 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
102 let np = &self.new_payload_result;
103 write!(
104 f,
105 "Block {} processed at {:.4} Ggas/s, used {} total gas. Combined: {:.4} Ggas/s. fcu: {:?}, newPayload: {:?}",
106 self.block_number,
107 np.gas_per_second() / GIGAGAS as f64,
108 np.gas_used,
109 self.combined_gas_per_second() / GIGAGAS as f64,
110 self.fcu_latency,
111 np.latency,
112 )?;
113 if !np.execution_cache_wait.is_zero() {
114 write!(f, ", execution cache wait: {:?}", np.execution_cache_wait)?;
115 }
116 if !np.sparse_trie_wait.is_zero() {
117 write!(f, ", trie cache wait: {:?}", np.sparse_trie_wait)?;
118 }
119 if let Some(d) = np.persistence_wait {
120 write!(f, ", persistence wait: {d:?}")?;
121 }
122 Ok(())
123 }
124}
125
126impl Serialize for CombinedResult {
129 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
130 where
131 S: serde::ser::Serializer,
132 {
133 let fcu_latency = self.fcu_latency.as_micros();
135 let new_payload_latency = self.new_payload_result.latency.as_micros();
136 let total_latency = self.total_latency.as_micros();
137 let mut state = serializer.serialize_struct("CombinedResult", 10)?;
138
139 state.serialize_field("block_number", &self.block_number)?;
141 state.serialize_field("gas_limit", &self.gas_limit)?;
142 state.serialize_field("transaction_count", &self.transaction_count)?;
143 state.serialize_field("gas_used", &self.new_payload_result.gas_used)?;
144 state.serialize_field("new_payload_latency", &new_payload_latency)?;
145 state.serialize_field("fcu_latency", &fcu_latency)?;
146 state.serialize_field("total_latency", &total_latency)?;
147 state.serialize_field(
148 "persistence_wait",
149 &self.new_payload_result.persistence_wait.map(|d| d.as_micros()),
150 )?;
151 state.serialize_field(
152 "execution_cache_wait",
153 &self.new_payload_result.execution_cache_wait.as_micros(),
154 )?;
155 state.serialize_field(
156 "sparse_trie_wait",
157 &self.new_payload_result.sparse_trie_wait.as_micros(),
158 )?;
159 state.end()
160 }
161}
162
163#[derive(Debug)]
165pub(crate) struct TotalGasRow {
166 pub(crate) block_number: u64,
168 pub(crate) transaction_count: u64,
170 pub(crate) gas_used: u64,
172 pub(crate) time: Duration,
174}
175
176#[derive(Debug)]
178pub(crate) struct TotalGasOutput {
179 pub(crate) total_gas_used: u64,
181 pub(crate) total_duration: Duration,
183 pub(crate) execution_duration: Duration,
185 pub(crate) blocks_processed: u64,
187}
188
189impl TotalGasOutput {
190 pub(crate) fn new(rows: Vec<TotalGasRow>) -> eyre::Result<Self> {
195 let total_duration = rows.last().map(|row| row.time).ok_or_eyre("empty results")?;
196 let blocks_processed = rows.len() as u64;
197 let total_gas_used: u64 = rows.into_iter().map(|row| row.gas_used).sum();
198
199 Ok(Self {
200 total_gas_used,
201 total_duration,
202 execution_duration: total_duration,
203 blocks_processed,
204 })
205 }
206
207 pub(crate) fn with_combined_results(
212 rows: Vec<TotalGasRow>,
213 combined_results: &[CombinedResult],
214 ) -> eyre::Result<Self> {
215 let total_duration = rows.last().map(|row| row.time).ok_or_eyre("empty results")?;
216 let blocks_processed = rows.len() as u64;
217 let total_gas_used: u64 = rows.into_iter().map(|row| row.gas_used).sum();
218
219 let execution_duration: Duration = combined_results.iter().map(|r| r.total_latency).sum();
221
222 Ok(Self { total_gas_used, total_duration, execution_duration, blocks_processed })
223 }
224
225 pub(crate) fn total_gigagas_per_second(&self) -> f64 {
227 self.total_gas_used as f64 / self.total_duration.as_secs_f64() / GIGAGAS as f64
228 }
229
230 pub(crate) fn execution_gigagas_per_second(&self) -> f64 {
232 self.total_gas_used as f64 / self.execution_duration.as_secs_f64() / GIGAGAS as f64
233 }
234}
235
236pub(crate) fn write_benchmark_results(
242 output_dir: &Path,
243 gas_results: &[TotalGasRow],
244 combined_results: &[CombinedResult],
245) -> eyre::Result<()> {
246 fs::create_dir_all(output_dir)?;
247
248 let output_path = output_dir.join(COMBINED_OUTPUT_SUFFIX);
249 info!(target: "reth-bench", "Writing engine api call latency output to file: {:?}", output_path);
250 let mut writer = Writer::from_path(&output_path)?;
251 for result in combined_results {
252 writer.serialize(result)?;
253 }
254 writer.flush()?;
255
256 let output_path = output_dir.join(GAS_OUTPUT_SUFFIX);
257 info!(target: "reth-bench", "Writing total gas output to file: {:?}", output_path);
258 let mut writer = Writer::from_path(&output_path)?;
259 for row in gas_results {
260 writer.serialize(row)?;
261 }
262 writer.flush()?;
263
264 info!(target: "reth-bench", "Finished writing benchmark output files to {:?}.", output_dir);
265 Ok(())
266}
267
268impl Serialize for TotalGasRow {
272 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
273 where
274 S: serde::ser::Serializer,
275 {
276 let time = self.time.as_micros();
278 let mut state = serializer.serialize_struct("TotalGasRow", 4)?;
279 state.serialize_field("block_number", &self.block_number)?;
280 state.serialize_field("transaction_count", &self.transaction_count)?;
281 state.serialize_field("gas_used", &self.gas_used)?;
282 state.serialize_field("time", &time)?;
283 state.end()
284 }
285}
286
287#[cfg(test)]
288mod tests {
289 use super::*;
290 use csv::Writer;
291 use std::io::BufRead;
292
293 #[test]
294 fn test_write_total_gas_row_csv() {
295 let row = TotalGasRow {
296 block_number: 1,
297 transaction_count: 10,
298 gas_used: 1_000,
299 time: Duration::from_secs(1),
300 };
301
302 let mut writer = Writer::from_writer(vec![]);
303 writer.serialize(row).unwrap();
304 let result = writer.into_inner().unwrap();
305
306 let mut result = result.as_slice().lines();
308
309 let expected_first_line = "block_number,transaction_count,gas_used,time";
311 let first_line = result.next().unwrap().unwrap();
312 assert_eq!(first_line, expected_first_line);
313
314 let expected_second_line = "1,10,1000,1000000";
315 let second_line = result.next().unwrap().unwrap();
316 assert_eq!(second_line, expected_second_line);
317 }
318}