From 7e63fe68f7912da32641b453f6a372dec9c2042d Mon Sep 17 00:00:00 2001 From: Jon Ayers Date: Fri, 3 Apr 2026 16:26:14 -0500 Subject: [PATCH] fix: avoid instantiating a logger if provided /dev/null (#24027) - Adds some additional context to workspace traffic logging - Fails traffic tests if 0 bytes read from connection --- cli/clilog/clilog.go | 2 +- cli/exp_scaletest.go | 3 +++ scaletest/workspacetraffic/config.go | 6 ++++++ scaletest/workspacetraffic/run.go | 29 ++++++++++++++++++++++++++-- 4 files changed, 37 insertions(+), 3 deletions(-) diff --git a/cli/clilog/clilog.go b/cli/clilog/clilog.go index 50a7b1c834..81c87bb033 100644 --- a/cli/clilog/clilog.go +++ b/cli/clilog/clilog.go @@ -104,7 +104,7 @@ func (b *Builder) Build(inv *serpent.Invocation) (log slog.Logger, closeLog func addSinkIfProvided := func(sinkFn func(io.Writer) slog.Sink, loc string) error { switch loc { - case "": + case "", "/dev/null": case "/dev/stdout": sinks = append(sinks, sinkFn(inv.Stdout)) diff --git a/cli/exp_scaletest.go b/cli/exp_scaletest.go index d46cca9b58..8173d76c3d 100644 --- a/cli/exp_scaletest.go +++ b/cli/exp_scaletest.go @@ -1401,6 +1401,9 @@ func (r *RootCmd) scaletestWorkspaceTraffic() *serpent.Command { // Setup our workspace agent connection. config := workspacetraffic.Config{ AgentID: agent.ID, + WorkspaceID: ws.ID, + WorkspaceName: ws.Name, + AgentName: agent.Name, BytesPerTick: bytesPerTick, Duration: strategy.timeout, TickInterval: tickInterval, diff --git a/scaletest/workspacetraffic/config.go b/scaletest/workspacetraffic/config.go index 0948d35ea7..415eb2284d 100644 --- a/scaletest/workspacetraffic/config.go +++ b/scaletest/workspacetraffic/config.go @@ -12,6 +12,12 @@ import ( type Config struct { // AgentID is the workspace agent ID to which to connect. AgentID uuid.UUID `json:"agent_id"` + // WorkspaceID is the workspace ID, used for logging. + WorkspaceID uuid.UUID `json:"workspace_id"` + // WorkspaceName is the workspace name, used for logging. + WorkspaceName string `json:"workspace_name"` + // AgentName is the agent name, used for logging. + AgentName string `json:"agent_name"` // BytesPerTick is the number of bytes to send to the agent per tick. BytesPerTick int64 `json:"bytes_per_tick"` diff --git a/scaletest/workspacetraffic/run.go b/scaletest/workspacetraffic/run.go index b3fedf385c..80cb83fd43 100644 --- a/scaletest/workspacetraffic/run.go +++ b/scaletest/workspacetraffic/run.go @@ -76,7 +76,12 @@ func (r *Runner) Run(ctx context.Context, _ string, logs io.Writer) (err error) echo = r.cfg.Echo ) - logger = logger.With(slog.F("agent_id", agentID)) + logger = logger.With( + slog.F("agent_id", agentID), + slog.F("workspace_id", r.cfg.WorkspaceID), + slog.F("workspace_name", r.cfg.WorkspaceName), + slog.F("agent_name", r.cfg.AgentName), + ) logger.Debug(ctx, "config", slog.F("reconnecting_pty_id", reconnect), @@ -153,6 +158,14 @@ func (r *Runner) Run(ctx context.Context, _ string, logs io.Writer) (err error) conn.readMetrics = r.cfg.ReadMetrics conn.writeMetrics = r.cfg.WriteMetrics + logTrafficSummary := func() { + //nolint:gocritic + logger.Info(ctx, "traffic summary", + slog.F("actual_bytes_read", r.cfg.ReadMetrics.GetTotalBytes()), + slog.F("actual_bytes_written", r.cfg.WriteMetrics.GetTotalBytes()), + ) + } + // Create a ticker for sending data to the conn. tick := time.NewTicker(tickInterval) defer tick.Stop() @@ -179,10 +192,18 @@ func (r *Runner) Run(ctx context.Context, _ string, logs io.Writer) (err error) var waitCloseTimeoutCh <-chan struct{} deadlineCtxCh := deadlineCtx.Done() + deadlineReached := false wchRef, rchRef := wch, rch for { if wchRef == nil && rchRef == nil { - logger.Info(ctx, "reading and writing to agent complete! Closing connection") + logTrafficSummary() + if !deadlineReached { + return xerrors.Errorf("test did not complete: context canceled after %s of %s", + time.Since(start).Truncate(time.Second), r.cfg.Duration) + } + if r.cfg.ReadMetrics.GetTotalBytes() == 0 { + return xerrors.Errorf("zero bytes read from agent") + } return nil } @@ -192,23 +213,27 @@ func (r *Runner) Run(ctx context.Context, _ string, logs io.Writer) (err error) slog.F("write_done", wchRef == nil), slog.F("read_done", rchRef == nil), ) + logTrafficSummary() return xerrors.Errorf("timed out waiting for read/write to complete: %w", ctx.Err()) case <-deadlineCtxCh: go func() { _ = closeConn() }() deadlineCtxCh = nil // Only trigger once. + deadlineReached = true // Wait at most closeTimeout for the connection to close cleanly. waitCtx, cancel := context.WithTimeout(context.Background(), waitCloseTimeout) defer cancel() //nolint:revive // Only called once. waitCloseTimeoutCh = waitCtx.Done() case err = <-wchRef: if err != nil { + logTrafficSummary() return xerrors.Errorf("write to agent: %w", err) } wchRef = nil case err = <-rchRef: if err != nil { + logTrafficSummary() return xerrors.Errorf("read from agent: %w", err) } rchRef = nil