Skip to content

Commit 2a37443

Browse files
committed
Merge branch 'main' into alex/blob-compression
* main: feat: adding spammoor test to benchmark (#3105)
2 parents db89458 + 212ac08 commit 2a37443

File tree

3 files changed

+169
-26
lines changed

3 files changed

+169
-26
lines changed

.github/workflows/benchmark.yml

Lines changed: 93 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,16 @@ permissions: {}
55
push:
66
branches:
77
- main
8+
pull_request:
9+
branches:
10+
- main
811
workflow_dispatch:
912

1013
jobs:
1114
evm-benchmark:
1215
name: EVM Contract Benchmark
1316
runs-on: ubuntu-latest
1417
timeout-minutes: 30
15-
permissions:
16-
contents: write
17-
issues: write
1818
steps:
1919
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
2020
- name: Set up Go
@@ -29,30 +29,112 @@ jobs:
2929
run: |
3030
cd test/e2e && go test -tags evm -bench=. -benchmem -run='^$' \
3131
-timeout=10m --evm-binary=../../build/evm | tee output.txt
32-
- name: Store benchmark result
32+
- name: Run Block Executor benchmarks
33+
run: |
34+
go test -bench=BenchmarkProduceBlock -benchmem -run='^$' \
35+
./block/internal/executing/... > block_executor_output.txt
36+
- name: Upload benchmark results
37+
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
38+
with:
39+
name: evm-benchmark-results
40+
path: |
41+
test/e2e/output.txt
42+
block_executor_output.txt
43+
44+
spamoor-benchmark:
45+
name: Spamoor Trace Benchmark
46+
runs-on: ubuntu-latest
47+
timeout-minutes: 30
48+
steps:
49+
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
50+
- name: Set up Go
51+
uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6.2.0
52+
with:
53+
go-version-file: ./go.mod
54+
- name: Set up Docker Buildx
55+
uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3.10.0
56+
- name: Build binaries
57+
run: make build-evm build-da
58+
- name: Run Spamoor smoke test
59+
run: |
60+
cd test/e2e && BENCH_JSON_OUTPUT=spamoor_bench.json go test -tags evm \
61+
-run='^TestSpamoorSmoke$' -v -timeout=15m --evm-binary=../../build/evm
62+
- name: Upload benchmark results
63+
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
64+
with:
65+
name: spamoor-benchmark-results
66+
path: test/e2e/spamoor_bench.json
67+
68+
# single job to push all results to gh-pages sequentially, avoiding race conditions
69+
publish-benchmarks:
70+
name: Publish Benchmark Results
71+
needs: [evm-benchmark, spamoor-benchmark]
72+
runs-on: ubuntu-latest
73+
permissions:
74+
contents: write
75+
issues: write
76+
pull-requests: write
77+
steps:
78+
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
79+
- name: Download EVM benchmark results
80+
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
81+
with:
82+
name: evm-benchmark-results
83+
- name: Download Spamoor benchmark results
84+
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
85+
with:
86+
name: spamoor-benchmark-results
87+
path: test/e2e/
88+
89+
# only update the benchmark baseline on push/dispatch, not on PRs
90+
- name: Store EVM Contract Roundtrip result
91+
if: always()
3392
uses: benchmark-action/github-action-benchmark@4bdcce38c94cec68da58d012ac24b7b1155efe8b # v1.20.7
3493
with:
3594
name: EVM Contract Roundtrip
3695
tool: 'go'
3796
output-file-path: test/e2e/output.txt
38-
auto-push: true
97+
auto-push: ${{ github.event_name != 'pull_request' }}
98+
save-data-file: ${{ github.event_name != 'pull_request' }}
3999
github-token: ${{ secrets.GITHUB_TOKEN }}
40100
alert-threshold: '150%'
41101
fail-on-alert: true
42102
comment-on-alert: true
43103

44-
- name: Run Block Executor benchmarks
45-
run: |
46-
go test -bench=BenchmarkProduceBlock -benchmem -run='^$' \
47-
./block/internal/executing/... > block_executor_output.txt
48-
- name: Store Block Executor benchmark result
104+
# delete local gh-pages so the next benchmark action step fetches fresh from remote
105+
- name: Reset local gh-pages branch
106+
if: always()
107+
run: git branch -D gh-pages || true
108+
109+
- name: Store Block Executor result
110+
if: always()
49111
uses: benchmark-action/github-action-benchmark@4bdcce38c94cec68da58d012ac24b7b1155efe8b # v1.20.7
50112
with:
51113
name: Block Executor Benchmark
52114
tool: 'go'
53115
output-file-path: block_executor_output.txt
54-
auto-push: true
116+
auto-push: ${{ github.event_name != 'pull_request' }}
117+
save-data-file: ${{ github.event_name != 'pull_request' }}
55118
github-token: ${{ secrets.GITHUB_TOKEN }}
56119
alert-threshold: '150%'
57120
fail-on-alert: true
58121
comment-on-alert: true
122+
123+
# delete local gh-pages so the next benchmark action step fetches fresh from remote
124+
- name: Reset local gh-pages branch
125+
if: always()
126+
run: git branch -D gh-pages || true
127+
128+
- name: Store Spamoor Trace result
129+
if: always()
130+
uses: benchmark-action/github-action-benchmark@4bdcce38c94cec68da58d012ac24b7b1155efe8b # v1.20.7
131+
with:
132+
name: Spamoor Trace Benchmarks
133+
tool: 'customSmallerIsBetter'
134+
output-file-path: test/e2e/spamoor_bench.json
135+
auto-push: ${{ github.event_name != 'pull_request' }}
136+
save-data-file: ${{ github.event_name != 'pull_request' }}
137+
github-token: ${{ secrets.GITHUB_TOKEN }}
138+
alert-threshold: '150%'
139+
fail-on-alert: false
140+
comment-on-alert: true

test/e2e/evm_spamoor_smoke_test.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"context"
77
"fmt"
88
"net/http"
9+
"os"
910
"path/filepath"
1011
"testing"
1112
"time"
@@ -164,6 +165,11 @@ func TestSpamoorSmoke(t *testing.T) {
164165
evRethSpans := extractSpansFromTraces(evRethTraces)
165166
printTraceReport(t, "ev-reth", toTraceSpans(evRethSpans))
166167

168+
// write benchmark JSON for ev-node spans when output path is configured
169+
if outputPath := os.Getenv("BENCH_JSON_OUTPUT"); outputPath != "" {
170+
writeTraceBenchmarkJSON(t, "SpamoorSmoke", toTraceSpans(evNodeSpans), outputPath)
171+
}
172+
167173
// assert expected ev-node span names are present.
168174
// these spans reliably appear during block production with transactions flowing.
169175
expectedSpans := []string{

test/e2e/evm_test_common.go

Lines changed: 70 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ package e2e
1616

1717
import (
1818
"context"
19+
"encoding/json"
1920
"flag"
2021
"fmt"
2122
"math/big"
@@ -855,21 +856,17 @@ type traceSpan interface {
855856
SpanDuration() time.Duration
856857
}
857858

858-
// printTraceReport aggregates spans by operation name and prints a timing breakdown.
859-
func printTraceReport(t testing.TB, label string, spans []traceSpan) {
860-
t.Helper()
861-
if len(spans) == 0 {
862-
t.Logf("WARNING: no spans found for %s", label)
863-
return
864-
}
859+
// spanStats holds aggregated timing statistics for a single span operation.
860+
type spanStats struct {
861+
count int
862+
total time.Duration
863+
min time.Duration
864+
max time.Duration
865+
}
865866

866-
type stats struct {
867-
count int
868-
total time.Duration
869-
min time.Duration
870-
max time.Duration
871-
}
872-
m := make(map[string]*stats)
867+
// aggregateSpanStats groups spans by operation name and computes count, total, min, max.
868+
func aggregateSpanStats(spans []traceSpan) map[string]*spanStats {
869+
m := make(map[string]*spanStats)
873870
for _, span := range spans {
874871
d := span.SpanDuration()
875872
if d <= 0 {
@@ -878,7 +875,7 @@ func printTraceReport(t testing.TB, label string, spans []traceSpan) {
878875
name := span.SpanName()
879876
s, ok := m[name]
880877
if !ok {
881-
s = &stats{min: d, max: d}
878+
s = &spanStats{min: d, max: d}
882879
m[name] = s
883880
}
884881
s.count++
@@ -890,6 +887,18 @@ func printTraceReport(t testing.TB, label string, spans []traceSpan) {
890887
s.max = d
891888
}
892889
}
890+
return m
891+
}
892+
893+
// printTraceReport aggregates spans by operation name and prints a timing breakdown.
894+
func printTraceReport(t testing.TB, label string, spans []traceSpan) {
895+
t.Helper()
896+
if len(spans) == 0 {
897+
t.Logf("WARNING: no spans found for %s", label)
898+
return
899+
}
900+
901+
m := aggregateSpanStats(spans)
893902

894903
names := make([]string, 0, len(m))
895904
for name := range m {
@@ -924,3 +933,49 @@ func printTraceReport(t testing.TB, label string, spans []traceSpan) {
924933
t.Logf("%-40s %5.1f%% %s", name, pct, bar)
925934
}
926935
}
936+
937+
// benchmarkEntry matches the customSmallerIsBetter format for github-action-benchmark.
938+
type benchmarkEntry struct {
939+
Name string `json:"name"`
940+
Unit string `json:"unit"`
941+
Value float64 `json:"value"`
942+
}
943+
944+
// writeTraceBenchmarkJSON aggregates spans and writes a customSmallerIsBetter JSON file.
945+
// If outputPath is empty, the function is a no-op.
946+
func writeTraceBenchmarkJSON(t testing.TB, label string, spans []traceSpan, outputPath string) {
947+
t.Helper()
948+
if outputPath == "" {
949+
return
950+
}
951+
m := aggregateSpanStats(spans)
952+
if len(m) == 0 {
953+
t.Logf("WARNING: no span stats to write for %s", label)
954+
return
955+
}
956+
957+
// sort by name for stable output
958+
names := make([]string, 0, len(m))
959+
for name := range m {
960+
names = append(names, name)
961+
}
962+
sort.Strings(names)
963+
964+
var entries []benchmarkEntry
965+
for _, name := range names {
966+
s := m[name]
967+
avg := float64(s.total.Microseconds()) / float64(s.count)
968+
entries = append(entries,
969+
benchmarkEntry{Name: fmt.Sprintf("%s - %s (avg)", label, name), Unit: "us", Value: avg},
970+
)
971+
}
972+
973+
data, err := json.MarshalIndent(entries, "", " ")
974+
if err != nil {
975+
t.Fatalf("failed to marshal benchmark JSON: %v", err)
976+
}
977+
if err := os.WriteFile(outputPath, data, 0644); err != nil {
978+
t.Fatalf("failed to write benchmark JSON to %s: %v", outputPath, err)
979+
}
980+
t.Logf("wrote %d benchmark entries to %s", len(entries), outputPath)
981+
}

0 commit comments

Comments
 (0)