master
go 317 lines 9.62 KB
Raw
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 package metrix
4
5 import (
6 "math"
7 "strconv"
8 "sync"
9 "testing"
10
11 "github.com/stretchr/testify/require"
12 )
13
14 func TestVecStoreScenarios(t *testing.T) {
15 tests := map[string]struct {
16 run func(t *testing.T)
17 }{
18 "snapshot gauge vec writes labeled series": {
19 run: func(t *testing.T) {
20 s := NewCollectorStore()
21 cc := cycleController(t, s)
22 vec := s.Write().SnapshotMeter("apache").Vec("state").Gauge("workers")
23
24 cc.BeginCycle()
25 vec.WithLabelValues("busy").Observe(7)
26 vec.WithLabelValues("idle").Observe(3)
27 cc.CommitCycleSuccess()
28
29 mustValue(t, s.Read(), "apache.workers", Labels{"state": "busy"}, 7)
30 mustValue(t, s.Read(), "apache.workers", Labels{"state": "idle"}, 3)
31 },
32 },
33 "snapshot vec scope shares label keys across metric families": {
34 run: func(t *testing.T) {
35 s := NewCollectorStore()
36 cc := cycleController(t, s)
37 vm := s.Write().SnapshotMeter("apache").Vec("state")
38 workers := vm.Gauge("workers")
39 requests := vm.Counter("requests_total")
40
41 cc.BeginCycle()
42 workers.WithLabelValues("busy").Observe(7)
43 requests.WithLabelValues("200").ObserveTotal(11)
44 cc.CommitCycleSuccess()
45
46 mustValue(t, s.Read(), "apache.workers", Labels{"state": "busy"}, 7)
47 mustValue(t, s.Read(), "apache.requests_total", Labels{"state": "200"}, 11)
48 },
49 },
50 "snapshot counter vec merges meter labels and vec labels": {
51 run: func(t *testing.T) {
52 s := NewCollectorStore()
53 cc := cycleController(t, s)
54 sm := s.Write().SnapshotMeter("http").WithLabels(Label{Key: "instance", Value: "job1"})
55 vec := sm.Vec("code").Counter("requests_total")
56
57 cc.BeginCycle()
58 vec.WithLabelValues("200").ObserveTotal(11)
59 cc.CommitCycleSuccess()
60
61 mustValue(t, s.Read(), "http.requests_total", Labels{"instance": "job1", "code": "200"}, 11)
62 },
63 },
64 "stateful counter vec preserves baseline and delta semantics": {
65 run: func(t *testing.T) {
66 s := NewCollectorStore()
67 cc := cycleController(t, s)
68 vec := s.Write().StatefulMeter("runtime").Vec("queue").Counter("jobs_total")
69
70 cc.BeginCycle()
71 vec.WithLabelValues("default").Add(5)
72 cc.CommitCycleSuccess()
73 mustValue(t, s.Read(), "runtime.jobs_total", Labels{"queue": "default"}, 5)
74 mustNoDelta(t, s.Read(), "runtime.jobs_total", Labels{"queue": "default"})
75
76 cc.BeginCycle()
77 vec.WithLabelValues("default").Add(2)
78 cc.CommitCycleSuccess()
79 mustValue(t, s.Read(), "runtime.jobs_total", Labels{"queue": "default"}, 7)
80 mustDelta(t, s.Read(), "runtime.jobs_total", Labels{"queue": "default"}, 2)
81 },
82 },
83 "vec scope validates label keys through family declaration": {
84 run: func(t *testing.T) {
85 s := NewCollectorStore()
86 vm := s.Write().SnapshotMeter("svc").Vec("zone", "zone")
87
88 expectPanic(t, func() {
89 _ = vm.Gauge("load")
90 })
91 },
92 },
93 "vec returns cached handle for same label values": {
94 run: func(t *testing.T) {
95 s := NewCollectorStore()
96 vec := s.Write().SnapshotMeter("svc").Vec("zone").Gauge("load")
97
98 a := vec.WithLabelValues("a")
99 b := vec.WithLabelValues("a")
100 require.Same(t, a, b, "expected same cached handle for repeated label values")
101 },
102 },
103 "vec handle cache remains race-safe under concurrent writes": {
104 run: func(t *testing.T) {
105 s := NewCollectorStore()
106 cc := cycleController(t, s)
107 vec := s.Write().SnapshotMeter("svc").Vec("zone").Gauge("load")
108
109 const (
110 workers = 32
111 iterations = 200
112 distinct = 64
113 )
114
115 cc.BeginCycle()
116 var wg sync.WaitGroup
117 wg.Add(workers)
118 for worker := range workers {
119 go func() {
120 defer wg.Done()
121 for i := range iterations {
122 label := strconv.Itoa((worker*iterations + i) % distinct)
123 vec.WithLabelValues(label).Observe(SampleValue(i))
124 }
125 }()
126 }
127 wg.Wait()
128 cc.CommitCycleSuccess()
129
130 seen := make(map[string]struct{}, distinct)
131 s.Read(ReadRaw()).ForEachByName("svc.load", func(labels LabelView, _ SampleValue) {
132 value, ok := labels.Get("zone")
133 require.True(t, ok, "expected zone label on vec series")
134 seen[value] = struct{}{}
135 })
136 require.Len(t, seen, distinct, "expected one committed scalar series per distinct vec label value")
137 },
138 },
139 "GetWithLabelValues validates label value count": {
140 run: func(t *testing.T) {
141 s := NewCollectorStore()
142 vec := s.Write().SnapshotMeter("svc").Vec("zone", "role").Gauge("load")
143
144 _, err := vec.GetWithLabelValues("only-one")
145 require.ErrorIs(t, err, errVecLabelValueCount)
146 expectPanic(t, func() {
147 _ = vec.WithLabelValues("only-one")
148 })
149 },
150 },
151 "vec with no label keys accepts empty label values": {
152 run: func(t *testing.T) {
153 s := NewCollectorStore()
154 cc := cycleController(t, s)
155 vec := s.Write().SnapshotMeter("svc").Vec().Gauge("load")
156
157 cc.BeginCycle()
158 vec.WithLabelValues().Observe(9)
159 cc.CommitCycleSuccess()
160
161 mustValue(t, s.Read(), "svc.load", nil, 9)
162 },
163 },
164 "vec constructor rejects invalid label keys": {
165 run: func(t *testing.T) {
166 s := NewCollectorStore()
167 expectPanic(t, func() {
168 _ = s.Write().SnapshotMeter("svc").Vec("zone", "zone").Gauge("load")
169 })
170 expectPanic(t, func() {
171 _ = s.Write().SnapshotMeter("svc").Vec("").Counter("requests_total")
172 })
173 },
174 },
175 "snapshot histogram vec writes and reads point": {
176 run: func(t *testing.T) {
177 s := NewCollectorStore()
178 cc := cycleController(t, s)
179 vec := s.Write().SnapshotMeter("mysql").Vec("database").Histogram(
180 "query_seconds",
181 WithHistogramBounds(0.1, 0.5),
182 )
183
184 cc.BeginCycle()
185 vec.WithLabelValues("db1").ObservePoint(HistogramPoint{
186 Count: 2,
187 Sum: 0.4,
188 Buckets: []BucketPoint{
189 {UpperBound: 0.1, CumulativeCount: 1},
190 {UpperBound: 0.5, CumulativeCount: 2},
191 },
192 })
193 cc.CommitCycleSuccess()
194
195 p, ok := s.Read().Histogram("mysql.query_seconds", Labels{"database": "db1"})
196 require.True(t, ok, "expected histogram point")
197 require.Equal(t, SampleValue(2), p.Count)
198 require.Equal(t, SampleValue(0.4), p.Sum)
199 require.Len(t, p.Buckets, 2)
200 },
201 },
202 "stateful histogram vec observes cumulative samples": {
203 run: func(t *testing.T) {
204 s := NewCollectorStore()
205 cc := cycleController(t, s)
206 vec := s.Write().StatefulMeter("mysql").Vec("database").Histogram(
207 "query_seconds",
208 WithHistogramBounds(0.1, 0.5),
209 )
210
211 cc.BeginCycle()
212 vec.WithLabelValues("db1").Observe(0.05)
213 cc.CommitCycleSuccess()
214
215 cc.BeginCycle()
216 vec.WithLabelValues("db1").Observe(0.2)
217 cc.CommitCycleSuccess()
218
219 p, ok := s.Read().Histogram("mysql.query_seconds", Labels{"database": "db1"})
220 require.True(t, ok, "expected histogram point")
221 require.Equal(t, SampleValue(2), p.Count)
222 require.LessOrEqual(t, math.Abs(float64(p.Sum-0.25)), 1e-9)
223 require.Len(t, p.Buckets, 2)
224 },
225 },
226 "snapshot summary vec writes and reads point": {
227 run: func(t *testing.T) {
228 s := NewCollectorStore()
229 cc := cycleController(t, s)
230 vec := s.Write().SnapshotMeter("mysql").Vec("database").Summary(
231 "query_seconds",
232 WithSummaryQuantiles(0.5),
233 )
234
235 cc.BeginCycle()
236 vec.WithLabelValues("db1").ObservePoint(SummaryPoint{
237 Count: 2,
238 Sum: 0.4,
239 Quantiles: []QuantilePoint{
240 {Quantile: 0.5, Value: 0.2},
241 },
242 })
243 cc.CommitCycleSuccess()
244
245 p, ok := s.Read().Summary("mysql.query_seconds", Labels{"database": "db1"})
246 require.True(t, ok, "expected summary point")
247 require.Equal(t, SampleValue(2), p.Count)
248 require.Equal(t, SampleValue(0.4), p.Sum)
249 require.Len(t, p.Quantiles, 1)
250 },
251 },
252 "stateful summary vec observes cumulative samples": {
253 run: func(t *testing.T) {
254 s := NewCollectorStore()
255 cc := cycleController(t, s)
256 vec := s.Write().StatefulMeter("mysql").Vec("database").Summary("query_seconds")
257
258 cc.BeginCycle()
259 vec.WithLabelValues("db1").Observe(0.1)
260 cc.CommitCycleSuccess()
261
262 cc.BeginCycle()
263 vec.WithLabelValues("db1").Observe(0.2)
264 cc.CommitCycleSuccess()
265
266 p, ok := s.Read().Summary("mysql.query_seconds", Labels{"database": "db1"})
267 require.True(t, ok, "expected summary point")
268 require.Equal(t, SampleValue(2), p.Count)
269 require.LessOrEqual(t, math.Abs(float64(p.Sum-0.3)), 1e-9)
270 },
271 },
272 "snapshot stateset vec enable writes active state": {
273 run: func(t *testing.T) {
274 s := NewCollectorStore()
275 cc := cycleController(t, s)
276 vec := s.Write().SnapshotMeter("net").Vec("nic").StateSet(
277 "link_state",
278 WithStateSetStates("up", "down"),
279 WithStateSetMode(ModeEnum),
280 )
281
282 cc.BeginCycle()
283 vec.WithLabelValues("eth0").Enable("up")
284 cc.CommitCycleSuccess()
285
286 p, ok := s.Read().StateSet("net.link_state", Labels{"nic": "eth0"})
287 require.True(t, ok, "expected stateset point")
288 require.True(t, p.States["up"], "unexpected stateset: %#v", p.States)
289 require.False(t, p.States["down"], "unexpected stateset: %#v", p.States)
290 },
291 },
292 "stateful stateset vec observe writes full state": {
293 run: func(t *testing.T) {
294 s := NewCollectorStore()
295 cc := cycleController(t, s)
296 vec := s.Write().StatefulMeter("net").Vec("nic").StateSet(
297 "link_state",
298 WithStateSetStates("up", "down"),
299 WithStateSetMode(ModeBitSet),
300 )
301
302 cc.BeginCycle()
303 vec.WithLabelValues("eth0").ObserveStateSet(StateSetPoint{States: map[string]bool{"down": true}})
304 cc.CommitCycleSuccess()
305
306 p, ok := s.Read().StateSet("net.link_state", Labels{"nic": "eth0"})
307 require.True(t, ok, "expected stateset point")
308 require.False(t, p.States["up"], "unexpected stateset: %#v", p.States)
309 require.True(t, p.States["down"], "unexpected stateset: %#v", p.States)
310 },
311 },
312 }
313
314 for name, tc := range tests {
315 t.Run(name, tc.run)
316 }
317 }