master
go 550 lines 17.5 KB
Raw
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 package metrix
4
5 import (
6 "time"
7 )
8
9 type runtimeStoreView struct {
10 core *storeCore
11 backend *runtimeStoreBackend
12 }
13
14 type runtimeStoreBackend struct {
15 core *storeCore
16 summarySketches map[string]*summaryQuantileSketch
17 retention runtimeRetentionPolicy
18 compaction runtimeCompactionPolicy
19 writesSinceCompaction uint64
20 now func() time.Time
21 }
22
23 type runtimeWriteView struct {
24 backend *runtimeStoreBackend
25 }
26
27 type runtimeRetentionPolicy struct {
28 ttl time.Duration
29 maxSeries int
30 }
31
32 type runtimeCompactionPolicy struct {
33 maxOverlayDepth int
34 maxOverlayWrites uint64
35 }
36
37 const (
38 defaultRuntimeRetentionTTL = 30 * time.Minute
39 defaultRuntimeRetentionMaxSeries = 0 // disabled
40 defaultRuntimeCompactionDepth = 64
41 defaultRuntimeCompactionWrites = 64
42 )
43
44 // NewRuntimeStore creates a dedicated runtime/internal metrics store with
45 // stateful-only, immediate-commit write semantics.
46 func NewRuntimeStore() RuntimeStore {
47 core := &storeCore{
48 instruments: make(map[string]*instrumentDescriptor),
49 }
50 core.snapshot.Store(&readSnapshot{
51 collectMeta: CollectMeta{LastAttemptStatus: CollectStatusUnknown},
52 series: make(map[string]*committedSeries),
53 })
54
55 backend := &runtimeStoreBackend{core: core}
56 backend.summarySketches = make(map[string]*summaryQuantileSketch)
57 backend.retention = runtimeRetentionPolicy{
58 ttl: defaultRuntimeRetentionTTL,
59 maxSeries: defaultRuntimeRetentionMaxSeries,
60 }
61 backend.compaction = runtimeCompactionPolicy{
62 maxOverlayDepth: defaultRuntimeCompactionDepth,
63 maxOverlayWrites: defaultRuntimeCompactionWrites,
64 }
65 backend.now = time.Now
66 return &runtimeStoreView{
67 core: core,
68 backend: backend,
69 }
70 }
71
72 func (s *runtimeStoreView) Read(opts ...ReadOption) Reader {
73 cfg := resolveReadConfig(opts...)
74 snap := s.core.snapshot.Load()
75 if cfg.flatten {
76 snap = flattenSnapshot(snap)
77 }
78 return &storeReader{snap: snap, raw: cfg.raw, flattened: cfg.flatten, hostScopeKey: cfg.hostScopeKey}
79 }
80
81 func (s *runtimeStoreView) Write() RuntimeWriter {
82 return &runtimeWriteView{backend: s.backend}
83 }
84
85 func (w *runtimeWriteView) StatefulMeter(prefix string) StatefulMeter {
86 return &statefulMeter{backend: w.backend, prefix: prefix}
87 }
88
89 func (r *runtimeStoreBackend) compileLabelSet(labels ...Label) LabelSet {
90 return compileLabelSetForOwner(r, labels...)
91 }
92
93 func (r *runtimeStoreBackend) registerInstrument(name string, kind metricKind, mode metricMode, opts ...InstrumentOption) (*instrumentDescriptor, error) {
94 if mode != modeStateful {
95 return nil, errRuntimeSnapshotWrite
96 }
97
98 cfg := instrumentConfig{}
99 for _, opt := range opts {
100 if opt != nil {
101 opt.apply(&cfg)
102 }
103 }
104 if cfg.freshnessSet && cfg.freshness != FreshnessCommitted {
105 return nil, errRuntimeFreshness
106 }
107 if cfg.windowSet && cfg.window == WindowCycle {
108 return nil, errRuntimeWindowCycle
109 }
110
111 desc, err := r.core.registerInstrument(name, kind, modeStateful, opts...)
112 if err != nil {
113 return nil, err
114 }
115 if desc.freshness != FreshnessCommitted {
116 return nil, errRuntimeFreshness
117 }
118 return desc, nil
119 }
120
121 func (r *runtimeStoreBackend) recordGaugeSet(desc *instrumentDescriptor, scope HostScope, value SampleValue, sets []LabelSet) {
122 mustFiniteSample(value)
123
124 labels, labelsKey, err := labelsFromSet(sets, r)
125 if err != nil {
126 panic(err)
127 }
128 scope = mustNormalizeHostScope(scope)
129 key := makeSeriesKey(scope.ScopeKey, desc.name, labelsKey)
130 r.commitRuntimeWrite(func(old, next *readSnapshot, seq uint64, nowUnixNano int64) {
131 series := runtimeEnsureSeriesMutable(old, next, key, desc.name, scope.ScopeKey, scope, labels, labelsKey, desc)
132 series.value = value
133 series.meta.LastSeenSuccessSeq = seq
134 series.runtimeLastSeenUnixNano = nowUnixNano
135 })
136 }
137
138 func (r *runtimeStoreBackend) recordGaugeAdd(desc *instrumentDescriptor, scope HostScope, delta SampleValue, sets []LabelSet) {
139 mustFiniteSample(delta)
140
141 labels, labelsKey, err := labelsFromSet(sets, r)
142 if err != nil {
143 panic(err)
144 }
145 scope = mustNormalizeHostScope(scope)
146 key := makeSeriesKey(scope.ScopeKey, desc.name, labelsKey)
147 r.commitRuntimeWrite(func(old, next *readSnapshot, seq uint64, nowUnixNano int64) {
148 series := runtimeEnsureSeriesMutable(old, next, key, desc.name, scope.ScopeKey, scope, labels, labelsKey, desc)
149 series.value += delta
150 series.meta.LastSeenSuccessSeq = seq
151 series.runtimeLastSeenUnixNano = nowUnixNano
152 })
153 }
154
155 func (r *runtimeStoreBackend) recordCounterObserveTotal(_ *instrumentDescriptor, _ HostScope, _ SampleValue, _ []LabelSet) {
156 panic(errRuntimeSnapshotWrite)
157 }
158
159 func (r *runtimeStoreBackend) recordCounterAdd(desc *instrumentDescriptor, scope HostScope, delta SampleValue, sets []LabelSet) {
160 mustFiniteSample(delta)
161
162 if delta < 0 {
163 panic(errCounterNegativeDelta)
164 }
165
166 labels, labelsKey, err := labelsFromSet(sets, r)
167 if err != nil {
168 panic(err)
169 }
170 scope = mustNormalizeHostScope(scope)
171 key := makeSeriesKey(scope.ScopeKey, desc.name, labelsKey)
172 r.commitRuntimeWrite(func(old, next *readSnapshot, seq uint64, nowUnixNano int64) {
173 series := runtimeEnsureSeriesMutable(old, next, key, desc.name, scope.ScopeKey, scope, labels, labelsKey, desc)
174
175 hadCurrent := series.desc != nil && series.desc.kind == kindCounter && series.counterCurrentSeq > 0
176 if hadCurrent {
177 series.counterPrevious = series.counterCurrent
178 series.counterPreviousSeq = series.counterCurrentSeq
179 series.counterHasPrev = true
180 } else {
181 series.counterPrevious = 0
182 series.counterPreviousSeq = 0
183 series.counterHasPrev = false
184 }
185
186 series.counterCurrent += delta
187 // Runtime delta contiguity is per-series, not global store sequence.
188 series.counterCurrentSeq++
189 series.value = series.counterCurrent
190 series.meta.LastSeenSuccessSeq = seq
191 series.runtimeLastSeenUnixNano = nowUnixNano
192 })
193 }
194
195 func (r *runtimeStoreBackend) recordHistogramObservePoint(_ *instrumentDescriptor, _ HostScope, _ HistogramPoint, _ []LabelSet) {
196 panic(errRuntimeSnapshotWrite)
197 }
198
199 func (r *runtimeStoreBackend) recordHistogramObserve(desc *instrumentDescriptor, scope HostScope, value SampleValue, sets []LabelSet) {
200 mustFiniteSample(value)
201
202 schema := desc.histogram
203 if schema == nil || len(schema.bounds) == 0 {
204 panic(errHistogramBounds)
205 }
206
207 labels, labelsKey, err := labelsFromSet(sets, r)
208 if err != nil {
209 panic(err)
210 }
211 if labelsContainKey(labels, HistogramBucketLabel) {
212 panic(errHistogramLabelKey)
213 }
214 scope = mustNormalizeHostScope(scope)
215
216 key := makeSeriesKey(scope.ScopeKey, desc.name, labelsKey)
217 r.commitRuntimeWrite(func(old, next *readSnapshot, seq uint64, nowUnixNano int64) {
218 series := runtimeEnsureSeriesMutable(old, next, key, desc.name, scope.ScopeKey, scope, labels, labelsKey, desc)
219
220 if series.desc.histogram == nil || !equalHistogramBounds(series.desc.histogram.bounds, schema.bounds) {
221 panic("metrix: histogram schema drift detected")
222 }
223 if len(series.histogramCumulative) == 0 {
224 series.histogramCumulative = make([]SampleValue, len(schema.bounds))
225 }
226
227 idx := findHistogramBucket(schema.bounds, value)
228 if idx < len(series.histogramCumulative) {
229 for i := idx; i < len(series.histogramCumulative); i++ {
230 series.histogramCumulative[i]++
231 }
232 }
233 series.histogramCount++
234 series.histogramSum += value
235 series.meta.LastSeenSuccessSeq = seq
236 series.runtimeLastSeenUnixNano = nowUnixNano
237 })
238 }
239
240 func (r *runtimeStoreBackend) recordSummaryObservePoint(_ *instrumentDescriptor, _ HostScope, _ SummaryPoint, _ []LabelSet) {
241 panic(errRuntimeSnapshotWrite)
242 }
243
244 func (r *runtimeStoreBackend) recordSummaryObserve(desc *instrumentDescriptor, scope HostScope, value SampleValue, sets []LabelSet) {
245 mustFiniteSample(value)
246
247 labels, labelsKey, err := labelsFromSet(sets, r)
248 if err != nil {
249 panic(err)
250 }
251 if labelsContainKey(labels, SummaryQuantileLabel) {
252 panic(errSummaryLabelKey)
253 }
254 scope = mustNormalizeHostScope(scope)
255
256 key := makeSeriesKey(scope.ScopeKey, desc.name, labelsKey)
257 r.commitRuntimeWrite(func(old, next *readSnapshot, seq uint64, nowUnixNano int64) {
258 series := runtimeEnsureSeriesMutable(old, next, key, desc.name, scope.ScopeKey, scope, labels, labelsKey, desc)
259
260 series.summaryCount++
261 series.summarySum += value
262
263 qs := desc.summaryQuantiles()
264 if len(qs) > 0 {
265 sketch := r.summarySketches[key]
266 if sketch == nil {
267 sketch = newSummaryQuantileSketch(desc.summaryReservoirSize(), summarySketchSeed(key))
268 r.summarySketches[key] = sketch
269 }
270 sketch.observe(value)
271 series.summaryQuantiles = sketch.quantiles(qs)
272 } else {
273 delete(r.summarySketches, key)
274 series.summaryQuantiles = nil
275 }
276 series.meta.LastSeenSuccessSeq = seq
277 series.runtimeLastSeenUnixNano = nowUnixNano
278 })
279 }
280
281 func (r *runtimeStoreBackend) recordStateSetObserve(desc *instrumentDescriptor, scope HostScope, point StateSetPoint, sets []LabelSet) {
282 schema := desc.stateSet
283 if schema == nil {
284 panic(errStateSetSchema)
285 }
286
287 labels, labelsKey, err := labelsFromSet(sets, r)
288 if err != nil {
289 panic(err)
290 }
291 if labelsContainKey(labels, desc.name) {
292 panic(errStateSetLabelKey)
293 }
294 scope = mustNormalizeHostScope(scope)
295 states := normalizeStateSetPoint(point, schema)
296
297 key := makeSeriesKey(scope.ScopeKey, desc.name, labelsKey)
298 r.commitRuntimeWrite(func(old, next *readSnapshot, seq uint64, nowUnixNano int64) {
299 series := runtimeEnsureSeriesMutable(old, next, key, desc.name, scope.ScopeKey, scope, labels, labelsKey, desc)
300 series.stateSetValues = cloneStateMap(states)
301 series.meta.LastSeenSuccessSeq = seq
302 series.runtimeLastSeenUnixNano = nowUnixNano
303 })
304 }
305
306 func (r *runtimeStoreBackend) recordMeasureSetGaugeObservePoint(_ *instrumentDescriptor, _ HostScope, _ MeasureSetPoint, _ []LabelSet) {
307 panic(errRuntimeSnapshotWrite)
308 }
309
310 func (r *runtimeStoreBackend) recordMeasureSetGaugeSetPoint(desc *instrumentDescriptor, scope HostScope, point MeasureSetPoint, sets []LabelSet) {
311 schema := desc.measureSet
312 if schema == nil || schema.semantics != MeasureSetSemanticsGauge {
313 panic(errMeasureSetSchema)
314 }
315
316 values := normalizeMeasureSetPoint(point, schema)
317
318 labels, labelsKey, err := labelsFromSet(sets, r)
319 if err != nil {
320 panic(err)
321 }
322 if labelsContainKey(labels, MeasureSetFieldLabel) {
323 panic(errMeasureSetLabelKey)
324 }
325 scope = mustNormalizeHostScope(scope)
326 key := makeSeriesKey(scope.ScopeKey, desc.name, labelsKey)
327 r.commitRuntimeWrite(func(old, next *readSnapshot, seq uint64, nowUnixNano int64) {
328 series := runtimeEnsureSeriesMutable(old, next, key, desc.name, scope.ScopeKey, scope, labels, labelsKey, desc)
329 series.measureSetValues = append(series.measureSetValues[:0], values...)
330 series.meta.LastSeenSuccessSeq = seq
331 series.runtimeLastSeenUnixNano = nowUnixNano
332 })
333 }
334
335 func (r *runtimeStoreBackend) recordMeasureSetGaugeAddPoint(desc *instrumentDescriptor, scope HostScope, delta MeasureSetPoint, sets []LabelSet) {
336 schema := desc.measureSet
337 if schema == nil || schema.semantics != MeasureSetSemanticsGauge {
338 panic(errMeasureSetSchema)
339 }
340
341 values := normalizeMeasureSetPoint(delta, schema)
342
343 labels, labelsKey, err := labelsFromSet(sets, r)
344 if err != nil {
345 panic(err)
346 }
347 if labelsContainKey(labels, MeasureSetFieldLabel) {
348 panic(errMeasureSetLabelKey)
349 }
350 scope = mustNormalizeHostScope(scope)
351 key := makeSeriesKey(scope.ScopeKey, desc.name, labelsKey)
352 r.commitRuntimeWrite(func(old, next *readSnapshot, seq uint64, nowUnixNano int64) {
353 series := runtimeEnsureSeriesMutable(old, next, key, desc.name, scope.ScopeKey, scope, labels, labelsKey, desc)
354 if len(series.measureSetValues) == 0 {
355 series.measureSetValues = make([]SampleValue, len(schema.fields))
356 }
357 for i, deltaValue := range values {
358 series.measureSetValues[i] += deltaValue
359 }
360 series.meta.LastSeenSuccessSeq = seq
361 series.runtimeLastSeenUnixNano = nowUnixNano
362 })
363 }
364
365 func (r *runtimeStoreBackend) recordMeasureSetGaugeSetField(desc *instrumentDescriptor, scope HostScope, field string, value SampleValue, sets []LabelSet) {
366 schema := desc.measureSet
367 if schema == nil || schema.semantics != MeasureSetSemanticsGauge {
368 panic(errMeasureSetSchema)
369 }
370
371 fieldIndex := mustMeasureSetFieldIndex(field, schema)
372 mustFiniteSample(value)
373
374 labels, labelsKey, err := labelsFromSet(sets, r)
375 if err != nil {
376 panic(err)
377 }
378 if labelsContainKey(labels, MeasureSetFieldLabel) {
379 panic(errMeasureSetLabelKey)
380 }
381 scope = mustNormalizeHostScope(scope)
382 key := makeSeriesKey(scope.ScopeKey, desc.name, labelsKey)
383 r.commitRuntimeWrite(func(old, next *readSnapshot, seq uint64, nowUnixNano int64) {
384 series := runtimeEnsureSeriesMutable(old, next, key, desc.name, scope.ScopeKey, scope, labels, labelsKey, desc)
385 if len(series.measureSetValues) == 0 {
386 series.measureSetValues = make([]SampleValue, len(schema.fields))
387 }
388 series.measureSetValues[fieldIndex] = value
389 series.meta.LastSeenSuccessSeq = seq
390 series.runtimeLastSeenUnixNano = nowUnixNano
391 })
392 }
393
394 func (r *runtimeStoreBackend) recordMeasureSetCounterObserveTotalPoint(_ *instrumentDescriptor, _ HostScope, _ MeasureSetPoint, _ []LabelSet) {
395 panic(errRuntimeSnapshotWrite)
396 }
397
398 func (r *runtimeStoreBackend) recordMeasureSetCounterAddPoint(desc *instrumentDescriptor, scope HostScope, delta MeasureSetPoint, sets []LabelSet) {
399 schema := desc.measureSet
400 if schema == nil || schema.semantics != MeasureSetSemanticsCounter {
401 panic(errMeasureSetSchema)
402 }
403
404 values := normalizeMeasureSetCounterDelta(delta, schema)
405
406 labels, labelsKey, err := labelsFromSet(sets, r)
407 if err != nil {
408 panic(err)
409 }
410 if labelsContainKey(labels, MeasureSetFieldLabel) {
411 panic(errMeasureSetLabelKey)
412 }
413 scope = mustNormalizeHostScope(scope)
414 key := makeSeriesKey(scope.ScopeKey, desc.name, labelsKey)
415 r.commitRuntimeWrite(func(old, next *readSnapshot, seq uint64, nowUnixNano int64) {
416 series := runtimeEnsureSeriesMutable(old, next, key, desc.name, scope.ScopeKey, scope, labels, labelsKey, desc)
417 if len(series.measureSetValues) == 0 {
418 series.measureSetValues = make([]SampleValue, len(schema.fields))
419 }
420 if series.measureSetCurrentSeq > 0 {
421 series.measureSetPreviousValues = append(series.measureSetPreviousValues[:0], series.measureSetValues...)
422 series.measureSetPreviousSeq = series.measureSetCurrentSeq
423 series.measureSetHasPrev = true
424 } else {
425 series.measureSetPreviousValues = nil
426 series.measureSetPreviousSeq = 0
427 series.measureSetHasPrev = false
428 }
429 for i, deltaValue := range values {
430 series.measureSetValues[i] += deltaValue
431 }
432 series.measureSetCurrentSeq++
433 series.meta.LastSeenSuccessSeq = seq
434 series.runtimeLastSeenUnixNano = nowUnixNano
435 })
436 }
437
438 func (r *runtimeStoreBackend) commitRuntimeWrite(apply func(old, next *readSnapshot, seq uint64, nowUnixNano int64)) {
439 r.core.mu.Lock()
440 defer r.core.mu.Unlock()
441
442 oldSnap := r.core.snapshot.Load()
443 next := &readSnapshot{
444 collectMeta: oldSnap.collectMeta,
445 series: make(map[string]*committedSeries, 1),
446 // byName index is built lazily by readers for runtime snapshots.
447 byName: nil,
448 runtimeBase: oldSnap,
449 runtimeDepth: oldSnap.runtimeDepth + 1,
450 }
451
452 nowUnixNano := r.now().UnixNano()
453 r.core.sequence++
454 seq := r.core.sequence
455 apply(oldSnap, next, seq, nowUnixNano)
456
457 r.writesSinceCompaction++
458 var evicted []string
459 if r.shouldCompactRuntimeSnapshot(next) {
460 next, evicted = r.compactRuntimeSnapshot(next, nowUnixNano)
461 r.writesSinceCompaction = 0
462 }
463 for _, key := range evicted {
464 delete(r.summarySketches, key)
465 }
466
467 next.collectMeta.LastAttemptSeq = seq
468 next.collectMeta.LastAttemptStatus = CollectStatusSuccess
469 next.collectMeta.LastSuccessSeq = seq
470 r.core.snapshot.Store(next)
471 }
472
473 func runtimeEnsureSeriesMutable(old, next *readSnapshot, key, name, hostScopeKey string, hostScope HostScope, labels []Label, labelsKey string, desc *instrumentDescriptor) *committedSeries {
474 series := next.series[key]
475 if series != nil {
476 ensureSeriesMeta(series.desc, &series.meta)
477 return series
478 }
479 if existing, ok := lookupSnapshotSeries(old, key); ok {
480 series = cloneCommittedSeries(existing)
481 ensureSeriesMeta(series.desc, &series.meta)
482 next.series[key] = series
483 return series
484 }
485 series = &committedSeries{
486 id: SeriesID(key),
487 hash64: seriesIDHash(SeriesID(key)),
488 key: key,
489 name: name,
490 hostScopeKey: hostScopeKey,
491 hostScope: cloneHostScope(hostScope),
492 labels: append([]Label(nil), labels...),
493 labelsKey: labelsKey,
494 desc: desc,
495 meta: baseSeriesMeta(desc),
496 }
497 next.series[key] = series
498 return series
499 }
500
501 func (r *runtimeStoreBackend) shouldCompactRuntimeSnapshot(next *readSnapshot) bool {
502 if r.compaction.maxOverlayDepth > 0 && next.runtimeDepth >= r.compaction.maxOverlayDepth {
503 return true
504 }
505 if r.compaction.maxOverlayWrites > 0 && r.writesSinceCompaction >= r.compaction.maxOverlayWrites {
506 return true
507 }
508 return false
509 }
510
511 func (r *runtimeStoreBackend) compactRuntimeSnapshot(snap *readSnapshot, nowUnixNano int64) (*readSnapshot, []string) {
512 series := snapshotSeriesView(snap)
513 evicted := applyRuntimeRetention(series, r.retention, nowUnixNano)
514 return &readSnapshot{
515 collectMeta: snap.collectMeta,
516 series: series,
517 byName: nil,
518 runtimeBase: nil,
519 runtimeDepth: 0,
520 }, evicted
521 }
522
523 func applyRuntimeRetention(series map[string]*committedSeries, policy runtimeRetentionPolicy, nowUnixNano int64) []string {
524 var evicted []string
525
526 if policy.ttl > 0 {
527 cutoff := nowUnixNano - int64(policy.ttl)
528 for key, s := range series {
529 if s.runtimeLastSeenUnixNano <= cutoff {
530 delete(series, key)
531 evicted = append(evicted, key)
532 }
533 }
534 }
535
536 evictOldestSeries(series, policy.maxSeries, func(s *committedSeries) int64 {
537 return s.runtimeLastSeenUnixNano
538 }, func(key string) {
539 evicted = append(evicted, key)
540 })
541
542 if len(evicted) == 0 {
543 return nil
544 }
545 return evicted
546 }
547
548 var _ RuntimeStore = (*runtimeStoreView)(nil)
549 var _ RuntimeWriter = (*runtimeWriteView)(nil)
550 var _ meterBackend = (*runtimeStoreBackend)(nil)