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

     1  package divideSeries
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"fmt"
     7  	"math"
     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 divideSeries 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 := &divideSeries{}
    24  	functions := []string{"divideSeries"}
    25  	for _, n := range functions {
    26  		res = append(res, interfaces.FunctionMetadata{Name: n, F: f})
    27  	}
    28  	return res
    29  }
    30  
    31  // divideSeries(dividendSeriesList, divisorSeriesList)
    32  func (f *divideSeries) Do(ctx context.Context, eval interfaces.Evaluator, e parser.Expr, from, until int64, values map[parser.MetricRequest][]*types.MetricData) ([]*types.MetricData, error) {
    33  	if e.ArgsLen() < 1 {
    34  		return nil, parser.ErrMissingTimeseries
    35  	}
    36  
    37  	firstArg, err := helper.GetSeriesArg(ctx, eval, e.Arg(0), from, until, values)
    38  	if err != nil {
    39  		return nil, err
    40  	}
    41  
    42  	var useMetricNames bool
    43  
    44  	var numerators []*types.MetricData
    45  	var denominator *types.MetricData
    46  	var results []*types.MetricData
    47  
    48  	if e.ArgsLen() == 2 {
    49  		useMetricNames = true
    50  		numerators = firstArg
    51  		denominators, err := helper.GetSeriesArg(ctx, eval, e.Arg(1), from, until, values)
    52  		if err != nil {
    53  			return nil, err
    54  		}
    55  		if len(denominators) == 0 {
    56  			results := make([]*types.MetricData, 0, len(numerators))
    57  			for _, numerator := range numerators {
    58  				r := numerator.CopyLink()
    59  				r.Values = make([]float64, len(numerator.Values))
    60  				r.Name = fmt.Sprintf("divideSeries(%s,MISSING)", numerator.Name)
    61  				for i := range numerator.Values {
    62  					r.Values[i] = math.NaN()
    63  				}
    64  				results = append(results, r)
    65  			}
    66  			return results, nil
    67  		}
    68  
    69  		if len(denominators) > 1 {
    70  			return nil, types.ErrWildcardNotAllowed
    71  		}
    72  
    73  		denominator = denominators[0]
    74  	} else if len(firstArg) == 2 && e.ArgsLen() == 1 {
    75  		numerators = append(numerators, firstArg[0])
    76  		denominator = firstArg[1]
    77  	} else {
    78  		return nil, errors.New("must be called with 2 series or a wildcard that matches exactly 2 series")
    79  	}
    80  
    81  	for _, numerator := range numerators {
    82  		var name string
    83  		if useMetricNames {
    84  			name = "divideSeries(" + numerator.Name + "," + denominator.Name + ")"
    85  		} else {
    86  			name = "divideSeries(" + e.RawArgs() + ")"
    87  		}
    88  
    89  		numerator, denominator = helper.ConsolidateSeriesByStep(numerator, denominator)
    90  
    91  		r := numerator.CopyTag(name, numerator.Tags)
    92  		r.Values = make([]float64, len(numerator.Values))
    93  
    94  		for i, v := range numerator.Values {
    95  			// math.IsNaN(v) || math.IsNaN(denominator.Values[i]) covered by nature of math.NaN
    96  			if denominator.Values[i] == 0 {
    97  				r.Values[i] = math.NaN()
    98  			} else {
    99  				r.Values[i] = v / denominator.Values[i]
   100  			}
   101  		}
   102  		results = append(results, r)
   103  	}
   104  
   105  	return results, nil
   106  
   107  }
   108  
   109  // Description is auto-generated description, based on output of https://github.com/graphite-project/graphite-web
   110  func (f *divideSeries) Description() map[string]types.FunctionDescription {
   111  	return map[string]types.FunctionDescription{
   112  		"divideSeries": {
   113  			Description: "Takes a dividend metric and a divisor metric and draws the division result.\nA constant may *not* be passed. To divide by a constant, use the scale()\nfunction (which is essentially a multiplication operation) and use the inverse\nof the dividend. (Division by 8 = multiplication by 1/8 or 0.125)\n\nExample:\n\n.. code-block:: none\n\n  &target=divideSeries(Series.dividends,Series.divisors)",
   114  			Function:    "divideSeries(dividendSeriesList, divisorSeries)",
   115  			Group:       "Combine",
   116  			Module:      "graphite.render.functions",
   117  			Name:        "divideSeries",
   118  			Params: []types.FunctionParam{
   119  				{
   120  					Name:     "dividendSeriesList",
   121  					Required: true,
   122  					Type:     types.SeriesList,
   123  				},
   124  				{
   125  					Name:     "divisorSeries",
   126  					Required: true,
   127  					Type:     types.SeriesList,
   128  				},
   129  			},
   130  			SeriesChange: true, // function aggregate metrics or change series items count
   131  			NameChange:   true, // name changed
   132  			TagsChange:   true, // name tag changed
   133  			ValuesChange: true, // values changed
   134  		},
   135  	}
   136  }