1//! Contains various benchmark output formats, either for logging or for
2//! serialization to / from files.
34use reth_primitives_traits::constants::GIGAGAS;
5use serde::{ser::SerializeStruct, Serialize};
6use std::time::Duration;
78/// This is the suffix for gas output csv files.
9pub(crate) const GAS_OUTPUT_SUFFIX: &str = "total_gas.csv";
1011/// This is the suffix for combined output csv files.
12pub(crate) const COMBINED_OUTPUT_SUFFIX: &str = "combined_latency.csv";
1314/// This is the suffix for new payload output csv files.
15pub(crate) const NEW_PAYLOAD_OUTPUT_SUFFIX: &str = "new_payload_latency.csv";
1617/// This represents the results of a single `newPayload` call in the benchmark, containing the gas
18/// used and the `newPayload` latency.
19#[derive(Debug)]
20pub(crate) struct NewPayloadResult {
21/// The gas used in the `newPayload` call.
22pub(crate) gas_used: u64,
23/// The latency of the `newPayload` call.
24pub(crate) latency: Duration,
25}
2627impl NewPayloadResult {
28/// Returns the gas per second processed in the `newPayload` call.
29pub(crate) fn gas_per_second(&self) -> f64 {
30self.gas_used as f64 / self.latency.as_secs_f64()
31 }
32}
3334impl std::fmt::Displayfor NewPayloadResult {
35fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
36write!(
37f,
38"New payload processed at {:.4} Ggas/s, used {} total gas. Latency: {:?}",
39self.gas_per_second() / GIGAGAS as f64,
40self.gas_used,
41self.latency
42 )
43 }
44}
4546/// This is another [`Serialize`] implementation for the [`NewPayloadResult`] struct, serializing
47/// the duration as microseconds because the csv writer would fail otherwise.
48impl Serializefor NewPayloadResult {
49fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
50where
51S: serde::ser::Serializer,
52 {
53// convert the time to microseconds
54let time = self.latency.as_micros();
55let mut state = serializer.serialize_struct("NewPayloadResult", 3)?;
56state.serialize_field("gas_used", &self.gas_used)?;
57state.serialize_field("latency", &time)?;
58state.end()
59 }
60}
6162/// This represents the combined results of a `newPayload` call and a `forkchoiceUpdated` call in
63/// the benchmark, containing the gas used, the `newPayload` latency, and the `forkchoiceUpdated`
64/// latency.
65#[derive(Debug)]
66pub(crate) struct CombinedResult {
67/// The block number of the block being processed.
68pub(crate) block_number: u64,
69/// The `newPayload` result.
70pub(crate) new_payload_result: NewPayloadResult,
71/// The latency of the `forkchoiceUpdated` call.
72pub(crate) fcu_latency: Duration,
73/// The latency of both calls combined.
74pub(crate) total_latency: Duration,
75}
7677impl CombinedResult {
78/// Returns the gas per second, including the `newPayload` _and_ `forkchoiceUpdated` duration.
79pub(crate) fn combined_gas_per_second(&self) -> f64 {
80self.new_payload_result.gas_used as f64 / self.total_latency.as_secs_f64()
81 }
82}
8384impl std::fmt::Displayfor CombinedResult {
85fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
86write!(
87f,
88"Payload {} processed at {:.4} Ggas/s, used {} total gas. Combined gas per second: {:.4} Ggas/s. fcu latency: {:?}, newPayload latency: {:?}",
89self.block_number,
90self.new_payload_result.gas_per_second() / GIGAGAS as f64,
91self.new_payload_result.gas_used,
92self.combined_gas_per_second() / GIGAGAS as f64,
93self.fcu_latency,
94self.new_payload_result.latency
95 )
96 }
97}
9899/// This is a [`Serialize`] implementation for the [`CombinedResult`] struct, serializing the
100/// durations as microseconds because the csv writer would fail otherwise.
101impl Serializefor CombinedResult {
102fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
103where
104S: serde::ser::Serializer,
105 {
106// convert the time to microseconds
107let fcu_latency = self.fcu_latency.as_micros();
108let new_payload_latency = self.new_payload_result.latency.as_micros();
109let total_latency = self.total_latency.as_micros();
110let mut state = serializer.serialize_struct("CombinedResult", 5)?;
111112// flatten the new payload result because this is meant for CSV writing
113state.serialize_field("block_number", &self.block_number)?;
114state.serialize_field("gas_used", &self.new_payload_result.gas_used)?;
115state.serialize_field("new_payload_latency", &new_payload_latency)?;
116state.serialize_field("fcu_latency", &fcu_latency)?;
117state.serialize_field("total_latency", &total_latency)?;
118state.end()
119 }
120}
121122/// This represents a row of total gas data in the benchmark.
123#[derive(Debug)]
124pub(crate) struct TotalGasRow {
125/// The block number of the block being processed.
126#[allow(dead_code)]
127pub(crate) block_number: u64,
128/// The total gas used in the block.
129pub(crate) gas_used: u64,
130/// Time since the start of the benchmark.
131pub(crate) time: Duration,
132}
133134/// This represents the aggregated output, meant to show gas per second metrics, of a benchmark run.
135#[derive(Debug)]
136pub(crate) struct TotalGasOutput {
137/// The total gas used in the benchmark.
138pub(crate) total_gas_used: u64,
139/// The total duration of the benchmark.
140pub(crate) total_duration: Duration,
141/// The total gas used per second.
142pub(crate) total_gas_per_second: f64,
143/// The number of blocks processed.
144pub(crate) blocks_processed: u64,
145}
146147impl TotalGasOutput {
148/// Create a new [`TotalGasOutput`] from a list of [`TotalGasRow`].
149pub(crate) fn new(rows: Vec<TotalGasRow>) -> Self {
150// the duration is obtained from the last row
151let total_duration =
152rows.last().map(|row| row.time).expect("the row has at least one element");
153let blocks_processed = rows.len() as u64;
154let total_gas_used: u64 = rows.into_iter().map(|row| row.gas_used).sum();
155let total_gas_per_second = total_gas_usedas f64 / total_duration.as_secs_f64();
156157Self { total_gas_used, total_duration, total_gas_per_second, blocks_processed }
158 }
159160/// Return the total gigagas per second.
161pub(crate) fn total_gigagas_per_second(&self) -> f64 {
162self.total_gas_per_second / GIGAGAS as f64163 }
164}
165166/// This serializes the `time` field of the [`TotalGasRow`] to microseconds.
167///
168/// This is essentially just for the csv writer, which would have headers
169impl Serializefor TotalGasRow {
170fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
171where
172S: serde::ser::Serializer,
173 {
174// convert the time to microseconds
175let time = self.time.as_micros();
176let mut state = serializer.serialize_struct("TotalGasRow", 3)?;
177state.serialize_field("block_number", &self.block_number)?;
178state.serialize_field("gas_used", &self.gas_used)?;
179state.serialize_field("time", &time)?;
180state.end()
181 }
182}
183184#[cfg(test)]
185mod tests {
186use super::*;
187use csv::Writer;
188use std::io::BufRead;
189190#[test]
191fn test_write_total_gas_row_csv() {
192let row = TotalGasRow { block_number: 1, gas_used: 1_000, time: Duration::from_secs(1) };
193194let mut writer = Writer::from_writer(vec![]);
195 writer.serialize(row).unwrap();
196let result = writer.into_inner().unwrap();
197198// parse into Lines
199let mut result = result.as_slice().lines();
200201// assert header
202let expected_first_line = "block_number,gas_used,time";
203let first_line = result.next().unwrap().unwrap();
204assert_eq!(first_line, expected_first_line);
205206let expected_second_line = "1,1000,1000000";
207let second_line = result.next().unwrap().unwrap();
208assert_eq!(second_line, expected_second_line);
209 }
210}