summaryrefslogtreecommitdiff
path: root/internal/statsengine/snapshot.go
blob: f2b617bc5ac008b0907fd893ee083a0c0fdd38e7 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
package statsengine

import (
	"slices"
	"time"

	"ior/internal/types"
)

// TrendDirection is the direction of a time-window comparison.
type TrendDirection string

const (
	// TrendStable indicates no meaningful movement between windows.
	TrendStable TrendDirection = "stable"
	// TrendRising indicates the most recent window is higher than the previous one.
	TrendRising TrendDirection = "rising"
	// TrendFalling indicates the most recent window is lower than the previous one.
	TrendFalling TrendDirection = "falling"
)

// Trend describes movement between two equivalent time windows.
type Trend struct {
	Direction    TrendDirection
	DeltaPercent float64
}

// Snapshot is an immutable point-in-time view of all aggregated statistics.
type Snapshot struct {
	GeneratedAt time.Time
	Elapsed     time.Duration

	TotalSyscalls uint64
	TotalErrors   uint64
	TotalBytes    uint64

	SyscallRatePerSec float64
	ErrorRatePerSec   float64
	ReadBytesPerSec   float64
	WriteBytesPerSec  float64

	LatencyMeanNs float64
	GapMeanNs     float64

	LatencyTrend    Trend
	GapTrend        Trend
	ThroughputTrend Trend

	latencySeriesNs   []float64
	gapSeriesNs       []float64
	throughputSeriesB []float64

	syscalls  []SyscallSnapshot
	files     []FileSnapshot
	processes []ProcessSnapshot

	LatencyHistogram HistogramSnapshot
	GapHistogram     HistogramSnapshot
}

// SyscallSnapshot is the per-syscall view used by the syscall table.
type SyscallSnapshot struct {
	TraceID types.TraceId
	Name    string

	Count      uint64
	RatePerSec float64
	Errors     uint64
	Bytes      uint64

	LatencyMinNs  uint64
	LatencyMaxNs  uint64
	LatencyMeanNs float64
	LatencyP50Ns  uint64
	LatencyP95Ns  uint64
	LatencyP99Ns  uint64
}

// FileSnapshot is an aggregated per-file ranking entry.
type FileSnapshot struct {
	Path string

	Accesses     uint64
	BytesRead    uint64
	BytesWritten uint64

	AvgLatencyNs float64
	MaxLatencyNs uint64
}

// ProcessSnapshot is an aggregated per-process entry.
type ProcessSnapshot struct {
	PID  uint32
	Comm string

	Syscalls   uint64
	RatePerSec float64
	Bytes      uint64

	AvgLatencyNs float64
}

// HistogramBucketSnapshot is one bucket of a histogram snapshot.
type HistogramBucketSnapshot struct {
	Label   string
	LowerNs uint64
	UpperNs uint64
	Count   uint64
}

// HistogramSnapshot is an immutable histogram view at snapshot time.
type HistogramSnapshot struct {
	Total   uint64
	buckets []HistogramBucketSnapshot
}

// NewSnapshot creates a snapshot while defensively copying all slice-backed
// inputs so callers cannot mutate shared snapshot state.
func NewSnapshot(
	latencySeriesNs []float64,
	gapSeriesNs []float64,
	throughputSeriesB []float64,
	syscalls []SyscallSnapshot,
	files []FileSnapshot,
	processes []ProcessSnapshot,
	latencyHistogram HistogramSnapshot,
	gapHistogram HistogramSnapshot,
) Snapshot {
	return Snapshot{
		latencySeriesNs:   slices.Clone(latencySeriesNs),
		gapSeriesNs:       slices.Clone(gapSeriesNs),
		throughputSeriesB: slices.Clone(throughputSeriesB),
		syscalls:          slices.Clone(syscalls),
		files:             slices.Clone(files),
		processes:         slices.Clone(processes),
		LatencyHistogram:  latencyHistogram.Clone(),
		GapHistogram:      gapHistogram.Clone(),
	}
}

// NewHistogramSnapshot creates an immutable histogram snapshot by copying
// bucket storage.
func NewHistogramSnapshot(total uint64, buckets []HistogramBucketSnapshot) HistogramSnapshot {
	return HistogramSnapshot{
		Total:   total,
		buckets: slices.Clone(buckets),
	}
}

// Clone returns a deep copy of the histogram snapshot.
func (h HistogramSnapshot) Clone() HistogramSnapshot {
	return HistogramSnapshot{
		Total:   h.Total,
		buckets: slices.Clone(h.buckets),
	}
}

// LatencySeriesNs returns latency sparkline samples.
// Callers must treat returned data as read-only.
func (s Snapshot) LatencySeriesNs() []float64 {
	return s.latencySeriesNs
}

// GapSeriesNs returns inter-syscall gap sparkline samples.
// Callers must treat returned data as read-only.
func (s Snapshot) GapSeriesNs() []float64 {
	return s.gapSeriesNs
}

// ThroughputSeriesB returns throughput sparkline samples.
// Callers must treat returned data as read-only.
func (s Snapshot) ThroughputSeriesB() []float64 {
	return s.throughputSeriesB
}

// Syscalls returns per-syscall snapshot rows.
// Callers must treat returned data as read-only.
func (s Snapshot) Syscalls() []SyscallSnapshot {
	return s.syscalls
}

// SyscallsCount returns number of syscall rows without cloning backing slices.
func (s Snapshot) SyscallsCount() int {
	return len(s.syscalls)
}

// TopNSyscalls returns at most n per-syscall rows in ranking order.
// Callers must treat returned data as read-only.
func (s Snapshot) TopNSyscalls(n int) []SyscallSnapshot {
	return topN(s.syscalls, n)
}

// Files returns per-file snapshot rows.
// Callers must treat returned data as read-only.
func (s Snapshot) Files() []FileSnapshot {
	return s.files
}

// FilesCount returns number of file rows without cloning backing slices.
func (s Snapshot) FilesCount() int {
	return len(s.files)
}

// TopNFiles returns at most n file rows in ranking order.
// Callers must treat returned data as read-only.
func (s Snapshot) TopNFiles(n int) []FileSnapshot {
	return topN(s.files, n)
}

// Processes returns per-process snapshot rows.
// Callers must treat returned data as read-only.
func (s Snapshot) Processes() []ProcessSnapshot {
	return s.processes
}

// ProcessesCount returns number of process rows without cloning backing slices.
func (s Snapshot) ProcessesCount() int {
	return len(s.processes)
}

// TopNProcesses returns at most n process rows in ranking order.
// Callers must treat returned data as read-only.
func (s Snapshot) TopNProcesses(n int) []ProcessSnapshot {
	return topN(s.processes, n)
}

// Buckets returns histogram buckets.
// Callers must treat returned data as read-only.
func (h HistogramSnapshot) Buckets() []HistogramBucketSnapshot {
	return h.buckets
}

func topN[T any](rows []T, n int) []T {
	if n <= 0 || len(rows) == 0 {
		return nil
	}
	if n > len(rows) {
		n = len(rows)
	}
	return rows[:n:n]
}