Skip to content
Closed
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
160 changes: 149 additions & 11 deletions .github/workflows/benchmark.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,22 @@ permissions: {}
push:
branches:
- main
pull_request:
branches:
- main
paths:
- 'test/e2e/benchmark/**'
- 'test/e2e/evm_contract_bench_test.go'
- 'test/e2e/evm_test_common.go'
- 'test/e2e/sut_helper.go'
- '.github/workflows/benchmark.yml'
workflow_dispatch:

jobs:
evm-benchmark:
name: EVM Contract Benchmark
runs-on: ubuntu-latest
timeout-minutes: 30
permissions:
contents: write
issues: write
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Set up Go
Expand All @@ -29,30 +35,162 @@ jobs:
run: |
cd test/e2e && go test -tags evm -bench=. -benchmem -run='^$' \
-timeout=10m --evm-binary=../../build/evm | tee output.txt
- name: Store benchmark result
- name: Run Block Executor benchmarks
run: |
go test -bench=BenchmarkProduceBlock -benchmem -run='^$' \
./block/internal/executing/... > block_executor_output.txt
- name: Upload benchmark results
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: evm-benchmark-results
path: |
test/e2e/output.txt
block_executor_output.txt

spamoor-benchmark:
name: Spamoor Trace Benchmark
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Set up Go
uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6.2.0
with:
go-version-file: ./go.mod
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3.10.0
- name: Build binaries
run: make build-evm build-da
- name: Run Spamoor smoke test
run: |
cd test/e2e && BENCH_JSON_OUTPUT=benchmark/spamoor_bench.json go test -tags evm \
-run='^TestSpamoorSuite$/^TestSpamoorSmoke$' -v -timeout=15m \
--evm-binary=../../build/evm ./benchmark/
- name: Upload benchmark results
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: spamoor-benchmark-results
path: test/e2e/benchmark/spamoor_bench.json

gasburner-benchmark:
name: Gasburner Trace Benchmark
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Set up Go
uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6.2.0
with:
go-version-file: ./go.mod
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3.10.0
- name: Build binaries
run: make build-evm build-da
- name: Run Gasburner benchmark
run: |
cd test/e2e && BENCH_JSON_OUTPUT=benchmark/gasburner_bench.json go test -tags evm \
-run='^TestSpamoorSuite$/^TestGasBurner$' -v -timeout=15m \
--evm-binary=../../build/evm ./benchmark/
- name: Upload benchmark results
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: gasburner-benchmark-results
path: test/e2e/benchmark/gasburner_bench.json

# single job to push all results to gh-pages sequentially, avoiding race conditions
publish-benchmarks:
name: Publish Benchmark Results
needs: [evm-benchmark, spamoor-benchmark, gasburner-benchmark]
runs-on: ubuntu-latest
permissions:
contents: write
issues: write
pull-requests: write
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Download EVM benchmark results
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
with:
name: evm-benchmark-results
- name: Download Spamoor benchmark results
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
with:
name: spamoor-benchmark-results
path: test/e2e/benchmark/
- name: Download Gasburner benchmark results
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
with:
name: gasburner-benchmark-results
path: test/e2e/benchmark/

# only update the benchmark baseline on push/dispatch, not on PRs
- name: Store EVM Contract Roundtrip result
if: always()
uses: benchmark-action/github-action-benchmark@4bdcce38c94cec68da58d012ac24b7b1155efe8b # v1.20.7
with:
name: EVM Contract Roundtrip
tool: 'go'
output-file-path: test/e2e/output.txt
auto-push: true
auto-push: ${{ github.event_name != 'pull_request' }}
save-data-file: ${{ github.event_name != 'pull_request' }}
github-token: ${{ secrets.GITHUB_TOKEN }}
alert-threshold: '150%'
fail-on-alert: true
comment-on-alert: true

- name: Run Block Executor benchmarks
run: |
go test -bench=BenchmarkProduceBlock -benchmem -run='^$' \
./block/internal/executing/... > block_executor_output.txt
- name: Store Block Executor benchmark result
# delete local gh-pages so the next benchmark action step fetches fresh from remote
- name: Reset local gh-pages branch
if: always()
run: git branch -D gh-pages || true

- name: Store Block Executor result
if: always()
uses: benchmark-action/github-action-benchmark@4bdcce38c94cec68da58d012ac24b7b1155efe8b # v1.20.7
with:
name: Block Executor Benchmark
tool: 'go'
output-file-path: block_executor_output.txt
auto-push: true
auto-push: ${{ github.event_name != 'pull_request' }}
save-data-file: ${{ github.event_name != 'pull_request' }}
github-token: ${{ secrets.GITHUB_TOKEN }}
alert-threshold: '150%'
fail-on-alert: true
comment-on-alert: true

# delete local gh-pages so the next benchmark action step fetches fresh from remote
- name: Reset local gh-pages branch
if: always()
run: git branch -D gh-pages || true

