github.com/m3db/m3@v1.5.0/src/query/graphite/native/expression.go (about) 1 // Copyright (c) 2019 Uber Technologies, Inc. 2 // 3 // Permission is hereby granted, free of charge, to any person obtaining a copy 4 // of this software and associated documentation files (the "Software"), to deal 5 // in the Software without restriction, including without limitation the rights 6 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 // copies of the Software, and to permit persons to whom the Software is 8 // furnished to do so, subject to the following conditions: 9 // 10 // The above copyright notice and this permission notice shall be included in 11 // all copies or substantial portions of the Software. 12 // 13 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 // THE SOFTWARE. 20 21 package native 22 23 import ( 24 "errors" 25 "fmt" 26 "reflect" 27 "time" 28 29 "github.com/m3db/m3/src/query/graphite/common" 30 "github.com/m3db/m3/src/query/graphite/storage" 31 "github.com/m3db/m3/src/query/graphite/ts" 32 xerrors "github.com/m3db/m3/src/x/errors" 33 ) 34 35 var ( 36 errTopLevelFunctionMustReturnTimeSeries = xerrors.NewInvalidParamsError( 37 errors.New("top-level functions must return timeseries data")) 38 ) 39 40 // An Expression is a metric query expression 41 type Expression interface { 42 CallASTNode 43 // Executes the expression against the given context, and returns the resulting time series data 44 Execute(ctx *common.Context) (ts.SeriesList, error) 45 } 46 47 // CallASTNode is an interface to help with printing the AST. 48 type CallASTNode interface { 49 // Name returns the name of the call. 50 Name() string 51 // Arguments describe each argument that the call has, some 52 // arguments that can either be a call or path expression. 53 Arguments() []ASTNode 54 } 55 56 // ASTNode is an interface to help with printing the AST. 57 type ASTNode interface { 58 // PathExpression returns the path expression and true if argument 59 // is a path. 60 PathExpression() (string, bool) 61 // CallExpression returns the call expression and true if argument 62 // is a call. 63 CallExpression() (CallASTNode, bool) 64 // String is the pretty printed format. 65 String() string 66 } 67 68 // A fetchExpression is an expression that fetches a bunch of data from storage based on a path expression 69 type fetchExpression struct { 70 // The path expression to fetch 71 pathArg fetchExpressionPathArg 72 } 73 74 type fetchExpressionPathArg struct { 75 path string 76 } 77 78 func (a fetchExpressionPathArg) PathExpression() (string, bool) { 79 return a.path, true 80 } 81 82 func (a fetchExpressionPathArg) CallExpression() (CallASTNode, bool) { 83 return nil, false 84 } 85 86 func (a fetchExpressionPathArg) String() string { 87 return a.path 88 } 89 90 // newFetchExpression creates a new fetch expression for a single path 91 func newFetchExpression(path string) *fetchExpression { 92 return &fetchExpression{pathArg: fetchExpressionPathArg{path: path}} 93 } 94 95 func (f *fetchExpression) Name() string { 96 return "fetch" 97 } 98 99 func (f *fetchExpression) Arguments() []ASTNode { 100 return []ASTNode{f.pathArg} 101 } 102 103 func (f *fetchExpression) PathExpression() (string, bool) { 104 return "", false 105 } 106 107 func (f *fetchExpression) CallExpression() (CallASTNode, bool) { 108 return f, true 109 } 110 111 // Execute fetches results from storage 112 func (f *fetchExpression) Execute(ctx *common.Context) (ts.SeriesList, error) { 113 begin := time.Now() 114 115 opts := storage.FetchOptions{ 116 StartTime: ctx.StartTime, 117 EndTime: ctx.EndTime, 118 DataOptions: storage.DataOptions{ 119 Timeout: ctx.Timeout, 120 }, 121 QueryFetchOpts: ctx.FetchOpts, 122 } 123 124 result, err := ctx.Engine.FetchByQuery(ctx, f.pathArg.path, opts) 125 if err != nil { 126 return ts.NewSeriesList(), err 127 } 128 129 if ctx.TracingEnabled() { 130 ctx.Trace(common.Trace{ 131 ActivityName: fmt.Sprintf("fetch %s", f.pathArg.path), 132 Duration: time.Since(begin), 133 Outputs: common.TraceStats{NumSeries: len(result.SeriesList)}, 134 }) 135 } 136 137 for _, r := range result.SeriesList { 138 r.Specification = f.pathArg.path 139 } 140 141 return ts.SeriesList{ 142 Values: result.SeriesList, 143 Metadata: result.Metadata, 144 }, nil 145 } 146 147 // Evaluate evaluates the fetch and returns its results as a reflection value, allowing it to be used 148 // as an input argument to a function that takes a time series 149 func (f *fetchExpression) Evaluate(ctx *common.Context) (reflect.Value, error) { 150 timeseries, err := f.Execute(ctx) 151 if err != nil { 152 return reflect.Value{}, err 153 } 154 155 return reflect.ValueOf(timeseries), nil 156 } 157 158 func (f *fetchExpression) Type() reflect.Type { 159 return reflect.ValueOf(f).Type() 160 } 161 162 // CompatibleWith returns true if the reflected type is a time series or a generic interface. 163 func (f *fetchExpression) CompatibleWith(reflectType reflect.Type) bool { 164 return reflectType == singlePathSpecType || reflectType == multiplePathSpecsType || reflectType == interfaceType 165 } 166 167 func (f *fetchExpression) String() string { 168 return fmt.Sprintf("fetch(%s)", f.pathArg.path) 169 } 170 171 // A funcExpression is an expression that evaluates a function returning a timeseries 172 type funcExpression struct { 173 call *functionCall 174 } 175 176 // newFuncExpression creates a new expressioon based on the given function call 177 func newFuncExpression(call *functionCall) (Expression, error) { 178 if !(call.f.out == seriesListType || call.f.out == unaryContextShifterPtrType) { 179 return nil, errTopLevelFunctionMustReturnTimeSeries 180 } 181 182 return &funcExpression{call: call}, nil 183 } 184 185 func (f *funcExpression) Name() string { 186 return f.call.Name() 187 } 188 189 func (f *funcExpression) Arguments() []ASTNode { 190 return f.call.Arguments() 191 } 192 193 // Execute evaluates the function and returns the result as a timeseries 194 func (f *funcExpression) Execute(ctx *common.Context) (ts.SeriesList, error) { 195 out, err := f.call.Evaluate(ctx) 196 if err != nil { 197 return ts.NewSeriesList(), err 198 } 199 200 return out.Interface().(ts.SeriesList), nil 201 } 202 203 func (f *funcExpression) String() string { return f.call.String() } 204 205 var _ ASTNode = noopExpression{} 206 207 // A noopExpression is an empty expression that returns nothing 208 type noopExpression struct{} 209 210 // Execute returns nothing 211 func (noop noopExpression) Execute(ctx *common.Context) (ts.SeriesList, error) { 212 return ts.NewSeriesList(), nil 213 } 214 215 func (noop noopExpression) Name() string { 216 return "noop" 217 } 218 219 func (noop noopExpression) Arguments() []ASTNode { 220 return nil 221 } 222 223 func (noop noopExpression) String() string { 224 return noop.Name() 225 } 226 227 func (noop noopExpression) PathExpression() (string, bool) { 228 return "", false 229 } 230 231 func (noop noopExpression) CallExpression() (CallASTNode, bool) { 232 return noop, true 233 } 234 235 var _ ASTNode = rootASTNode{} 236 237 // A rootASTNode is the root AST node which returns child nodes 238 // when parsing the grammar. 239 type rootASTNode struct { 240 expr Expression 241 } 242 243 func (r rootASTNode) Name() string { 244 return r.expr.Name() 245 } 246 247 func (r rootASTNode) Arguments() []ASTNode { 248 return r.expr.Arguments() 249 } 250 251 func (r rootASTNode) String() string { 252 return r.expr.(ASTNode).String() 253 } 254 255 func (r rootASTNode) PathExpression() (string, bool) { 256 return "", false 257 } 258 259 func (r rootASTNode) CallExpression() (CallASTNode, bool) { 260 return r.expr, true 261 }