From e096dc14192ae4f430cdb0887ca53fc653853571 Mon Sep 17 00:00:00 2001 From: Rodrigo Villar Date: Tue, 2 Dec 2025 10:22:34 -0500 Subject: [PATCH 01/24] refactor: block export --- Taskfile.yml | 2 +- tests/reexecute/blockexport/main.go | 62 ++++++++++++++++++++++++++ tests/reexecute/c/vm_reexecute_test.go | 37 +-------------- tests/reexecute/db.go | 9 ++-- 4 files changed, 67 insertions(+), 43 deletions(-) create mode 100644 tests/reexecute/blockexport/main.go diff --git a/Taskfile.yml b/Taskfile.yml index 7f127831eb1e..230daca54dd0 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -107,7 +107,7 @@ tasks: START_BLOCK: '{{.START_BLOCK}}' END_BLOCK: '{{.END_BLOCK}}' cmds: - - cmd: go test -timeout=0 -run=TestExportBlockRange github.com/ava-labs/avalanchego/tests/reexecute/c --block-dir-src={{.BLOCK_DIR_SRC}} --block-dir-dst={{.BLOCK_DIR_DST}} --start-block={{.START_BLOCK}} --end-block={{.END_BLOCK}} + - cmd: go run github.com/ava-labs/avalanchego/tests/reexecute/blockexport --block-dir-src={{.BLOCK_DIR_SRC}} --block-dir-dst={{.BLOCK_DIR_DST}} --start-block={{.START_BLOCK}} --end-block={{.END_BLOCK}} export-dir-to-s3: desc: Copies a directory to s3 diff --git a/tests/reexecute/blockexport/main.go b/tests/reexecute/blockexport/main.go new file mode 100644 index 000000000000..201f22713c58 --- /dev/null +++ b/tests/reexecute/blockexport/main.go @@ -0,0 +1,62 @@ +// Copyright (C) 2019-2025, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package main + +import ( + "flag" + + "github.com/prometheus/client_golang/prometheus" + "github.com/stretchr/testify/require" + + "github.com/ava-labs/avalanchego/database/leveldb" + "github.com/ava-labs/avalanchego/tests" + "github.com/ava-labs/avalanchego/tests/reexecute" + "github.com/ava-labs/avalanchego/utils/logging" + "github.com/ava-labs/avalanchego/utils/units" +) + +var ( + blockDirSrcArg string + blockDirDstArg string + startBlockArg uint64 + endBlockArg uint64 + chanSizeArg int +) + +func init() { + flag.StringVar(&blockDirSrcArg, "block-dir-src", blockDirSrcArg, "Source block directory to copy from when running TestExportBlockRange.") + flag.StringVar(&blockDirDstArg, "block-dir-dst", blockDirDstArg, "Destination block directory to write blocks into when executing TestExportBlockRange.") + flag.Uint64Var(&startBlockArg, "start-block", 101, "Start block to begin execution (exclusive).") + flag.Uint64Var(&endBlockArg, "end-block", 200, "End block to end execution (inclusive).") + flag.IntVar(&chanSizeArg, "chan-size", 100, "Size of the channel to use for block processing.") + + flag.Parse() +} + +func main() { + tc := tests.NewTestContext(tests.NewDefaultLogger("")) + defer tc.RecoverAndExit() + + r := require.New(tc) + blockChan, err := reexecute.CreateBlockChanFromLevelDB(blockDirSrcArg, startBlockArg, endBlockArg, chanSizeArg, tc.DeferCleanup) + r.NoError(err) + + db, err := leveldb.New(blockDirDstArg, nil, logging.NoLog{}, prometheus.NewRegistry()) + r.NoError(err) + tc.DeferCleanup(func() { + r.NoError(db.Close()) + }) + + batch := db.NewBatch() + for blkResult := range blockChan { + r.NoError(batch.Put(reexecute.BlockKey(blkResult.Height), blkResult.BlockBytes)) + + if batch.Size() > 10*units.MiB { + r.NoError(batch.Write()) + batch = db.NewBatch() + } + } + + r.NoError(batch.Write()) +} diff --git a/tests/reexecute/c/vm_reexecute_test.go b/tests/reexecute/c/vm_reexecute_test.go index 5efe7a8446fa..b17b62771c32 100644 --- a/tests/reexecute/c/vm_reexecute_test.go +++ b/tests/reexecute/c/vm_reexecute_test.go @@ -42,7 +42,6 @@ import ( "github.com/ava-labs/avalanchego/utils/crypto/bls/signer/localsigner" "github.com/ava-labs/avalanchego/utils/logging" "github.com/ava-labs/avalanchego/utils/timer" - "github.com/ava-labs/avalanchego/utils/units" "github.com/ava-labs/avalanchego/vms/metervm" "github.com/ava-labs/avalanchego/vms/platformvm/warp" ) @@ -55,8 +54,6 @@ var ( var ( blockDirArg string - blockDirSrcArg string - blockDirDstArg string currentStateDirArg string startBlockArg uint64 endBlockArg uint64 @@ -122,10 +119,6 @@ func TestMain(m *testing.M) { flag.StringVar(&configNameArg, configKey, defaultConfigKey, fmt.Sprintf("Specifies the predefined config to use for the VM. Options include %s.", predefinedConfigOptionsStr)) flag.StringVar(&runnerNameArg, "runner", "dev", "Name of the runner executing this test. Added as a metric label and to the sub-benchmark's name to differentiate results on the runner key.") - // Flags specific to TestExportBlockRange. - flag.StringVar(&blockDirSrcArg, "block-dir-src", blockDirSrcArg, "Source block directory to copy from when running TestExportBlockRange.") - flag.StringVar(&blockDirDstArg, "block-dir-dst", blockDirDstArg, "Destination block directory to write blocks into when executing TestExportBlockRange.") - flag.Parse() if metricsCollectorEnabledArg { @@ -232,7 +225,7 @@ func benchmarkReexecuteRange( zap.Int("chan-size", chanSize), ) - blockChan, err := reexecute.CreateBlockChanFromLevelDB(b, blockDir, startBlock, endBlock, chanSize) + blockChan, err := reexecute.CreateBlockChanFromLevelDB(blockDir, startBlock, endBlock, chanSize, b.Cleanup) r.NoError(err) dbLogger := tests.NewDefaultLogger("db") @@ -477,34 +470,6 @@ func (e *vmExecutor) executeSequence(ctx context.Context, blkChan <-chan reexecu return nil } -func TestExportBlockRange(t *testing.T) { - exportBlockRange(t, blockDirSrcArg, blockDirDstArg, startBlockArg, endBlockArg, chanSizeArg) -} - -func exportBlockRange(tb testing.TB, blockDirSrc string, blockDirDst string, startBlock, endBlock uint64, chanSize int) { - r := require.New(tb) - blockChan, err := reexecute.CreateBlockChanFromLevelDB(tb, blockDirSrc, startBlock, endBlock, chanSize) - r.NoError(err) - - db, err := leveldb.New(blockDirDst, nil, logging.NoLog{}, prometheus.NewRegistry()) - r.NoError(err) - tb.Cleanup(func() { - r.NoError(db.Close()) - }) - - batch := db.NewBatch() - for blkResult := range blockChan { - r.NoError(batch.Put(reexecute.BlockKey(blkResult.Height), blkResult.BlockBytes)) - - if batch.Size() > 10*units.MiB { - r.NoError(batch.Write()) - batch = db.NewBatch() - } - } - - r.NoError(batch.Write()) -} - type consensusMetrics struct { lastAcceptedHeight prometheus.Gauge } diff --git a/tests/reexecute/db.go b/tests/reexecute/db.go index 04353db62d82..8432a65b5707 100644 --- a/tests/reexecute/db.go +++ b/tests/reexecute/db.go @@ -6,10 +6,8 @@ package reexecute import ( "encoding/binary" "fmt" - "testing" "github.com/prometheus/client_golang/prometheus" - "github.com/stretchr/testify/require" "github.com/ava-labs/avalanchego/database" "github.com/ava-labs/avalanchego/database/leveldb" @@ -29,16 +27,15 @@ type BlockResult struct { // Blocks are read sequentially and sent to the returned channel as BlockResult values. // // Any validation errors or iteration errors are sent as BlockResult with Err set, then the channel is closed. -func CreateBlockChanFromLevelDB(tb testing.TB, sourceDir string, startBlock, endBlock uint64, chanSize int) (<-chan BlockResult, error) { - r := require.New(tb) +func CreateBlockChanFromLevelDB(sourceDir string, startBlock, endBlock uint64, chanSize int, cleanup func(func())) (<-chan BlockResult, error) { ch := make(chan BlockResult, chanSize) db, err := leveldb.New(sourceDir, nil, logging.NoLog{}, prometheus.NewRegistry()) if err != nil { return nil, fmt.Errorf("failed to create leveldb database from %q: %w", sourceDir, err) } - tb.Cleanup(func() { - r.NoError(db.Close()) + cleanup(func() { + db.Close() }) go func() { From fa99ce5c1d7f29f3094fbf8bd0cfd2aadd1f5232 Mon Sep 17 00:00:00 2001 From: Rodrigo Villar Date: Wed, 3 Dec 2025 17:55:05 -0500 Subject: [PATCH 02/24] chore: nits --- tests/reexecute/blockexport/main.go | 9 ++++++++- tests/reexecute/c/vm_reexecute_test.go | 2 +- tests/reexecute/db.go | 6 ++++-- 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/tests/reexecute/blockexport/main.go b/tests/reexecute/blockexport/main.go index 201f22713c58..d00385cd9ac0 100644 --- a/tests/reexecute/blockexport/main.go +++ b/tests/reexecute/blockexport/main.go @@ -39,7 +39,14 @@ func main() { defer tc.RecoverAndExit() r := require.New(tc) - blockChan, err := reexecute.CreateBlockChanFromLevelDB(blockDirSrcArg, startBlockArg, endBlockArg, chanSizeArg, tc.DeferCleanup) + blockChan, err := reexecute.CreateBlockChanFromLevelDB( + tc, + blockDirSrcArg, + startBlockArg, + endBlockArg, + chanSizeArg, + tc.DeferCleanup, + ) r.NoError(err) db, err := leveldb.New(blockDirDstArg, nil, logging.NoLog{}, prometheus.NewRegistry()) diff --git a/tests/reexecute/c/vm_reexecute_test.go b/tests/reexecute/c/vm_reexecute_test.go index b17b62771c32..b5247d3239d7 100644 --- a/tests/reexecute/c/vm_reexecute_test.go +++ b/tests/reexecute/c/vm_reexecute_test.go @@ -225,7 +225,7 @@ func benchmarkReexecuteRange( zap.Int("chan-size", chanSize), ) - blockChan, err := reexecute.CreateBlockChanFromLevelDB(blockDir, startBlock, endBlock, chanSize, b.Cleanup) + blockChan, err := reexecute.CreateBlockChanFromLevelDB(b, blockDir, startBlock, endBlock, chanSize, b.Cleanup) r.NoError(err) dbLogger := tests.NewDefaultLogger("db") diff --git a/tests/reexecute/db.go b/tests/reexecute/db.go index 8432a65b5707..348ddfd35f51 100644 --- a/tests/reexecute/db.go +++ b/tests/reexecute/db.go @@ -8,6 +8,7 @@ import ( "fmt" "github.com/prometheus/client_golang/prometheus" + "github.com/stretchr/testify/require" "github.com/ava-labs/avalanchego/database" "github.com/ava-labs/avalanchego/database/leveldb" @@ -27,7 +28,8 @@ type BlockResult struct { // Blocks are read sequentially and sent to the returned channel as BlockResult values. // // Any validation errors or iteration errors are sent as BlockResult with Err set, then the channel is closed. -func CreateBlockChanFromLevelDB(sourceDir string, startBlock, endBlock uint64, chanSize int, cleanup func(func())) (<-chan BlockResult, error) { +func CreateBlockChanFromLevelDB(t require.TestingT, sourceDir string, startBlock, endBlock uint64, chanSize int, cleanup func(func())) (<-chan BlockResult, error) { + r := require.New(t) ch := make(chan BlockResult, chanSize) db, err := leveldb.New(sourceDir, nil, logging.NoLog{}, prometheus.NewRegistry()) @@ -35,7 +37,7 @@ func CreateBlockChanFromLevelDB(sourceDir string, startBlock, endBlock uint64, c return nil, fmt.Errorf("failed to create leveldb database from %q: %w", sourceDir, err) } cleanup(func() { - db.Close() + r.NoError(db.Close()) }) go func() { From a433a8154217797aed06d6774061e101c78bc9c7 Mon Sep 17 00:00:00 2001 From: Rodrigo Villar Date: Tue, 2 Dec 2025 11:50:25 -0500 Subject: [PATCH 03/24] chore(reexecute/c): remove go bench from benchmark --- Taskfile.yml | 2 +- scripts/benchmark_cchain_range.sh | 9 +- tests/reexecute/blockexport/main.go | 1 - .../{vm_reexecute_test.go => vm_reexecute.go} | 140 ++++++++++++------ tests/reexecute/db.go | 9 +- 5 files changed, 105 insertions(+), 56 deletions(-) rename tests/reexecute/c/{vm_reexecute_test.go => vm_reexecute.go} (85%) diff --git a/Taskfile.yml b/Taskfile.yml index 230daca54dd0..a703db7e66cd 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -212,7 +212,7 @@ tasks: LABELS: '{{.LABELS | default ""}}' BENCHMARK_OUTPUT_FILE: '{{.BENCHMARK_OUTPUT_FILE | default ""}}' TIMESTAMP: '{{.TIMESTAMP | default (now | date "20060102-150405")}}' - EXECUTION_DATA_DIR: '{{.EXECUTION_DATA_DIR | default (printf "/tmp/%s-%s" .TASK_NAME .TIMESTAMP)}}' + EXECUTION_DATA_DIR: '{{.EXECUTION_DATA_DIR | default (printf "/tmp/%s-%s" .TASK .TIMESTAMP)}}' cmd: | CURRENT_STATE_DIR={{.CURRENT_STATE_DIR}} \ BLOCK_DIR={{.BLOCK_DIR}} \ diff --git a/scripts/benchmark_cchain_range.sh b/scripts/benchmark_cchain_range.sh index 896e68f8a559..466b5db9bd4d 100755 --- a/scripts/benchmark_cchain_range.sh +++ b/scripts/benchmark_cchain_range.sh @@ -19,7 +19,8 @@ set -euo pipefail : "${START_BLOCK:?START_BLOCK must be set}" : "${END_BLOCK:?END_BLOCK must be set}" -cmd="go test -timeout=0 -v -benchtime=1x -bench=BenchmarkReexecuteRange -run=^$ github.com/ava-labs/avalanchego/tests/reexecute/c \ +cmd="go run github.com/ava-labs/avalanchego/tests/reexecute/c \ + --benchmark-output-file=\"${BENCHMARK_OUTPUT_FILE}\" \ --block-dir=\"${BLOCK_DIR}\" \ --current-state-dir=\"${CURRENT_STATE_DIR}\" \ ${RUNNER_NAME:+--runner=\"${RUNNER_NAME}\"} \ @@ -31,8 +32,4 @@ cmd="go test -timeout=0 -v -benchtime=1x -bench=BenchmarkReexecuteRange -run=^$ ${METRICS_SERVER_PORT:+--metrics-server-port=\"${METRICS_SERVER_PORT}\"} \ ${METRICS_COLLECTOR_ENABLED:+--metrics-collector-enabled=\"${METRICS_COLLECTOR_ENABLED}\"}" -if [ -n "${BENCHMARK_OUTPUT_FILE:-}" ]; then - eval "$cmd" | tee "${BENCHMARK_OUTPUT_FILE}" -else - eval "$cmd" -fi +eval "$cmd" diff --git a/tests/reexecute/blockexport/main.go b/tests/reexecute/blockexport/main.go index d00385cd9ac0..76c1c1b5ee0f 100644 --- a/tests/reexecute/blockexport/main.go +++ b/tests/reexecute/blockexport/main.go @@ -45,7 +45,6 @@ func main() { startBlockArg, endBlockArg, chanSizeArg, - tc.DeferCleanup, ) r.NoError(err) diff --git a/tests/reexecute/c/vm_reexecute_test.go b/tests/reexecute/c/vm_reexecute.go similarity index 85% rename from tests/reexecute/c/vm_reexecute_test.go rename to tests/reexecute/c/vm_reexecute.go index b5247d3239d7..20048fe7b97f 100644 --- a/tests/reexecute/c/vm_reexecute_test.go +++ b/tests/reexecute/c/vm_reexecute.go @@ -1,10 +1,11 @@ // Copyright (C) 2019-2025, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. -package vm +package main import ( "context" + "encoding/json" "flag" "fmt" "maps" @@ -13,7 +14,6 @@ import ( "slices" "strconv" "strings" - "testing" "time" "github.com/google/uuid" @@ -41,6 +41,7 @@ import ( "github.com/ava-labs/avalanchego/utils/constants" "github.com/ava-labs/avalanchego/utils/crypto/bls/signer/localsigner" "github.com/ava-labs/avalanchego/utils/logging" + "github.com/ava-labs/avalanchego/utils/perms" "github.com/ava-labs/avalanchego/utils/timer" "github.com/ava-labs/avalanchego/vms/metervm" "github.com/ava-labs/avalanchego/vms/platformvm/warp" @@ -97,9 +98,11 @@ var ( configNameArg string runnerNameArg string configBytesArg []byte + + benchmarkOutputFileArg string ) -func TestMain(m *testing.M) { +func init() { evm.RegisterAllLibEVMExtras() flag.StringVar(&blockDirArg, "block-dir", blockDirArg, "Block DB directory to read from during re-execution.") @@ -119,6 +122,8 @@ func TestMain(m *testing.M) { flag.StringVar(&configNameArg, configKey, defaultConfigKey, fmt.Sprintf("Specifies the predefined config to use for the VM. Options include %s.", predefinedConfigOptionsStr)) flag.StringVar(&runnerNameArg, "runner", "dev", "Name of the runner executing this test. Added as a metric label and to the sub-benchmark's name to differentiate results on the runner key.") + flag.StringVar(&benchmarkOutputFileArg, "benchmark-output-file", benchmarkOutputFileArg, "The filepath where benchmark results should be written to.") + flag.Parse() if metricsCollectorEnabledArg { @@ -143,30 +148,40 @@ func TestMain(m *testing.M) { // Set the runner name label on the metrics. labels["runner"] = runnerNameArg - - m.Run() } -func BenchmarkReexecuteRange(b *testing.B) { - require.Equalf(b, 1, b.N, "BenchmarkReexecuteRange expects to run a single iteration because it overwrites the input current-state, but found (b.N=%d)", b.N) - b.Run(fmt.Sprintf("[%d,%d]-Config-%s-Runner-%s", startBlockArg, endBlockArg, configNameArg, runnerNameArg), func(b *testing.B) { - benchmarkReexecuteRange( - b, - blockDirArg, - currentStateDirArg, - configBytesArg, - startBlockArg, - endBlockArg, - chanSizeArg, - metricsServerEnabledArg, - metricsServerPortArg, - metricsCollectorEnabledArg, - ) - }) +func main() { + tc := tests.NewTestContext(tests.NewDefaultLogger("c-chain-reexecution")) + tc.SetDefaultContextParent(context.Background()) + defer tc.RecoverAndExit() + + benchmarkName := fmt.Sprintf( + "BenchmarkReexecuteRange/[%d,%d]-Config-%s-Runner-%s", + startBlockArg, + endBlockArg, + configNameArg, + runnerNameArg, + ) + + benchmarkReexecuteRange( + tc, + benchmarkName, + blockDirArg, + currentStateDirArg, + configBytesArg, + startBlockArg, + endBlockArg, + chanSizeArg, + metricsServerEnabledArg, + metricsServerPortArg, + metricsCollectorEnabledArg, + benchmarkOutputFileArg, + ) } func benchmarkReexecuteRange( - b *testing.B, + tc tests.TestContext, + benchmarkName string, blockDir string, currentStateDir string, configBytes []byte, @@ -176,9 +191,10 @@ func benchmarkReexecuteRange( metricsServerEnabled bool, metricsPort uint64, metricsCollectorEnabled bool, + benchmarkOutputFile string, ) { - r := require.New(b) - ctx := b.Context() + r := require.New(tc) + ctx := tc.GetDefaultContextParent() // Create the prefix gatherer passed to the VM and register it with the top-level, // labeled gatherer. @@ -198,10 +214,10 @@ func benchmarkReexecuteRange( log := tests.NewDefaultLogger("c-chain-reexecution") if metricsServerEnabled { - serverAddr := startServer(b, log, prefixGatherer, metricsPort) + serverAddr := startServer(tc, log, prefixGatherer, metricsPort) if metricsCollectorEnabled { - startCollector(b, log, "c-chain-reexecution", labels, serverAddr) + startCollector(tc, log, "c-chain-reexecution", labels, serverAddr) } } @@ -225,7 +241,7 @@ func benchmarkReexecuteRange( zap.Int("chan-size", chanSize), ) - blockChan, err := reexecute.CreateBlockChanFromLevelDB(b, blockDir, startBlock, endBlock, chanSize, b.Cleanup) + blockChan, err := reexecute.CreateBlockChanFromLevelDB(tc, blockDir, startBlock, endBlock, chanSize) r.NoError(err) dbLogger := tests.NewDefaultLogger("db") @@ -265,8 +281,11 @@ func benchmarkReexecuteRange( r.NoError(executor.executeSequence(ctx, blockChan)) elapsed := time.Since(start) - b.ReportMetric(0, "ns/op") // Set default ns/op to 0 to hide from the output - getTopLevelMetrics(b, prefixGatherer, elapsed) // Report the desired top-level metrics + benchmarkTool := newBenchmarkTool(benchmarkName) + getTopLevelMetrics(tc, benchmarkTool, prefixGatherer, elapsed) // Report the desired top-level metrics + if len(benchmarkOutputFile) != 0 { + r.NoError(benchmarkTool.saveToFile(benchmarkOutputFile)) + } } func newMainnetCChainVM( @@ -496,12 +515,12 @@ func newConsensusMetrics(registry prometheus.Registerer) (*consensusMetrics, err // startServer starts a Prometheus server for the provided gatherer and returns // the server address. func startServer( - tb testing.TB, + tc tests.TestContext, log logging.Logger, gatherer prometheus.Gatherer, port uint64, ) string { - r := require.New(tb) + r := require.New(tc) server, err := tests.NewPrometheusServerWithPort(gatherer, port) r.NoError(err) @@ -510,7 +529,7 @@ func startServer( zap.String("url", fmt.Sprintf("http://%s/ext/metrics", server.Address())), ) - tb.Cleanup(func() { + tc.DeferCleanup(func() { r.NoError(server.Stop()) }) @@ -520,17 +539,17 @@ func startServer( // startCollector starts a Prometheus collector configured to scrape the server // listening on serverAddr. startCollector also attaches the provided labels + // Github labels if available to the collected metrics. -func startCollector(tb testing.TB, log logging.Logger, name string, labels map[string]string, serverAddr string) { - r := require.New(tb) +func startCollector(tc tests.TestContext, log logging.Logger, name string, labels map[string]string, serverAddr string) { + r := require.New(tc) - startPromCtx, cancel := context.WithTimeout(tb.Context(), tests.DefaultTimeout) + startPromCtx, cancel := context.WithTimeout(tc.GetDefaultContextParent(), tests.DefaultTimeout) defer cancel() logger := tests.NewDefaultLogger("prometheus") r.NoError(tmpnet.StartPrometheus(startPromCtx, logger)) var sdConfigFilePath string - tb.Cleanup(func() { + tc.DeferCleanup(func() { // Ensure a final metrics scrape. // This default delay is set above the default scrape interval used by StartPrometheus. time.Sleep(tmpnet.NetworkShutdownDelay) @@ -543,10 +562,7 @@ func startCollector(tb testing.TB, log logging.Logger, name string, labels map[s }(), ) - //nolint:usetesting // t.Context() is already canceled inside the cleanup function - checkMetricsCtx, cancel := context.WithTimeout(context.Background(), tests.DefaultTimeout) - defer cancel() - r.NoError(tmpnet.CheckMetricsExist(checkMetricsCtx, logger, networkUUID)) + r.NoError(tmpnet.CheckMetricsExist(tc.DefaultContext(), logger, networkUUID)) }) sdConfigFilePath, err := tmpnet.WritePrometheusSDConfig(name, tmpnet.SDConfig{ @@ -569,6 +585,42 @@ func startCollector(tb testing.TB, log logging.Logger, name string, labels map[s ) } +type benchmarkResult struct { + Name string `json:"name"` + Value string `json:"value"` + Unit string `json:"unit"` +} + +type benchmarkTool struct { + name string + benchmarkResults []benchmarkResult +} + +func newBenchmarkTool(name string) *benchmarkTool { + return &benchmarkTool{ + name: name, + benchmarkResults: make([]benchmarkResult, 0), + } +} + +func (b *benchmarkTool) addResult(value float64, unit string) { + result := benchmarkResult{ + Name: b.name, + Value: strconv.FormatFloat(value, 'f', -1, 64), + Unit: unit, + } + b.benchmarkResults = append(b.benchmarkResults, result) +} + +func (b *benchmarkTool) saveToFile(filePath string) error { + output, err := json.MarshalIndent(b.benchmarkResults, "", " ") + if err != nil { + return err + } + + return os.WriteFile(filePath, output, perms.ReadWrite) +} + // parseCustomLabels parses a comma-separated list of key-value pairs into a map // of custom labels. func parseCustomLabels(labelsStr string) (map[string]string, error) { @@ -586,13 +638,15 @@ func parseCustomLabels(labelsStr string) (map[string]string, error) { return labels, nil } -func getTopLevelMetrics(b *testing.B, registry prometheus.Gatherer, elapsed time.Duration) { - r := require.New(b) +func getTopLevelMetrics(tc tests.TestContext, tool *benchmarkTool, registry prometheus.Gatherer, elapsed time.Duration) { + r := require.New(tc) gasUsed, err := getCounterMetricValue(registry, "avalanche_evm_eth_chain_block_gas_used_processed") r.NoError(err) mgasPerSecond := gasUsed / 1_000_000 / elapsed.Seconds() - b.ReportMetric(mgasPerSecond, "mgas/s") + + tool.addResult(mgasPerSecond, "mgas/s") + tc.Log().Info("block processing", zap.Float64("mgas/s", mgasPerSecond)) } func getCounterMetricValue(registry prometheus.Gatherer, query string) (float64, error) { diff --git a/tests/reexecute/db.go b/tests/reexecute/db.go index 348ddfd35f51..fd248ed49a0c 100644 --- a/tests/reexecute/db.go +++ b/tests/reexecute/db.go @@ -8,10 +8,10 @@ import ( "fmt" "github.com/prometheus/client_golang/prometheus" - "github.com/stretchr/testify/require" "github.com/ava-labs/avalanchego/database" "github.com/ava-labs/avalanchego/database/leveldb" + "github.com/ava-labs/avalanchego/tests" "github.com/ava-labs/avalanchego/utils/logging" ) @@ -28,16 +28,15 @@ type BlockResult struct { // Blocks are read sequentially and sent to the returned channel as BlockResult values. // // Any validation errors or iteration errors are sent as BlockResult with Err set, then the channel is closed. -func CreateBlockChanFromLevelDB(t require.TestingT, sourceDir string, startBlock, endBlock uint64, chanSize int, cleanup func(func())) (<-chan BlockResult, error) { - r := require.New(t) +func CreateBlockChanFromLevelDB(tc tests.TestContext, sourceDir string, startBlock, endBlock uint64, chanSize int) (<-chan BlockResult, error) { ch := make(chan BlockResult, chanSize) db, err := leveldb.New(sourceDir, nil, logging.NoLog{}, prometheus.NewRegistry()) if err != nil { return nil, fmt.Errorf("failed to create leveldb database from %q: %w", sourceDir, err) } - cleanup(func() { - r.NoError(db.Close()) + tc.DeferCleanup(func() { + db.Close() }) go func() { From c1203b1267670e7f520f6c5768c9a307dbb896b9 Mon Sep 17 00:00:00 2001 From: Rodrigo Villar Date: Tue, 2 Dec 2025 14:05:16 -0500 Subject: [PATCH 04/24] chore: remove go bench in workflow --- .github/actions/c-chain-reexecution-benchmark/action.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/actions/c-chain-reexecution-benchmark/action.yml b/.github/actions/c-chain-reexecution-benchmark/action.yml index 6217ba3824d3..5fa7cdd8df5b 100644 --- a/.github/actions/c-chain-reexecution-benchmark/action.yml +++ b/.github/actions/c-chain-reexecution-benchmark/action.yml @@ -183,7 +183,7 @@ runs: - name: Compare Benchmark Results uses: benchmark-action/github-action-benchmark@v1 with: - tool: 'go' + tool: 'customBiggerIsBetter' output-file-path: ${{ env.BENCHMARK_OUTPUT_FILE }} summary-always: true github-token: ${{ inputs.github-token }} From 1935adf345ccaa9b9dcb7a0c1428f12f4cecb741 Mon Sep 17 00:00:00 2001 From: Rodrigo Villar Date: Tue, 2 Dec 2025 14:16:32 -0500 Subject: [PATCH 05/24] chore: reduce diff --- Taskfile.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Taskfile.yml b/Taskfile.yml index a703db7e66cd..230daca54dd0 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -212,7 +212,7 @@ tasks: LABELS: '{{.LABELS | default ""}}' BENCHMARK_OUTPUT_FILE: '{{.BENCHMARK_OUTPUT_FILE | default ""}}' TIMESTAMP: '{{.TIMESTAMP | default (now | date "20060102-150405")}}' - EXECUTION_DATA_DIR: '{{.EXECUTION_DATA_DIR | default (printf "/tmp/%s-%s" .TASK .TIMESTAMP)}}' + EXECUTION_DATA_DIR: '{{.EXECUTION_DATA_DIR | default (printf "/tmp/%s-%s" .TASK_NAME .TIMESTAMP)}}' cmd: | CURRENT_STATE_DIR={{.CURRENT_STATE_DIR}} \ BLOCK_DIR={{.BLOCK_DIR}} \ From b5088269023016d5acf31be83c3ddab17ea129c1 Mon Sep 17 00:00:00 2001 From: Rodrigo Villar Date: Thu, 4 Dec 2025 08:12:35 -0500 Subject: [PATCH 06/24] chore: use test context logger --- tests/reexecute/c/vm_reexecute.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/reexecute/c/vm_reexecute.go b/tests/reexecute/c/vm_reexecute.go index 20048fe7b97f..8dcb2d86d22d 100644 --- a/tests/reexecute/c/vm_reexecute.go +++ b/tests/reexecute/c/vm_reexecute.go @@ -211,7 +211,7 @@ func benchmarkReexecuteRange( consensusRegistry := prometheus.NewRegistry() r.NoError(prefixGatherer.Register("avalanche_snowman", consensusRegistry)) - log := tests.NewDefaultLogger("c-chain-reexecution") + log := tc.Log() if metricsServerEnabled { serverAddr := startServer(tc, log, prefixGatherer, metricsPort) From 953a76ee6cb7769d2d3efc2c8abaca266bf57163 Mon Sep 17 00:00:00 2001 From: Rodrigo Villar Date: Thu, 4 Dec 2025 08:16:19 -0500 Subject: [PATCH 07/24] chore: clean up benchmarkTool --- tests/reexecute/c/vm_reexecute.go | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/tests/reexecute/c/vm_reexecute.go b/tests/reexecute/c/vm_reexecute.go index 8dcb2d86d22d..59d97bb7cd46 100644 --- a/tests/reexecute/c/vm_reexecute.go +++ b/tests/reexecute/c/vm_reexecute.go @@ -592,14 +592,14 @@ type benchmarkResult struct { } type benchmarkTool struct { - name string - benchmarkResults []benchmarkResult + name string + results []benchmarkResult } func newBenchmarkTool(name string) *benchmarkTool { return &benchmarkTool{ - name: name, - benchmarkResults: make([]benchmarkResult, 0), + name: name, + results: make([]benchmarkResult, 0), } } @@ -609,16 +609,16 @@ func (b *benchmarkTool) addResult(value float64, unit string) { Value: strconv.FormatFloat(value, 'f', -1, 64), Unit: unit, } - b.benchmarkResults = append(b.benchmarkResults, result) + b.results = append(b.results, result) } -func (b *benchmarkTool) saveToFile(filePath string) error { - output, err := json.MarshalIndent(b.benchmarkResults, "", " ") +func (b *benchmarkTool) saveToFile(path string) error { + output, err := json.MarshalIndent(b.results, "", " ") if err != nil { return err } - return os.WriteFile(filePath, output, perms.ReadWrite) + return os.WriteFile(path, output, perms.ReadWrite) } // parseCustomLabels parses a comma-separated list of key-value pairs into a map From e6481ab8aa540b2f5092e1bc32cd1a1fe49246c4 Mon Sep 17 00:00:00 2001 From: Rodrigo Villar Date: Thu, 4 Dec 2025 08:20:24 -0500 Subject: [PATCH 08/24] docs: benchmarking types --- tests/reexecute/c/vm_reexecute.go | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/tests/reexecute/c/vm_reexecute.go b/tests/reexecute/c/vm_reexecute.go index 59d97bb7cd46..1190bdedb9c6 100644 --- a/tests/reexecute/c/vm_reexecute.go +++ b/tests/reexecute/c/vm_reexecute.go @@ -585,17 +585,22 @@ func startCollector(tc tests.TestContext, log logging.Logger, name string, label ) } +// benchmarkResult represents a single benchmark measurement. type benchmarkResult struct { Name string `json:"name"` Value string `json:"value"` Unit string `json:"unit"` } +// benchmarkTool collects and manages benchmark results for a named benchmark. +// It allows adding multiple results and saving them to a file in JSON format. type benchmarkTool struct { name string results []benchmarkResult } +// newBenchmarkTool creates a new benchmarkTool instance with the given name. +// The name is used to identify all results collected by this tool. func newBenchmarkTool(name string) *benchmarkTool { return &benchmarkTool{ name: name, @@ -603,6 +608,9 @@ func newBenchmarkTool(name string) *benchmarkTool { } } +// addResult adds a new benchmark result with the given value and unit. +// All results added share the same benchmark name set during tool creation. +// This is analogous to calling `b.ReportMetric()`. func (b *benchmarkTool) addResult(value float64, unit string) { result := benchmarkResult{ Name: b.name, @@ -612,6 +620,9 @@ func (b *benchmarkTool) addResult(value float64, unit string) { b.results = append(b.results, result) } +// saveToFile writes all collected benchmark results to a JSON file at the +// specified path. The output is formatted with indentation for readability. +// Returns an error if marshaling or file writing fails. func (b *benchmarkTool) saveToFile(path string) error { output, err := json.MarshalIndent(b.results, "", " ") if err != nil { From 89abbdee9ddaa3ad2b1c4d181f4debc1cd87fd99 Mon Sep 17 00:00:00 2001 From: Rodrigo Villar Date: Thu, 4 Dec 2025 08:39:19 -0500 Subject: [PATCH 09/24] refactor: use tc wherever convenient --- tests/reexecute/c/vm_reexecute.go | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/tests/reexecute/c/vm_reexecute.go b/tests/reexecute/c/vm_reexecute.go index 1190bdedb9c6..af0365b652f5 100644 --- a/tests/reexecute/c/vm_reexecute.go +++ b/tests/reexecute/c/vm_reexecute.go @@ -211,13 +211,11 @@ func benchmarkReexecuteRange( consensusRegistry := prometheus.NewRegistry() r.NoError(prefixGatherer.Register("avalanche_snowman", consensusRegistry)) - log := tc.Log() - if metricsServerEnabled { - serverAddr := startServer(tc, log, prefixGatherer, metricsPort) + serverAddr := startServer(tc, prefixGatherer, metricsPort) if metricsCollectorEnabled { - startCollector(tc, log, "c-chain-reexecution", labels, serverAddr) + startCollector(tc, "c-chain-reexecution", labels, serverAddr) } } @@ -226,6 +224,7 @@ func benchmarkReexecuteRange( chainDataDir = filepath.Join(currentStateDir, "chain-data-dir") ) + log := tc.Log() log.Info("re-executing block range with params", zap.String("runner", runnerNameArg), zap.String("config", configNameArg), @@ -516,7 +515,6 @@ func newConsensusMetrics(registry prometheus.Registerer) (*consensusMetrics, err // the server address. func startServer( tc tests.TestContext, - log logging.Logger, gatherer prometheus.Gatherer, port uint64, ) string { @@ -525,7 +523,7 @@ func startServer( server, err := tests.NewPrometheusServerWithPort(gatherer, port) r.NoError(err) - log.Info("metrics endpoint available", + tc.Log().Info("metrics endpoint available", zap.String("url", fmt.Sprintf("http://%s/ext/metrics", server.Address())), ) @@ -539,14 +537,11 @@ func startServer( // startCollector starts a Prometheus collector configured to scrape the server // listening on serverAddr. startCollector also attaches the provided labels + // Github labels if available to the collected metrics. -func startCollector(tc tests.TestContext, log logging.Logger, name string, labels map[string]string, serverAddr string) { +func startCollector(tc tests.TestContext, name string, labels map[string]string, serverAddr string) { r := require.New(tc) - startPromCtx, cancel := context.WithTimeout(tc.GetDefaultContextParent(), tests.DefaultTimeout) - defer cancel() - logger := tests.NewDefaultLogger("prometheus") - r.NoError(tmpnet.StartPrometheus(startPromCtx, logger)) + r.NoError(tmpnet.StartPrometheus(tc.DefaultContext(), logger)) var sdConfigFilePath string tc.DeferCleanup(func() { @@ -577,7 +572,7 @@ func startCollector(tc tests.TestContext, log logging.Logger, name string, label startTime = strconv.FormatInt(time.Now().UnixMilli(), 10) ) - log.Info("metrics available via grafana", + tc.Log().Info("metrics available via grafana", zap.String( "url", tmpnet.NewGrafanaURI(networkUUID, startTime, "", grafanaURI), From 5f55711e9ec385ec47fe746291b763ce24cf9c74 Mon Sep 17 00:00:00 2001 From: Rodrigo Villar Date: Thu, 4 Dec 2025 08:47:52 -0500 Subject: [PATCH 10/24] refactor: clean up result logging --- tests/reexecute/c/vm_reexecute.go | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/tests/reexecute/c/vm_reexecute.go b/tests/reexecute/c/vm_reexecute.go index af0365b652f5..8da7539016ff 100644 --- a/tests/reexecute/c/vm_reexecute.go +++ b/tests/reexecute/c/vm_reexecute.go @@ -282,6 +282,8 @@ func benchmarkReexecuteRange( benchmarkTool := newBenchmarkTool(benchmarkName) getTopLevelMetrics(tc, benchmarkTool, prefixGatherer, elapsed) // Report the desired top-level metrics + + benchmarkTool.logResults(log) if len(benchmarkOutputFile) != 0 { r.NoError(benchmarkTool.saveToFile(benchmarkOutputFile)) } @@ -627,6 +629,16 @@ func (b *benchmarkTool) saveToFile(path string) error { return os.WriteFile(path, output, perms.ReadWrite) } +// logResults logs all collected benchmark results using the provided logger. +func (b *benchmarkTool) logResults(log logging.Logger) { + for _, result := range b.results { + log.Info(b.name, + zap.String("value", result.Value), + zap.String("unit", result.Unit), + ) + } +} + // parseCustomLabels parses a comma-separated list of key-value pairs into a map // of custom labels. func parseCustomLabels(labelsStr string) (map[string]string, error) { From afac7bcb99e325d26eace44ecbbce6e5bd370281 Mon Sep 17 00:00:00 2001 From: Rodrigo Villar Date: Thu, 4 Dec 2025 09:31:19 -0500 Subject: [PATCH 11/24] fix: append unit to base name --- tests/reexecute/c/vm_reexecute.go | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/tests/reexecute/c/vm_reexecute.go b/tests/reexecute/c/vm_reexecute.go index 8da7539016ff..3146be5c0dfc 100644 --- a/tests/reexecute/c/vm_reexecute.go +++ b/tests/reexecute/c/vm_reexecute.go @@ -597,7 +597,8 @@ type benchmarkTool struct { } // newBenchmarkTool creates a new benchmarkTool instance with the given name. -// The name is used to identify all results collected by this tool. +// The name is used as the base name for all results collected by this tool. +// When results are added, the unit is appended to this base name. func newBenchmarkTool(name string) *benchmarkTool { return &benchmarkTool{ name: name, @@ -606,11 +607,11 @@ func newBenchmarkTool(name string) *benchmarkTool { } // addResult adds a new benchmark result with the given value and unit. -// All results added share the same benchmark name set during tool creation. -// This is analogous to calling `b.ReportMetric()`. +// The result name is constructed by appending the unit to the benchmark name +// Calling `addResult` is analogous to calling `b.ReportMetric()`. func (b *benchmarkTool) addResult(value float64, unit string) { result := benchmarkResult{ - Name: b.name, + Name: fmt.Sprintf("%s - %s", b.name, unit), Value: strconv.FormatFloat(value, 'f', -1, 64), Unit: unit, } From a817e732f9d8d5f17903c92a17ace899ac7b99f9 Mon Sep 17 00:00:00 2001 From: Rodrigo Villar Date: Thu, 4 Dec 2025 09:31:47 -0500 Subject: [PATCH 12/24] chore: remove unnecessary log --- tests/reexecute/c/vm_reexecute.go | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/reexecute/c/vm_reexecute.go b/tests/reexecute/c/vm_reexecute.go index 3146be5c0dfc..ec121b2d8d9f 100644 --- a/tests/reexecute/c/vm_reexecute.go +++ b/tests/reexecute/c/vm_reexecute.go @@ -665,7 +665,6 @@ func getTopLevelMetrics(tc tests.TestContext, tool *benchmarkTool, registry prom mgasPerSecond := gasUsed / 1_000_000 / elapsed.Seconds() tool.addResult(mgasPerSecond, "mgas/s") - tc.Log().Info("block processing", zap.Float64("mgas/s", mgasPerSecond)) } func getCounterMetricValue(registry prometheus.Gatherer, query string) (float64, error) { From 828abe3817077d62f16d5e74baff38ac84d02f4a Mon Sep 17 00:00:00 2001 From: Rodrigo Villar Date: Tue, 9 Dec 2025 11:56:05 -0500 Subject: [PATCH 13/24] chore: nit --- tests/reexecute/blockexport/main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/reexecute/blockexport/main.go b/tests/reexecute/blockexport/main.go index 973054dada00..7f7c7275bd65 100644 --- a/tests/reexecute/blockexport/main.go +++ b/tests/reexecute/blockexport/main.go @@ -44,7 +44,7 @@ func main() { blockDirSrcArg, startBlockArg, endBlockArg, - chanSizeArg, + chanSize, ) r.NoError(err) From 01f0663276a8be1a142ecb527db58f87ae71721c Mon Sep 17 00:00:00 2001 From: Rodrigo Villar Date: Tue, 9 Dec 2025 11:57:19 -0500 Subject: [PATCH 14/24] chore: add back require --- tests/reexecute/db.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/reexecute/db.go b/tests/reexecute/db.go index d7ce05961203..b0573b54a1ff 100644 --- a/tests/reexecute/db.go +++ b/tests/reexecute/db.go @@ -8,6 +8,7 @@ import ( "fmt" "github.com/prometheus/client_golang/prometheus" + "github.com/stretchr/testify/require" "github.com/ava-labs/avalanchego/database" "github.com/ava-labs/avalanchego/database/leveldb" @@ -32,6 +33,7 @@ type BlockResult struct { // // Any validation errors or iteration errors are sent as BlockResult with Err set, then the channel is closed. func CreateBlockChanFromLevelDB(tc tests.TestContext, sourceDir string, startBlock, endBlock uint64, chanSize int) (<-chan BlockResult, error) { + r := require.New(tc) ch := make(chan BlockResult, chanSize) db, err := leveldb.New(sourceDir, nil, logging.NoLog{}, prometheus.NewRegistry()) @@ -39,7 +41,7 @@ func CreateBlockChanFromLevelDB(tc tests.TestContext, sourceDir string, startBlo return nil, fmt.Errorf("failed to create leveldb database from %q: %w", sourceDir, err) } tc.DeferCleanup(func() { - db.Close() + r.NoError(db.Close()) }) go func() { From 18b884d2851828d2db6e43fff6705ee41e8d5f4e Mon Sep 17 00:00:00 2001 From: Rodrigo Villar Date: Tue, 9 Dec 2025 12:38:37 -0500 Subject: [PATCH 15/24] chore: txt => json --- .github/actions/c-chain-reexecution-benchmark/action.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/actions/c-chain-reexecution-benchmark/action.yml b/.github/actions/c-chain-reexecution-benchmark/action.yml index 5fa7cdd8df5b..c5e5b1b80d96 100644 --- a/.github/actions/c-chain-reexecution-benchmark/action.yml +++ b/.github/actions/c-chain-reexecution-benchmark/action.yml @@ -147,7 +147,7 @@ runs: run: | TIMESTAMP=$(date '+%Y%m%d-%H%M%S') echo "EXECUTION_DATA_DIR=/tmp/reexecution-data-${TIMESTAMP}" >> "$GITHUB_ENV" - echo "BENCHMARK_OUTPUT_FILE=${GITHUB_WORKSPACE}/benchmark-output.txt" >> "$GITHUB_ENV" + echo "BENCHMARK_OUTPUT_FILE=${GITHUB_WORKSPACE}/benchmark-output.json" >> "$GITHUB_ENV" - name: Run C-Chain Re-execution Benchmark shell: nix develop --impure --command bash -x {0} run: | From d4639ff6d1e792432bca06eed8b56cda56afae5a Mon Sep 17 00:00:00 2001 From: Rodrigo Villar Date: Tue, 9 Dec 2025 12:39:37 -0500 Subject: [PATCH 16/24] docs: remove stale ref --- tests/reexecute/db.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/tests/reexecute/db.go b/tests/reexecute/db.go index b0573b54a1ff..609eb9a9ce3c 100644 --- a/tests/reexecute/db.go +++ b/tests/reexecute/db.go @@ -28,9 +28,6 @@ type BlockResult struct { // It opens the database at sourceDir and iterates through blocks from startBlock to endBlock (inclusive). // Blocks are read sequentially and sent to the returned channel as BlockResult values. // -// cleanup is a function that registers cleanup callbacks and is used to ensure -// that the database being read from is properly closed prior to the test terminating. -// // Any validation errors or iteration errors are sent as BlockResult with Err set, then the channel is closed. func CreateBlockChanFromLevelDB(tc tests.TestContext, sourceDir string, startBlock, endBlock uint64, chanSize int) (<-chan BlockResult, error) { r := require.New(tc) From 718bc52cc4766cda92299f730c34ddac9d28302e Mon Sep 17 00:00:00 2001 From: Rodrigo Villar Date: Tue, 9 Dec 2025 12:52:12 -0500 Subject: [PATCH 17/24] docs: nit --- tests/reexecute/c/vm_reexecute.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/reexecute/c/vm_reexecute.go b/tests/reexecute/c/vm_reexecute.go index ec121b2d8d9f..a3e921f8369d 100644 --- a/tests/reexecute/c/vm_reexecute.go +++ b/tests/reexecute/c/vm_reexecute.go @@ -122,7 +122,7 @@ func init() { flag.StringVar(&configNameArg, configKey, defaultConfigKey, fmt.Sprintf("Specifies the predefined config to use for the VM. Options include %s.", predefinedConfigOptionsStr)) flag.StringVar(&runnerNameArg, "runner", "dev", "Name of the runner executing this test. Added as a metric label and to the sub-benchmark's name to differentiate results on the runner key.") - flag.StringVar(&benchmarkOutputFileArg, "benchmark-output-file", benchmarkOutputFileArg, "The filepath where benchmark results should be written to.") + flag.StringVar(&benchmarkOutputFileArg, "benchmark-output-file", benchmarkOutputFileArg, "Filepath where benchmark results will be written to.") flag.Parse() From b26e8c4639a5e2ffeafe579f007572b8b8a43c75 Mon Sep 17 00:00:00 2001 From: Rodrigo Villar Date: Tue, 9 Dec 2025 13:06:58 -0500 Subject: [PATCH 18/24] chore: improve logResults() --- tests/reexecute/c/vm_reexecute.go | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/tests/reexecute/c/vm_reexecute.go b/tests/reexecute/c/vm_reexecute.go index a3e921f8369d..73efc3b97b1f 100644 --- a/tests/reexecute/c/vm_reexecute.go +++ b/tests/reexecute/c/vm_reexecute.go @@ -632,11 +632,9 @@ func (b *benchmarkTool) saveToFile(path string) error { // logResults logs all collected benchmark results using the provided logger. func (b *benchmarkTool) logResults(log logging.Logger) { - for _, result := range b.results { - log.Info(b.name, - zap.String("value", result.Value), - zap.String("unit", result.Unit), - ) + for _, r := range b.results { + result := fmt.Sprintf("%s %s", r.Value, r.Unit) + log.Info(b.name, zap.String("result", result)) } } From 358897882682d4bed657657bc3977ffdee0ab2f0 Mon Sep 17 00:00:00 2001 From: Rodrigo Villar Date: Tue, 9 Dec 2025 13:22:43 -0500 Subject: [PATCH 19/24] chore: address PR review --- tests/reexecute/c/vm_reexecute.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/reexecute/c/vm_reexecute.go b/tests/reexecute/c/vm_reexecute.go index 73efc3b97b1f..586b4c6a2116 100644 --- a/tests/reexecute/c/vm_reexecute.go +++ b/tests/reexecute/c/vm_reexecute.go @@ -607,7 +607,7 @@ func newBenchmarkTool(name string) *benchmarkTool { } // addResult adds a new benchmark result with the given value and unit. -// The result name is constructed by appending the unit to the benchmark name +// The result name is constructed by appending the unit to the benchmark name. // Calling `addResult` is analogous to calling `b.ReportMetric()`. func (b *benchmarkTool) addResult(value float64, unit string) { result := benchmarkResult{ From bc78644b239427f7aef446ac5a158e45db3f7b93 Mon Sep 17 00:00:00 2001 From: Rodrigo Villar Date: Wed, 10 Dec 2025 12:06:21 -0500 Subject: [PATCH 20/24] chore(reexecute/c): add back scraping of multiple metrics --- tests/reexecute/c/metrics.go | 106 ++++++++++++++++++++++++++++++ tests/reexecute/c/vm_reexecute.go | 25 ------- 2 files changed, 106 insertions(+), 25 deletions(-) create mode 100644 tests/reexecute/c/metrics.go diff --git a/tests/reexecute/c/metrics.go b/tests/reexecute/c/metrics.go new file mode 100644 index 000000000000..d1dea451f666 --- /dev/null +++ b/tests/reexecute/c/metrics.go @@ -0,0 +1,106 @@ +// Copyright (C) 2019-2025, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package main + +import ( + "fmt" + "time" + + "github.com/prometheus/client_golang/prometheus" + "github.com/stretchr/testify/require" + + "github.com/ava-labs/avalanchego/tests" +) + +type metricKind uint + +const ( + counter metricKind = iota + 1 + gauge +) + +var ( + gasMetric = topLevelMetric{ + name: "gas", + query: "avalanche_evm_eth_chain_block_gas_used_processed", + kind: counter, + } + meterVMMetrics = []topLevelMetric{ + { + name: "block_parse", + query: "avalanche_meterchainvm_parse_block_sum", + kind: gauge, + }, + { + name: "block_verify", + query: "avalanche_meterchainvm_verify_sum", + kind: gauge, + }, + { + name: "block_accept", + query: "avalanche_meterchainvm_accept_sum", + kind: gauge, + }, + } +) + +func getMetricValue(registry prometheus.Gatherer, metric topLevelMetric) (float64, error) { + metricFamilies, err := registry.Gather() + if err != nil { + return 0, fmt.Errorf("failed to gather metrics: %w", err) + } + + query := metric.query + for _, mf := range metricFamilies { + switch metric.kind { + case counter: + if mf.GetName() == query { + return mf.GetMetric()[0].Counter.GetValue(), nil + } + case gauge: + if mf.GetName() == query { + return mf.GetMetric()[0].Gauge.GetValue(), nil + } + default: + return 0, fmt.Errorf("metric type unknown: %d", metric.kind) + } + } + + return 0, fmt.Errorf("metric %s not found", query) +} + +type topLevelMetric struct { + name string + query string + kind metricKind +} + +func getTopLevelMetrics(tc tests.TestContext, tool *benchmarkTool, registry prometheus.Gatherer, elapsed time.Duration) { + r := require.New(tc) + + totalGas, err := getMetricValue(registry, gasMetric) + r.NoError(err) + r.NotZero(totalGas, "denominator metric %q has value 0", gasMetric.name) + + var ( + mgas float64 = 1_000_000 + ggas float64 = 1_000_000_000 + nsPerMs float64 = 1_000_000 + ) + + mgasPerSecond := (totalGas / mgas) / elapsed.Seconds() + tool.addResult(mgasPerSecond, "mgas/s") + + totalGGas := totalGas / ggas + msPerGGas := (float64(elapsed) / nsPerMs) / totalGGas + tool.addResult(msPerGGas, "ms/ggas") + + for _, metric := range meterVMMetrics { + metricVal, err := getMetricValue(registry, metric) + r.NoError(err) + + metricValMS := (metricVal / nsPerMs) / totalGGas + tool.addResult(metricValMS, metric.name+"_ms/ggas") + } +} diff --git a/tests/reexecute/c/vm_reexecute.go b/tests/reexecute/c/vm_reexecute.go index 586b4c6a2116..d30f8429a1c7 100644 --- a/tests/reexecute/c/vm_reexecute.go +++ b/tests/reexecute/c/vm_reexecute.go @@ -654,28 +654,3 @@ func parseCustomLabels(labelsStr string) (map[string]string, error) { } return labels, nil } - -func getTopLevelMetrics(tc tests.TestContext, tool *benchmarkTool, registry prometheus.Gatherer, elapsed time.Duration) { - r := require.New(tc) - - gasUsed, err := getCounterMetricValue(registry, "avalanche_evm_eth_chain_block_gas_used_processed") - r.NoError(err) - mgasPerSecond := gasUsed / 1_000_000 / elapsed.Seconds() - - tool.addResult(mgasPerSecond, "mgas/s") -} - -func getCounterMetricValue(registry prometheus.Gatherer, query string) (float64, error) { - metricFamilies, err := registry.Gather() - if err != nil { - return 0, fmt.Errorf("failed to gather metrics: %w", err) - } - - for _, mf := range metricFamilies { - if mf.GetName() == query { - return mf.GetMetric()[0].Counter.GetValue(), nil - } - } - - return 0, fmt.Errorf("metric %s not found", query) -} From 72507784c1a62ca49f796958b0a06742ab8128a1 Mon Sep 17 00:00:00 2001 From: Rodrigo Villar Date: Tue, 16 Dec 2025 09:45:16 -0500 Subject: [PATCH 21/24] chore: nit --- tests/reexecute/c/vm_reexecute.go | 28 ---------------------------- 1 file changed, 28 deletions(-) diff --git a/tests/reexecute/c/vm_reexecute.go b/tests/reexecute/c/vm_reexecute.go index 725465111864..d30f8429a1c7 100644 --- a/tests/reexecute/c/vm_reexecute.go +++ b/tests/reexecute/c/vm_reexecute.go @@ -654,31 +654,3 @@ func parseCustomLabels(labelsStr string) (map[string]string, error) { } return labels, nil } -<<<<<<< HEAD -======= - -func getTopLevelMetrics(tc tests.TestContext, tool *benchmarkTool, registry prometheus.Gatherer, elapsed time.Duration) { - r := require.New(tc) - - gasUsed, err := getCounterMetricValue(registry, "avalanche_evm_eth_chain_block_gas_used_processed") - r.NoError(err) - mgasPerSecond := gasUsed / 1_000_000 / elapsed.Seconds() - - tool.addResult(mgasPerSecond, "mgas/s") -} - -func getCounterMetricValue(registry prometheus.Gatherer, query string) (float64, error) { - metricFamilies, err := registry.Gather() - if err != nil { - return 0, fmt.Errorf("failed to gather metrics: %w", err) - } - - for _, mf := range metricFamilies { - if mf.GetName() == query { - return mf.GetMetric()[0].Counter.GetValue(), nil - } - } - - return 0, fmt.Errorf("metric %s not found", query) -} ->>>>>>> master From 43f9633f1965b275a97c4bf30836737232d4e58e Mon Sep 17 00:00:00 2001 From: Rodrigo Villar Date: Tue, 16 Dec 2025 14:31:43 -0500 Subject: [PATCH 22/24] chore: nsPerMs => nanosecondsPerMillisecond --- tests/reexecute/c/metrics.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/reexecute/c/metrics.go b/tests/reexecute/c/metrics.go index d1dea451f666..8aa92201ef6d 100644 --- a/tests/reexecute/c/metrics.go +++ b/tests/reexecute/c/metrics.go @@ -84,23 +84,23 @@ func getTopLevelMetrics(tc tests.TestContext, tool *benchmarkTool, registry prom r.NotZero(totalGas, "denominator metric %q has value 0", gasMetric.name) var ( - mgas float64 = 1_000_000 - ggas float64 = 1_000_000_000 - nsPerMs float64 = 1_000_000 + mgas float64 = 1_000_000 + ggas float64 = 1_000_000_000 + nanosecondsPerMillisecond float64 = 1_000_000 ) mgasPerSecond := (totalGas / mgas) / elapsed.Seconds() tool.addResult(mgasPerSecond, "mgas/s") totalGGas := totalGas / ggas - msPerGGas := (float64(elapsed) / nsPerMs) / totalGGas + msPerGGas := (float64(elapsed) / nanosecondsPerMillisecond) / totalGGas tool.addResult(msPerGGas, "ms/ggas") for _, metric := range meterVMMetrics { metricVal, err := getMetricValue(registry, metric) r.NoError(err) - metricValMS := (metricVal / nsPerMs) / totalGGas + metricValMS := (metricVal / nanosecondsPerMillisecond) / totalGGas tool.addResult(metricValMS, metric.name+"_ms/ggas") } } From 9a97ce8dac45cd4a5033d42a3b352f71ce78fba5 Mon Sep 17 00:00:00 2001 From: Rodrigo Villar Date: Tue, 16 Dec 2025 14:41:37 -0500 Subject: [PATCH 23/24] chore: make clear that metric values are in terms of ns --- tests/reexecute/c/metrics.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/tests/reexecute/c/metrics.go b/tests/reexecute/c/metrics.go index 8aa92201ef6d..b841c4e57a34 100644 --- a/tests/reexecute/c/metrics.go +++ b/tests/reexecute/c/metrics.go @@ -97,10 +97,11 @@ func getTopLevelMetrics(tc tests.TestContext, tool *benchmarkTool, registry prom tool.addResult(msPerGGas, "ms/ggas") for _, metric := range meterVMMetrics { - metricVal, err := getMetricValue(registry, metric) + // MeterVM counters are in terms of nanoseconds + metricValNanoseconds, err := getMetricValue(registry, metric) r.NoError(err) - metricValMS := (metricVal / nanosecondsPerMillisecond) / totalGGas - tool.addResult(metricValMS, metric.name+"_ms/ggas") + msPerGGas := (metricValNanoseconds / nanosecondsPerMillisecond) / totalGGas + tool.addResult(msPerGGas, metric.name+"_ms/ggas") } } From 4144d9eefaadc38f8e70fcbf4ebe6b47064bb456 Mon Sep 17 00:00:00 2001 From: Rodrigo Villar Date: Thu, 18 Dec 2025 12:33:41 -0500 Subject: [PATCH 24/24] refactor: move topLevelMetric def --- tests/reexecute/c/metrics.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/reexecute/c/metrics.go b/tests/reexecute/c/metrics.go index b841c4e57a34..57adfd78fbf8 100644 --- a/tests/reexecute/c/metrics.go +++ b/tests/reexecute/c/metrics.go @@ -45,6 +45,12 @@ var ( } ) +type topLevelMetric struct { + name string + query string + kind metricKind +} + func getMetricValue(registry prometheus.Gatherer, metric topLevelMetric) (float64, error) { metricFamilies, err := registry.Gather() if err != nil { @@ -70,12 +76,6 @@ func getMetricValue(registry prometheus.Gatherer, metric topLevelMetric) (float6 return 0, fmt.Errorf("metric %s not found", query) } -type topLevelMetric struct { - name string - query string - kind metricKind -} - func getTopLevelMetrics(tc tests.TestContext, tool *benchmarkTool, registry prometheus.Gatherer, elapsed time.Duration) { r := require.New(tc)