master
go 297 lines 7.22 KB
Raw
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 package chartengine
4
5 import (
6 "sort"
7 "strings"
8
9 "github.com/netdata/netdata/go/plugins/pkg/metrix"
10 "github.com/netdata/netdata/go/plugins/plugin/framework/chartengine/internal/program"
11 )
12
13 const collectJobLabel = "_collect_job"
14
15 type compiledInstanceLabelPlan struct {
16 explicitKeys []string
17 explicitSet map[string]struct{}
18 excludeSet map[string]struct{}
19 includeAll bool
20 }
21
22 type chartLabelAccumulator struct {
23 mode program.PromotionMode
24 promoteKeys map[string]struct{}
25 excluded map[string]struct{}
26 instance map[string]string
27 selected map[string]string
28 initialized bool
29
30 instancePlan compiledInstanceLabelPlan
31 instanceKeys map[string]struct{}
32 resolvedScratch []instanceLabelValue
33 includeAllScratch []string
34 }
35
36 func newChartLabelAccumulator(chart program.Chart) *chartLabelAccumulator {
37 plan := compileInstanceLabelPlan(chart.Identity)
38 acc := &chartLabelAccumulator{
39 mode: chart.Labels.Mode,
40 promoteKeys: make(map[string]struct{}, len(chart.Labels.PromoteKeys)),
41 excluded: make(map[string]struct{},
42 len(chart.Labels.Exclusions.SelectorConstrainedKeys)+len(chart.Labels.Exclusions.DimensionKeyLabels)),
43 instance: make(map[string]string),
44 selected: make(map[string]string),
45 instancePlan: plan,
46 instanceKeys: make(map[string]struct{}, len(plan.explicitKeys)),
47 resolvedScratch: make([]instanceLabelValue, 0, len(plan.explicitKeys)),
48 includeAllScratch: make([]string, 0, len(plan.explicitKeys)),
49 }
50 for _, key := range chart.Labels.PromoteKeys {
51 key = strings.TrimSpace(key)
52 if key == "" {
53 continue
54 }
55 acc.promoteKeys[key] = struct{}{}
56 }
57 for _, key := range chart.Labels.Exclusions.SelectorConstrainedKeys {
58 key = strings.TrimSpace(key)
59 if key == "" {
60 continue
61 }
62 acc.excluded[key] = struct{}{}
63 }
64 for _, key := range chart.Labels.Exclusions.DimensionKeyLabels {
65 key = strings.TrimSpace(key)
66 if key == "" {
67 continue
68 }
69 acc.excluded[key] = struct{}{}
70 }
71 return acc
72 }
73
74 func newAutogenChartLabelAccumulator() *chartLabelAccumulator {
75 return &chartLabelAccumulator{
76 mode: program.PromotionModeAutoIntersection,
77 promoteKeys: make(map[string]struct{}),
78 excluded: make(map[string]struct{}),
79 instance: make(map[string]string),
80 selected: make(map[string]string),
81 instancePlan: compileInstanceLabelPlan(program.ChartIdentity{}),
82 instanceKeys: make(map[string]struct{}),
83 resolvedScratch: make([]instanceLabelValue, 0),
84 includeAllScratch: make([]string, 0),
85 }
86 }
87
88 func compileInstanceLabelPlan(identity program.ChartIdentity) compiledInstanceLabelPlan {
89 plan := compiledInstanceLabelPlan{
90 explicitKeys: make([]string, 0, len(identity.InstanceByLabels)),
91 explicitSet: make(map[string]struct{}, len(identity.InstanceByLabels)),
92 excludeSet: make(map[string]struct{}),
93 }
94
95 for _, token := range identity.InstanceByLabels {
96 switch {
97 case token.Exclude:
98 if token.Key != "" {
99 plan.excludeSet[token.Key] = struct{}{}
100 }
101 case token.IncludeAll:
102 plan.includeAll = true
103 }
104 }
105
106 seenExplicit := make(map[string]struct{}, len(identity.InstanceByLabels))
107 for _, token := range identity.InstanceByLabels {
108 if token.Exclude || token.IncludeAll || token.Key == "" {
109 continue
110 }
111
112 key := token.Key
113 if _, excluded := plan.excludeSet[key]; excluded {
114 continue
115 }
116 if _, exists := seenExplicit[key]; exists {
117 continue
118 }
119 seenExplicit[key] = struct{}{}
120 plan.explicitKeys = append(plan.explicitKeys, key)
121 plan.explicitSet[key] = struct{}{}
122 }
123 return plan
124 }
125
126 func (a *chartLabelAccumulator) observe(labels metrix.LabelView, dimensionKeyLabel string) error {
127 if a == nil || labels.Len() == 0 {
128 return nil
129 }
130
131 if key := strings.TrimSpace(dimensionKeyLabel); key != "" {
132 a.excluded[key] = struct{}{}
133 }
134
135 ok := a.resolveInstanceLabelsForObserve(labels)
136 if !ok {
137 return nil
138 }
139
140 switch a.mode {
141 case program.PromotionModeExplicitIntersection:
142 if !a.initialized {
143 for key := range a.promoteKeys {
144 if _, excluded := a.excluded[key]; excluded {
145 continue
146 }
147 if a.isInstanceKey(key) {
148 continue
149 }
150 value, ok := labels.Get(key)
151 if !ok {
152 continue
153 }
154 a.selected[key] = value
155 }
156 a.initialized = true
157 return nil
158 }
159 for key, value := range a.selected {
160 if _, excluded := a.excluded[key]; excluded {
161 delete(a.selected, key)
162 continue
163 }
164 next, ok := labels.Get(key)
165 if !ok {
166 delete(a.selected, key)
167 continue
168 }
169 if a.isInstanceKey(key) {
170 delete(a.selected, key)
171 continue
172 }
173 if next != value {
174 delete(a.selected, key)
175 }
176 }
177 return nil
178 default:
179 if !a.initialized {
180 labels.Range(func(key, value string) bool {
181 if _, excluded := a.excluded[key]; excluded {
182 return true
183 }
184 if a.isInstanceKey(key) {
185 return true
186 }
187 a.selected[key] = value
188 return true
189 })
190 a.initialized = true
191 return nil
192 }
193 for key, value := range a.selected {
194 if _, excluded := a.excluded[key]; excluded {
195 delete(a.selected, key)
196 continue
197 }
198 next, ok := labels.Get(key)
199 if !ok {
200 delete(a.selected, key)
201 continue
202 }
203 if a.isInstanceKey(key) {
204 delete(a.selected, key)
205 continue
206 }
207 if next != value {
208 delete(a.selected, key)
209 }
210 }
211 return nil
212 }
213 }
214
215 func (a *chartLabelAccumulator) resetInstanceKeys() {
216 clear(a.instanceKeys)
217 }
218
219 func (a *chartLabelAccumulator) addInstanceKey(key, value string) {
220 if _, exists := a.instanceKeys[key]; !exists {
221 a.instanceKeys[key] = struct{}{}
222 }
223 if _, exists := a.instance[key]; !exists {
224 a.instance[key] = value
225 }
226 }
227
228 func (a *chartLabelAccumulator) resolveInstanceLabelsForObserve(labels metrix.LabelView) bool {
229 a.resetInstanceKeys()
230
231 resolved := a.resolvedScratch[:0]
232 for _, key := range a.instancePlan.explicitKeys {
233 value, ok := labels.Get(key)
234 if !ok {
235 a.resolvedScratch = resolved[:0]
236 return false
237 }
238 resolved = append(resolved, instanceLabelValue{Key: key, Value: value})
239 }
240
241 if a.instancePlan.includeAll {
242 extra := a.includeAllScratch[:0]
243 labels.Range(func(key, _ string) bool {
244 if _, excluded := a.instancePlan.excludeSet[key]; excluded {
245 return true
246 }
247 if _, already := a.instancePlan.explicitSet[key]; already {
248 return true
249 }
250 extra = append(extra, key)
251 return true
252 })
253 sort.Strings(extra)
254 for _, key := range extra {
255 value, ok := labels.Get(key)
256 if !ok {
257 a.resolvedScratch = resolved[:0]
258 a.includeAllScratch = extra[:0]
259 return false
260 }
261 resolved = append(resolved, instanceLabelValue{Key: key, Value: value})
262 }
263 a.includeAllScratch = extra
264 }
265
266 for _, item := range resolved {
267 a.addInstanceKey(item.Key, item.Value)
268 }
269 a.resolvedScratch = resolved
270 return true
271 }
272
273 func (a *chartLabelAccumulator) isInstanceKey(key string) bool {
274 _, ok := a.instanceKeys[key]
275 return ok
276 }
277
278 func (a *chartLabelAccumulator) materialize() (map[string]string, error) {
279 if a == nil {
280 return nil, nil
281 }
282 out := make(map[string]string, len(a.instance)+len(a.selected))
283 for key, value := range a.instance {
284 if strings.TrimSpace(key) == "" {
285 continue
286 }
287 out[key] = value
288 }
289 for key, value := range a.selected {
290 if strings.TrimSpace(key) == "" {
291 continue
292 }
293 out[key] = value
294 }
295 delete(out, collectJobLabel)
296 return out, nil
297 }