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 }