- name: Store Spamoor Trace result
if: always()
uses: benchmark-action/github-action-benchmark@4bdcce38c94cec68da58d012ac24b7b1155efe8b # v1.20.7
with:
name: Spamoor Trace Benchmarks
tool: 'customSmallerIsBetter'
output-file-path: test/e2e/benchmark/spamoor_bench.json
auto-push: ${{ github.event_name != 'pull_request' }}
save-data-file: ${{ github.event_name != 'pull_request' }}
github-token: ${{ secrets.GITHUB_TOKEN }}
alert-threshold: '150%'
fail-on-alert: false
comment-on-alert: true

# delete local gh-pages so the next benchmark action step fetches fresh from remote
- name: Reset local gh-pages branch
if: always()
run: git branch -D gh-pages || true

- name: Store Gasburner Trace result
if: always()
uses: benchmark-action/github-action-benchmark@4bdcce38c94cec68da58d012ac24b7b1155efe8b # v1.20.7
with:
name: Gasburner Trace Benchmarks
tool: 'customSmallerIsBetter'
output-file-path: test/e2e/benchmark/gasburner_bench.json
auto-push: ${{ github.event_name != 'pull_request' }}
save-data-file: ${{ github.event_name != 'pull_request' }}
github-token: ${{ secrets.GITHUB_TOKEN }}
alert-threshold: '150%'
fail-on-alert: false
comment-on-alert: true
94 changes: 94 additions & 0 deletions test/e2e/benchmark/gasburner_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
//go:build evm

package benchmark

import (
"fmt"
"time"

"github.com/celestiaorg/tastora/framework/docker/evstack/spamoor"
e2e "github.com/evstack/ev-node/test/e2e"
)

// TestGasBurner measures gas throughput using a deterministic gasburner
// workload. The result is tracked via BENCH_JSON_OUTPUT as seconds_per_gigagas
// (lower is better) on the benchmark dashboard.
func (s *SpamoorSuite) TestGasBurner() {
t := s.T()
w := newResultWriter(t, "GasBurner")
defer w.flush()

e := s.setupEnv(config{
rethTag: "pr-142",
serviceName: "ev-node-gasburner",
})
api := e.spamoorAPI

const totalCount = 10000
gasburnerCfg := map[string]any{
"gas_units_to_burn": 3_000_000,
"total_count": totalCount,
"throughput": 1000,
"max_pending": 5000,
"max_wallets": 500,
"rebroadcast": 0,
"base_fee": 20,
"tip_fee": 5,
"refill_amount": "5000000000000000000",
"refill_balance": "2000000000000000000",
"refill_interval": 300,
}

id, err := api.CreateSpammer("bench-gasburner", spamoor.ScenarioGasBurnerTX, gasburnerCfg, true)
s.Require().NoError(err, "failed to create gasburner spammer")
t.Cleanup(func() { _ = api.DeleteSpammer(id) })

// wait for wallet prep and contract deployment to finish before
// recording start block so warmup is excluded from the measurement.
const warmupTxs = 50
pollSentTotal := func() (float64, error) {
metrics, err := api.GetMetrics()
if err != nil {
return 0, err
}
return sumCounter(metrics["spamoor_transactions_sent_total"]), nil
}
waitForMetricTarget(t, "spamoor_transactions_sent_total (warmup)", pollSentTotal, warmupTxs, 5*time.Minute)

startHeader, err := e.ethClient.HeaderByNumber(t.Context(), nil)
s.Require().NoError(err, "failed to get start block header")
startBlock := startHeader.Number.Uint64()
t.Logf("start block: %d (after wallet prep)", startBlock)

waitForMetricTarget(t, "spamoor_transactions_sent_total", pollSentTotal, float64(totalCount), 5*time.Minute)

endHeader, err := e.ethClient.HeaderByNumber(t.Context(), nil)
s.Require().NoError(err, "failed to get end block header")
endBlock := endHeader.Number.Uint64()
t.Logf("end block: %d (range %d blocks)", endBlock, endBlock-startBlock)

gas := measureGasThroughput(t, t.Context(), e.ethClient, startBlock, endBlock)

// collect traces
evNodeSpans := s.collectServiceTraces(e, "ev-node-gasburner")
evRethSpans := s.collectServiceTraces(e, "ev-reth")
e2e.PrintTraceReport(t, "ev-node-gasburner", evNodeSpans)
e2e.PrintTraceReport(t, "ev-reth", evRethSpans)

// assert expected ev-reth spans
assertSpanNames(t, evRethSpans, []string{
"build_payload",
"try_build",
"validate_transaction",
"validate_evnode",
"try_new",
"execute_tx",
}, "ev-reth")

w.addSpans(append(evNodeSpans, evRethSpans...))
w.addEntry(entry{
Name: fmt.Sprintf("%s - seconds_per_gigagas", w.label),
Unit: "s/Ggas",
Value: 1.0 / gas.gigagasPerSec,
})
}
Loading
Loading