master
go 205 lines 4.53 KB
Raw
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 package collecttest
4
5 import (
6 "context"
7 "fmt"
8 "sort"
9 "strconv"
10 "strings"
11
12 "github.com/netdata/netdata/go/plugins/pkg/metrix"
13 "github.com/netdata/netdata/go/plugins/plugin/framework/chartengine"
14 )
15
16 func collectOnce(store metrix.CollectorStore, collectFn func(ctx context.Context) error) error {
17 if store == nil {
18 return fmt.Errorf("collecttest: nil metric store")
19 }
20
21 managed, ok := metrix.AsCycleManagedStore(store)
22 if !ok {
23 return fmt.Errorf("collecttest: metric store is not cycle-managed")
24 }
25
26 cc := managed.CycleController()
27 committed := false
28 cc.BeginCycle()
29 defer func() {
30 if committed {
31 return
32 }
33 cc.AbortCycle()
34 }()
35 if err := collectFn(context.Background()); err != nil {
36 return err
37 }
38 cc.CommitCycleSuccess()
39 committed = true
40 return nil
41 }
42
43 func scalarKeyFromLabelView(name string, labels metrix.LabelView) string {
44 name = strings.TrimSpace(name)
45 if name == "" {
46 return ""
47 }
48 if labels == nil || labels.Len() == 0 {
49 return name
50 }
51
52 var b strings.Builder
53 b.WriteString(name)
54 b.WriteByte('{')
55 first := true
56 labels.Range(func(key, value string) bool {
57 if !first {
58 b.WriteByte(',')
59 }
60 first = false
61 b.WriteString(key)
62 b.WriteByte('=')
63 b.WriteString(strconv.Quote(value))
64 return true
65 })
66 b.WriteByte('}')
67 return b.String()
68 }
69
70 func scalarKeyFromLabelsMap(name string, labels metrix.Labels) string {
71 name = strings.TrimSpace(name)
72 if name == "" {
73 return ""
74 }
75 if len(labels) == 0 {
76 return name
77 }
78
79 keys := make([]string, 0, len(labels))
80 for key := range labels {
81 keys = append(keys, key)
82 }
83 sort.Strings(keys)
84
85 var b strings.Builder
86 b.WriteString(name)
87 b.WriteByte('{')
88 for i, key := range keys {
89 if i > 0 {
90 b.WriteByte(',')
91 }
92 b.WriteString(key)
93 b.WriteByte('=')
94 b.WriteString(strconv.Quote(labels[key]))
95 }
96 b.WriteByte('}')
97 return b.String()
98 }
99
100 func readScalarSeries(reader metrix.Reader) map[string]metrix.SampleValue {
101 out := make(map[string]metrix.SampleValue)
102 if reader == nil {
103 return out
104 }
105 reader.ForEachSeries(func(name string, labels metrix.LabelView, v metrix.SampleValue) {
106 out[scalarKeyFromLabelView(name, labels)] = v
107 })
108 return out
109 }
110
111 // CollectScalarSeries executes one collector cycle and returns
112 // scalar points from the collector store using the requested read view.
113 func CollectScalarSeries(
114 collector interface {
115 MetricStore() metrix.CollectorStore
116 Collect(context.Context) error
117 },
118 readOpts ...metrix.ReadOption,
119 ) (map[string]metrix.SampleValue, error) {
120 if collector == nil {
121 return nil, fmt.Errorf("collecttest: nil collector")
122 }
123 if err := collectOnce(collector.MetricStore(), collector.Collect); err != nil {
124 return nil, err
125 }
126 return readScalarSeries(collector.MetricStore().Read(readOpts...)), nil
127 }
128
129 func buildPlanFromTemplate(templateYAML string, revision uint64, reader metrix.Reader) (chartengine.Plan, error) {
130 engine, err := chartengine.New()
131 if err != nil {
132 return chartengine.Plan{}, err
133 }
134 if err := engine.LoadYAML([]byte(templateYAML), revision); err != nil {
135 return chartengine.Plan{}, err
136 }
137 attempt, err := engine.PreparePlan(reader)
138 if err != nil {
139 return chartengine.Plan{}, err
140 }
141 defer attempt.Abort()
142
143 plan := attempt.Plan()
144 if err := attempt.Commit(); err != nil {
145 return chartengine.Plan{}, err
146 }
147 return plan, nil
148 }
149
150 type planFilter struct {
151 ExcludeContexts map[string]struct{}
152 ExcludeChartIDs map[string]struct{}
153 }
154
155 type materializedChart struct {
156 ID string
157 Context string
158 Dimensions map[string]struct{}
159 }
160
161 func materializedCharts(plan chartengine.Plan, filter planFilter) map[string]materializedChart {
162 out := make(map[string]materializedChart)
163
164 include := func(chartID, context string) bool {
165 if _, ok := filter.ExcludeChartIDs[chartID]; ok {
166 return false
167 }
168 if _, ok := filter.ExcludeContexts[context]; ok {
169 return false
170 }
171 return true
172 }
173
174 for _, action := range plan.Actions {
175 switch v := action.(type) {
176 case chartengine.CreateChartAction:
177 if !include(v.ChartID, v.Meta.Context) {
178 continue
179 }
180 mc := out[v.ChartID]
181 mc.ID = v.ChartID
182 mc.Context = v.Meta.Context
183 if mc.Dimensions == nil {
184 mc.Dimensions = make(map[string]struct{})
185 }
186 out[v.ChartID] = mc
187 case chartengine.CreateDimensionAction:
188 if !include(v.ChartID, v.ChartMeta.Context) {
189 continue
190 }
191 mc := out[v.ChartID]
192 mc.ID = v.ChartID
193 if mc.Context == "" {
194 mc.Context = v.ChartMeta.Context
195 }
196 if mc.Dimensions == nil {
197 mc.Dimensions = make(map[string]struct{})
198 }
199 mc.Dimensions[v.Name] = struct{}{}
200 out[v.ChartID] = mc
201 }
202 }
203
204 return out
205 }