From 0d4ef22478a470d86ce907beedcaa726d0d46c73 Mon Sep 17 00:00:00 2001 From: Paul Buetow Date: Tue, 24 Feb 2026 12:09:31 +0200 Subject: statsengine: stop cloning snapshot accessors on reads --- internal/statsengine/snapshot.go | 35 +++++++++++++++++++++-------------- internal/statsengine/snapshot_test.go | 14 +++++++------- 2 files changed, 28 insertions(+), 21 deletions(-) (limited to 'internal/statsengine') diff --git a/internal/statsengine/snapshot.go b/internal/statsengine/snapshot.go index 6583514..6e38739 100644 --- a/internal/statsengine/snapshot.go +++ b/internal/statsengine/snapshot.go @@ -154,24 +154,28 @@ func (h HistogramSnapshot) Clone() HistogramSnapshot { } } -// LatencySeriesNs returns a defensive copy of latency sparkline samples. +// LatencySeriesNs returns latency sparkline samples. +// Callers must treat returned data as read-only. func (s Snapshot) LatencySeriesNs() []float64 { - return slices.Clone(s.latencySeriesNs) + return s.latencySeriesNs } -// GapSeriesNs returns a defensive copy of inter-syscall gap sparkline samples. +// GapSeriesNs returns inter-syscall gap sparkline samples. +// Callers must treat returned data as read-only. func (s Snapshot) GapSeriesNs() []float64 { - return slices.Clone(s.gapSeriesNs) + return s.gapSeriesNs } -// ThroughputSeriesB returns a defensive copy of throughput sparkline samples. +// ThroughputSeriesB returns throughput sparkline samples. +// Callers must treat returned data as read-only. func (s Snapshot) ThroughputSeriesB() []float64 { - return slices.Clone(s.throughputSeriesB) + return s.throughputSeriesB } -// Syscalls returns a defensive copy of per-syscall snapshot rows. +// Syscalls returns per-syscall snapshot rows. +// Callers must treat returned data as read-only. func (s Snapshot) Syscalls() []SyscallSnapshot { - return slices.Clone(s.syscalls) + return s.syscalls } // SyscallsCount returns number of syscall rows without cloning backing slices. @@ -179,9 +183,10 @@ func (s Snapshot) SyscallsCount() int { return len(s.syscalls) } -// Files returns a defensive copy of per-file snapshot rows. +// Files returns per-file snapshot rows. +// Callers must treat returned data as read-only. func (s Snapshot) Files() []FileSnapshot { - return slices.Clone(s.files) + return s.files } // FilesCount returns number of file rows without cloning backing slices. @@ -189,9 +194,10 @@ func (s Snapshot) FilesCount() int { return len(s.files) } -// Processes returns a defensive copy of per-process snapshot rows. +// Processes returns per-process snapshot rows. +// Callers must treat returned data as read-only. func (s Snapshot) Processes() []ProcessSnapshot { - return slices.Clone(s.processes) + return s.processes } // ProcessesCount returns number of process rows without cloning backing slices. @@ -199,7 +205,8 @@ func (s Snapshot) ProcessesCount() int { return len(s.processes) } -// Buckets returns a defensive copy of histogram buckets. +// Buckets returns histogram buckets. +// Callers must treat returned data as read-only. func (h HistogramSnapshot) Buckets() []HistogramBucketSnapshot { - return slices.Clone(h.buckets) + return h.buckets } diff --git a/internal/statsengine/snapshot_test.go b/internal/statsengine/snapshot_test.go index 065eded..e5c3caa 100644 --- a/internal/statsengine/snapshot_test.go +++ b/internal/statsengine/snapshot_test.go @@ -58,7 +58,7 @@ func TestNewSnapshotDefensivelyCopiesSlices(t *testing.T) { } } -func TestSnapshotAccessorsReturnCopies(t *testing.T) { +func TestSnapshotAccessorsReturnReadOnlyViews(t *testing.T) { s := NewSnapshot( []float64{1}, []float64{2}, @@ -72,20 +72,20 @@ func TestSnapshotAccessorsReturnCopies(t *testing.T) { lat := s.LatencySeriesNs() lat[0] = 100 - if got := s.LatencySeriesNs()[0]; got != 1 { - t.Fatalf("latency accessor leaked mutability: got %v", got) + if got := s.LatencySeriesNs()[0]; got != 100 { + t.Fatalf("expected accessor to return backing slice view, got %v", got) } syscalls := s.Syscalls() syscalls[0].Name = "write" - if got := s.Syscalls()[0].Name; got != "read" { - t.Fatalf("syscalls accessor leaked mutability: got %q", got) + if got := s.Syscalls()[0].Name; got != "write" { + t.Fatalf("expected accessor to return backing slice view, got %q", got) } buckets := s.LatencyHistogram.Buckets() buckets[0].Count = 99 - if got := s.LatencyHistogram.Buckets()[0].Count; got != 1 { - t.Fatalf("bucket accessor leaked mutability: got %d", got) + if got := s.LatencyHistogram.Buckets()[0].Count; got != 99 { + t.Fatalf("expected accessor to return backing slice view, got %d", got) } } -- cgit v1.2.3