github.com/go-graphite/carbonapi@v0.17.0/expr/functions/baselines/function.go (about)

     1  package baselines
     2  
     3  import (
     4  	"context"
     5  	"math"
     6  
     7  	"github.com/go-graphite/carbonapi/expr/consolidations"
     8  	"github.com/go-graphite/carbonapi/expr/helper"
     9  	"github.com/go-graphite/carbonapi/expr/interfaces"
    10  	"github.com/go-graphite/carbonapi/expr/types"
    11  	"github.com/go-graphite/carbonapi/pkg/parser"
    12  )
    13  
    14  type baselines struct{}
    15  
    16  func GetOrder() interfaces.Order {
    17  	return interfaces.Any
    18  }
    19  
    20  func New(configFile string) []interfaces.FunctionMetadata {
    21  	res := make([]interfaces.FunctionMetadata, 0)
    22  	f := &baselines{}
    23  	functions := []string{"baseline", "baselineAberration"}
    24  	for _, n := range functions {
    25  		res = append(res, interfaces.FunctionMetadata{Name: n, F: f})
    26  	}
    27  	return res
    28  }
    29  
    30  func (f *baselines) Do(ctx context.Context, eval interfaces.Evaluator, e parser.Expr, from, until int64, values map[parser.MetricRequest][]*types.MetricData) ([]*types.MetricData, error) {
    31  	unit, err := e.GetIntervalArg(1, -1)
    32  	if err != nil {
    33  		return nil, err
    34  	}
    35  	start, err := e.GetIntArg(2)
    36  	if err != nil {
    37  		return nil, err
    38  	}
    39  	end, err := e.GetIntArg(3)
    40  	if err != nil {
    41  		return nil, err
    42  	}
    43  	maxAbsentPercent, err := e.GetFloatArgDefault(4, math.NaN())
    44  	if err != nil {
    45  		return nil, err
    46  	}
    47  	minAvgLimit, err := e.GetFloatArgDefault(5, math.NaN())
    48  	if err != nil {
    49  		return nil, err
    50  	}
    51  
    52  	isAberration := false
    53  	if e.Target() == "baselineAberration" {
    54  		isAberration = true
    55  	}
    56  
    57  	current := make(map[string]*types.MetricData)
    58  	arg, _ := helper.GetSeriesArg(ctx, eval, e.Arg(0), from, until, values)
    59  	for _, a := range arg {
    60  		current[a.Name] = a
    61  	}
    62  
    63  	groups := make(map[string][]*types.MetricData)
    64  	for i := int32(start); i < int32(end); i++ {
    65  		if i == 0 {
    66  			continue
    67  		}
    68  		offs := int64(i * unit)
    69  		arg, _ := helper.GetSeriesArg(ctx, eval, e.Arg(0), from+offs, until+offs, values)
    70  		for _, a := range arg {
    71  			r := a.CopyLinkTags()
    72  			if _, ok := current[r.Name]; ok || !isAberration {
    73  				r.StartTime = a.StartTime - offs
    74  				r.StopTime = a.StopTime - offs
    75  				groups[r.Name] = append(groups[r.Name], r)
    76  			}
    77  		}
    78  	}
    79  
    80  	results := make([]*types.MetricData, 0, len(groups))
    81  	for name, args := range groups {
    82  		var newName string
    83  		if isAberration {
    84  			newName = "baselineAberration(" + name + ")"
    85  		} else {
    86  			newName = "baseline(" + name + ")"
    87  		}
    88  		r := args[0].CopyName(newName)
    89  		r.Values = make([]float64, len(args[0].Values))
    90  
    91  		tmp := make([][]float64, len(args[0].Values)) // number of points
    92  		lengths := make([]int, len(args[0].Values))   // number of points with data
    93  		atLeastOne := make([]bool, len(args[0].Values))
    94  		for _, arg := range args {
    95  			for i, v := range arg.Values {
    96  				if math.IsNaN(arg.Values[i]) {
    97  					continue
    98  				}
    99  				atLeastOne[i] = true
   100  				tmp[i] = append(tmp[i], v)
   101  				lengths[i]++
   102  			}
   103  		}
   104  
   105  		totalSum := 0.0
   106  		totalNotAbsent := 0
   107  		totalCnt := len(r.Values)
   108  
   109  		for i, v := range atLeastOne {
   110  			if v {
   111  				r.Values[i] = consolidations.Percentile(tmp[i][0:lengths[i]], 50, true)
   112  				totalSum += r.Values[i]
   113  				totalNotAbsent++
   114  				if isAberration {
   115  					if math.IsNaN(current[name].Values[i]) {
   116  						r.Values[i] = math.NaN()
   117  					} else if r.Values[i] != 0 {
   118  						r.Values[i] = current[name].Values[i] / r.Values[i]
   119  					}
   120  				}
   121  			} else {
   122  				r.Values[i] = math.NaN()
   123  			}
   124  		}
   125  
   126  		if !math.IsNaN(maxAbsentPercent) {
   127  			absentPercent := float64(100*(totalCnt-totalNotAbsent)) / float64(totalCnt)
   128  			if absentPercent > maxAbsentPercent {
   129  				continue
   130  			}
   131  		}
   132  
   133  		if !math.IsNaN(minAvgLimit) && (totalNotAbsent != 0) {
   134  			avg := totalSum / float64(totalNotAbsent)
   135  			if avg < minAvgLimit {
   136  				continue
   137  			}
   138  		}
   139  
   140  		results = append(results, r)
   141  	}
   142  
   143  	return results, nil
   144  }
   145  
   146  const baselineDescription = `Produce a baseline for the seriesList. Arguments are similar to timestack function.
   147  
   148  For each series takes an array of shifted points and computes a median for that.
   149  
   150  Example:
   151  .. code-block:: none
   152  
   153    baseline(metric, "1w", 1, 4)
   154  
   155  This would take 4 points of a metric, with 1-week interval and for each point will compute a median.
   156  
   157  Optional arguments:
   158    * maxAbsentPercent - do not compute a baseline is percentage of absent points is higher than this value.
   159    * minAvg - do not compute a baseline if average is lower than this value
   160  `
   161  
   162  const baselineAberrationDescription = `Deviation from baseline, in fractions. E.x. if value is over baseline by 10% result will be 1.1`
   163  
   164  func (f *baselines) Description() map[string]types.FunctionDescription {
   165  	return map[string]types.FunctionDescription{
   166  		"baseline": {
   167  			Description: baselineDescription,
   168  			Function:    "baseline(seriesList, timeShiftUnit, timeShiftStart, timeShiftEnd, [maxAbsentPercent, minAvg])",
   169  			Group:       "Calculate",
   170  			Module:      "graphite.render.functions",
   171  			Name:        "baseline",
   172  			Params: []types.FunctionParam{
   173  				{
   174  					Name:     "seriesList",
   175  					Required: true,
   176  					Type:     types.SeriesList,
   177  				},
   178  				{
   179  					Default: types.NewSuggestion("1d"),
   180  					Name:    "timeShiftUnit",
   181  					Suggestions: types.NewSuggestions(
   182  						"1h",
   183  						"6h",
   184  						"12h",
   185  						"1d",
   186  						"2d",
   187  						"7d",
   188  						"14d",
   189  						"30d",
   190  					),
   191  					Type: types.Interval,
   192  				},
   193  				{
   194  					Default: types.NewSuggestion(0),
   195  					Name:    "timeShiftStart",
   196  					Type:    types.Integer,
   197  				},
   198  				{
   199  					Default: types.NewSuggestion(7),
   200  					Name:    "timeShiftEnd",
   201  					Type:    types.Integer,
   202  				},
   203  			},
   204  			SeriesChange: true, // function aggregate metrics or change series items count
   205  			NameChange:   true, // name changed
   206  			TagsChange:   true, // name tag changed
   207  			ValuesChange: true, // values changed
   208  		},
   209  		"baselineAberration": {
   210  			Description: baselineAberrationDescription,
   211  			Function:    "baselineAberration(seriesList, timeShiftUnit, timeShiftStart, timeShiftEnd, [maxAbsentPercent, minAvg])",
   212  			Group:       "Calculate",
   213  			Module:      "graphite.render.functions",
   214  			Name:        "baselineAberration",
   215  			Params: []types.FunctionParam{
   216  				{
   217  					Default: types.NewSuggestion("1d"),
   218  					Name:    "timeShiftUnit",
   219  					Suggestions: types.NewSuggestions(
   220  						"1h",
   221  						"6h",
   222  						"12h",
   223  						"1d",
   224  						"2d",
   225  						"7d",
   226  						"14d",
   227  						"30d",
   228  					),
   229  					Type: types.Interval,
   230  				},
   231  				{
   232  					Default: types.NewSuggestion(0),
   233  					Name:    "timeShiftStart",
   234  					Type:    types.Integer,
   235  				},
   236  				{
   237  					Default: types.NewSuggestion(7),
   238  					Name:    "timeShiftEnd",
   239  					Type:    types.Integer,
   240  				},
   241  				{
   242  					Default: types.NewSuggestion(0.0),
   243  					Name:    "maxAbsentPercent",
   244  					Type:    types.Float,
   245  				},
   246  				{
   247  					Default: types.NewSuggestion(0.0),
   248  					Name:    "minAvg",
   249  					Type:    types.Float,
   250  				},
   251  			},
   252  			SeriesChange: true, // function aggregate metrics or change series items count
   253  			NameChange:   true, // name changed
   254  			TagsChange:   true, // name tag changed
   255  			ValuesChange: true, // values changed
   256  		},
   257  	}
   258  }