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

     1  package seriesList
     2  
     3  import (
     4  	"context"
     5  	"math"
     6  	"sort"
     7  	"strconv"
     8  
     9  	"github.com/go-graphite/carbonapi/expr/helper"
    10  	"github.com/go-graphite/carbonapi/expr/interfaces"
    11  	"github.com/go-graphite/carbonapi/expr/types"
    12  	"github.com/go-graphite/carbonapi/pkg/parser"
    13  )
    14  
    15  type seriesList struct{}
    16  
    17  func GetOrder() interfaces.Order {
    18  	return interfaces.Any
    19  }
    20  
    21  func New(configFile string) []interfaces.FunctionMetadata {
    22  	res := make([]interfaces.FunctionMetadata, 0)
    23  	f := &seriesList{}
    24  	functions := []string{"divideSeriesLists", "diffSeriesLists", "multiplySeriesLists", "powSeriesLists", "sumSeriesLists"}
    25  	for _, n := range functions {
    26  		res = append(res, interfaces.FunctionMetadata{Name: n, F: f})
    27  	}
    28  	return res
    29  }
    30  
    31  func (f *seriesList) Do(ctx context.Context, eval interfaces.Evaluator, e parser.Expr, from, until int64, values map[parser.MetricRequest][]*types.MetricData) ([]*types.MetricData, error) {
    32  	if e.ArgsLen() < 2 {
    33  		return nil, parser.ErrMissingArgument
    34  	}
    35  
    36  	useConstant := false
    37  	useDenom := false
    38  
    39  	defaultValue, err := e.GetFloatNamedOrPosArgDefault("default", 3, math.NaN())
    40  	if err != nil {
    41  		return nil, err
    42  	}
    43  
    44  	numerators, err := helper.GetSeriesArg(ctx, eval, e.Arg(0), from, until, values)
    45  	if err != nil {
    46  		return nil, err
    47  	}
    48  	if len(numerators) == 0 {
    49  		if !math.IsNaN(defaultValue) {
    50  			useConstant = true
    51  			useDenom = true
    52  		} else {
    53  			return nil, nil
    54  		}
    55  	}
    56  
    57  	denominators, err := helper.GetSeriesArg(ctx, eval, e.Arg(1), from, until, values)
    58  	if err != nil {
    59  		return nil, err
    60  	}
    61  	if len(denominators) == 0 {
    62  		if !math.IsNaN(defaultValue) && !useConstant {
    63  			useConstant = true
    64  		} else {
    65  			return nil, nil
    66  		}
    67  	}
    68  
    69  	sizeMatch := len(denominators) == len(numerators) || len(denominators) == 1
    70  	useMatching, err := e.GetBoolNamedOrPosArgDefault("matching", 2, !useConstant && !sizeMatch)
    71  	if err != nil {
    72  		return nil, err
    73  	}
    74  
    75  	sort.Slice(numerators, func(i, j int) bool { return numerators[i].Name < numerators[j].Name })
    76  	sort.Slice(denominators, func(i, j int) bool { return denominators[i].Name < denominators[j].Name })
    77  
    78  	functionName := e.Target()[:len(e.Target())-len("Lists")]
    79  
    80  	var compute func(l, r float64) float64
    81  
    82  	switch e.Target() {
    83  	case "divideSeriesLists":
    84  		compute = func(l, r float64) float64 { return l / r }
    85  	case "multiplySeriesLists":
    86  		compute = func(l, r float64) float64 { return l * r }
    87  	case "diffSeriesLists":
    88  		compute = func(l, r float64) float64 { return l - r }
    89  	case "powSeriesLists":
    90  		compute = math.Pow
    91  	case "sumSeriesLists":
    92  		compute = func(l, r float64) float64 { return l + r }
    93  	}
    94  
    95  	if useConstant {
    96  		var single []*types.MetricData
    97  		if useDenom {
    98  			single = denominators
    99  		} else {
   100  			single = numerators
   101  		}
   102  		results := make([]*types.MetricData, len(single))
   103  		for n, s := range single {
   104  			r := s.CopyLinkTags()
   105  			r.Name = functionName + "(" + s.Name + "," + s.Name + ")"
   106  			r.Values = make([]float64, len(s.Values))
   107  			for i, v := range s.Values {
   108  				if math.IsNaN(v) {
   109  					r.Values[i] = math.NaN()
   110  					continue
   111  				}
   112  
   113  				if e.Target() == "divideSeriesLists" {
   114  					if (useDenom && v == 0) || (!useDenom && defaultValue == 0) {
   115  						r.Values[i] = math.NaN()
   116  						continue
   117  					}
   118  				}
   119  				if useDenom {
   120  					r.Values[i] = compute(defaultValue, v)
   121  				} else {
   122  					r.Values[i] = compute(v, defaultValue)
   123  				}
   124  
   125  			}
   126  			results[n] = r
   127  		}
   128  		return results, nil
   129  	}
   130  
   131  	var denomMap map[string]*types.MetricData
   132  	if useMatching {
   133  		denomMap = make(map[string]*types.MetricData, len(denominators))
   134  		for _, s := range denominators {
   135  			denomMap[s.Name] = s
   136  		}
   137  	}
   138  
   139  	var denominator *types.MetricData
   140  
   141  	results := make([]*types.MetricData, 0, len(numerators))
   142  	for n, numerator := range numerators {
   143  		pairFound := false
   144  		if useMatching {
   145  			denominator, pairFound = denomMap[numerator.Name]
   146  			if !pairFound && math.IsNaN(defaultValue) {
   147  				continue
   148  			}
   149  		} else {
   150  			pairFound = true
   151  			if len(denominators) == 1 {
   152  				denominator = denominators[0]
   153  			} else {
   154  				denominator = denominators[n]
   155  			}
   156  		}
   157  		if pairFound {
   158  			numerator, denominator = helper.ConsolidateSeriesByStep(numerator, denominator)
   159  		}
   160  
   161  		r := numerator.CopyLink()
   162  		var denomName string
   163  		if pairFound {
   164  			denomName = denominator.Name
   165  		} else {
   166  			denomName = strconv.FormatFloat(defaultValue, 'f', -1, 64)
   167  		}
   168  		r.Name = functionName + "(" + numerator.Name + "," + denomName + ")"
   169  		r.Values = make([]float64, len(numerator.Values))
   170  
   171  		for i, v := range numerator.Values {
   172  			denomIsAbsent := pairFound && math.IsNaN(denominator.Values[i])
   173  			if math.IsNaN(numerator.Values[i]) || denomIsAbsent {
   174  				r.Values[i] = math.NaN()
   175  				continue
   176  			}
   177  
   178  			denomValue := defaultValue
   179  			if pairFound {
   180  				denomValue = denominator.Values[i]
   181  			}
   182  
   183  			switch e.Target() {
   184  			case "divideSeriesLists":
   185  				if denominator.Values[i] == 0 {
   186  					r.Values[i] = math.NaN()
   187  					continue
   188  				}
   189  				r.Values[i] = compute(v, denomValue)
   190  			default:
   191  				r.Values[i] = compute(v, denomValue)
   192  			}
   193  		}
   194  		results = append(results, r)
   195  	}
   196  	return results, nil
   197  }
   198  
   199  // Description is auto-generated description, based on output of https://github.com/graphite-project/graphite-web
   200  func (f *seriesList) Description() map[string]types.FunctionDescription {
   201  	return map[string]types.FunctionDescription{
   202  		"divideSeriesLists": {
   203  			Description: "Iterates over a two lists and divides list1[0} by list2[0}, list1[1} by list2[1} and so on.\nThe lists need to be the same length\nCarbonAPI-specific extension allows to specify default value as 3rd optional argument in case series doesn't exist or value is missing",
   204  			Function:    "divideSeriesLists(dividendSeriesList, divisorSeriesList)",
   205  			Group:       "Combine",
   206  			Module:      "graphite.render.functions",
   207  			Name:        "divideSeriesLists",
   208  			Params: []types.FunctionParam{
   209  				{
   210  					Name:     "dividendSeriesList",
   211  					Required: true,
   212  					Type:     types.SeriesList,
   213  				},
   214  				{
   215  					Name:     "divisorSeriesList",
   216  					Required: true,
   217  					Type:     types.SeriesList,
   218  				},
   219  				{
   220  					Name:     "default",
   221  					Required: false,
   222  					Type:     types.Float,
   223  				},
   224  			},
   225  			SeriesChange: true, // function aggregate metrics or change series items count
   226  			NameChange:   true, // name changed
   227  			TagsChange:   true, // name tag changed
   228  			ValuesChange: true, // values changed
   229  		},
   230  		"diffSeriesLists": {
   231  			Description: "Iterates over a two lists and substracts list1[0} by list2[0}, list1[1} by list2[1} and so on.\nThe lists need to be the same length\nCarbonAPI-specific extension allows to specify default value as 3rd optional argument in case series doesn't exist or value is missing",
   232  			Function:    "diffSeriesLists(firstSeriesList, secondSeriesList)",
   233  			Group:       "Combine",
   234  			Module:      "graphite.render.functions.custom",
   235  			Name:        "diffSeriesLists",
   236  			Params: []types.FunctionParam{
   237  				{
   238  					Name:     "firstSeriesList",
   239  					Required: true,
   240  					Type:     types.SeriesList,
   241  				},
   242  				{
   243  					Name:     "secondSeriesList",
   244  					Required: true,
   245  					Type:     types.SeriesList,
   246  				},
   247  				{
   248  					Name:     "default",
   249  					Required: false,
   250  					Type:     types.Float,
   251  				},
   252  			},
   253  			SeriesChange: true, // function aggregate metrics or change series items count
   254  			NameChange:   true, // name changed
   255  			TagsChange:   true, // name tag changed
   256  			ValuesChange: true, // values changed
   257  		},
   258  		"multiplySeriesLists": {
   259  			Description: "Iterates over a two lists and multiplies list1[0} by list2[0}, list1[1} by list2[1} and so on.\nThe lists need to be the same length\nCarbonAPI-specific extension allows to specify default value as 3rd optional argument in case series doesn't exist or value is missing",
   260  			Function:    "multiplySeriesLists(sourceSeriesList, factorSeriesList)",
   261  			Group:       "Combine",
   262  			Module:      "graphite.render.functions.custom",
   263  			Name:        "multiplySeriesLists",
   264  			Params: []types.FunctionParam{
   265  				{
   266  					Name:     "sourceSeriesList",
   267  					Required: true,
   268  					Type:     types.SeriesList,
   269  				},
   270  				{
   271  					Name:     "factorSeriesList",
   272  					Required: true,
   273  					Type:     types.SeriesList,
   274  				},
   275  				{
   276  					Name:     "default",
   277  					Required: false,
   278  					Type:     types.Float,
   279  				},
   280  			},
   281  			SeriesChange: true, // function aggregate metrics or change series items count
   282  			NameChange:   true, // name changed
   283  			TagsChange:   true, // name tag changed
   284  			ValuesChange: true, // values changed
   285  		},
   286  		"powSeriesLists": {
   287  			Description: "Iterates over a two lists and do list1[0} in power of list2[0}, list1[1} in power of  list2[1} and so on.\nThe lists need to be the same length\nCarbonAPI-specific extension allows to specify default value as 3rd optional argument in case series doesn't exist or value is missing",
   288  			Function:    "powSeriesLists(sourceSeriesList, factorSeriesList)",
   289  			Group:       "Combine",
   290  			Module:      "graphite.render.functions.custom",
   291  			Name:        "powSeriesLists",
   292  			Params: []types.FunctionParam{
   293  				{
   294  					Name:     "sourceSeriesList",
   295  					Required: true,
   296  					Type:     types.SeriesList,
   297  				},
   298  				{
   299  					Name:     "factorSeriesList",
   300  					Required: true,
   301  					Type:     types.SeriesList,
   302  				},
   303  				{
   304  					Name:     "default",
   305  					Required: false,
   306  					Type:     types.Float,
   307  				},
   308  			},
   309  			SeriesChange: true, // function aggregate metrics or change series items count
   310  			NameChange:   true, // name changed
   311  			TagsChange:   true, // name tag changed
   312  			ValuesChange: true, // values changed
   313  		},
   314  		"sumSeriesLists": {
   315  			Description: "Iterates over a two lists and subtracts series lists 2 through n from series 1 list1[0] to list2[0], list1[1] to list2[1] and so on. \n The lists will need to be the same length\nCarbonAPI-specific extension allows to specify default value as 3rd optional argument in case series doesn't exist or value is missing Example:\n\n.. code-block:: none\n\n  &target=sumSeriesLists(mining.{carbon,graphite,diamond}.extracted,mining.{carbon,graphite,diamond}.shipped)\n\n",
   316  			Function:    "sumSeriesLists(seriesListFirstPos, seriesListSecondPos)",
   317  			Group:       "Combine",
   318  			Module:      "graphite.render.functions.custom",
   319  			Name:        "sumSeriesLists",
   320  			Params: []types.FunctionParam{
   321  				{
   322  					Name:     "seriesListFirstPos",
   323  					Required: true,
   324  					Type:     types.SeriesList,
   325  				},
   326  				{
   327  					Name:     "seriesListSecondPos",
   328  					Required: true,
   329  					Type:     types.SeriesList,
   330  				},
   331  				{
   332  					Name:     "default",
   333  					Required: false,
   334  					Type:     types.Float,
   335  				},
   336  			},
   337  			SeriesChange: true, // function aggregate metrics or change series items count
   338  			NameChange:   true, // name changed
   339  			TagsChange:   true, // name tag changed
   340  			ValuesChange: true, // values changed
   341  		},
   342  	}
   343  }