master
go 300 lines 8.86 KB
Raw
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 package metrix
4
5 import (
6 "math"
7 "testing"
8
9 "github.com/stretchr/testify/require"
10 )
11
12 func TestSummaryStoreScenarios(t *testing.T) {
13 tests := map[string]struct {
14 run func(t *testing.T)
15 }{
16 "snapshot summary stores NaN quantile values (sparse window -> gap downstream)": {
17 run: func(t *testing.T) {
18 s := NewCollectorStore()
19 cc := cycleController(t, s)
20 sum := s.Write().SnapshotMeter("svc").Summary("latency", WithSummaryQuantiles(0.5, 0.9))
21
22 cc.BeginCycle()
23 require.NotPanics(t, func() {
24 sum.ObservePoint(SummaryPoint{
25 Count: 0,
26 Sum: 0,
27 Quantiles: []QuantilePoint{
28 {Quantile: 0.5, Value: SampleValue(math.NaN())},
29 {Quantile: 0.9, Value: SampleValue(math.NaN())},
30 },
31 })
32 })
33 cc.CommitCycleSuccess()
34
35 point, ok := s.Read().Summary("svc.latency", nil)
36 require.True(t, ok)
37 require.Len(t, point.Quantiles, 2)
38 for _, q := range point.Quantiles {
39 require.Truef(t, math.IsNaN(float64(q.Value)),
40 "quantile %v value must be stored as NaN, got %v", q.Quantile, q.Value)
41 }
42 },
43 },
44 "snapshot summary count sum read and flatten": {
45 run: func(t *testing.T) {
46 s := NewCollectorStore()
47 cc := cycleController(t, s)
48 sm := s.Write().SnapshotMeter("svc")
49 sum := sm.Summary("latency")
50
51 cc.BeginCycle()
52 sum.ObservePoint(SummaryPoint{Count: 3, Sum: 1.2})
53 cc.CommitCycleSuccess()
54
55 mustSummary(t, s.Read(), "svc.latency", nil, SummaryPoint{Count: 3, Sum: 1.2})
56 _, ok := s.Read().Value("svc.latency", nil)
57 require.False(t, ok, "expected non-scalar summary unavailable via Value")
58
59 fr := s.Read(ReadFlatten())
60 mustValue(t, fr, "svc.latency_count", nil, 3)
61 mustValue(t, fr, "svc.latency_sum", nil, 1.2)
62 _, ok = fr.Summary("svc.latency", nil)
63 require.False(t, ok, "expected flattened reader to hide typed summary getter")
64 },
65 },
66 "snapshot summary quantiles are validated and flattened": {
67 run: func(t *testing.T) {
68 s := NewCollectorStore()
69 cc := cycleController(t, s)
70 sum := s.Write().SnapshotMeter("svc").Summary("latency", WithSummaryQuantiles(0.5, 0.9))
71
72 cc.BeginCycle()
73 sum.ObservePoint(SummaryPoint{
74 Count: 2,
75 Sum: 1.0,
76 Quantiles: []QuantilePoint{
77 {Quantile: 0.9, Value: 0.8},
78 {Quantile: 0.5, Value: 0.4},
79 },
80 })
81 cc.CommitCycleSuccess()
82
83 mustSummary(t, s.Read(), "svc.latency", nil, SummaryPoint{
84 Count: 2,
85 Sum: 1.0,
86 Quantiles: []QuantilePoint{
87 {Quantile: 0.5, Value: 0.4},
88 {Quantile: 0.9, Value: 0.8},
89 },
90 })
91
92 fr := s.Read(ReadFlatten())
93 mustValue(t, fr, "svc.latency", Labels{"quantile": "0.5"}, 0.4)
94 mustValue(t, fr, "svc.latency", Labels{"quantile": "0.9"}, 0.8)
95 mustValue(t, fr, "svc.latency_count", nil, 2)
96 mustValue(t, fr, "svc.latency_sum", nil, 1.0)
97 },
98 },
99 "snapshot summary point quantiles must match declaration": {
100 run: func(t *testing.T) {
101 s := NewCollectorStore()
102 cc := cycleController(t, s)
103 sum := s.Write().SnapshotMeter("svc").Summary("latency", WithSummaryQuantiles(0.5, 0.9))
104
105 cc.BeginCycle()
106 expectPanic(t, func() {
107 sum.ObservePoint(SummaryPoint{
108 Count: 1,
109 Sum: 0.2,
110 Quantiles: []QuantilePoint{
111 {Quantile: 0.5, Value: 0.2},
112 },
113 })
114 })
115 cc.AbortCycle()
116 },
117 },
118 "stateful summary cumulative accumulates count sum and quantiles": {
119 run: func(t *testing.T) {
120 s := NewCollectorStore()
121 cc := cycleController(t, s)
122 sum := s.Write().StatefulMeter("svc").Summary("latency", WithSummaryQuantiles(0.5, 1.0))
123
124 cc.BeginCycle()
125 sum.Observe(1)
126 sum.Observe(2)
127 sum.Observe(10)
128 cc.CommitCycleSuccess()
129 mustSummary(t, s.Read(), "svc.latency", nil, SummaryPoint{
130 Count: 3,
131 Sum: 13,
132 Quantiles: []QuantilePoint{
133 {Quantile: 0.5, Value: 2},
134 {Quantile: 1.0, Value: 10},
135 },
136 })
137
138 cc.BeginCycle()
139 sum.Observe(3)
140 cc.CommitCycleSuccess()
141 mustSummary(t, s.Read(), "svc.latency", nil, SummaryPoint{
142 Count: 4,
143 Sum: 16,
144 Quantiles: []QuantilePoint{
145 {Quantile: 0.5, Value: 2.5},
146 {Quantile: 1.0, Value: 10},
147 },
148 })
149 },
150 },
151 "stateful summary cycle window resets and uses freshness cycle": {
152 run: func(t *testing.T) {
153 s := NewCollectorStore()
154 cc := cycleController(t, s)
155 sum := s.Write().StatefulMeter("svc").Summary(
156 "latency",
157 WithSummaryQuantiles(0.5),
158 WithWindow(WindowCycle),
159 )
160
161 cc.BeginCycle()
162 sum.Observe(2)
163 sum.Observe(4)
164 cc.CommitCycleSuccess()
165 mustSummary(t, s.Read(), "svc.latency", nil, SummaryPoint{
166 Count: 2,
167 Sum: 6,
168 Quantiles: []QuantilePoint{
169 {Quantile: 0.5, Value: 3},
170 },
171 })
172
173 cc.BeginCycle()
174 sum.Observe(1)
175 cc.CommitCycleSuccess()
176 mustSummary(t, s.Read(), "svc.latency", nil, SummaryPoint{
177 Count: 1,
178 Sum: 1,
179 Quantiles: []QuantilePoint{
180 {Quantile: 0.5, Value: 1},
181 },
182 })
183
184 cc.BeginCycle()
185 cc.CommitCycleSuccess()
186 _, ok := s.Read().Summary("svc.latency", nil)
187 require.False(t, ok, "expected stale cycle-window summary hidden from Read")
188 mustSummary(t, s.Read(ReadRaw()), "svc.latency", nil, SummaryPoint{
189 Count: 1,
190 Sum: 1,
191 Quantiles: []QuantilePoint{
192 {Quantile: 0.5, Value: 1},
193 },
194 })
195 _, ok = s.Read(ReadFlatten()).Value("svc.latency_count", nil)
196 require.False(t, ok, "expected stale cycle-window summary flatten hidden from Read(ReadFlatten())")
197 mustValue(t, s.Read(ReadRaw(), ReadFlatten()), "svc.latency_count", nil, 1)
198 },
199 },
200 "window option on snapshot summary panics": {
201 run: func(t *testing.T) {
202 s := NewCollectorStore()
203 expectPanic(t, func() {
204 _ = s.Write().SnapshotMeter("svc").Summary("latency", WithWindow(WindowCycle))
205 })
206 },
207 },
208 "summary reservoir option validation and custom size": {
209 run: func(t *testing.T) {
210 s := NewCollectorStore()
211 cc := cycleController(t, s)
212
213 expectPanic(t, func() {
214 _ = s.Write().SnapshotMeter("svc").Summary("latency", WithSummaryReservoirSize(128))
215 })
216 expectPanic(t, func() {
217 _ = s.Write().StatefulMeter("svc").Summary("latency_bad", WithSummaryQuantiles(0.9), WithSummaryReservoirSize(0))
218 })
219
220 sum := s.Write().StatefulMeter("svc").Summary(
221 "latency_small_reservoir",
222 WithSummaryQuantiles(0.5),
223 WithSummaryReservoirSize(8),
224 )
225 cc.BeginCycle()
226 for i := range 100 {
227 sum.Observe(SampleValue(i))
228 }
229 cc.CommitCycleSuccess()
230
231 p, ok := s.Read().Summary("svc.latency_small_reservoir", nil)
232 require.True(t, ok, "expected summary for custom reservoir series")
233 require.Equal(t, SampleValue(100), p.Count, "unexpected count")
234 require.Len(t, p.Quantiles, 1)
235 require.False(t, math.IsNaN(p.Quantiles[0].Value), "expected non-NaN quantile value with custom reservoir")
236 },
237 },
238 "summary flatten quantile label collision panics": {
239 run: func(t *testing.T) {
240 s := NewCollectorStore()
241 cc := cycleController(t, s)
242 sum := s.Write().SnapshotMeter("svc").
243 WithLabels(Label{Key: "quantile", Value: "x"}).
244 Summary("latency", WithSummaryQuantiles(0.5))
245
246 cc.BeginCycle()
247 expectPanic(t, func() {
248 sum.ObservePoint(SummaryPoint{
249 Count: 1,
250 Sum: 0.2,
251 Quantiles: []QuantilePoint{
252 {Quantile: 0.5, Value: 0.2},
253 },
254 })
255 })
256 cc.AbortCycle()
257 },
258 },
259 "summary schema mismatch and mode mixing panic": {
260 run: func(t *testing.T) {
261 s := NewCollectorStore()
262 _ = s.Write().SnapshotMeter("svc").Summary("latency", WithSummaryQuantiles(0.5))
263
264 expectPanic(t, func() {
265 _ = s.Write().SnapshotMeter("svc").Summary("latency", WithSummaryQuantiles(0.9))
266 })
267 expectPanic(t, func() {
268 _ = s.Write().StatefulMeter("svc").Summary("latency", WithSummaryQuantiles(0.5))
269 })
270 },
271 },
272 }
273
274 for name, tc := range tests {
275 t.Run(name, tc.run)
276 }
277 }
278
279 func mustSummary(t *testing.T, r Reader, name string, labels Labels, want SummaryPoint) {
280 t.Helper()
281 got, ok := r.Summary(name, labels)
282 require.True(t, ok, "expected summary for %s", name)
283 require.True(t, equalSample(got.Count, want.Count), "unexpected summary count for %s: got=%v want=%v", name, got.Count, want.Count)
284 require.True(t, equalSample(got.Sum, want.Sum), "unexpected summary sum for %s: got=%v want=%v", name, got.Sum, want.Sum)
285 require.Len(t, got.Quantiles, len(want.Quantiles), "unexpected summary quantiles length for %s", name)
286 for i := range want.Quantiles {
287 gq := got.Quantiles[i]
288 wq := want.Quantiles[i]
289 require.Equal(t, wq.Quantile, gq.Quantile, "unexpected summary quantile[%d] for %s", i, name)
290 require.True(t, equalSample(gq.Value, wq.Value), "unexpected summary quantile value[%d] for %s: got=%v want=%v", i, name, gq.Value, wq.Value)
291 }
292 }
293
294 func equalSample(a, b SampleValue) bool {
295 if math.IsNaN(a) || math.IsNaN(b) {
296 return math.IsNaN(a) && math.IsNaN(b)
297 }
298 const eps = 1e-9
299 return math.Abs(a-b) <= eps
300 }