github.com/go-graphite/carbonapi@v0.17.0/expr/helper/helper.go (about)

     1  package helper
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"math"
     7  	"regexp"
     8  	"strings"
     9  
    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  // Backref is a pre-compiled expression for backref
    16  var Backref = regexp.MustCompile(`\\(\d+)`)
    17  
    18  // ErrUnknownFunction is an error message about unknown function
    19  type ErrUnknownFunction string
    20  
    21  func (e ErrUnknownFunction) Error() string {
    22  	return fmt.Sprintf("unknown function in evalExpr: %q", string(e))
    23  }
    24  
    25  // GetSeriesArg returns argument from series.
    26  func GetSeriesArg(ctx context.Context, eval interfaces.Evaluator, arg parser.Expr, from, until int64, values map[parser.MetricRequest][]*types.MetricData) ([]*types.MetricData, error) {
    27  	if !arg.IsName() && !arg.IsFunc() {
    28  		return nil, parser.ErrMissingTimeseries
    29  	}
    30  
    31  	a, err := eval.Eval(ctx, arg, from, until, values)
    32  	if err != nil {
    33  		return nil, err
    34  	}
    35  
    36  	return a, nil
    37  }
    38  
    39  // RemoveEmptySeriesFromName removes empty series from list of names.
    40  func RemoveEmptySeriesFromName(args []*types.MetricData) string {
    41  	var argNames []string
    42  	for _, arg := range args {
    43  		argNames = append(argNames, arg.Name)
    44  	}
    45  
    46  	return strings.Join(argNames, ",")
    47  }
    48  
    49  // GetSeriesArgs returns arguments of series
    50  func GetSeriesArgs(ctx context.Context, eval interfaces.Evaluator, e []parser.Expr, from, until int64, values map[parser.MetricRequest][]*types.MetricData) ([]*types.MetricData, error) {
    51  	var args []*types.MetricData
    52  
    53  	for _, arg := range e {
    54  		a, err := GetSeriesArg(ctx, eval, arg, from, until, values)
    55  		if err != nil {
    56  			return nil, err
    57  		}
    58  		args = append(args, a...)
    59  	}
    60  
    61  	if len(args) == 0 {
    62  		return nil, nil
    63  	}
    64  
    65  	return args, nil
    66  }
    67  
    68  // GetSeriesArgsAndRemoveNonExisting will fetch all required arguments, but will also filter out non existing Series
    69  // This is needed to be graphite-web compatible in cases when you pass non-existing Series to, for example, sumSeries
    70  func GetSeriesArgsAndRemoveNonExisting(ctx context.Context, eval interfaces.Evaluator, e parser.Expr, from, until int64, values map[parser.MetricRequest][]*types.MetricData) ([]*types.MetricData, error) {
    71  	args, err := GetSeriesArgs(ctx, eval, e.Args(), from, until, values)
    72  	if err != nil {
    73  		return nil, err
    74  	}
    75  
    76  	// We need to rewrite name if there are some missing metrics
    77  	if len(args) < e.ArgsLen() {
    78  		e.SetRawArgs(RemoveEmptySeriesFromName(args))
    79  	}
    80  
    81  	return args, nil
    82  }
    83  
    84  // AggKey returns joined by dot nodes of tags names
    85  func AggKey(arg *types.MetricData, nodesOrTags []parser.NodeOrTag) string {
    86  	matched := make([]string, 0, len(nodesOrTags))
    87  	metricTags := arg.Tags
    88  	name := types.ExtractNameTag(arg.Name)
    89  	nodes := strings.Split(name, ".")
    90  	for _, nt := range nodesOrTags {
    91  		if nt.IsTag {
    92  			tagStr := nt.Value.(string)
    93  			matched = append(matched, metricTags[tagStr])
    94  		} else {
    95  			f := nt.Value.(int)
    96  			if f < 0 {
    97  				f += len(nodes)
    98  			}
    99  			if f >= len(nodes) || f < 0 {
   100  				continue
   101  			}
   102  			matched = append(matched, nodes[f])
   103  		}
   104  	}
   105  	if len(matched) > 0 {
   106  		return strings.Join(matched, ".")
   107  	}
   108  	return ""
   109  }
   110  
   111  type seriesFunc1 func(*types.MetricData) *types.MetricData
   112  
   113  // ForEachSeriesDo do action for each serie in list.
   114  func ForEachSeriesDo1(ctx context.Context, eval interfaces.Evaluator, e parser.Expr, from, until int64, values map[parser.MetricRequest][]*types.MetricData, function seriesFunc1) ([]*types.MetricData, error) {
   115  	arg, err := GetSeriesArg(ctx, eval, e.Arg(0), from, until, values)
   116  	if err != nil {
   117  		return nil, err
   118  	}
   119  
   120  	var results []*types.MetricData
   121  
   122  	for _, a := range arg {
   123  		results = append(results, function(a))
   124  	}
   125  	return results, nil
   126  }
   127  
   128  type seriesFunc func(*types.MetricData, *types.MetricData) *types.MetricData
   129  
   130  // ForEachSeriesDo do action for each serie in list.
   131  func ForEachSeriesDo(ctx context.Context, eval interfaces.Evaluator, e parser.Expr, from, until int64, values map[parser.MetricRequest][]*types.MetricData, function seriesFunc) ([]*types.MetricData, error) {
   132  	arg, err := GetSeriesArg(ctx, eval, e.Arg(0), from, until, values)
   133  	if err != nil {
   134  		return nil, err
   135  	}
   136  
   137  	var results []*types.MetricData
   138  
   139  	for _, a := range arg {
   140  		r := a.CopyName(e.Target() + "(" + a.Name + ")")
   141  		r.Values = make([]float64, len(a.Values))
   142  		results = append(results, function(a, r))
   143  	}
   144  	return results, nil
   145  }
   146  
   147  // AggregateFunc type that defined aggregate function
   148  type AggregateFunc func([]float64) float64
   149  
   150  // AggregateSeries aggregates series
   151  func AggregateSeries(e parser.Expr, args []*types.MetricData, function AggregateFunc, xFilesFactor float64, extractTagsFromArgs bool) ([]*types.MetricData, error) {
   152  	if len(args) == 0 {
   153  		// GraphiteWeb does this, no matter the function
   154  		// https://github.com/graphite-project/graphite-web/blob/b52987ac97f49dcfb401a21d4b92860cfcbcf074/webapp/graphite/render/functions.py#L228
   155  		return args, nil
   156  	}
   157  
   158  	var applyXFilesFactor = xFilesFactor >= 0
   159  
   160  	args = ScaleSeries(args)
   161  	length := len(args[0].Values)
   162  	r := args[0].CopyNameArg(e.Target()+"("+e.RawArgs()+")", e.Target(), GetCommonTags(args), extractTagsFromArgs)
   163  	r.Values = make([]float64, length)
   164  
   165  	if _, ok := r.Tags["name"]; !ok {
   166  		r.Tags["name"] = r.Name
   167  	}
   168  
   169  	values := make([]float64, len(args))
   170  	for i := range args[0].Values {
   171  		for n, arg := range args {
   172  			values[n] = arg.Values[i]
   173  		}
   174  
   175  		r.Values[i] = math.NaN()
   176  		if len(values) > 0 {
   177  			if applyXFilesFactor && !XFilesFactorValues(values, xFilesFactor) {
   178  				// if an xFileFactor is specified and the ratio of NaN values to non-NaN values is not equal to
   179  				// or greater than the xFilesFactor, the value should be NaN
   180  				r.Values[i] = math.NaN()
   181  			} else {
   182  				r.Values[i] = function(values)
   183  			}
   184  		}
   185  	}
   186  
   187  	return []*types.MetricData{r}, nil
   188  }
   189  
   190  // Contains check if slice 'a' contains value 'i'
   191  func Contains(a []int, i int) bool {
   192  	for _, aa := range a {
   193  		if aa == i {
   194  			return true
   195  		}
   196  	}
   197  	return false
   198  }
   199  
   200  // CopyTags makes a deep copy of the tags
   201  func CopyTags(series *types.MetricData) map[string]string {
   202  	out := make(map[string]string, len(series.Tags))
   203  	for k, v := range series.Tags {
   204  		out[k] = v
   205  	}
   206  	return out
   207  }
   208  
   209  func GetCommonTags(series []*types.MetricData) map[string]string {
   210  	if len(series) == 0 {
   211  		return make(map[string]string)
   212  	}
   213  	commonTags := CopyTags(series[0])
   214  	for _, serie := range series {
   215  		for k, v := range commonTags {
   216  			if serie.Tags[k] != v {
   217  				delete(commonTags, k)
   218  			}
   219  		}
   220  	}
   221  
   222  	return commonTags
   223  }
   224  
   225  func SafeRound(x float64, precision int) float64 {
   226  	if math.IsNaN(x) {
   227  		return x
   228  	}
   229  	roundTo := math.Pow10(precision)
   230  	return math.RoundToEven(x*roundTo) / roundTo
   231  }
   232  
   233  func XFilesFactorValues(values []float64, xFilesFactor float64) bool {
   234  	if math.IsNaN(xFilesFactor) || xFilesFactor == 0 {
   235  		return true
   236  	}
   237  	nonNull := 0
   238  	for _, val := range values {
   239  		if !math.IsNaN(val) {
   240  			nonNull++
   241  		}
   242  	}
   243  	return XFilesFactor(nonNull, len(values), xFilesFactor)
   244  }
   245  
   246  func XFilesFactor(nonNull int, total int, xFilesFactor float64) bool {
   247  	if nonNull < 0 || total <= 0 {
   248  		return false
   249  	}
   250  	return float64(nonNull)/float64(total) >= xFilesFactor
   251  }
   252  
   253  type unitPrefix struct {
   254  	prefix string
   255  	size   uint64
   256  }
   257  
   258  const floatEpsilon = 0.00000000001
   259  
   260  const (
   261  	unitSystemBinary = "binary"
   262  	unitSystemSI     = "si"
   263  )
   264  
   265  var UnitSystems = map[string][]unitPrefix{
   266  	unitSystemBinary: {
   267  		{"Pi", 1125899906842624}, // 1024^5
   268  		{"Ti", 1099511627776},    // 1024^4
   269  		{"Gi", 1073741824},       // 1024^3
   270  		{"Mi", 1048576},          // 1024^2
   271  		{"Ki", 1024},
   272  	},
   273  	unitSystemSI: {
   274  		{"P", 1000000000000000}, // 1000^5
   275  		{"T", 1000000000000},    // 1000^4
   276  		{"G", 1000000000},       // 1000^3
   277  		{"M", 1000000},          // 1000^2
   278  		{"K", 1000},
   279  	},
   280  }
   281  
   282  // formatUnits formats the given value according to the given unit prefix system
   283  func FormatUnits(v float64, system string) (float64, string) {
   284  	unitsystem := UnitSystems[system]
   285  	for _, p := range unitsystem {
   286  		fsize := float64(p.size)
   287  		if math.Abs(v) >= fsize {
   288  			v2 := v / fsize
   289  			if (v2-math.Floor(v2)) < floatEpsilon && v > 1 {
   290  				v2 = math.Floor(v2)
   291  			}
   292  			return v2, p.prefix
   293  		}
   294  	}
   295  	if (v-math.Floor(v)) < floatEpsilon && v > 1 {
   296  		v = math.Floor(v)
   297  	}
   298  	return v, ""
   299  }