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  }