| 1 | // SPDX-License-Identifier: GPL-3.0-or-later |
| 2 | |
| 3 | package metrix |
| 4 | |
| 5 | import ( |
| 6 | "sort" |
| 7 | "strings" |
| 8 | ) |
| 9 | |
| 10 | type compiledLabelSet struct { |
| 11 | owner meterBackend // owning backend; used to reject foreign handles |
| 12 | items []Label // canonical sorted labels |
| 13 | key string // canonical packed key for fast identity joins |
| 14 | } |
| 15 | |
| 16 | type labelView struct { |
| 17 | items []Label |
| 18 | } |
| 19 | |
| 20 | func (v labelView) Len() int { return len(v.items) } |
| 21 | |
| 22 | func (v labelView) Get(key string) (string, bool) { |
| 23 | for _, l := range v.items { |
| 24 | if l.Key == key { |
| 25 | return l.Value, true |
| 26 | } |
| 27 | } |
| 28 | return "", false |
| 29 | } |
| 30 | |
| 31 | func (v labelView) Range(fn func(key, value string) bool) { |
| 32 | for _, l := range v.items { |
| 33 | if !fn(l.Key, l.Value) { |
| 34 | return |
| 35 | } |
| 36 | } |
| 37 | } |
| 38 | |
| 39 | func (v labelView) CloneMap() map[string]string { |
| 40 | m := make(map[string]string, len(v.items)) |
| 41 | for _, l := range v.items { |
| 42 | m[l.Key] = l.Value |
| 43 | } |
| 44 | return m |
| 45 | } |
| 46 | |
| 47 | func canonicalizeLabels(input map[string]string) ([]Label, string, error) { |
| 48 | if len(input) == 0 { |
| 49 | return nil, "", nil |
| 50 | } |
| 51 | |
| 52 | keys := make([]string, 0, len(input)) |
| 53 | for k := range input { |
| 54 | keys = append(keys, k) |
| 55 | } |
| 56 | sort.Strings(keys) |
| 57 | |
| 58 | labels := make([]Label, 0, len(keys)) |
| 59 | var b strings.Builder |
| 60 | for _, k := range keys { |
| 61 | if k == "" { |
| 62 | return nil, "", errInvalidLabelKey |
| 63 | } |
| 64 | v := input[k] |
| 65 | labels = append(labels, Label{Key: k, Value: v}) |
| 66 | b.WriteString(k) |
| 67 | b.WriteByte('\xff') |
| 68 | b.WriteString(v) |
| 69 | b.WriteByte('\xff') |
| 70 | } |
| 71 | return labels, b.String(), nil |
| 72 | } |
| 73 | |
| 74 | // labelsFromSet merges precompiled label sets and validates ownership/duplicates. |
| 75 | func labelsFromSet(sets []LabelSet, owner meterBackend) ([]Label, string, error) { |
| 76 | if len(sets) == 0 { |
| 77 | return nil, "", nil |
| 78 | } |
| 79 | |
| 80 | if len(sets) == 1 { |
| 81 | set, err := validatedLabelSet(sets[0], owner) |
| 82 | if err != nil { |
| 83 | return nil, "", err |
| 84 | } |
| 85 | // Reuse immutable precompiled labels directly on the single-set path. |
| 86 | return set.items, set.key, nil |
| 87 | } |
| 88 | |
| 89 | if len(sets) == 2 { |
| 90 | left, err := validatedLabelSet(sets[0], owner) |
| 91 | if err != nil { |
| 92 | return nil, "", err |
| 93 | } |
| 94 | right, err := validatedLabelSet(sets[1], owner) |
| 95 | if err != nil { |
| 96 | return nil, "", err |
| 97 | } |
| 98 | return mergeTwoCompiledLabelSets(left, right) |
| 99 | } |
| 100 | |
| 101 | merged := make(map[string]string) |
| 102 | for _, ls := range sets { |
| 103 | set, err := validatedLabelSet(ls, owner) |
| 104 | if err != nil { |
| 105 | return nil, "", err |
| 106 | } |
| 107 | for _, l := range set.items { |
| 108 | if _, ok := merged[l.Key]; ok { |
| 109 | return nil, "", errDuplicateLabelKey |
| 110 | } |
| 111 | merged[l.Key] = l.Value |
| 112 | } |
| 113 | } |
| 114 | |
| 115 | return canonicalizeLabels(merged) |
| 116 | } |
| 117 | |
| 118 | func validatedLabelSet(ls LabelSet, owner meterBackend) (*compiledLabelSet, error) { |
| 119 | if ls.set == nil { |
| 120 | return nil, errInvalidLabelSet |
| 121 | } |
| 122 | if ls.set.owner != owner { |
| 123 | return nil, errForeignLabelSet |
| 124 | } |
| 125 | return ls.set, nil |
| 126 | } |
| 127 | |
| 128 | func mergeTwoCompiledLabelSets(left, right *compiledLabelSet) ([]Label, string, error) { |
| 129 | if len(left.items) == 0 { |
| 130 | // Reuse immutable precompiled labels directly. |
| 131 | return right.items, right.key, nil |
| 132 | } |
| 133 | if len(right.items) == 0 { |
| 134 | // Reuse immutable precompiled labels directly. |
| 135 | return left.items, left.key, nil |
| 136 | } |
| 137 | |
| 138 | out := make([]Label, 0, len(left.items)+len(right.items)) |
| 139 | var b strings.Builder |
| 140 | |
| 141 | i := 0 |
| 142 | j := 0 |
| 143 | for i < len(left.items) && j < len(right.items) { |
| 144 | li := left.items[i] |
| 145 | rj := right.items[j] |
| 146 | if li.Key == rj.Key { |
| 147 | return nil, "", errDuplicateLabelKey |
| 148 | } |
| 149 | if li.Key < rj.Key { |
| 150 | out = append(out, li) |
| 151 | b.WriteString(li.Key) |
| 152 | b.WriteByte('\xff') |
| 153 | b.WriteString(li.Value) |
| 154 | b.WriteByte('\xff') |
| 155 | i++ |
| 156 | continue |
| 157 | } |
| 158 | out = append(out, rj) |
| 159 | b.WriteString(rj.Key) |
| 160 | b.WriteByte('\xff') |
| 161 | b.WriteString(rj.Value) |
| 162 | b.WriteByte('\xff') |
| 163 | j++ |
| 164 | } |
| 165 | |
| 166 | for ; i < len(left.items); i++ { |
| 167 | li := left.items[i] |
| 168 | out = append(out, li) |
| 169 | b.WriteString(li.Key) |
| 170 | b.WriteByte('\xff') |
| 171 | b.WriteString(li.Value) |
| 172 | b.WriteByte('\xff') |
| 173 | } |
| 174 | for ; j < len(right.items); j++ { |
| 175 | rj := right.items[j] |
| 176 | out = append(out, rj) |
| 177 | b.WriteString(rj.Key) |
| 178 | b.WriteByte('\xff') |
| 179 | b.WriteString(rj.Value) |
| 180 | b.WriteByte('\xff') |
| 181 | } |
| 182 | return out, b.String(), nil |
| 183 | } |
| 184 | |
| 185 | func appendLabelSets(base []LabelSet, extra []LabelSet) []LabelSet { |
| 186 | if len(extra) == 0 { |
| 187 | return base |
| 188 | } |
| 189 | if len(base) == 0 { |
| 190 | return extra |
| 191 | } |
| 192 | out := make([]LabelSet, 0, len(base)+len(extra)) |
| 193 | out = append(out, base...) |
| 194 | out = append(out, extra...) |
| 195 | return out |
| 196 | } |