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