Skip to content

perf(l1,l2): use the new load test for the CI scripts #2467

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 14 commits into from
Apr 21, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 17 additions & 20 deletions .github/scripts/flamegraph_watcher.sh
100644 → 100755
Original file line number Diff line number Diff line change
@@ -1,31 +1,28 @@
#!/bin/bash
set -e

# This script sends 171 * <iterations> transactions to a test account, per defined private key
# then polls the account balance until the expected balance has been reached
# and then kills the process. It also measures the elapsed time of the test and
# outputs it to Github Action's outputs.
iterations=3500
value=1
account=0x33c6b73432B3aeA0C1725E415CC40D04908B85fd
end_val=$((171 * $iterations * $value))
# This script runs a load test and then kills the node under test. The load test sends a
# transaction from each rich account to a random one, so we can check their nonce to
# determine that the load test finished.
#
# Usage:
# ./flamegraph_watcher.sh
# Requires a PROGRAM variable to be set (e.g. ethrex). This $PROGRAM will be killed when the
# load test finishes. Must be run from the context of the repo root.

start_time=$(date +%s)
ethrex_l2 test load --path /home/runner/work/ethrex/ethrex/test_data/private_keys.txt -i $iterations -v --value $value --to $account >/dev/null
# TODO(#2486): Move this to a cached build outside.
echo "Building load test"
cargo build --release --manifest-path ./cmd/load_test/Cargo.toml

output=$(ethrex_l2 info -b -a $account --wei 2>&1)
echo "balance: $output"
while [[ $output -lt $end_val ]]; do
sleep 2
output=$(ethrex_l2 info -b -a $account --wei 2>&1)
echo "balance: $output"
done
echo "Starting load test"
start_time=$(date +%s)
RUST_BACKTRACE=1 ./target/release/load_test -k ./test_data/private_keys.txt -t eth-transfers -N 1000 -n http://localhost:1729 -w 5 >/dev/null
end_time=$(date +%s)
elapsed=$((end_time - start_time))

elapsed=$((end_time - start_time))
minutes=$((elapsed / 60))
seconds=$((elapsed % 60))
output=$(ethrex_l2 info -b -a $account --wei 2>&1)
echo "Balance of $output reached in $minutes min $seconds s, killing process"
echo "All load test transactions included in $minutes min $seconds s, killing node process."

echo killing "$PROGRAM"
sudo pkill "$PROGRAM"
Expand Down
63 changes: 12 additions & 51 deletions .github/workflows/main_flamegraph_report.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,8 @@ permissions:
id-token: write

on:
# re-enable this when fixing the workflow
# push:
# branches: ["main"]
push:
branches: ["main"]
workflow_dispatch:

env:
Expand Down Expand Up @@ -183,13 +182,6 @@ jobs:
${{ env.HOME }}/.cargo/bin/inferno-*
key: ${{ runner.os }}-extra-binaries

- name: Cache ethrex_l2
id: cache-ethrex-l2
uses: actions/cache@v4
with:
path: ${{ env.HOME }}/.cargo/bin/ethrex_l2
key: ${{ runner.os }}-ethrex-l2-${{ hashFiles('cmd/ethrex_l2/Cargo.lock') }}

- name: Change perf settings
run: |
sudo sysctl kernel.perf_event_paranoid=-1
Expand Down Expand Up @@ -236,30 +228,18 @@ jobs:
echo "$HOME/.cargo/bin/inferno-collapse-perf" already found
fi

- name: Install ethrex_l2 cli
run: |
if [ -f "$HOME/.cargo/bin/ethrex_l2" ]; then
echo "$HOME/.cargo/bin/ethrex_l2" already found
else
cargo install --force --path cmd/ethrex_l2
fi
ethrex_l2 config create default --default
ethrex_l2 config set default

# By default ethrex uses revm as evm backend.
- id: generate-flamegraph-ethrex
name: Generate Flamegraph data for Ethrex
shell: bash
run: |
rm -rf target/release/ethrex
cargo build --release --bin ethrex --features dev
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you'll get better results building with:

Suggested change
cargo build --release --bin ethrex --features dev
cargo build --profile release-with-debug --bin ethrex --features dev

CARGO_PROFILE_RELEASE_DEBUG=true cargo flamegraph -c "record -o perf.data -F997 --call-graph dwarf,16384 -g" \
--bin ethrex --features dev -- --dev --network /home/runner/work/ethrex/ethrex/test_data/genesis-l2.json --http.port 1729 >/dev/null &
while [ ! -x "./target/release/ethrex" ]; do
echo "Waiting for ethrex binary to be ready..."
sleep 2
done
--bin ethrex --release --features dev -- \
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ditto.

