From ceebf5eccc5886da1cb5da7952c14b07d4bc5530 Mon Sep 17 00:00:00 2001 From: Albert Bausili Date: Sun, 29 Mar 2026 04:54:13 +0200 Subject: [PATCH 1/2] deps: upgrade goceleris/loadgen to v1.0.0 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Breaking API migration: - Config: WarmupTime→Warmup, H2C→HTTP2, KeepAlive→DisableKeepAlive, H2Connections/H2MaxStreams→HTTP2Options struct - New() returns (*Benchmarker, error) — add error handling - Checkpoint types moved to loadgen/checkpoint subpackage - result.ToServerResult() → checkpoint.NewServerResult() - internal/bench imports replaced with loadgen + loadgen/checkpoint --- cmd/bench/c2client.go | 23 ++++++------- cmd/bench/config.go | 26 +++++++-------- cmd/bench/runner.go | 55 ++++++++++++++++++------------- go.mod | 1 + go.sum | 2 ++ internal/dashboard/format.go | 8 ++--- internal/dashboard/format_test.go | 33 ++++++++++--------- 7 files changed, 81 insertions(+), 67 deletions(-) diff --git a/cmd/bench/c2client.go b/cmd/bench/c2client.go index b707fd6..e6bd687 100644 --- a/cmd/bench/c2client.go +++ b/cmd/bench/c2client.go @@ -11,7 +11,8 @@ import ( "sync" "time" - "github.com/goceleris/benchmarks/internal/bench" + "github.com/goceleris/loadgen" + "github.com/goceleris/loadgen/checkpoint" ) // C2Client handles communication with the C2 orchestration server. @@ -46,15 +47,15 @@ type C2BenchResult struct { Errors int64 `json:"errors"` // System metrics — client-side and server-side resource usage. - ClientCPUPercent float64 `json:"client_cpu_percent,omitempty"` - ServerCPUPercent float64 `json:"server_cpu_percent,omitempty"` - ServerCPUUserPercent float64 `json:"server_cpu_user_percent,omitempty"` - ServerCPUSysPercent float64 `json:"server_cpu_sys_percent,omitempty"` - ServerMemoryRSSMB float64 `json:"server_memory_rss_mb,omitempty"` - GCTotalPauseMs float64 `json:"gc_total_pause_ms,omitempty"` - GCMaxPauseMs float64 `json:"gc_max_pause_ms,omitempty"` - GCNumGC int `json:"gc_num_gc,omitempty"` - Timeseries []bench.TimeseriesPoint `json:"timeseries,omitempty"` + ClientCPUPercent float64 `json:"client_cpu_percent,omitempty"` + ServerCPUPercent float64 `json:"server_cpu_percent,omitempty"` + ServerCPUUserPercent float64 `json:"server_cpu_user_percent,omitempty"` + ServerCPUSysPercent float64 `json:"server_cpu_sys_percent,omitempty"` + ServerMemoryRSSMB float64 `json:"server_memory_rss_mb,omitempty"` + GCTotalPauseMs float64 `json:"gc_total_pause_ms,omitempty"` + GCMaxPauseMs float64 `json:"gc_max_pause_ms,omitempty"` + GCNumGC int `json:"gc_num_gc,omitempty"` + Timeseries []loadgen.TimeseriesPoint `json:"timeseries,omitempty"` } // runHeartbeat sends periodic heartbeats to C2. @@ -116,7 +117,7 @@ func (c *C2Client) sendHeartbeat(ctx context.Context) { } // sendResult sends a single benchmark result to C2. -func (c *C2Client) sendResult(ctx context.Context, sr bench.ServerResult, result *bench.Result) error { +func (c *C2Client) sendResult(ctx context.Context, sr checkpoint.ServerResult, result *loadgen.Result) error { url := fmt.Sprintf("%s/api/worker/results", c.endpoint) // Convert to C2 format with proper duration values diff --git a/cmd/bench/config.go b/cmd/bench/config.go index 24c5127..32c2ae8 100644 --- a/cmd/bench/config.go +++ b/cmd/bench/config.go @@ -10,7 +10,7 @@ import ( "strings" "time" - "github.com/goceleris/benchmarks/internal/bench" + "github.com/goceleris/loadgen/checkpoint" ) // benchConfig holds all parsed configuration for the benchmark runner. @@ -333,22 +333,22 @@ func parseFlags() *benchConfig { } // setupCheckpoint creates or resumes a benchmark checkpoint. -func setupCheckpoint(cfg *benchConfig) *bench.Checkpoint { - var checkpoint *bench.Checkpoint +func setupCheckpoint(cfg *benchConfig) *checkpoint.Checkpoint { + var cp *checkpoint.Checkpoint if cfg.resume { - if cp, err := bench.LoadCheckpoint(cfg.checkpointFile); err == nil { - checkpoint = cp - log.Printf("Resuming from checkpoint: %s (%d results completed)", cfg.checkpointFile, len(cp.Results)) + if loaded, err := checkpoint.LoadCheckpoint(cfg.checkpointFile); err == nil { + cp = loaded + log.Printf("Resuming from checkpoint: %s (%d results completed)", cfg.checkpointFile, len(loaded.Results)) } else { log.Printf("No existing checkpoint found, starting fresh") } } - if checkpoint == nil { - output := &bench.BenchmarkOutput{ + if cp == nil { + output := &checkpoint.BenchmarkOutput{ Timestamp: time.Now().UTC().Format("2006-01-02T15_04_05Z"), Architecture: cfg.arch, - Config: bench.BenchmarkConfig{ + Config: checkpoint.BenchmarkConfig{ Duration: cfg.duration.String(), DurationSecs: cfg.duration.Seconds(), WarmupSecs: cfg.warmup.Seconds(), @@ -359,9 +359,9 @@ func setupCheckpoint(cfg *benchConfig) *bench.Checkpoint { H2MaxStreams: cfg.h2Streams, BodySizeBytes: defaultBodySize, }, - Results: []bench.ServerResult{}, + Results: []checkpoint.ServerResult{}, } - checkpoint = bench.NewCheckpoint(output) + cp = checkpoint.NewCheckpoint(output) } if cfg.skipBenchmarks != "" { @@ -374,7 +374,7 @@ func setupCheckpoint(cfg *benchConfig) *bench.Checkpoint { } parts := strings.SplitN(pair, ":", 2) if len(parts) == 2 { - checkpoint.MarkCompleted(parts[0], parts[1]) + cp.MarkCompleted(parts[0], parts[1]) skippedCount++ } } @@ -383,5 +383,5 @@ func setupCheckpoint(cfg *benchConfig) *bench.Checkpoint { } } - return checkpoint + return cp } diff --git a/cmd/bench/runner.go b/cmd/bench/runner.go index 8e9e875..b530274 100644 --- a/cmd/bench/runner.go +++ b/cmd/bench/runner.go @@ -14,14 +14,15 @@ import ( "syscall" "time" - "github.com/goceleris/benchmarks/internal/bench" "github.com/goceleris/benchmarks/internal/dashboard" + "github.com/goceleris/loadgen" + "github.com/goceleris/loadgen/checkpoint" ) // benchRunner coordinates benchmark execution across servers. type benchRunner struct { cfg *benchConfig - checkpoint *bench.Checkpoint + checkpoint *checkpoint.Checkpoint rc *RemoteController c2 *C2Client } @@ -150,19 +151,20 @@ func (r *benchRunner) runServerBenchmarks(ctx context.Context, serverType, serve } isH2 := strings.Contains(serverType, "-h2") || strings.Contains(serverType, "-hybrid") - cfg := bench.Config{ - URL: fmt.Sprintf("http://%s:%s%s", serverHost, r.cfg.port, bt.Path), - Method: bt.Method, - Body: bt.Body, - Headers: bt.Headers, - Duration: r.cfg.duration, - Connections: r.cfg.connections, - Workers: r.cfg.workers, - WarmupTime: r.cfg.warmup, - KeepAlive: true, - H2C: isH2, - H2Connections: r.cfg.h2Conns, - H2MaxStreams: r.cfg.h2Streams, + cfg := loadgen.Config{ + URL: fmt.Sprintf("http://%s:%s%s", serverHost, r.cfg.port, bt.Path), + Method: bt.Method, + Body: bt.Body, + Headers: bt.Headers, + Duration: r.cfg.duration, + Connections: r.cfg.connections, + Workers: r.cfg.workers, + Warmup: r.cfg.warmup, + HTTP2: isH2, + HTTP2Options: loadgen.HTTP2Options{ + Connections: r.cfg.h2Conns, + MaxStreams: r.cfg.h2Streams, + }, } // Fetch baseline server metrics before benchmark (remote mode only) @@ -174,13 +176,17 @@ func (r *benchRunner) runServerBenchmarks(ctx context.Context, serverType, serve } // In remote mode, wrap the benchmark with retry logic - var result *bench.Result + var result *loadgen.Result var err error if r.cfg.remoteMode { result, err = runBenchmarkWithRetry(ctx, cfg, r.rc, serverType, r.cfg.benchmarkTimeout, r.cfg.serverRetryInterval) } else { - benchmarker := bench.New(cfg) + benchmarker, benchErr := loadgen.New(cfg) + if benchErr != nil { + log.Printf("ERROR: Failed to create benchmarker for %s/%s: %v — continuing with next benchmark", serverType, bt.Name, benchErr) + continue + } result, err = benchmarker.Run(ctx) } @@ -204,21 +210,21 @@ func (r *benchRunner) runServerBenchmarks(ctx context.Context, serverType, serve log.Printf("Completed: %.2f req/s, avg latency: %s", result.RequestsPerSec, result.Latency.Avg) - serverResult := result.ToServerResult(serverType, bt.Name, bt.Method, bt.Path) + serverResult := checkpoint.NewServerResult(result, serverType, bt.Name, bt.Method, bt.Path) // Fetch post-benchmark server metrics and compute deltas if r.cfg.remoteMode && r.rc != nil { if postMetrics, err := r.rc.GetServerMetrics(ctx); err == nil { sm := serverResult.Metrics if sm == nil { - sm = &bench.SystemMetrics{} + sm = &checkpoint.SystemMetrics{} } sm.ServerCPUPercent = postMetrics.CPUPercent sm.ServerCPUUserPercent = postMetrics.CPUUserPercent sm.ServerCPUSysPercent = postMetrics.CPUSysPercent sm.ServerMemoryRSSMB = postMetrics.MemoryRSSMB if postMetrics.HasGC { - gc := &bench.GCPauseStats{ + gc := &checkpoint.GCPauseStats{ TotalPauseMs: postMetrics.GCTotalPauseMs, MaxPauseMs: postMetrics.GCMaxPauseMs, NumGC: postMetrics.GCNumGC, @@ -253,7 +259,7 @@ func (r *benchRunner) runServerBenchmarks(ctx context.Context, serverType, serve // saveResults writes final output files, dashboard JSON, and logs the summary. func (r *benchRunner) saveResults(completedBefore, skipped int) { if r.cfg.mergeFile != "" { - if otherCP, err := bench.LoadCheckpoint(r.cfg.mergeFile); err == nil { + if otherCP, err := checkpoint.LoadCheckpoint(r.cfg.mergeFile); err == nil { beforeMerge := len(r.checkpoint.Results) r.checkpoint.MergeResults(otherCP) afterMerge := len(r.checkpoint.Results) @@ -326,7 +332,7 @@ func (r *benchRunner) saveResults(completedBefore, skipped int) { } // runBenchmarkWithRetry runs a benchmark with automatic retry on server failure. -func runBenchmarkWithRetry(ctx context.Context, cfg bench.Config, rc *RemoteController, serverType string, timeout, interval time.Duration) (*bench.Result, error) { +func runBenchmarkWithRetry(ctx context.Context, cfg loadgen.Config, rc *RemoteController, serverType string, timeout, interval time.Duration) (*loadgen.Result, error) { deadline := time.Now().Add(timeout) var lastErr error @@ -383,7 +389,10 @@ func runBenchmarkWithRetry(ctx context.Context, cfg bench.Config, rc *RemoteCont } // Run the benchmark - benchmarker := bench.New(cfg) + benchmarker, benchErr := loadgen.New(cfg) + if benchErr != nil { + return nil, benchErr + } result, err := benchmarker.Run(ctx) if err != nil { // Check if it's a connection error (server might have died) diff --git a/go.mod b/go.mod index 6ad33ba..ca3a28c 100644 --- a/go.mod +++ b/go.mod @@ -16,6 +16,7 @@ require ( github.com/gin-gonic/gin v1.12.0 github.com/go-chi/chi/v5 v5.2.5 github.com/goceleris/celeris v1.0.0 + github.com/goceleris/loadgen v1.0.0 github.com/gofiber/fiber/v2 v2.52.12 github.com/google/uuid v1.6.0 github.com/hertz-contrib/http2 v0.1.8 diff --git a/go.sum b/go.sum index b3b0410..8c0118a 100644 --- a/go.sum +++ b/go.sum @@ -141,6 +141,8 @@ github.com/goccy/go-yaml v1.19.2 h1:PmFC1S6h8ljIz6gMRBopkjP1TVT7xuwrButHID66PoM= github.com/goccy/go-yaml v1.19.2/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= github.com/goceleris/celeris v1.0.0 h1:MZsvsQVqck4lSxVom+6w02IHJva5hgwvydlMFtFTzP0= github.com/goceleris/celeris v1.0.0/go.mod h1:RiNVnUsmvfsPxGQaT7XQduPCF+amSn1AsipteFNO+2o= +github.com/goceleris/loadgen v1.0.0 h1:3ViKRy+r1MUXtRvNrgWekZd1g9+9e+Yt/v43LnhUL2k= +github.com/goceleris/loadgen v1.0.0/go.mod h1:/GSoaCJ2VR2Kt/nzfRLrJTXLrZN5lEF+bR9AqfzQRTc= github.com/gofiber/fiber/v2 v2.52.12 h1:0LdToKclcPOj8PktUdIKo9BUohjjwfnQl42Dhw8/WUw= github.com/gofiber/fiber/v2 v2.52.12/go.mod h1:YEcBbO/FB+5M1IZNBP9FO3J9281zgPAreiI1oqg8nDw= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= diff --git a/internal/dashboard/format.go b/internal/dashboard/format.go index f6c1b47..96e0624 100644 --- a/internal/dashboard/format.go +++ b/internal/dashboard/format.go @@ -6,8 +6,8 @@ import ( "strings" "time" - "github.com/goceleris/benchmarks/internal/bench" "github.com/goceleris/benchmarks/internal/version" + "github.com/goceleris/loadgen/checkpoint" ) // DashboardData is the top-level dashboard JSON structure. @@ -127,7 +127,7 @@ type Fastest struct { } // ConvertToDashboard converts a BenchmarkOutput to the dashboard JSON format. -func ConvertToDashboard(output *bench.BenchmarkOutput, env EnvironmentInfo) *DashboardData { +func ConvertToDashboard(output *checkpoint.BenchmarkOutput, env EnvironmentInfo) *DashboardData { // Group results by benchmark type suiteMap := make(map[string][]ServerBenchmark) suiteOrder := []string{} @@ -232,7 +232,7 @@ func ConvertToDashboard(output *bench.BenchmarkOutput, env EnvironmentInfo) *Das } // convertSystemMetrics converts bench.SystemMetrics to DashboardSystemMetrics. -func convertSystemMetrics(m *bench.SystemMetrics) *DashboardSystemMetrics { +func convertSystemMetrics(m *checkpoint.SystemMetrics) *DashboardSystemMetrics { dm := &DashboardSystemMetrics{ ServerCPUPercent: m.ServerCPUPercent, ServerCPUUserPercent: m.ServerCPUUserPercent, @@ -355,7 +355,7 @@ func parseCelerisObjective(server string) string { } // parseLatency converts string latency values to float64 milliseconds. -func parseLatency(l bench.LatencyResult) DashboardLatency { +func parseLatency(l checkpoint.LatencyResult) DashboardLatency { return DashboardLatency{ Avg: parseDurationMs(l.Avg), Max: parseDurationMs(l.Max), diff --git a/internal/dashboard/format_test.go b/internal/dashboard/format_test.go index 5b1a6aa..71ff31a 100644 --- a/internal/dashboard/format_test.go +++ b/internal/dashboard/format_test.go @@ -3,8 +3,9 @@ package dashboard import ( "testing" - "github.com/goceleris/benchmarks/internal/bench" "github.com/goceleris/benchmarks/internal/version" + "github.com/goceleris/loadgen" + "github.com/goceleris/loadgen/checkpoint" ) func TestClassifyCategory(t *testing.T) { @@ -163,10 +164,10 @@ func TestParseDurationMs(t *testing.T) { } func TestConvertToDashboard(t *testing.T) { - output := &bench.BenchmarkOutput{ + output := &checkpoint.BenchmarkOutput{ Timestamp: "2024-01-01T00_00_00Z", Architecture: "arm64", - Config: bench.BenchmarkConfig{ + Config: checkpoint.BenchmarkConfig{ Duration: "30s", DurationSecs: 30, WarmupSecs: 5, @@ -176,7 +177,7 @@ func TestConvertToDashboard(t *testing.T) { H2MaxStreams: 100, BodySizeBytes: 2048, }, - Results: []bench.ServerResult{ + Results: []checkpoint.ServerResult{ { Server: "gin-h1", Benchmark: "simple", @@ -187,7 +188,7 @@ func TestConvertToDashboard(t *testing.T) { DurationSecs: 30.1, TotalRequests: 3000000, Errors: 0, - Latency: bench.LatencyResult{ + Latency: checkpoint.LatencyResult{ Avg: "500us", Max: "5ms", P50: "400us", @@ -204,7 +205,7 @@ func TestConvertToDashboard(t *testing.T) { DurationSecs: 30.0, TotalRequests: 2850000, Errors: 0, - Latency: bench.LatencyResult{ + Latency: checkpoint.LatencyResult{ Avg: "550us", Max: "6ms", P50: "450us", @@ -219,7 +220,7 @@ func TestConvertToDashboard(t *testing.T) { RequestsPerSec: 80000, DurationSecs: 30.0, TotalRequests: 2400000, - Latency: bench.LatencyResult{ + Latency: checkpoint.LatencyResult{ Avg: "600us", Max: "7ms", P50: "500us", @@ -235,7 +236,7 @@ func TestConvertToDashboard(t *testing.T) { ThroughputBPS: 100000000, DurationSecs: 30.0, TotalRequests: 6000000, - Latency: bench.LatencyResult{ + Latency: checkpoint.LatencyResult{ Avg: "250us", Max: "2ms", P50: "200us", @@ -251,7 +252,7 @@ func TestConvertToDashboard(t *testing.T) { ThroughputBPS: 150000000, DurationSecs: 30.0, TotalRequests: 9000000, - Latency: bench.LatencyResult{ + Latency: checkpoint.LatencyResult{ Avg: "150us", Max: "1.5ms", P50: "120us", @@ -435,21 +436,21 @@ func TestConvertToDashboard(t *testing.T) { } func TestConvertToDashboardWithSystemMetrics(t *testing.T) { - output := &bench.BenchmarkOutput{ - Config: bench.BenchmarkConfig{ + output := &checkpoint.BenchmarkOutput{ + Config: checkpoint.BenchmarkConfig{ Workers: 32, Connections: 64, }, - Results: []bench.ServerResult{ + Results: []checkpoint.ServerResult{ { Server: "gin-h1", Benchmark: "simple", RequestsPerSec: 100000, - Metrics: &bench.SystemMetrics{ + Metrics: &checkpoint.SystemMetrics{ ClientCPUPercent: 75.5, ServerCPUPercent: 60.2, ServerMemoryRSSMB: 48.3, - Timeseries: []bench.TimeseriesPoint{ + Timeseries: []loadgen.TimeseriesPoint{ {TimestampSec: 1.0, RequestsPerSec: 95000}, {TimestampSec: 2.0, RequestsPerSec: 102000}, }, @@ -476,8 +477,8 @@ func TestConvertToDashboardWithSystemMetrics(t *testing.T) { } func TestConvertToDashboardEmpty(t *testing.T) { - output := &bench.BenchmarkOutput{ - Results: []bench.ServerResult{}, + output := &checkpoint.BenchmarkOutput{ + Results: []checkpoint.ServerResult{}, } result := ConvertToDashboard(output, EnvironmentInfo{}) From 8013d88527597e9cc1b3cc7e84ded711ab419237 Mon Sep 17 00:00:00 2001 From: Albert Bausili Date: Sun, 29 Mar 2026 04:58:28 +0200 Subject: [PATCH 2/2] =?UTF-8?q?fix:=20goimports=20grouping=20=E2=80=94=20s?= =?UTF-8?q?eparate=20local=20from=20third-party=20imports?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cmd/bench/runner.go | 3 ++- internal/dashboard/format.go | 3 ++- internal/dashboard/format_test.go | 3 ++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/cmd/bench/runner.go b/cmd/bench/runner.go index b530274..b18bc11 100644 --- a/cmd/bench/runner.go +++ b/cmd/bench/runner.go @@ -14,9 +14,10 @@ import ( "syscall" "time" - "github.com/goceleris/benchmarks/internal/dashboard" "github.com/goceleris/loadgen" "github.com/goceleris/loadgen/checkpoint" + + "github.com/goceleris/benchmarks/internal/dashboard" ) // benchRunner coordinates benchmark execution across servers. diff --git a/internal/dashboard/format.go b/internal/dashboard/format.go index 96e0624..9bc1a0c 100644 --- a/internal/dashboard/format.go +++ b/internal/dashboard/format.go @@ -6,8 +6,9 @@ import ( "strings" "time" - "github.com/goceleris/benchmarks/internal/version" "github.com/goceleris/loadgen/checkpoint" + + "github.com/goceleris/benchmarks/internal/version" ) // DashboardData is the top-level dashboard JSON structure. diff --git a/internal/dashboard/format_test.go b/internal/dashboard/format_test.go index 71ff31a..0c27f82 100644 --- a/internal/dashboard/format_test.go +++ b/internal/dashboard/format_test.go @@ -3,9 +3,10 @@ package dashboard import ( "testing" - "github.com/goceleris/benchmarks/internal/version" "github.com/goceleris/loadgen" "github.com/goceleris/loadgen/checkpoint" + + "github.com/goceleris/benchmarks/internal/version" ) func TestClassifyCategory(t *testing.T) {