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  }