From 28402bda7b1e114dae2e3abd0b259c42edfcd5a8 Mon Sep 17 00:00:00 2001 From: PJ Date: Sat, 22 Aug 2026 21:41:40 +0530 Subject: [PATCH] fix(trace): cpu_percent is omitted when no CPU was sampled The replay UI already declared it optional, so a web run's steps recording a zero per step were the only thing claiming a reading. --- internal/runner/runner.go | 2 +- internal/runner/runner_test.go | 5 ++++- internal/trace/writer.go | 8 +++++--- internal/trace/writer_test.go | 6 ++++-- 4 files changed, 14 insertions(+), 7 deletions(-) diff --git a/internal/runner/runner.go b/internal/runner/runner.go index 91be589..9090e41 100644 --- a/internal/runner/runner.go +++ b/internal/runner/runner.go @@ -1702,7 +1702,7 @@ func captureMetrics(ctx context.Context, options Options, logger *slog.Logger, s logger.Warn("metrics capture failed", "step", stepIndex, "err", err) return nil, true } - if sample.CPUPercent == 0 && sample.HeapBytes == 0 && sample.TotalMemoryBytes == 0 { + if sample.CPUPercent == nil && sample.HeapBytes == 0 && sample.TotalMemoryBytes == 0 { return nil, true } return &trace.Metrics{ diff --git a/internal/runner/runner_test.go b/internal/runner/runner_test.go index 1daf5ff..9ddae5a 100644 --- a/internal/runner/runner_test.go +++ b/internal/runner/runner_test.go @@ -1525,7 +1525,10 @@ func readTraceLines(t *testing.T, directory string) []traceStepLine { func TestRunner_ParallelFetchCallsAllDriverMethods(t *testing.T) { state := newHarness(t) - state.mock.MetricsData = driver.Metrics{CPUPercent: 5.0, HeapBytes: 1024, TotalMemoryBytes: 4096} + cpuPercent := 5.0 + state.mock.MetricsData = driver.Metrics{ + CPUPercent: &cpuPercent, HeapBytes: 1024, TotalMemoryBytes: 4096, + } state.mock.LogEntries = []driver.LogEntry{ {UnixMillis: 1000, Level: "E", Tag: "test", Message: "boom"}, } diff --git a/internal/trace/writer.go b/internal/trace/writer.go index a8c4ee6..5f618a6 100644 --- a/internal/trace/writer.go +++ b/internal/trace/writer.go @@ -104,9 +104,11 @@ type ExtractorChange struct { } type Metrics struct { - CPUPercent float64 `json:"cpu_percent"` - HeapBytes int64 `json:"heap_bytes,omitempty"` - TotalMemoryBytes int64 `json:"total_memory_bytes,omitempty"` + // CPUPercent is omitted when the driver samples no CPU, so a web run's + // steps do not each record a zero the sampler never took. + CPUPercent *float64 `json:"cpu_percent,omitempty"` + HeapBytes int64 `json:"heap_bytes,omitempty"` + TotalMemoryBytes int64 `json:"total_memory_bytes,omitempty"` } // The three producers an action can come from. The spec's setup drives the app diff --git a/internal/trace/writer_test.go b/internal/trace/writer_test.go index 668cd2a..f0dd12c 100644 --- a/internal/trace/writer_test.go +++ b/internal/trace/writer_test.go @@ -228,6 +228,7 @@ func TestWriteStep_DiagnosticsRoundTrip(t *testing.T) { directory := t.TempDir() writer, _ := NewWriter(directory) defer writer.Close() + cpuPercent := 12.5 step := Step{ Index: 4, @@ -244,7 +245,7 @@ func TestWriteStep_DiagnosticsRoundTrip(t *testing.T) { ExtractorChanges: map[string]ExtractorChange{ "balance": {Prev: json.RawMessage(`10`), Curr: json.RawMessage(`-5`)}, }, - Metrics: &Metrics{CPUPercent: 12.5, HeapBytes: 4096}, + Metrics: &Metrics{CPUPercent: &cpuPercent, HeapBytes: 4096}, Exceptions: []Exception{{Class: "NullPointerException", Message: "boom"}}, } if err := writer.WriteStep(step); err != nil { @@ -271,7 +272,8 @@ func TestWriteStep_DiagnosticsRoundTrip(t *testing.T) { if c := got.ExtractorChanges["balance"]; string(c.Prev) != `10` || string(c.Curr) != `-5` { t.Errorf("extractor change round-trip wrong: %+v", c) } - if got.Metrics == nil || got.Metrics.CPUPercent != 12.5 || got.Metrics.HeapBytes != 4096 { + if got.Metrics == nil || got.Metrics.CPUPercent == nil || + *got.Metrics.CPUPercent != 12.5 || got.Metrics.HeapBytes != 4096 { t.Errorf("metrics round-trip wrong: %+v", got.Metrics) } if len(got.Exceptions) != 1 || got.Exceptions[0].Class != "NullPointerException" {