Skip to main content

o2_tools/
bench.rs

1// Shared utilities for gas benchmarks across all contract packages.
2
3use fuels::prelude::*;
4use regex::Regex;
5use serde::Serialize;
6use std::io::Write;
7
8// Fuel mainnet (Ignition) consensus parameters — version 7
9// Source: https://github.com/FuelLabs/chain-configuration/blob/49065098b50aceaecdd11eeabacb3ef94f60dc64/upgradelog/ignition/consensus_parameters/7.json
10//
11// NOTE: Gas costs use the SDK default (V4) rather than mainnet's V6 because
12// fuel-tx v0.56.0 predates V6. Gas measurements are therefore approximations.
13// The structural parameters (gas limits, sizes, fees) match mainnet exactly.
14
15// tx_params
16pub const MAINNET_MAX_GAS_PER_TX: u64 = 30_000_000;
17pub const MAINNET_MAX_TX_SIZE: u64 = 112_640;
18
19// contract_params
20pub const MAINNET_CONTRACT_MAX_SIZE: u64 = 112_640;
21pub const MAINNET_MAX_STORAGE_SLOTS: u64 = 1_760;
22
23// fee_params
24pub const MAINNET_GAS_PRICE_FACTOR: u64 = 1_150_000;
25pub const MAINNET_GAS_PER_BYTE: u64 = 1;
26
27// predicate_params
28pub const MAINNET_MAX_PREDICATE_LENGTH: u64 = 24_576;
29pub const MAINNET_MAX_PREDICATE_DATA_LENGTH: u64 = 24_576;
30pub const MAINNET_MAX_MESSAGE_DATA_LENGTH: u64 = 102_400;
31pub const MAINNET_MAX_GAS_PER_PREDICATE: u64 = 1_000_000;
32
33// block limits
34pub const MAINNET_BLOCK_GAS_LIMIT: u64 = 42_000_000;
35
36/// True if benchmarks should only be listed and not executed.
37/// Configured via environment variable `BENCH_LIST_ONLY`:
38///   `BENCH_LIST_ONLY=1`
39pub fn bench_is_list_only() -> bool {
40    std::env::var("BENCH_LIST_ONLY").as_deref() == Ok("1")
41}
42
43/// Regex for filtering benchmarks to execute.
44/// Configured via environment variable `BENCH_FILTER`.
45pub fn bench_filter() -> Option<Regex> {
46    std::env::var("BENCH_FILTER")
47        .ok()
48        .map(|s| Regex::new(&s).expect("BENCH_FILTER: invalid regex"))
49}
50
51/// Runs a single named benchmark, gated by the `BENCH_LIST_ONLY` and
52/// `BENCH_FILTER` environment variables. No caller-scope variables are
53/// needed — the macro reads the env vars itself on every invocation
54/// (negligible cost compared to actual benchmark execution).
55#[macro_export]
56macro_rules! run_bench {
57    ($desc:expr, $body:block) => {{
58        let desc: &str = $desc;
59        if $crate::bench::bench_is_list_only() {
60            println!("{desc}");
61        } else if $crate::bench::bench_filter()
62            .as_ref()
63            .map_or(true, |re| re.is_match(desc))
64        {
65            println!("Running {desc}...");
66            let __t0 = std::time::Instant::now();
67            $body
68            println!("Duration {:.2?}", __t0.elapsed());
69        }
70    }};
71}
72
73/// A single benchmark result.
74#[derive(Serialize, Clone, Debug)]
75pub struct BenchmarkEntry {
76    pub name: String,
77    pub unit: String,
78    pub value: f64,
79}
80
81/// Returns the total gas consumed by a contract call response.
82pub fn extract_gas_used<T>(
83    response: &fuels::programs::responses::CallResponse<T>,
84) -> u64 {
85    response.tx_status.total_gas
86}
87
88/// Applies Fuel mainnet (Ignition v7) consensus parameters to `chain_config`
89/// in-place. Call this inside `setup_wallets_mainnet` before launching the
90/// provider so benchmarks reflect real on-chain constraints.
91pub fn apply_mainnet_consensus_params(chain_config: &mut ChainConfig) {
92    let cp = &mut chain_config.consensus_parameters;
93
94    let mut tx_params = cp.tx_params().with_max_gas_per_tx(MAINNET_MAX_GAS_PER_TX);
95    tx_params.set_max_size(MAINNET_MAX_TX_SIZE);
96    cp.set_tx_params(tx_params);
97
98    let contract_params = cp
99        .contract_params()
100        .with_contract_max_size(MAINNET_CONTRACT_MAX_SIZE)
101        .with_max_storage_slots(MAINNET_MAX_STORAGE_SLOTS);
102    cp.set_contract_params(contract_params);
103
104    let fee_params = cp
105        .fee_params()
106        .with_gas_price_factor(MAINNET_GAS_PRICE_FACTOR)
107        .with_gas_per_byte(MAINNET_GAS_PER_BYTE);
108    cp.set_fee_params(fee_params);
109
110    let predicate_params = cp
111        .predicate_params()
112        .with_max_predicate_length(MAINNET_MAX_PREDICATE_LENGTH)
113        .with_max_predicate_data_length(MAINNET_MAX_PREDICATE_DATA_LENGTH)
114        .with_max_message_data_length(MAINNET_MAX_MESSAGE_DATA_LENGTH)
115        .with_max_gas_per_predicate(MAINNET_MAX_GAS_PER_PREDICATE);
116    cp.set_predicate_params(predicate_params);
117
118    cp.set_block_gas_limit(MAINNET_BLOCK_GAS_LIMIT);
119}
120
121/// Writes benchmark results to the JSON output files specified by the
122/// `BENCH_OUTPUT_GAS` and `BENCH_OUTPUT_MATCHES` environment variables
123/// (defaulting to `benchmark-results-{gas|matches}.json` in the current
124/// directory).
125///
126/// Pass all smaller-is-better results (gas, gas/match) as `gas` and all
127/// bigger-is-better results (match counts) as `matches`. Either slice may
128/// be empty.
129pub fn write_bench_results(gas: &[BenchmarkEntry], matches: &[BenchmarkEntry]) {
130    let gas_path = std::env::var("BENCH_OUTPUT_GAS")
131        .unwrap_or_else(|_| "benchmark-results-gas.json".to_string());
132    let mut f = std::fs::File::create(&gas_path).unwrap();
133    f.write_all(serde_json::to_string_pretty(gas).unwrap().as_bytes())
134        .unwrap();
135
136    let matches_path = std::env::var("BENCH_OUTPUT_MATCHES")
137        .unwrap_or_else(|_| "benchmark-results-matches.json".to_string());
138    let mut f = std::fs::File::create(&matches_path).unwrap();
139    f.write_all(serde_json::to_string_pretty(matches).unwrap().as_bytes())
140        .unwrap();
141
142    println!("\nGas results written to {gas_path}");
143    println!("Matches results written to {matches_path}");
144}