Skip to content

Commit fa30616

Browse files
committed
refactor(loadgen): split internal package and simplify
- Split flat internal/ into internal/{matrix,spamoor,runner} packages - Remove check subcommand (unused) - Remove intermediate exported functions (ExecuteMatrix, ExecuteMatrixWithOverrides) - Remove double validation in executeMatrix (already done by matrix.Load) - Extract metric name and env var constants - Store parsed timeout on Entry to avoid double time.ParseDuration - Replace time.NewTimer per-iteration with single ticker in waitForSync - Fix double time.Since call in poll loop - Update README: fix burst-per-day default (0 not 2), remove stale serialization claim, document all subcommands clearly
1 parent 2a4e863 commit fa30616

14 files changed

Lines changed: 323 additions & 390 deletions

File tree

apps/loadgen/README.md

Lines changed: 45 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# loadgen
22

3-
Standalone load generator for ev-node stress testing. Talks to a [spamoor-daemon](https://github.com/ethpandaops/spamoor) sidecar via HTTP API. Runs an in-process scheduler with configurable regular and burst workloads.
3+
Standalone load generator for ev-node stress testing. Talks to a [spamoor-daemon](https://github.com/ethpandaops/spamoor) sidecar via HTTP API.
44

55
## Architecture
66

@@ -14,32 +14,57 @@ ev-loadgen (this binary) --> spamoor-daemon --> ev-reth RPC
1414
- **spamoor-daemon** needs: a funded private key + ev-reth RPC URL
1515
- **ev-loadgen** needs: spamoor-daemon API URL + matrix JSON files
1616

17-
## Commands
17+
## Modes
1818

19-
```text
20-
ev-loadgen start # run continuous scheduler (regular + burst)
21-
ev-loadgen check # send 1 tx to verify spamoor → ev-reth connectivity
22-
ev-loadgen run <matrix.json> # one-shot: run a custom matrix file
19+
### Daemon mode (`start`)
20+
21+
Runs a continuous scheduler with regular and burst workloads. Designed for long-running deployments.
22+
23+
```sh
24+
ev-loadgen start --spamoor-url=http://localhost:8080
2325
```
2426

25-
### start flags
27+
Regular workloads fire immediately at startup, then repeat at `--interval`. Per-run tx count = `tx-per-day / (24h / interval)`, overriding each matrix entry's `BENCH_COUNT_PER_SPAMMER`.
28+
29+
Bursts are randomly spaced throughout a rolling 24h window. Set `--burst-per-day=0` (the default) to disable bursts entirely.
30+
31+
#### start flags
2632

2733
| Flag | Env | Default | Description |
2834
|------|-----|---------|-------------|
2935
| `--tx-per-day` | `BENCH_TX_PER_DAY` | `1000000` | sustained txs/day |
3036
| `--interval` | `BENCH_INTERVAL` | `1h` | regular workload frequency |
3137
| `--burst-tx-count` | `BENCH_BURST_TX_COUNT` | `500000` | txs per burst |
32-
| `--burst-per-day` | `BENCH_BURST_PER_DAY` | `2` | bursts per day, randomly spaced |
38+
| `--burst-per-day` | `BENCH_BURST_PER_DAY` | `0` | bursts per day, randomly spaced (0 = disabled) |
3339
| `--regular-matrix` | `BENCH_REGULAR_MATRIX` | `/home/ev/baseline.json` | path to regular matrix JSON |
3440
| `--burst-matrix` | `BENCH_BURST_MATRIX` | `/home/ev/burst.json` | path to burst matrix JSON |
3541

36-
Global flag: `--spamoor-url` (or `BENCH_SPAMOOR_URL` env, default `http://spamoor-daemon:8080`)
42+
### CLI mode (one-shot commands)
43+
44+
#### `run` — execute a matrix file
45+
46+
Runs all entries from a matrix JSON file with probability filtering and sync waiting, then exits.
47+
48+
```sh
49+
ev-loadgen run matrices/baseline.json --spamoor-url=http://localhost:8080
50+
```
3751

38-
### Scheduling
52+
#### `burst` — trigger a single burst
3953

40-
- **Regular**: fires immediately at startup, then repeats at `--interval`. Per-run tx count = `tx-per-day / (24h / interval)`. Overrides each matrix entry's `BENCH_COUNT_PER_SPAMMER`.
41-
- **Burst**: at startup + each midnight UTC, generates N random times across the day. Each burst overrides `BENCH_COUNT_PER_SPAMMER` = `burst-tx-count / NumSpammers`.
42-
- **Serialization**: a mutex prevents concurrent spamoor access. If burst fires during regular (or vice versa), it waits for the lock.
54+
Fires one burst workload immediately and exits.
55+
56+
```sh
57+
ev-loadgen burst --spamoor-url=http://localhost:8080
58+
```
59+
60+
| Flag | Env | Default | Description |
61+
|------|-----|---------|-------------|
62+
| `--tx-count` | `BENCH_BURST_TX_COUNT` | `500000` | total transactions for the burst |
63+
| `--burst-matrix` | `BENCH_BURST_MATRIX` | `/home/ev/burst.json` | path to burst matrix JSON |
64+
65+
### Global flag
66+
67+
`--spamoor-url` (or `BENCH_SPAMOOR_URL` env, default `http://spamoor-daemon:8080`)
4368

4469
## Quick Start
4570

@@ -59,10 +84,13 @@ docker run -d --name spamoor -p 8080:8080 \
5984
# build
6085
cd apps/loadgen && go build -o ev-loadgen .
6186

62-
# run with defaults (~1M tx/day, 2 bursts/day)
87+
# one-shot matrix run
88+
./ev-loadgen run matrices/baseline.json --spamoor-url=http://localhost:8080
89+
90+
# continuous daemon (~1M tx/day, no bursts)
6391
./ev-loadgen start --spamoor-url=http://localhost:8080
6492

65-
# custom config
93+
# continuous daemon with bursts
6694
./ev-loadgen start \
6795
--spamoor-url=http://localhost:8080 \
6896
--tx-per-day=500000 \
@@ -109,10 +137,10 @@ Each entry specifies a spamoor scenario, tx counts, and optional probability:
109137
| Field | Description |
110138
|---|---|
111139
| `scenario` | spamoor scenario name (`eoatx`, `gasburnertx`, `erc20tx`, `uniswap-swaps`, etc.) |
112-
| `probability` | 0.01.0, chance of running per invocation (omit = always run) |
140+
| `probability` | 0.0-1.0, chance of running per invocation (omit = always run) |
113141
| `timeout` | max duration per entry (default `15m`) |
114142

115-
When using `start`, the `BENCH_COUNT_PER_SPAMMER` value in the matrix is overridden by the computed per-run count. The matrix value is still used by the `run` command.
143+
When using `start` or `burst`, `BENCH_COUNT_PER_SPAMMER` is overridden by the computed per-run count. The matrix value is used as-is by `run`.
116144

117145
## Build
118146

apps/loadgen/cmd/burst.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@ package cmd
33
import (
44
"log"
55

6-
"github.com/evstack/ev-node/apps/loadgen/internal"
6+
"github.com/evstack/ev-node/apps/loadgen/internal/runner"
7+
"github.com/evstack/ev-node/apps/loadgen/internal/spamoor"
78
"github.com/spf13/cobra"
89
)
910

@@ -18,15 +19,14 @@ func newBurstCmd() *cobra.Command {
1819
Short: "trigger a single burst workload immediately",
1920
Args: cobra.NoArgs,
2021
RunE: func(cmd *cobra.Command, args []string) error {
21-
spamoorAddr := resolveSpamoorURL()
22-
api := internal.NewSpamoorClient(spamoorAddr)
22+
api := spamoor.NewClient(resolveSpamoorURL())
2323

24-
if err := internal.WaitForSync(cmd.Context(), api); err != nil {
24+
if err := runner.WaitForSync(cmd.Context(), api); err != nil {
2525
return err
2626
}
2727

2828
log.Printf("==> burst workload starting (%d tx)", txCount)
29-
return internal.ExecuteMatrixWithOverridesFromFile(cmd.Context(), matrixPath, api, txCount)
29+
return runner.ExecuteMatrixWithOverridesFromFile(cmd.Context(), matrixPath, api, txCount)
3030
},
3131
}
3232

apps/loadgen/cmd/check.go

Lines changed: 0 additions & 25 deletions
This file was deleted.

apps/loadgen/cmd/root.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
package cmd
22

33
import (
4-
"github.com/evstack/ev-node/apps/loadgen/internal"
4+
"github.com/evstack/ev-node/apps/loadgen/internal/spamoor"
55
"github.com/spf13/cobra"
66
)
77

@@ -16,7 +16,7 @@ func NewRootCmd() *cobra.Command {
1616

1717
rootCmd.PersistentFlags().StringVar(&spamoorFlag, "spamoor-url", "", "spamoor-daemon API URL (env: BENCH_SPAMOOR_URL)")
1818

19-
rootCmd.AddCommand(newRunCmd(), newStartCmd(), newCheckCmd(), newBurstCmd())
19+
rootCmd.AddCommand(newRunCmd(), newStartCmd(), newBurstCmd())
2020

2121
return rootCmd
2222
}
@@ -25,5 +25,5 @@ func resolveSpamoorURL() string {
2525
if spamoorFlag != "" {
2626
return spamoorFlag
2727
}
28-
return internal.SpamoorURL()
28+
return spamoor.URL()
2929
}

apps/loadgen/cmd/run.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
package cmd
22

33
import (
4-
"github.com/evstack/ev-node/apps/loadgen/internal"
4+
"github.com/evstack/ev-node/apps/loadgen/internal/runner"
5+
"github.com/evstack/ev-node/apps/loadgen/internal/spamoor"
56
"github.com/spf13/cobra"
67
)
78

@@ -11,7 +12,7 @@ func newRunCmd() *cobra.Command {
1112
Short: "run benchmarks from a matrix JSON file",
1213
Args: cobra.ExactArgs(1),
1314
RunE: func(cmd *cobra.Command, args []string) error {
14-
return internal.ExecuteMatrixFromFile(cmd.Context(), args[0], internal.NewSpamoorClient(resolveSpamoorURL()))
15+
return runner.ExecuteMatrixFromFile(cmd.Context(), args[0], spamoor.NewClient(resolveSpamoorURL()))
1516
},
1617
}
1718
}

apps/loadgen/cmd/start.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@ import (
1212
"syscall"
1313
"time"
1414

15-
"github.com/evstack/ev-node/apps/loadgen/internal"
15+
"github.com/evstack/ev-node/apps/loadgen/internal/runner"
16+
"github.com/evstack/ev-node/apps/loadgen/internal/spamoor"
1617
"github.com/spf13/cobra"
1718
)
1819

@@ -62,7 +63,7 @@ func runScheduler(parent context.Context, cfg startConfig) error {
6263
defer cancel()
6364

6465
spamoorAddr := resolveSpamoorURL()
65-
api := internal.NewSpamoorClient(spamoorAddr)
66+
api := spamoor.NewClient(spamoorAddr)
6667

6768
runsPerDay := float64(24*time.Hour) / float64(cfg.interval)
6869
regularTxPerRun := int(float64(cfg.txPerDay) / runsPerDay)
@@ -71,15 +72,15 @@ func runScheduler(parent context.Context, cfg startConfig) error {
7172
cfg.txPerDay, cfg.interval, regularTxPerRun, cfg.burstTxCount, cfg.burstPerDay)
7273
log.Printf("regular-matrix=%s burst-matrix=%s spamoor=%s", cfg.regularMatrix, cfg.burstMatrix, spamoorAddr)
7374

74-
if err := internal.WaitForSync(ctx, api); err != nil {
75+
if err := runner.WaitForSync(ctx, api); err != nil {
7576
return err
7677
}
7778

7879
var wg sync.WaitGroup
7980
runWorkload := func(label, matrixPath string, txCount int) {
8081
defer wg.Done()
8182
log.Printf("==> %s workload starting (%d tx)", label, txCount)
82-
if err := internal.ExecuteMatrixWithOverridesFromFile(ctx, matrixPath, api, txCount); err != nil {
83+
if err := runner.ExecuteMatrixWithOverridesFromFile(ctx, matrixPath, api, txCount); err != nil {
8384
log.Printf("%s workload error: %v", label, err)
8485
}
8586
}
@@ -105,7 +106,7 @@ func runScheduler(parent context.Context, cfg startConfig) error {
105106
burstTimer.Stop()
106107
wg.Wait()
107108
log.Printf("cleaning up spammers")
108-
if err := internal.DeleteAllSpammers(api); err != nil {
109+
if err := runner.DeleteAllSpammers(api); err != nil {
109110
log.Printf("warning: shutdown cleanup failed: %v", err)
110111
}
111112
return nil
@@ -153,7 +154,6 @@ func nextBurstTimer(remaining int, window time.Duration) *time.Timer {
153154
if remaining <= 0 {
154155
t := time.NewTimer(0)
155156
t.Stop()
156-
// drain channel in case it fired before Stop
157157
select {
158158
case <-t.C:
159159
default:

apps/loadgen/internal/client.go

Lines changed: 0 additions & 94 deletions
This file was deleted.

0 commit comments

Comments
 (0)