R2: load-test harness + contour resource observability
CI / changes (pull_request) Successful in 2s
CI / unit (pull_request) Successful in 9s
CI / integration (pull_request) Successful in 11s
CI / ui (pull_request) Successful in 38s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Failing after 3s
CI / changes (pull_request) Successful in 2s
CI / unit (pull_request) Successful in 9s
CI / integration (pull_request) Successful in 11s
CI / ui (pull_request) Successful in 38s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Failing after 3s
New scrabble/loadtest module (the pre-release stress harness): seeds 1000 guest + 10000 durable accounts with pre-created sessions directly in Postgres (token hash matches backend/internal/session), drives virtual players through the edge protocol (real 2-4p games assembled via invitations, mid-ranked legal moves generated locally by the embedded scrabble-solver — the edge carries no board, so the client replays history), plus nudge/chat/check-word/draft/profile/stats and a gateway-hammer that verifies the rate limiter. Prints a trip-report summary (per-op latency percentiles, result codes, live-event tally). Go unit tests cover the pure pieces; the DAWG-backed move test runs under BACKEND_DICT_DIR. Contour: add cAdvisor + postgres_exporter + a 'Scrabble - Resources' Grafana dashboard and the two Prometheus scrape jobs, for the R2/R7 stress-run resource baseline. CI: gate ./loadtest/... (path filter + vet/build/test). Docs: TESTING, ARCHITECTURE, project CLAUDE repo layout.
This commit is contained in:
@@ -0,0 +1,45 @@
|
||||
package scenario
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"scrabble/loadtest/internal/seed"
|
||||
)
|
||||
|
||||
// HammerConfig parameterises the gateway-hammer: how many concurrent callers and for
|
||||
// how long to deliberately exceed the per-user rate limit from a single account.
|
||||
type HammerConfig struct {
|
||||
Workers int
|
||||
Duration time.Duration
|
||||
}
|
||||
|
||||
// DefaultHammer returns a hammer that comfortably exceeds the 300/min per-user limit.
|
||||
func DefaultHammer() HammerConfig {
|
||||
return HammerConfig{Workers: 20, Duration: 15 * time.Second}
|
||||
}
|
||||
|
||||
// Hammer drives games.list from a single account far above the per-user rate limit to
|
||||
// verify the limiter holds — rejections surface as the "rate_limited" code — and to
|
||||
// measure its cost. Every call is recorded under "hammer:games.list" so the report
|
||||
// shows the ok/rate_limited split and the rejection latency separately from the
|
||||
// realistic traffic.
|
||||
func (d *Driver) Hammer(ctx context.Context, acc seed.Account, cfg HammerConfig) {
|
||||
runCtx, cancel := context.WithTimeout(ctx, cfg.Duration)
|
||||
defer cancel()
|
||||
d.log.Info("gateway-hammer", "workers", cfg.Workers, "duration", cfg.Duration)
|
||||
var wg sync.WaitGroup
|
||||
for w := 0; w < cfg.Workers; w++ {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for runCtx.Err() == nil {
|
||||
t0 := time.Now()
|
||||
_, code, _ := d.edge.GamesList(runCtx, acc.Token)
|
||||
d.rec.Record("hammer:games.list", code, time.Since(t0))
|
||||
}
|
||||
}()
|
||||
}
|
||||
wg.Wait()
|
||||
}
|
||||
Reference in New Issue
Block a user