master
go 475 lines 10.1 KB
Raw
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 package collectorapi
4
5 import (
6 "errors"
7 "fmt"
8 "strings"
9 "unicode"
10 )
11
12 type (
13 ChartType string
14 DimAlgo string
15 )
16
17 const (
18 // Line chart type.
19 Line ChartType = "line"
20 // Area chart type.
21 Area ChartType = "area"
22 // Stacked chart type.
23 Stacked ChartType = "stacked"
24 // Heatmap chart type for Prometheus histogram visualization.
25 Heatmap ChartType = "heatmap"
26
27 // Absolute dimension algorithm.
28 // The value is to drawn as-is (interpolated to second boundary).
29 Absolute DimAlgo = "absolute"
30 // Incremental dimension algorithm.
31 // The value increases over time, the difference from the last value is presented in the chart,
32 // the server interpolates the value and calculates a per second figure.
33 Incremental DimAlgo = "incremental"
34 // PercentOfAbsolute dimension algorithm.
35 // The percent of this value compared to the total of all dimensions.
36 PercentOfAbsolute DimAlgo = "percentage-of-absolute-row"
37 // PercentOfIncremental dimension algorithm.
38 // The percent of this value compared to the incremental total of all dimensions
39 PercentOfIncremental DimAlgo = "percentage-of-incremental-row"
40 )
41
42 const (
43 // Not documented.
44 // https://github.com/netdata/netdata/blob/cc2586de697702f86a3c34e60e23652dd4ddcb42/database/rrd.h#L204
45
46 LabelSourceAuto = 1 << 0
47 LabelSourceConf = 1 << 1
48 LabelSourceK8s = 1 << 2
49 )
50
51 func (d DimAlgo) String() string {
52 switch d {
53 case Absolute, Incremental, PercentOfAbsolute, PercentOfIncremental:
54 return string(d)
55 }
56 return string(Absolute)
57 }
58
59 func (c ChartType) String() string {
60 switch c {
61 case Line, Area, Stacked, Heatmap:
62 return string(c)
63 }
64 return string(Line)
65 }
66
67 type (
68 // Charts is a collection of Charts.
69 Charts []*Chart
70
71 // Opts represents chart options.
72 Opts struct {
73 Obsolete bool
74 Detail bool
75 StoreFirst bool
76 Hidden bool
77 }
78
79 // Chart represents a chart.
80 // For the full description please visit https://docs.netdata.cloud/plugins.d/#chart
81 Chart struct {
82 // typeID is the unique identification of the chart, if not specified,
83 // the orchestrator will use job full name + chart ID as typeID (default behaviour).
84 typ string
85 id string
86
87 OverModule string
88 IDSep bool
89 ID string
90 OverID string
91 Title string
92 Units string
93 Fam string
94 Ctx string
95 Type ChartType
96 Priority int
97 UpdateEvery int // Override for this chart's update interval (0 means use job default)
98 SkipGaps bool // Skip chart entirely (no BEGIN/END) when no dimensions have data
99 Opts
100
101 Labels []Label
102 Dims Dims
103 Vars Vars
104
105 Retries int
106
107 remove bool
108 // created flag is used to indicate whether the chart needs to be created by the orchestrator.
109 created bool
110 // updated flag is used to indicate whether the chart was updated on last data collection interval.
111 updated bool
112
113 // ignore flag is used to indicate that the chart shouldn't be sent to the netdata plugins.d
114 ignore bool
115 }
116
117 Label struct {
118 Key string
119 Value string
120 Source int
121 }
122
123 // DimOpts represents dimension options.
124 DimOpts struct {
125 Obsolete bool
126 Hidden bool
127 NoReset bool
128 NoOverflow bool
129 Float bool
130 }
131
132 // Dim represents a chart dimension.
133 // For detailed description please visit https://docs.netdata.cloud/plugins.d/#dimension.
134 Dim struct {
135 ID string
136 Name string
137 Algo DimAlgo
138 Mul int
139 Div int
140 DimOpts
141
142 remove bool
143 }
144
145 // Var represents a chart variable.
146 // For detailed description please visit https://docs.netdata.cloud/plugins.d/#variable
147 Var struct {
148 ID string
149 Name string
150 Value float64
151 }
152
153 // Dims is a collection of dims.
154 Dims []*Dim
155 // Vars is a collection of vars.
156 Vars []*Var
157 )
158
159 func (o Opts) String() string {
160 var b strings.Builder
161 if o.Detail {
162 b.WriteString(" detail")
163 }
164 if o.Hidden {
165 b.WriteString(" hidden")
166 }
167 if o.Obsolete {
168 b.WriteString(" obsolete")
169 }
170 if o.StoreFirst {
171 b.WriteString(" store_first")
172 }
173
174 if len(b.String()) == 0 {
175 return ""
176 }
177 return b.String()[1:]
178 }
179
180 func (o DimOpts) String() string {
181 var b strings.Builder
182 if o.Hidden {
183 b.WriteString(" hidden")
184 }
185 if o.NoOverflow {
186 b.WriteString(" nooverflow")
187 }
188 if o.NoReset {
189 b.WriteString(" noreset")
190 }
191 if o.Obsolete {
192 b.WriteString(" obsolete")
193 }
194 if o.Float {
195 b.WriteString(" type=float")
196 }
197
198 if len(b.String()) == 0 {
199 return ""
200 }
201 return b.String()[1:]
202 }
203
204 // Add adds (appends) a variable number of Charts.
205 func (c *Charts) Add(charts ...*Chart) error {
206 for _, chart := range charts {
207 err := checkChart(chart)
208 if err != nil {
209 return fmt.Errorf("error on adding chart '%s' : %s", chart.ID, err)
210 }
211 if chart := c.Get(chart.ID); chart != nil && !chart.remove {
212 return fmt.Errorf("error on adding chart : '%s' is already in charts", chart.ID)
213 }
214 *c = append(*c, chart)
215 }
216
217 return nil
218 }
219
220 // Get returns the chart by ID.
221 func (c Charts) Get(chartID string) *Chart {
222 idx := c.index(chartID)
223 if idx == -1 {
224 return nil
225 }
226 return c[idx]
227 }
228
229 // Has returns true if ChartsFunc contain the chart with the given ID, false otherwise.
230 func (c Charts) Has(chartID string) bool {
231 return c.index(chartID) != -1
232 }
233
234 // Remove removes the chart from Charts by ID.
235 // Avoid to use it in runtime.
236 func (c *Charts) Remove(chartID string) error {
237 idx := c.index(chartID)
238 if idx == -1 {
239 return fmt.Errorf("error on removing chart : '%s' is not in charts", chartID)
240 }
241 copy((*c)[idx:], (*c)[idx+1:])
242 (*c)[len(*c)-1] = nil
243 *c = (*c)[:len(*c)-1]
244 return nil
245 }
246
247 // Copy returns a deep copy of ChartsFunc.
248 func (c Charts) Copy() *Charts {
249 charts := Charts{}
250 for idx := range c {
251 charts = append(charts, c[idx].Copy())
252 }
253 return &charts
254 }
255
256 func (c Charts) index(chartID string) int {
257 for idx := range c {
258 if c[idx].ID == chartID {
259 return idx
260 }
261 }
262 return -1
263 }
264
265 // MarkNotCreated changes 'created' chart flag to false.
266 // Use it to add dimension in runtime.
267 func (c *Chart) MarkNotCreated() {
268 c.created = false
269 }
270
271 // MarkRemove sets 'remove' flag and Obsolete option to true.
272 // Use it to remove chart in runtime.
273 func (c *Chart) MarkRemove() {
274 c.Obsolete = true
275 c.remove = true
276 }
277
278 // MarkDimRemove sets 'remove' flag, Obsolete and optionally Hidden options to true.
279 // Use it to remove dimension in runtime.
280 func (c *Chart) MarkDimRemove(dimID string, hide bool) error {
281 if !c.HasDim(dimID) {
282 return fmt.Errorf("chart '%s' has no '%s' dimension", c.ID, dimID)
283 }
284 dim := c.GetDim(dimID)
285 dim.Obsolete = true
286 if hide {
287 dim.Hidden = true
288 }
289 dim.remove = true
290 return nil
291 }
292
293 // AddDim adds new dimension to the chart dimensions.
294 func (c *Chart) AddDim(newDim *Dim) error {
295 err := checkDim(newDim)
296 if err != nil {
297 return fmt.Errorf("error on adding dim to chart '%s' : %s", c.ID, err)
298 }
299 if c.HasDim(newDim.ID) {
300 return fmt.Errorf("error on adding dim : '%s' is already in chart '%s' dims", newDim.ID, c.ID)
301 }
302 c.Dims = append(c.Dims, newDim)
303
304 return nil
305 }
306
307 // AddVar adds new variable to the chart variables.
308 func (c *Chart) AddVar(newVar *Var) error {
309 err := checkVar(newVar)
310 if err != nil {
311 return fmt.Errorf("error on adding var to chart '%s' : %s", c.ID, err)
312 }
313 if c.indexVar(newVar.ID) != -1 {
314 return fmt.Errorf("error on adding var : '%s' is already in chart '%s' vars", newVar.ID, c.ID)
315 }
316 c.Vars = append(c.Vars, newVar)
317
318 return nil
319 }
320
321 // GetDim returns dimension by ID.
322 func (c *Chart) GetDim(dimID string) *Dim {
323 idx := c.indexDim(dimID)
324 if idx == -1 {
325 return nil
326 }
327 return c.Dims[idx]
328 }
329
330 // RemoveDim removes dimension by ID.
331 // Avoid to use it in runtime.
332 func (c *Chart) RemoveDim(dimID string) error {
333 idx := c.indexDim(dimID)
334 if idx == -1 {
335 return fmt.Errorf("error on removing dim : '%s' isn't in chart '%s'", dimID, c.ID)
336 }
337 c.Dims = append(c.Dims[:idx], c.Dims[idx+1:]...)
338
339 return nil
340 }
341
342 // HasDim returns true if the chart contains dimension with the given ID, false otherwise.
343 func (c Chart) HasDim(dimID string) bool {
344 return c.indexDim(dimID) != -1
345 }
346
347 // Copy returns a deep copy of the chart.
348 func (c Chart) Copy() *Chart {
349 chart := c
350 chart.Dims = Dims{}
351 chart.Vars = Vars{}
352
353 for idx := range c.Dims {
354 chart.Dims = append(chart.Dims, c.Dims[idx].copy())
355 }
356 for idx := range c.Vars {
357 chart.Vars = append(chart.Vars, c.Vars[idx].copy())
358 }
359
360 return &chart
361 }
362
363 func (c Chart) indexDim(dimID string) int {
364 for idx := range c.Dims {
365 if c.Dims[idx].ID == dimID {
366 return idx
367 }
368 }
369 return -1
370 }
371
372 func (c Chart) indexVar(varID string) int {
373 for idx := range c.Vars {
374 if c.Vars[idx].ID == varID {
375 return idx
376 }
377 }
378 return -1
379 }
380
381 func (d Dim) copy() *Dim {
382 return &d
383 }
384
385 func (v Var) copy() *Var {
386 return &v
387 }
388
389 func checkCharts(charts ...*Chart) error {
390 for _, chart := range charts {
391 err := checkChart(chart)
392 if err != nil {
393 return fmt.Errorf("chart '%s' : %v", chart.ID, err)
394 }
395 }
396 return nil
397 }
398
399 // CheckCharts validates chart definitions.
400 func CheckCharts(charts ...*Chart) error {
401 return checkCharts(charts...)
402 }
403
404 func checkChart(chart *Chart) error {
405 if chart.ID == "" {
406 return errors.New("empty ID")
407 }
408
409 if chart.Title == "" {
410 return errors.New("empty Title")
411 }
412
413 if chart.Units == "" {
414 return errors.New("empty Units")
415 }
416
417 if id := checkID(chart.ID); id != -1 {
418 return fmt.Errorf("unacceptable symbol in ID : '%c'", id)
419 }
420
421 set := make(map[string]bool)
422
423 for _, d := range chart.Dims {
424 err := checkDim(d)
425 if err != nil {
426 return err
427 }
428 if set[d.ID] {
429 return fmt.Errorf("duplicate dim '%s'", d.ID)
430 }
431 set[d.ID] = true
432 }
433
434 set = make(map[string]bool)
435
436 for _, v := range chart.Vars {
437 if err := checkVar(v); err != nil {
438 return err
439 }
440 if set[v.ID] {
441 return fmt.Errorf("duplicate var '%s'", v.ID)
442 }
443 set[v.ID] = true
444 }
445 return nil
446 }
447
448 func checkDim(d *Dim) error {
449 if d.ID == "" {
450 return errors.New("empty dim ID")
451 }
452 if id := checkID(d.ID); id != -1 && (d.Name == "" || checkID(d.Name) != -1) {
453 return fmt.Errorf("unacceptable symbol in dim ID '%s' : '%c'", d.ID, id)
454 }
455 return nil
456 }
457
458 func checkVar(v *Var) error {
459 if v.ID == "" {
460 return errors.New("empty var ID")
461 }
462 if id := checkID(v.ID); id != -1 {
463 return fmt.Errorf("unacceptable symbol in var ID '%s' : '%c'", v.ID, id)
464 }
465 return nil
466 }
467
468 func checkID(id string) int {
469 for _, r := range id {
470 if unicode.IsSpace(r) {
471 return int(r)
472 }
473 }
474 return -1
475 }