--dev --network /home/runner/work/ethrex/ethrex/test_data/genesis-l2-ci.json --http.port 1729 >/dev/null &
sleep 10
echo "Compilation finished. Executing load test..."
echo "Executing load test..."
bash /home/runner/work/ethrex/ethrex/.github/scripts/flamegraph_watcher.sh &&
echo "Load test finished"

Expand Down Expand Up @@ -293,12 +273,13 @@ jobs:
with:
toolchain: ${{ env.RUST_RETH_VERSION }}

# We need a reth version that requires a rustc version <= 1.82.0
- name: Checkout reth
uses: actions/checkout@v4
with:
repository: paradigmxyz/reth
path: "reth"
ref: main
ref: b2ead06d1d0804101de0d1eb3a070e08d8eab857

- name: Caching
uses: Swatinem/rust-cache@v2
Expand All @@ -316,13 +297,6 @@ jobs:
${{ env.HOME }}/.cargo/bin/inferno-*
key: ${{ runner.os }}-extra-binaries

- name: Cache ethrex_l2
id: cache-ethrex-l2
uses: actions/cache@v4
with:
path: ${{ env.HOME }}/.cargo/bin/ethrex_l2
key: ${{ runner.os }}-ethrex-l2-${{ hashFiles('cmd/ethrex_l2/Cargo.lock') }}

- name: Change perf settings
run: |
sudo sysctl kernel.perf_event_paranoid=-1
Expand Down Expand Up @@ -369,35 +343,22 @@ jobs:
echo "$HOME/.cargo/bin/inferno-collapse-perf" already found
fi

- name: Install ethrex_l2 cli
run: |
if [ -f "$HOME/.cargo/bin/ethrex_l2" ]; then
echo "$HOME/.cargo/bin/ethrex_l2" already found
else
cargo install --force --path cmd/ethrex_l2
fi
ethrex_l2 config create default --default
ethrex_l2 config set default

- id: generate-flamegraph-reth
name: Build and test reth
shell: bash
# --dev.block-time 1000ms set to 1000ms to match ethrex block generation time
run: |
cd ./reth
rm -rf target/profiling/reth
CARGO_PROFILE_RELEASE_DEBUG=true cargo flamegraph -c "record -o perf.data -F997 --call-graph dwarf,16384 -g" \
--bin reth --profile profiling -- node --chain /home/runner/work/ethrex/ethrex/test_data/genesis-load-test.json --dev \
cargo build --bin reth --profile profiling
CARGO_PROFILE_RELEASE_DEBUG=true cargo flamegraph -c "record -o perf.data -F997 --call-graph dwarf,16384 -g" --bin reth --profile profiling -- \
node --chain /home/runner/work/ethrex/ethrex/test_data/genesis-l2-ci.json --dev \
--dev.block-time 1000ms --http.port 1729 --txpool.max-pending-txns 100000000 --txpool.max-new-txns 1000000000 \
--txpool.pending-max-count 100000000 --txpool.pending-max-size 10000000000 --txpool.basefee-max-count 100000000000 \
--txpool.basefee-max-size 1000000000000 --txpool.queued-max-count 1000000000 >/dev/null &
while [ ! -x "./target/profiling/reth" ]; do
echo "Waiting for reth binary to be ready..."
sleep 10
done
sleep 30
echo "Executing load test..."
bash /home/runner/work/ethrex/ethrex/.github/scripts/flamegraph_watcher.sh &&
(cd /home/runner/work/ethrex/ethrex; ./.github/scripts/flamegraph_watcher.sh)
echo "Load test finished"

- name: Generate SVG
Expand Down
8 changes: 4 additions & 4 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -161,16 +161,16 @@ start-node-with-flamegraph: rm-test-db ## 🚀🔥 Starts an ethrex client used
--datadir test_ethrex

load-test: ## 🚧 Runs a load-test. Run make start-node-with-flamegraph and in a new terminal make load-node
cargo run --manifest-path ./cmd/load_test/Cargo.toml -- -k ./test_data/private_keys.txt -t eth-transfers
cargo run --release --manifest-path ./cmd/load_test/Cargo.toml -- -k ./test_data/private_keys.txt -t eth-transfers

load-test-erc20:
cargo run --manifest-path ./cmd/load_test/Cargo.toml -- -k ./test_data/private_keys.txt -t erc20
cargo run --release --manifest-path ./cmd/load_test/Cargo.toml -- -k ./test_data/private_keys.txt -t erc20

