| 1 | // SPDX-License-Identifier: GPL-3.0-or-later |
| 2 | |
| 3 | package metrix |
| 4 | |
| 5 | import ( |
| 6 | "cmp" |
| 7 | "sort" |
| 8 | ) |
| 9 | |
| 10 | type retentionCandidate[T cmp.Ordered] struct { |
| 11 | key string |
| 12 | lastSeen T |
| 13 | } |
| 14 | |
| 15 | // evictOldestSeries enforces max-series cardinality with deterministic ordering. |
| 16 | // Oldest lastSeen values are evicted first; equal ages tie-break by series key. |
| 17 | func evictOldestSeries[T cmp.Ordered]( |
| 18 | series map[string]*committedSeries, |
| 19 | maxSeries int, |
| 20 | lastSeen func(*committedSeries) T, |
| 21 | onEvict func(key string), |
| 22 | ) { |
| 23 | if maxSeries <= 0 || len(series) <= maxSeries { |
| 24 | return |
| 25 | } |
| 26 | |
| 27 | candidates := make([]retentionCandidate[T], 0, len(series)) |
| 28 | for key, s := range series { |
| 29 | candidates = append(candidates, retentionCandidate[T]{ |
| 30 | key: key, |
| 31 | lastSeen: lastSeen(s), |
| 32 | }) |
| 33 | } |
| 34 | |
| 35 | sort.Slice(candidates, func(i, j int) bool { |
| 36 | if candidates[i].lastSeen != candidates[j].lastSeen { |
| 37 | return candidates[i].lastSeen < candidates[j].lastSeen |
| 38 | } |
| 39 | return candidates[i].key < candidates[j].key |
| 40 | }) |
| 41 | |
| 42 | evictCount := len(series) - maxSeries |
| 43 | for i := range evictCount { |
| 44 | key := candidates[i].key |
| 45 | delete(series, key) |
| 46 | if onEvict != nil { |
| 47 | onEvict(key) |
| 48 | } |
| 49 | } |
| 50 | } |