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

     1  package expr
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  
     7  	"github.com/ansel1/merry"
     8  	pb "github.com/go-graphite/protocol/carbonapi_v3_pb"
     9  
    10  	_ "github.com/go-graphite/carbonapi/expr/functions"
    11  	"github.com/go-graphite/carbonapi/expr/functions/consolidateBy"
    12  	"github.com/go-graphite/carbonapi/expr/helper"
    13  	"github.com/go-graphite/carbonapi/expr/interfaces"
    14  	"github.com/go-graphite/carbonapi/expr/metadata"
    15  	"github.com/go-graphite/carbonapi/expr/types"
    16  	"github.com/go-graphite/carbonapi/limiter"
    17  	"github.com/go-graphite/carbonapi/pkg/parser"
    18  	utilctx "github.com/go-graphite/carbonapi/util/ctx"
    19  	zipper "github.com/go-graphite/carbonapi/zipper/interfaces"
    20  )
    21  
    22  var ErrZipperNotInit = errors.New("zipper not initialized")
    23  
    24  type Evaluator struct {
    25  	limiter                limiter.SimpleLimiter
    26  	zipper                 zipper.CarbonZipper
    27  	passFunctionsToBackend bool
    28  }
    29  
    30  func (eval Evaluator) Fetch(ctx context.Context, exprs []parser.Expr, from, until int64, values map[parser.MetricRequest][]*types.MetricData) (map[parser.MetricRequest][]*types.MetricData, error) {
    31  	if err := eval.limiter.Enter(ctx); err != nil {
    32  		return nil, err
    33  	}
    34  	defer eval.limiter.Leave()
    35  
    36  	multiFetchRequest := pb.MultiFetchRequest{}
    37  	metricRequestCache := make(map[string]parser.MetricRequest)
    38  	maxDataPoints := utilctx.GetMaxDatapoints(ctx)
    39  	// values related to this particular `target=`
    40  	targetValues := make(map[parser.MetricRequest][]*types.MetricData)
    41  
    42  	haveFallbackSeries := false
    43  	for _, exp := range exprs {
    44  		for _, m := range exp.Metrics(from, until) {
    45  			fetchRequest := pb.FetchRequest{
    46  				Name:           m.Metric,
    47  				PathExpression: m.Metric,
    48  				StartTime:      m.From,
    49  				StopTime:       m.Until,
    50  				MaxDataPoints:  maxDataPoints,
    51  			}
    52  			metricRequest := parser.MetricRequest{
    53  				Metric: fetchRequest.PathExpression,
    54  				From:   fetchRequest.StartTime,
    55  				Until:  fetchRequest.StopTime,
    56  			}
    57  
    58  			if eval.passFunctionsToBackend && m.ConsolidationFunc != "" {
    59  				if _, ok := consolidateBy.ValidAggregateFunctions[m.ConsolidationFunc]; !ok {
    60  					return nil, merry.WithMessagef(parser.ErrInvalidArg, "invalid consolidateBy argument: '%s'", m.ConsolidationFunc)
    61  				}
    62  				fetchRequest.FilterFunctions = append(fetchRequest.FilterFunctions, &pb.FilteringFunction{
    63  					Name:      "consolidateBy",
    64  					Arguments: []string{m.ConsolidationFunc},
    65  				})
    66  			}
    67  
    68  			if exp.Target() == "fallbackSeries" {
    69  				haveFallbackSeries = true
    70  			}
    71  
    72  			// avoid multiple requests in a function, E.g divideSeries(a.b, a.b)
    73  			if cachedMetricRequest, ok := metricRequestCache[m.Metric]; ok &&
    74  				cachedMetricRequest.From == metricRequest.From &&
    75  				cachedMetricRequest.Until == metricRequest.Until {
    76  				continue
    77  			}
    78  
    79  			// avoid multiple requests in a http request, E.g render?target=a.b&target=a.b
    80  			if _, ok := values[metricRequest]; ok {
    81  				targetValues[metricRequest] = nil
    82  				continue
    83  			}
    84  
    85  			// avoid multiple requests from the same target, e.g. target=max(a,asPercent(holtWintersForecast(a),a))
    86  			if _, ok := targetValues[metricRequest]; ok {
    87  				continue
    88  			}
    89  
    90  			metricRequestCache[m.Metric] = metricRequest
    91  			targetValues[metricRequest] = nil
    92  			multiFetchRequest.Metrics = append(multiFetchRequest.Metrics, fetchRequest)
    93  		}
    94  	}
    95  
    96  	if len(multiFetchRequest.Metrics) > 0 {
    97  		metrics, _, err := eval.zipper.Render(ctx, multiFetchRequest)
    98  		// If we had only partial result, we want to do our best to actually do our job
    99  		if err != nil && merry.HTTPCode(err) >= 400 && !haveFallbackSeries {
   100  			return nil, err
   101  		}
   102  		for _, metric := range metrics {
   103  			metricRequest := metricRequestCache[metric.PathExpression]
   104  			if metric.RequestStartTime != 0 && metric.RequestStopTime != 0 {
   105  				metricRequest.From = metric.RequestStartTime
   106  				metricRequest.Until = metric.RequestStopTime
   107  			}
   108  			data, ok := values[metricRequest]
   109  			if !ok {
   110  				data = make([]*types.MetricData, 0, 1)
   111  			}
   112  			values[metricRequest] = append(data, metric)
   113  		}
   114  	}
   115  
   116  	for m := range targetValues {
   117  		targetValues[m] = values[m]
   118  	}
   119  
   120  	if eval.zipper.ScaleToCommonStep() {
   121  		targetValues = helper.ScaleValuesToCommonStep(targetValues)
   122  	}
   123  
   124  	return targetValues, nil
   125  }
   126  
   127  // Eval evaluates expressions.
   128  func (eval Evaluator) Eval(ctx context.Context, exp parser.Expr, from, until int64, values map[parser.MetricRequest][]*types.MetricData) (results []*types.MetricData, err error) {
   129  	rewritten, targets, err := RewriteExpr(ctx, eval, exp, from, until, values)
   130  	if err != nil {
   131  		return nil, err
   132  	}
   133  	if rewritten {
   134  		for _, target := range targets {
   135  			exp, _, err = parser.ParseExpr(target)
   136  			if err != nil {
   137  				return nil, err
   138  			}
   139  			targetValues, err := eval.Fetch(ctx, []parser.Expr{exp}, from, until, values)
   140  			if err != nil {
   141  				return nil, err
   142  			}
   143  			result, err := eval.Eval(ctx, exp, from, until, targetValues)
   144  			if err != nil {
   145  				return nil, err
   146  			}
   147  			results = append(results, result...)
   148  		}
   149  		return results, nil
   150  	}
   151  	return EvalExpr(ctx, eval, exp, from, until, values)
   152  }
   153  
   154  // NewEvaluator create evaluator with limiter and zipper
   155  func NewEvaluator(limiter limiter.SimpleLimiter, zipper zipper.CarbonZipper, passFunctionsToBackend bool) (*Evaluator, error) {
   156  	if zipper == nil {
   157  		return nil, ErrZipperNotInit
   158  	}
   159  	return &Evaluator{limiter: limiter, zipper: zipper, passFunctionsToBackend: passFunctionsToBackend}, nil
   160  }
   161  
   162  // EvalExpr is the main expression evaluator.
   163  func EvalExpr(ctx context.Context, eval interfaces.Evaluator, e parser.Expr, from, until int64, values map[parser.MetricRequest][]*types.MetricData) ([]*types.MetricData, error) {
   164  	if e.IsName() {
   165  		return values[parser.MetricRequest{Metric: e.Target(), From: from, Until: until}], nil
   166  	} else if e.IsConst() {
   167  		p := types.MetricData{
   168  			FetchResponse: pb.FetchResponse{
   169  				Name:      e.ToString(),
   170  				Values:    []float64{e.FloatValue()},
   171  				StartTime: from,
   172  				StopTime:  until,
   173  				StepTime:  until - from,
   174  			},
   175  			Tags: map[string]string{"name": e.ToString()},
   176  		}
   177  		return []*types.MetricData{&p}, nil
   178  	}
   179  	// evaluate the function
   180  
   181  	// all functions have arguments -- check we do too
   182  	if e.ArgsLen() == 0 {
   183  		err := merry.WithMessagef(parser.ErrMissingArgument, "target=%s: %s", e.Target(), parser.ErrMissingArgument)
   184  		return nil, merry.WithHTTPCode(err, 400)
   185  	}
   186  
   187  	metadata.FunctionMD.RLock()
   188  	f, ok := metadata.FunctionMD.Functions[e.Target()]
   189  	metadata.FunctionMD.RUnlock()
   190  	if ok {
   191  		v, err := f.Do(ctx, eval, e, from, until, values)
   192  		if err != nil {
   193  			err = merry.WithMessagef(err, "function=%s: %s", e.Target(), err.Error())
   194  			if merry.Is(
   195  				err,
   196  				parser.ErrMissingExpr,
   197  				parser.ErrMissingComma,
   198  				parser.ErrMissingQuote,
   199  				parser.ErrUnexpectedCharacter,
   200  				parser.ErrBadType,
   201  				parser.ErrMissingArgument,
   202  				parser.ErrMissingTimeseries,
   203  				parser.ErrMissingValues,
   204  				parser.ErrUnknownTimeUnits,
   205  				parser.ErrInvalidArg,
   206  			) {
   207  				err = merry.WithHTTPCode(err, 400)
   208  			}
   209  		}
   210  		return v, err
   211  	}
   212  
   213  	return nil, merry.WithHTTPCode(helper.ErrUnknownFunction(e.Target()), 400)
   214  }
   215  
   216  // RewriteExpr expands targets that use applyByNode into a new list of targets.
   217  // eg:
   218  // applyByNode(foo*, 1, "%") -> (true, ["foo1", "foo2"], nil)
   219  // sumSeries(foo) -> (false, nil, nil)
   220  // Assumes that applyByNode only appears as the outermost function.
   221  func RewriteExpr(ctx context.Context, eval interfaces.Evaluator, e parser.Expr, from, until int64, values map[parser.MetricRequest][]*types.MetricData) (bool, []string, error) {
   222  	if e.IsFunc() {
   223  		metadata.FunctionMD.RLock()
   224  		f, ok := metadata.FunctionMD.RewriteFunctions[e.Target()]
   225  		metadata.FunctionMD.RUnlock()
   226  		if ok {
   227  			return f.Do(ctx, eval, e, from, until, values)
   228  		}
   229  	}
   230  	return false, nil, nil
   231  }
   232  
   233  // FetchAndEvalExp fetch data and evaluates expressions
   234  func FetchAndEvalExp(ctx context.Context, eval interfaces.Evaluator, e parser.Expr, from, until int64, values map[parser.MetricRequest][]*types.MetricData) ([]*types.MetricData, merry.Error) {
   235  	targetValues, err := eval.Fetch(ctx, []parser.Expr{e}, from, until, values)
   236  	if err != nil {
   237  		return nil, merry.Wrap(err)
   238  	}
   239  
   240  	res, err := eval.Eval(ctx, e, from, until, targetValues)
   241  	if err != nil {
   242  		return nil, merry.Wrap(err)
   243  	}
   244  
   245  	for mReq := range values {
   246  		SortMetrics(values[mReq], mReq)
   247  	}
   248  
   249  	return res, nil
   250  }
   251  
   252  func FetchAndEvalExprs(ctx context.Context, eval interfaces.Evaluator, exprs []parser.Expr, from, until int64, values map[parser.MetricRequest][]*types.MetricData) ([]*types.MetricData, map[string]merry.Error) {
   253  	targetValues, err := eval.Fetch(ctx, exprs, from, until, values)
   254  	if err != nil {
   255  		return nil, map[string]merry.Error{"*": merry.Wrap(err)}
   256  	}
   257  
   258  	res := make([]*types.MetricData, 0, len(exprs))
   259  	var errors map[string]merry.Error
   260  	for _, exp := range exprs {
   261  		evaluationResult, err := eval.Eval(ctx, exp, from, until, targetValues)
   262  		if err != nil {
   263  			if errors == nil {
   264  				errors = make(map[string]merry.Error)
   265  			}
   266  			errors[exp.Target()] = merry.Wrap(err)
   267  		}
   268  		res = append(res, evaluationResult...)
   269  	}
   270  
   271  	for mReq := range values {
   272  		SortMetrics(values[mReq], mReq)
   273  	}
   274  
   275  	return res, errors
   276  }