master
go 287 lines 8.71 KB
Raw
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 package metrix
4
5 import (
6 "testing"
7
8 "github.com/stretchr/testify/require"
9 )
10
11 func TestHistogramStoreScenarios(t *testing.T) {
12 tests := map[string]struct {
13 run func(t *testing.T)
14 }{
15 "snapshot histogram with explicit bounds read and flatten": {
16 run: func(t *testing.T) {
17 s := NewCollectorStore()
18 cc := cycleController(t, s)
19 h := s.Write().SnapshotMeter("svc").Histogram("request_duration_seconds", WithHistogramBounds(0.1, 0.5, 1))
20
21 cc.BeginCycle()
22 h.ObservePoint(HistogramPoint{
23 Count: 3,
24 Sum: 1.2,
25 Buckets: []BucketPoint{
26 {UpperBound: 0.1, CumulativeCount: 1},
27 {UpperBound: 0.5, CumulativeCount: 2},
28 {UpperBound: 1, CumulativeCount: 3},
29 },
30 })
31 cc.CommitCycleSuccess()
32
33 mustHistogram(t, s.Read(), "svc.request_duration_seconds", nil, HistogramPoint{
34 Count: 3,
35 Sum: 1.2,
36 Buckets: []BucketPoint{
37 {UpperBound: 0.1, CumulativeCount: 1},
38 {UpperBound: 0.5, CumulativeCount: 2},
39 {UpperBound: 1, CumulativeCount: 3},
40 },
41 })
42 _, ok := s.Read().Value("svc.request_duration_seconds", nil)
43 require.False(t, ok, "expected non-scalar histogram unavailable via Value")
44
45 fr := s.Read(ReadFlatten())
46 mustValue(t, fr, "svc.request_duration_seconds_bucket", Labels{"le": "0.1"}, 1)
47 mustValue(t, fr, "svc.request_duration_seconds_bucket", Labels{"le": "0.5"}, 2)
48 mustValue(t, fr, "svc.request_duration_seconds_bucket", Labels{"le": "1"}, 3)
49 mustValue(t, fr, "svc.request_duration_seconds_bucket", Labels{"le": "+Inf"}, 3)
50 mustValue(t, fr, "svc.request_duration_seconds_count", nil, 3)
51 mustValue(t, fr, "svc.request_duration_seconds_sum", nil, 1.2)
52 },
53 },
54 "snapshot histogram without bounds captures schema after successful cycle": {
55 run: func(t *testing.T) {
56 s := NewCollectorStore()
57 cc := cycleController(t, s)
58 h := s.Write().SnapshotMeter("svc").Histogram("latency")
59 view, ok := s.(*storeView)
60 require.True(t, ok, "expected *storeView")
61
62 cc.BeginCycle()
63 h.ObservePoint(HistogramPoint{
64 Count: 1, Sum: 0.2,
65 Buckets: []BucketPoint{
66 {UpperBound: 0.5, CumulativeCount: 1},
67 {UpperBound: 1, CumulativeCount: 1},
68 },
69 })
70 cc.AbortCycle() // schema must not be captured on abort
71
72 desc := view.core.instruments["svc.latency"]
73 require.NotNil(t, desc, "expected registered descriptor")
74 require.Nil(t, desc.histogram, "expected shared descriptor histogram schema to remain unset after abort")
75
76 cc.BeginCycle()
77 h.ObservePoint(HistogramPoint{
78 Count: 2, Sum: 0.8,
79 Buckets: []BucketPoint{
80 {UpperBound: 1, CumulativeCount: 2},
81 {UpperBound: 2, CumulativeCount: 2},
82 },
83 })
84 cc.CommitCycleSuccess()
85
86 // Shared descriptor remains immutable after publish; per-series descriptor carries schema.
87 require.Nil(t, desc.histogram, "expected shared descriptor histogram schema to remain unset")
88 mustHistogram(t, s.Read(), "svc.latency", nil, HistogramPoint{
89 Count: 2, Sum: 0.8,
90 Buckets: []BucketPoint{
91 {UpperBound: 1, CumulativeCount: 2},
92 {UpperBound: 2, CumulativeCount: 2},
93 },
94 })
95
96 cc.BeginCycle()
97 expectPanic(t, func() {
98 h.ObservePoint(HistogramPoint{
99 Count: 2, Sum: 0.8,
100 Buckets: []BucketPoint{
101 {UpperBound: 0.5, CumulativeCount: 2},
102 {UpperBound: 1, CumulativeCount: 2},
103 },
104 })
105 })
106 cc.AbortCycle()
107 },
108 },
109 "stateful histogram requires bounds": {
110 run: func(t *testing.T) {
111 s := NewCollectorStore()
112 expectPanic(t, func() {
113 _ = s.Write().StatefulMeter("svc").Histogram("latency")
114 })
115 },
116 },
117 "stateful histogram cumulative window accumulates across cycles": {
118 run: func(t *testing.T) {
119 s := NewCollectorStore()
120 cc := cycleController(t, s)
121 h := s.Write().StatefulMeter("svc").Histogram("latency", WithHistogramBounds(1, 2))
122
123 cc.BeginCycle()
124 h.Observe(0.5)
125 h.Observe(1.5)
126 h.Observe(3)
127 cc.CommitCycleSuccess()
128 mustHistogram(t, s.Read(), "svc.latency", nil, HistogramPoint{
129 Count: 3, Sum: 5,
130 Buckets: []BucketPoint{
131 {UpperBound: 1, CumulativeCount: 1},
132 {UpperBound: 2, CumulativeCount: 2},
133 },
134 })
135
136 cc.BeginCycle()
137 h.Observe(0.2)
138 cc.CommitCycleSuccess()
139 mustHistogram(t, s.Read(), "svc.latency", nil, HistogramPoint{
140 Count: 4, Sum: 5.2,
141 Buckets: []BucketPoint{
142 {UpperBound: 1, CumulativeCount: 2},
143 {UpperBound: 2, CumulativeCount: 3},
144 },
145 })
146 },
147 },
148 "stateful histogram window cycle resets each cycle and uses FreshnessCycle": {
149 run: func(t *testing.T) {
150 s := NewCollectorStore()
151 cc := cycleController(t, s)
152 h := s.Write().StatefulMeter("svc").Histogram("latency", WithHistogramBounds(1, 2), WithWindow(WindowCycle))
153
154 cc.BeginCycle()
155 h.Observe(0.5)
156 h.Observe(1.5)
157 cc.CommitCycleSuccess()
158 mustHistogram(t, s.Read(), "svc.latency", nil, HistogramPoint{
159 Count: 2, Sum: 2,
160 Buckets: []BucketPoint{
161 {UpperBound: 1, CumulativeCount: 1},
162 {UpperBound: 2, CumulativeCount: 2},
163 },
164 })
165
166 cc.BeginCycle()
167 h.Observe(1.7)
168 cc.CommitCycleSuccess()
169 mustHistogram(t, s.Read(), "svc.latency", nil, HistogramPoint{
170 Count: 1, Sum: 1.7,
171 Buckets: []BucketPoint{
172 {UpperBound: 1, CumulativeCount: 0},
173 {UpperBound: 2, CumulativeCount: 1},
174 },
175 })
176
177 cc.BeginCycle()
178 cc.CommitCycleSuccess()
179 _, ok := s.Read().Histogram("svc.latency", nil)
180 require.False(t, ok, "expected stale cycle-window histogram hidden from Read")
181 _, ok = s.Read(ReadRaw()).Histogram("svc.latency", nil)
182 require.True(t, ok, "expected raw histogram to remain visible")
183 },
184 },
185 "window option on snapshot histogram panics": {
186 run: func(t *testing.T) {
187 s := NewCollectorStore()
188 expectPanic(t, func() {
189 _ = s.Write().SnapshotMeter("svc").Histogram("latency", WithWindow(WindowCycle))
190 })
191 },
192 },
193 "histogram point validation panics on invalid buckets": {
194 run: func(t *testing.T) {
195 s := NewCollectorStore()
196 cc := cycleController(t, s)
197 h := s.Write().SnapshotMeter("svc").Histogram("latency")
198
199 cc.BeginCycle()
200 expectPanic(t, func() {
201 h.ObservePoint(HistogramPoint{
202 Count: 2, Sum: 1.0,
203 Buckets: []BucketPoint{
204 {UpperBound: 1, CumulativeCount: 2},
205 {UpperBound: 0.5, CumulativeCount: 2},
206 },
207 })
208 })
209 cc.AbortCycle()
210
211 cc.BeginCycle()
212 expectPanic(t, func() {
213 h.ObservePoint(HistogramPoint{
214 Count: 2, Sum: 1.0,
215 Buckets: []BucketPoint{
216 {UpperBound: 0.5, CumulativeCount: 2},
217 {UpperBound: 1, CumulativeCount: 1},
218 },
219 })
220 })
221 cc.AbortCycle()
222 },
223 },
224 "snapshot histogram stale visibility behavior for flatten": {
225 run: func(t *testing.T) {
226 s := NewCollectorStore()
227 cc := cycleController(t, s)
228 h := s.Write().SnapshotMeter("svc").Histogram("latency", WithHistogramBounds(1))
229
230 cc.BeginCycle()
231 h.ObservePoint(HistogramPoint{
232 Count: 1, Sum: 0.1,
233 Buckets: []BucketPoint{
234 {UpperBound: 1, CumulativeCount: 1},
235 },
236 })
237 cc.CommitCycleSuccess()
238
239 cc.BeginCycle()
240 cc.CommitCycleSuccess()
241
242 _, ok := s.Read(ReadFlatten()).Value("svc.latency_bucket", Labels{"le": "1"})
243 require.False(t, ok, "expected stale snapshot flattened histogram hidden from Read(ReadFlatten())")
244 mustValue(t, s.Read(ReadRaw(), ReadFlatten()), "svc.latency_bucket", Labels{"le": "1"}, 1)
245 },
246 },
247 "histogram flatten label collision panics": {
248 run: func(t *testing.T) {
249 s := NewCollectorStore()
250 cc := cycleController(t, s)
251 h := s.Write().SnapshotMeter("svc").
252 WithLabels(Label{Key: "le", Value: "x"}).
253 Histogram("latency", WithHistogramBounds(1))
254
255 cc.BeginCycle()
256 expectPanic(t, func() {
257 h.ObservePoint(HistogramPoint{
258 Count: 1, Sum: 0.1,
259 Buckets: []BucketPoint{
260 {UpperBound: 1, CumulativeCount: 1},
261 },
262 })
263 })
264 cc.AbortCycle()
265 },
266 },
267 }
268
269 for name, tc := range tests {
270 t.Run(name, tc.run)
271 }
272 }
273
274 func mustHistogram(t *testing.T, r Reader, name string, labels Labels, want HistogramPoint) {
275 t.Helper()
276 got, ok := r.Histogram(name, labels)
277 require.True(t, ok, "expected histogram for %s", name)
278 require.Equal(t, want.Count, got.Count, "unexpected histogram count for %s", name)
279 require.Equal(t, want.Sum, got.Sum, "unexpected histogram sum for %s", name)
280 require.Len(t, got.Buckets, len(want.Buckets), "unexpected histogram bucket count for %s", name)
281 for i := range want.Buckets {
282 gb := got.Buckets[i]
283 wb := want.Buckets[i]
284 require.Equal(t, wb.UpperBound, gb.UpperBound, "unexpected histogram bucket upper-bound[%d] for %s", i, name)
285 require.Equal(t, wb.CumulativeCount, gb.CumulativeCount, "unexpected histogram bucket cumulative count[%d] for %s", i, name)
286 }
287 }