diff --git a/docs/ops/100k-capacity-baseline.md b/docs/ops/100k-capacity-baseline.md index d4d9d444..2675cdcc 100644 --- a/docs/ops/100k-capacity-baseline.md +++ b/docs/ops/100k-capacity-baseline.md @@ -84,7 +84,8 @@ go run ./cmd/load-sim \ -connect-rate 100 \ -send-interval 10s \ -duration 2m \ - -template 0200 + -template 0200 \ + -send=false ``` 1. 100 连接 smoke test。 diff --git a/docs/ops/vehicle-ingest-runbook.md b/docs/ops/vehicle-ingest-runbook.md index 3f016149..c37ac248 100644 --- a/docs/ops/vehicle-ingest-runbook.md +++ b/docs/ops/vehicle-ingest-runbook.md @@ -104,7 +104,8 @@ go run ./cmd/load-sim \ -connect-rate 100 \ -send-interval 10s \ -duration 2m \ - -template 0200 + -template 0200 \ + -send=false ``` ## 告警阈值建议 diff --git a/docs/superpowers/plans/2026-07-03-100k-vehicle-ingest-hardening.md b/docs/superpowers/plans/2026-07-03-100k-vehicle-ingest-hardening.md index 43da3932..7ff6b6fe 100644 --- a/docs/superpowers/plans/2026-07-03-100k-vehicle-ingest-hardening.md +++ b/docs/superpowers/plans/2026-07-03-100k-vehicle-ingest-hardening.md @@ -488,7 +488,7 @@ Extend load-sim to open N TCP connections and keep them open for `duration`, wit Expected CLI: ```bash -go run ./cmd/load-sim -protocol jt808 -addr 115.29.187.205:808 -connections 10000 -send-interval 30s -duration 5m +go run ./cmd/load-sim -protocol jt808 -addr 115.29.187.205:808 -connections 10000 -send-interval 30s -duration 5m -send=false ``` - [ ] **Step 2: Run from non-production client** @@ -496,7 +496,7 @@ go run ./cmd/load-sim -protocol jt808 -addr 115.29.187.205:808 -connections 1000 Run a small smoke first: ```bash -go run ./cmd/load-sim -protocol jt808 -addr 115.29.187.205:808 -connections 100 -duration 30s +go run ./cmd/load-sim -protocol jt808 -addr 115.29.187.205:808 -connections 100 -duration 30s -send=false ``` Then run 10K only after smoke succeeds. diff --git a/go/vehicle-gateway/internal/loadsim/config.go b/go/vehicle-gateway/internal/loadsim/config.go index 3dabb107..454798fa 100644 --- a/go/vehicle-gateway/internal/loadsim/config.go +++ b/go/vehicle-gateway/internal/loadsim/config.go @@ -24,6 +24,7 @@ type Config struct { SendInterval time.Duration Duration time.Duration Template string + SendFrames bool } type FlagConfig struct { @@ -34,6 +35,7 @@ type FlagConfig struct { sendInterval time.Duration duration time.Duration template string + sendFrames bool } func RegisterFlags(fs *flag.FlagSet) *FlagConfig { @@ -45,6 +47,7 @@ func RegisterFlags(fs *flag.FlagSet) *FlagConfig { fs.DurationVar(&cfg.sendInterval, "send-interval", 10*time.Second, "frame send interval per connection") fs.DurationVar(&cfg.duration, "duration", 10*time.Minute, "load test duration") fs.StringVar(&cfg.template, "template", "", "frame template name") + fs.BoolVar(&cfg.sendFrames, "send", true, "send protocol frames while connections are open") return cfg } @@ -78,5 +81,6 @@ func (c *FlagConfig) Build() (Config, error) { SendInterval: c.sendInterval, Duration: c.duration, Template: strings.TrimSpace(c.template), + SendFrames: c.sendFrames, }, nil } diff --git a/go/vehicle-gateway/internal/loadsim/config_test.go b/go/vehicle-gateway/internal/loadsim/config_test.go index 3938c3ca..d3c968a7 100644 --- a/go/vehicle-gateway/internal/loadsim/config_test.go +++ b/go/vehicle-gateway/internal/loadsim/config_test.go @@ -18,6 +18,7 @@ func TestConfigFromFlagsParsesCapacityKnobs(t *testing.T) { "-send-interval", "5s", "-duration", "30m", "-template", "0200", + "-send=false", }) if err != nil { t.Fatalf("parse flags: %v", err) @@ -49,6 +50,9 @@ func TestConfigFromFlagsParsesCapacityKnobs(t *testing.T) { if got.Template != "0200" { t.Fatalf("Template = %q", got.Template) } + if got.SendFrames { + t.Fatal("SendFrames = true, want false") + } } func TestConfigFromFlagsRejectsUnsafeValues(t *testing.T) { diff --git a/go/vehicle-gateway/internal/loadsim/runner.go b/go/vehicle-gateway/internal/loadsim/runner.go index f8cbc19a..6726a2e4 100644 --- a/go/vehicle-gateway/internal/loadsim/runner.go +++ b/go/vehicle-gateway/internal/loadsim/runner.go @@ -37,6 +37,9 @@ func (r Runner) Run(ctx context.Context, cfg Config) (Stats, error) { if err != nil { return Stats{}, err } + if !cfg.SendFrames { + payload = nil + } dial := r.Dial if dial == nil { dialer := &net.Dialer{Timeout: 5 * time.Second, KeepAlive: 30 * time.Second} @@ -68,7 +71,11 @@ func (r Runner) Run(ctx context.Context, cfg Config) (Stats, error) { go func() { defer wg.Done() defer conn.Close() - writeLoop(runCtx, conn, payload, cfg.SendInterval, &stats) + if cfg.SendFrames { + writeLoop(runCtx, conn, payload, cfg.SendInterval, &stats) + return + } + <-runCtx.Done() }() } } diff --git a/go/vehicle-gateway/internal/loadsim/runner_test.go b/go/vehicle-gateway/internal/loadsim/runner_test.go index 6d610b72..e2d79b35 100644 --- a/go/vehicle-gateway/internal/loadsim/runner_test.go +++ b/go/vehicle-gateway/internal/loadsim/runner_test.go @@ -34,6 +34,7 @@ func TestRunnerDialsTargetConnectionsAndWritesFrames(t *testing.T) { SendInterval: time.Millisecond, Duration: 5 * time.Millisecond, Template: "0200", + SendFrames: true, }) if err != nil { t.Fatalf("Run() error = %v", err) @@ -53,6 +54,41 @@ func TestRunnerDialsTargetConnectionsAndWritesFrames(t *testing.T) { } } +func TestRunnerCanHoldConnectionsWithoutWritingFrames(t *testing.T) { + var writes atomic.Int64 + runner := Runner{ + Dial: func(ctx context.Context, network, addr string) (net.Conn, error) { + return &recordingConn{ + write: func(p []byte) (int, error) { + writes.Add(1) + return len(p), nil + }, + close: func() error { return nil }, + }, nil + }, + } + + stats, err := runner.Run(context.Background(), Config{ + Protocol: ProtocolJT808, + Addr: "127.0.0.1:808", + Connections: 2, + ConnectRatePerSecond: 1000, + SendInterval: time.Millisecond, + Duration: 5 * time.Millisecond, + Template: "0200", + SendFrames: false, + }) + if err != nil { + t.Fatalf("Run() error = %v", err) + } + if stats.ConnectionsOpened != 2 { + t.Fatalf("ConnectionsOpened = %d, want 2", stats.ConnectionsOpened) + } + if stats.FramesWritten != 0 || writes.Load() != 0 { + t.Fatalf("frames written stats=%d writes=%d, want 0", stats.FramesWritten, writes.Load()) + } +} + type recordingConn struct { write func([]byte) (int, error) close func() error