load-test-fibonacci:
cargo run --manifest-path ./cmd/load_test/Cargo.toml -- -k ./test_data/private_keys.txt -t fibonacci
cargo run --release --manifest-path ./cmd/load_test/Cargo.toml -- -k ./test_data/private_keys.txt -t fibonacci

load-test-io:
cargo run --manifest-path ./cmd/load_test/Cargo.toml -- -k ./test_data/private_keys.txt -t io-heavy
cargo run --release --manifest-path ./cmd/load_test/Cargo.toml -- -k ./test_data/private_keys.txt -t io-heavy

rm-test-db: ## 🛑 Removes the DB used by the ethrex client used for testing
sudo cargo run --release --bin ethrex -- removedb --force --datadir test_ethrex
Expand Down
3 changes: 2 additions & 1 deletion cmd/load_test/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@ Usage: load_test [OPTIONS] --pkeys <PKEYS>
Options:
-n, --node <NODE> URL of the node being tested. [default: http://localhost:8545]
-k, --pkeys <PKEYS> Path to the file containing private keys.
-t, --test-type <TEST_TYPE> Type of test to run. Can be eth_transfers or erc20. [default: erc20] [possible values: eth-transfers, erc20]
-t, --test-type <TEST_TYPE> Type of test to run. Can be eth_transfers or erc20. [default: erc20] [possible values: eth-transfers, erc20, fibonacci, io-heavy]
-N, --tx-amount <TX_AMOUNT> Number of transactions to send for each account. [default: 1000]
-w, --wait <WAIT> Timeout to wait for all transactions to be included. If 0 is specified, wait indefinitely. [default: 0]
-h, --help Print help
```

Expand Down
76 changes: 76 additions & 0 deletions cmd/load_test/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,15 @@ struct Cli {
help = "Number of transactions to send for each account."
)]
tx_amount: u64,

// Amount of minutes to wait before exiting. If the value is 0, the program will wait indefinitely. If not present, the program will not wait for transactions to be included in blocks.
#[arg(
long,
short = 'w',
default_value_t = 0,
help = "Timeout to wait for all transactions to be included. If 0 is specified, wait indefinitely."
)]
wait: u64,
}

#[derive(ValueEnum, Clone, Debug)] // Derive ValueEnum for TestType
Expand Down Expand Up @@ -264,6 +273,49 @@ async fn load_test(
Ok(())
}

// Waits until the nonce of each account has reached the tx_amount.
async fn wait_until_all_included(
client: EthClient,
wait: Option<Duration>,
accounts: &[Account],
tx_amount: u64,
) -> Result<(), String> {
let start_time = tokio::time::Instant::now();

for (pk, _) in accounts {
let pk = *pk;
let client = client.clone();
let src = address_from_pub_key(pk);
let encoded_src: String = src.encode_hex();
loop {
let elapsed = start_time.elapsed();
let nonce = client.get_nonce(src, BlockByNumber::Latest).await.unwrap();
if nonce >= tx_amount {
println!(
"All transactions sent from {} have been included in blocks. Nonce: {}",
encoded_src, nonce
);
break;
} else {
println!(
"Waiting for transactions to be included from {}. Nonce: {}. Needs: {}. Percentage: {:2}%. Elapsed time: {}s.",
encoded_src, nonce, tx_amount, (nonce as f64 / tx_amount as f64) * 100.0, elapsed.as_secs()
);
}

if let Some(wait) = wait {
if elapsed > wait {
return Err("Timeout reached for transactions to be included".to_string());
}
}

sleep(Duration::from_secs(5)).await;
}
}

Ok(())
}

fn parse_pk_file(path: &Path) -> eyre::Result<Vec<Account>> {
let pkeys_content = fs::read_to_string(path).expect("Unable to read private keys file");
let accounts: Vec<Account> = pkeys_content
Expand Down Expand Up @@ -335,6 +387,12 @@ async fn main() {
}
};

println!(
"Starting load test with {} transactions per account",
cli.tx_amount
);
let time_now = tokio::time::Instant::now();

load_test(
cli.tx_amount,
&accounts,
Expand All @@ -344,4 +402,22 @@ async fn main() {
)
.await
.expect("Failed to load test");

let wait_time = if cli.wait > 0 {
Some(Duration::from_secs(cli.wait * 60))
} else {
None
};

println!("Waiting for all transactions to be included in blocks...");
wait_until_all_included(client, wait_time, &accounts, cli.tx_amount)
.await
.unwrap();

let elapsed_time = time_now.elapsed();

println!(
"Load test finished. Elapsed time: {} seconds",
elapsed_time.as_secs()
);
}