github.com/m3db/m3@v1.5.0/src/query/graphite/native/functions.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  	"bytes"
    25  	"errors"
    26  	"fmt"
    27  	"reflect"
    28  	"runtime"
    29  	"strings"
    30  	"sync"
    31  	"time"
    32  
    33  	"github.com/m3db/m3/src/query/block"
    34  	"github.com/m3db/m3/src/query/graphite/common"
    35  	"github.com/m3db/m3/src/query/graphite/ts"
    36  	xerrors "github.com/m3db/m3/src/x/errors"
    37  )
    38  
    39  var (
    40  	funcMut   sync.RWMutex
    41  	functions = map[string]*Function{}
    42  )
    43  
    44  // list of graphite function name strings. (not whole list, update on-demand)
    45  const (
    46  	averageFnName        = "average"
    47  	averageSeriesFnName  = "averageSeries"
    48  	avgFnName            = "avg"
    49  	countFnName          = "count"
    50  	countSeriesFnName    = "countSeries"
    51  	currentFnName        = "current"
    52  	diffFnName           = "diff"
    53  	diffSeriesFnName     = "diffSeries"
    54  	emptyFnName          = ""
    55  	lastFnName           = "last"
    56  	lastSeriesFnName     = "lastSeries"
    57  	maxFnName            = "max"
    58  	maxSeriesFnName      = "maxSeries"
    59  	medianFnName         = "median"
    60  	medianSeriesFnName   = "medianSeries"
    61  	minFnName            = "min"
    62  	minSeriesFnName      = "minSeries"
    63  	multiplyFnName       = "multiply"
    64  	multiplySeriesFnName = "multiplySeries"
    65  	powSeriesFnName      = "powSeries"
    66  	rangeFnName          = "range"
    67  	rangeOfFnName        = "rangeOf"
    68  	rangeOfSeriesFnName  = "rangeOfSeries"
    69  	stdevFnName          = "stdev"
    70  	stddevFnName         = "stddev"
    71  	stddevSeriesFnName   = "stddevSeries"
    72  	sumFnName            = "sum"
    73  	sumSeriesFnName      = "sumSeries"
    74  	totalFnName          = "total"
    75  )
    76  
    77  // registerFunction is used to register a function under a specific name
    78  func registerFunction(f interface{}) (*Function, error) {
    79  	fn, err := buildFunction(f)
    80  	if err != nil {
    81  		return nil, err
    82  	}
    83  
    84  	funcMut.Lock()
    85  	defer funcMut.Unlock()
    86  
    87  	if functions[fn.name] != nil {
    88  		return nil, fmt.Errorf("func %s already registered", fn.name)
    89  	}
    90  	functions[fn.name] = fn
    91  	return fn, nil
    92  }
    93  
    94  // MustRegisterFunction registers a function, issuing a panic if the function cannot be registered
    95  func MustRegisterFunction(f interface{}) *Function {
    96  	if fn, err := registerFunction(f); err != nil {
    97  		if name, nerr := functionName(f); nerr == nil {
    98  			err = fmt.Errorf("could not register %s: %v", name, err)
    99  		}
   100  		panic(err)
   101  	} else {
   102  		return fn
   103  	}
   104  }
   105  
   106  // registerAliasedFunction is used to register a function under an alias
   107  func registerAliasedFunction(alias string, f interface{}) error {
   108  	fname, err := functionName(f)
   109  	if err != nil {
   110  		return err
   111  	}
   112  
   113  	funcMut.Lock()
   114  	defer funcMut.Unlock()
   115  
   116  	if functions[alias] != nil {
   117  		return fmt.Errorf("func %s already registered", alias)
   118  	}
   119  
   120  	fn := functions[fname]
   121  	if fn == nil {
   122  		return fmt.Errorf("target function %s not registered", fname)
   123  	}
   124  
   125  	functions[alias] = fn
   126  	return nil
   127  }
   128  
   129  // MustRegisterAliasedFunction registers a function under an alias, issuing a panic if the function
   130  // cannot be registered
   131  func MustRegisterAliasedFunction(fname string, f interface{}) {
   132  	if err := registerAliasedFunction(fname, f); err != nil {
   133  		panic(err)
   134  	}
   135  }
   136  
   137  // findFunction finds a function with the given name
   138  func findFunction(name string) *Function {
   139  	funcMut.RLock()
   140  	defer funcMut.RUnlock()
   141  
   142  	return functions[name]
   143  }
   144  
   145  // reflectTypeSet is a set of reflect.Type objects
   146  type reflectTypeSet []reflect.Type
   147  
   148  // contains checks whether the type set contains the given type
   149  func (ts reflectTypeSet) contains(reflectType reflect.Type) bool {
   150  	for i := range ts {
   151  		if ts[i] == reflectType {
   152  			return true
   153  		}
   154  	}
   155  
   156  	return false
   157  }
   158  
   159  // singlePathSpec represents one wildcard pathspec argument that may fetch multiple time series
   160  type singlePathSpec ts.SeriesList
   161  
   162  // multiplePathSpecs represents a variadic number of wildcard pathspecs
   163  type multiplePathSpecs ts.SeriesList
   164  
   165  // genericInterface represents a value with an arbitrary type
   166  type genericInterface interface{}
   167  
   168  // contextShiftFunc generates a shifted context based on an input context
   169  type contextShiftFunc func(*common.Context) *common.Context
   170  
   171  // unaryTransformer takes in one series and returns a transformed series.
   172  type unaryTransformer func(ts.SeriesList) (ts.SeriesList, error)
   173  
   174  // contextShiftAdjustFunc determines after an initial context shift whether
   175  // an adjustment is necessary or not
   176  type contextShiftAdjustFunc func(
   177  	shiftedContext *common.Context,
   178  	bootstrappedSeries ts.SeriesList,
   179  ) (*common.Context, bool, error)
   180  
   181  // unaryContextShifter contains a contextShiftFunc for generating shift contexts
   182  // as well as a unaryTransformer for transforming one series to another.
   183  type unaryContextShifter struct {
   184  	ContextShiftFunc       contextShiftFunc
   185  	UnaryTransformer       unaryTransformer
   186  	ContextShiftAdjustFunc contextShiftAdjustFunc
   187  }
   188  
   189  var (
   190  	contextPtrType             = reflect.TypeOf(&common.Context{})
   191  	timeSeriesType             = reflect.TypeOf(&ts.Series{})
   192  	timeSeriesListType         = reflect.SliceOf(timeSeriesType)
   193  	seriesListType             = reflect.TypeOf(ts.NewSeriesList())
   194  	unaryContextShifterPtrType = reflect.TypeOf(&unaryContextShifter{})
   195  	singlePathSpecType         = reflect.TypeOf(singlePathSpec{})
   196  	multiplePathSpecsType      = reflect.TypeOf(multiplePathSpecs{})
   197  	interfaceType              = reflect.TypeOf([]genericInterface{}).Elem()
   198  	float64Type                = reflect.TypeOf(float64(100))
   199  	float64SliceType           = reflect.SliceOf(float64Type)
   200  	intType                    = reflect.TypeOf(int(0))
   201  	intSliceType               = reflect.SliceOf(intType)
   202  	stringType                 = reflect.TypeOf("")
   203  	stringSliceType            = reflect.SliceOf(stringType)
   204  	boolType                   = reflect.TypeOf(false)
   205  	boolSliceType              = reflect.SliceOf(boolType)
   206  	errorType                  = reflect.TypeOf((*error)(nil)).Elem()
   207  	genericInterfaceType       = reflect.TypeOf((*genericInterface)(nil)).Elem()
   208  )
   209  
   210  var allowableTypes = reflectTypeSet{
   211  	// these are for return types
   212  	timeSeriesListType,
   213  	unaryContextShifterPtrType,
   214  	seriesListType,
   215  	singlePathSpecType,
   216  	multiplePathSpecsType,
   217  	interfaceType, // only for function parameters
   218  	float64Type,
   219  	float64SliceType,
   220  	intType,
   221  	intSliceType,
   222  	stringType,
   223  	stringSliceType,
   224  	boolType,
   225  	boolSliceType,
   226  }
   227  
   228  var (
   229  	errNonFunction   = xerrors.NewInvalidParamsError(errors.New("not a function"))
   230  	errNeedsArgument = xerrors.NewInvalidParamsError(errors.New("functions must take at least 1 argument"))
   231  	errNoContext     = xerrors.NewInvalidParamsError(errors.New("first argument must be a context"))
   232  	errInvalidReturn = xerrors.NewInvalidParamsError(errors.New("functions must return a value and an error"))
   233  )
   234  
   235  // Function contains a function to invoke along with metadata about
   236  // the function's argument and return type.
   237  type Function struct {
   238  	name     string
   239  	f        reflect.Value
   240  	in       []reflect.Type
   241  	defaults map[uint8]interface{}
   242  	out      reflect.Type
   243  	variadic bool
   244  
   245  	disableUnaryContextShiftFetchOptimization bool
   246  }
   247  
   248  // WithDefaultParams provides default parameters for functions
   249  func (f *Function) WithDefaultParams(defaultParams map[uint8]interface{}) *Function {
   250  	for index := range defaultParams {
   251  		if int(index) <= 0 || int(index) > len(f.in) {
   252  			panic(fmt.Sprintf("Default parameter #%d is out-of-range", index))
   253  		}
   254  	}
   255  	f.defaults = defaultParams
   256  	return f
   257  }
   258  
   259  // WithoutUnaryContextShifterSkipFetchOptimization allows a function to skip
   260  // the optimization that avoids fetching data for the first execution phase
   261  // of a unary context shifted function (where it is called the first time
   262  // without any series populated as input to the function and relies on
   263  // execution of the unary context shifter with the shifted series solely).
   264  // Note: This is useful for the "movingX" family of functions that require
   265  // that require actually seeing what the step size is sometimes of the series
   266  // from the first phase of execution to determine how much to look back for the
   267  // context shift phase.
   268  func (f *Function) WithoutUnaryContextShifterSkipFetchOptimization() *Function {
   269  	if f.out != unaryContextShifterPtrType {
   270  		panic("Skip fetch optimization only available to unary context shifters")
   271  	}
   272  	f.disableUnaryContextShiftFetchOptimization = true
   273  	return f
   274  }
   275  
   276  func functionName(f interface{}) (string, error) {
   277  	v := reflect.ValueOf(f)
   278  	t := v.Type()
   279  	if t.Kind() != reflect.Func {
   280  		return "", errNonFunction
   281  	}
   282  
   283  	nameParts := strings.Split(runtime.FuncForPC(v.Pointer()).Name(), ".")
   284  	return nameParts[len(nameParts)-1], nil
   285  }
   286  
   287  // validateContextShiftingFn validates if a function is a context shifting function.
   288  func validateContextShiftingFn(in []reflect.Type) {
   289  	// check that we have exactly *one* singlePathSpec parameter
   290  	singlePathSpecParams := 0
   291  	singlePathSpecIndex := -1
   292  	for i, param := range in {
   293  		if param == singlePathSpecType {
   294  			singlePathSpecParams++
   295  			singlePathSpecIndex = i
   296  		}
   297  	}
   298  	if singlePathSpecParams != 1 {
   299  		panic("A context-shifting function must have exactly one singlePathSpec parameter")
   300  	}
   301  	if singlePathSpecIndex != 0 {
   302  		panic("A context-shifting function must have the singlePathSpec parameter as its first parameter")
   303  	}
   304  }
   305  
   306  // buildFunction takes a reflection reference to a function and returns
   307  // the function metadata
   308  func buildFunction(f interface{}) (*Function, error) {
   309  	fname, err := functionName(f)
   310  	if err != nil {
   311  		return nil, err
   312  	}
   313  	v := reflect.ValueOf(f)
   314  	t := v.Type()
   315  	if t.NumIn() == 0 {
   316  		return nil, errNeedsArgument
   317  	}
   318  
   319  	if ctx := t.In(0); ctx != contextPtrType {
   320  		return nil, errNoContext
   321  	}
   322  
   323  	var lastType reflect.Type
   324  	in := make([]reflect.Type, 0, t.NumIn()-1)
   325  	for i := 1; i < t.NumIn(); i++ {
   326  		inArg := t.In(i)
   327  		if !(allowableTypes.contains(inArg)) {
   328  			return nil, fmt.Errorf("invalid arg %d: %s is not supported", i, inArg.Name())
   329  		}
   330  		if inArg == multiplePathSpecsType && i != t.NumIn()-1 {
   331  			return nil, fmt.Errorf("invalid arg %d: multiplePathSpecs must be the last arg", i)
   332  		}
   333  
   334  		lastType = inArg
   335  		in = append(in, inArg)
   336  	}
   337  
   338  	variadic := lastType == multiplePathSpecsType ||
   339  		(lastType != nil &&
   340  			lastType.Kind() == reflect.Slice &&
   341  			lastType != singlePathSpecType)
   342  
   343  	if variadic { // remove slice-ness of the variadic arg
   344  		if lastType != multiplePathSpecsType {
   345  			in[len(in)-1] = in[len(in)-1].Elem()
   346  		}
   347  	}
   348  
   349  	if t.NumOut() != 2 {
   350  		return nil, errInvalidReturn
   351  	}
   352  
   353  	out := t.Out(0)
   354  	if !allowableTypes.contains(out) {
   355  		return nil, fmt.Errorf("invalid return type %s", out.Name())
   356  	} else if out == unaryContextShifterPtrType {
   357  		validateContextShiftingFn(in)
   358  	}
   359  
   360  	if ret2 := t.Out(1); ret2 != errorType {
   361  		return nil, errInvalidReturn
   362  	}
   363  
   364  	return &Function{
   365  		name:     fname,
   366  		f:        v,
   367  		in:       in,
   368  		out:      out,
   369  		variadic: variadic,
   370  	}, nil
   371  }
   372  
   373  // call calls the function with non-reflected values
   374  func (f *Function) call(ctx *common.Context, args []interface{}) (interface{}, error) {
   375  	values := make([]reflect.Value, len(args))
   376  	for i := range args {
   377  		values[i] = reflect.ValueOf(args[i])
   378  	}
   379  
   380  	out, err := f.reflectCall(ctx, values)
   381  	if err != nil {
   382  		return nil, err
   383  	}
   384  
   385  	return out.Interface(), err
   386  }
   387  
   388  // reflectCall calls the function with reflected values, passing in the provided context and parameters
   389  func (f *Function) reflectCall(ctx *common.Context, args []reflect.Value) (reflect.Value, error) {
   390  	var instats []common.TraceStats
   391  
   392  	in := make([]reflect.Value, 0, len(args)+1)
   393  	in = append(in, reflect.ValueOf(ctx))
   394  	for _, arg := range args {
   395  		in = append(in, arg)
   396  		if isTimeSeries(arg) {
   397  			instats = append(instats, getStats(arg))
   398  		}
   399  	}
   400  
   401  	// special case handling of multiplePathSpecs
   402  	// NB(r): This code sucks, and it would be better if we just removed
   403  	// multiplePathSpecs altogether and have the functions use real variadic
   404  	// ts.SeriesList arguments so we don't have to autocollapse when calling here.
   405  	// Notably singlePathSpec should also go and just replace usages with
   406  	// barebones ts.SeriesList.  Then we can get rid of this code below and
   407  	// the code the casts ts.SeriesList to the correct typealias of ts.SeriesList.
   408  	if len(in) > len(f.in)+1 && len(f.in) > 0 && f.in[len(f.in)-1] == multiplePathSpecsType {
   409  		var (
   410  			series = make([]*ts.Series, 0, len(in))
   411  			// Assume all sorted until proven otherwise
   412  			sortedAll = true
   413  			meta      = block.NewResultMetadata()
   414  		)
   415  		for i := len(f.in); i < len(in); i++ {
   416  			v := in[i].Interface().(ts.SeriesList)
   417  
   418  			// If any series lists are not sorted then the result
   419  			// is not in deterministic sort order
   420  			if sortedAll && !v.SortApplied {
   421  				sortedAll = false
   422  			}
   423  
   424  			meta = meta.CombineMetadata(v.Metadata)
   425  			series = append(series, v.Values...)
   426  		}
   427  
   428  		in[len(f.in)] = reflect.ValueOf(ts.SeriesList{
   429  			Values: series,
   430  			// Only consider the aggregation of all these series lists
   431  			// sorted if and only if all originally had a sort applied
   432  			SortApplied: sortedAll,
   433  			Metadata:    meta,
   434  		})
   435  
   436  		in = in[:len(f.in)+1]
   437  	}
   438  
   439  	numTypes := len(f.in)
   440  	numRequiredTypes := numTypes
   441  	if f.variadic {
   442  		// Variadic can avoid specifying the last arg.
   443  		numRequiredTypes--
   444  	}
   445  	if len(in) < numRequiredTypes {
   446  		err := fmt.Errorf("call args mismatch: expected at least %d, actual %d",
   447  			len(f.in), len(in))
   448  		return reflect.Value{}, err
   449  	}
   450  
   451  	// Cast to the expected typealias type of ts.SeriesList before calling
   452  	for i, arg := range in {
   453  		if !arg.IsValid() {
   454  			// Zero value arg is a nil.
   455  			in[i] = reflect.New(genericInterfaceType).Elem()
   456  			continue
   457  		}
   458  		typeArg := arg.Type()
   459  		if typeArg != seriesListType {
   460  			continue
   461  		}
   462  		// NB(r): Poor form, ctx is not in f.in for no reason it seems...
   463  		typeIdx := i - 1
   464  		if i >= numTypes {
   465  			typeIdx = numTypes - 1
   466  		}
   467  		l := arg.Interface().(ts.SeriesList)
   468  		switch f.in[typeIdx] {
   469  		case singlePathSpecType, genericInterfaceType:
   470  			in[i] = reflect.ValueOf(singlePathSpec(l))
   471  		case multiplePathSpecsType:
   472  			in[i] = reflect.ValueOf(multiplePathSpecs(l))
   473  		default:
   474  			err := fmt.Errorf("cannot cast series to unexpected type: %s",
   475  				f.in[typeIdx].String())
   476  			return reflect.Value{}, err
   477  		}
   478  	}
   479  
   480  	beginCall := time.Now()
   481  	out := f.f.Call(in)
   482  	outVal, errVal := out[0], out[1]
   483  	var err error
   484  	if !errVal.IsNil() {
   485  		err = errVal.Interface().(error)
   486  		return outVal, err
   487  	}
   488  
   489  	if ctx.TracingEnabled() {
   490  		var outstats common.TraceStats
   491  		if isTimeSeries(outVal) {
   492  			outstats = getStats(outVal)
   493  		}
   494  
   495  		ctx.Trace(common.Trace{
   496  			ActivityName: f.name,
   497  			Inputs:       instats,
   498  			Outputs:      outstats,
   499  			Duration:     time.Since(beginCall),
   500  		})
   501  	}
   502  
   503  	return outVal, nil
   504  }
   505  
   506  // A funcArg is an argument to a function that gets resolved at runtime
   507  type funcArg interface {
   508  	ASTNode
   509  	Evaluate(ctx *common.Context) (reflect.Value, error)
   510  	Type() reflect.Type
   511  	CompatibleWith(reflectType reflect.Type) bool
   512  }
   513  
   514  // A constFuncArg is a function argument that is a constant value
   515  type constFuncArg struct {
   516  	value reflect.Value
   517  }
   518  
   519  func newConstArg(i interface{}) funcArg { return constFuncArg{value: reflect.ValueOf(i)} }
   520  func newBoolConst(b bool) funcArg       { return constFuncArg{value: reflect.ValueOf(b)} }
   521  func newStringConst(s string) funcArg   { return constFuncArg{value: reflect.ValueOf(s)} }
   522  func newFloat64Const(n float64) funcArg { return constFuncArg{value: reflect.ValueOf(n)} }
   523  func newIntConst(n int) funcArg         { return constFuncArg{value: reflect.ValueOf(n)} }
   524  
   525  func (c constFuncArg) Evaluate(ctx *common.Context) (reflect.Value, error) { return c.value, nil }
   526  func (c constFuncArg) Type() reflect.Type                                  { return c.value.Type() }
   527  func (c constFuncArg) CompatibleWith(reflectType reflect.Type) bool {
   528  	return c.value.Type() == reflectType || reflectType == interfaceType
   529  }
   530  func (c constFuncArg) String() string                      { return fmt.Sprintf("%v", c.value.Interface()) }
   531  func (c constFuncArg) PathExpression() (string, bool)      { return "", false }
   532  func (c constFuncArg) CallExpression() (CallASTNode, bool) { return nil, false }
   533  
   534  // A functionCall is an actual call to a function, with resolution for arguments
   535  type functionCall struct {
   536  	f  *Function
   537  	in []funcArg
   538  }
   539  
   540  func (call *functionCall) Name() string {
   541  	return call.f.name
   542  }
   543  
   544  func (call *functionCall) Arguments() []ASTNode {
   545  	args := make([]ASTNode, 0, len(call.in))
   546  	for _, arg := range call.in {
   547  		args = append(args, arg)
   548  	}
   549  	return args
   550  }
   551  
   552  func (call *functionCall) PathExpression() (string, bool) {
   553  	return "", false
   554  }
   555  
   556  func (call *functionCall) CallExpression() (CallASTNode, bool) {
   557  	return call, true
   558  }
   559  
   560  // Evaluate evaluates the function call and returns the result as a reflect.Value
   561  func (call *functionCall) Evaluate(ctx *common.Context) (reflect.Value, error) {
   562  	values := make([]reflect.Value, len(call.in))
   563  	for i, param := range call.in {
   564  		// Optimization to skip fetching series for a unary context shift
   565  		// operation since they'll refetch after shifting.
   566  		// Note: You can call WithoutUnaryContextShifterSkipFetchOptimization()
   567  		// after registering a function if you need a unary context shift
   568  		// operation to have series for the initial time window fetched.
   569  		if call.f.out == unaryContextShifterPtrType &&
   570  			!call.f.disableUnaryContextShiftFetchOptimization &&
   571  			(call.f.in[i] == singlePathSpecType || call.f.in[i] == multiplePathSpecsType) {
   572  			values[i] = reflect.ValueOf(singlePathSpec{}) // fake parameter
   573  			continue
   574  		}
   575  		value, err := param.Evaluate(ctx)
   576  		if err != nil {
   577  			return reflect.Value{}, err
   578  		}
   579  		values[i] = value
   580  	}
   581  
   582  	result, err := call.f.reflectCall(ctx, values)
   583  	// if we have errors, or if we succeed and this is not a context-shifting function,
   584  	// we return immediately
   585  	if err != nil || call.f.out == seriesListType {
   586  		return result, err
   587  	}
   588  
   589  	// context shifter ptr is nil, nothing to do here, return empty series.
   590  	if result.IsNil() {
   591  		return reflect.ValueOf(ts.NewSeriesList()), nil
   592  	}
   593  
   594  	contextShifter := result.Elem()
   595  	ctxShiftingFn := contextShifter.Field(0)
   596  	reflected := ctxShiftingFn.Call([]reflect.Value{reflect.ValueOf(ctx)})
   597  	shiftedCtx := reflected[0].Interface().(*common.Context)
   598  	shiftedSeries, err := call.in[0].Evaluate(shiftedCtx)
   599  	if err != nil {
   600  		return reflect.Value{}, err
   601  	}
   602  
   603  	// Determine if need to adjust the shift based on fetched series
   604  	// from the context shift.
   605  MaybeAdjustShiftLoop:
   606  	for {
   607  		adjustFn := contextShifter.Field(2)
   608  		if adjustFn.IsNil() {
   609  			break MaybeAdjustShiftLoop
   610  		}
   611  
   612  		reflected := adjustFn.Call([]reflect.Value{
   613  			reflect.ValueOf(shiftedCtx),
   614  			shiftedSeries,
   615  		})
   616  		if reflectedErr := reflected[2]; !reflectedErr.IsNil() {
   617  			return reflect.Value{}, reflectedErr.Interface().(error)
   618  		}
   619  
   620  		if reflectedAdjust := reflected[1]; !reflectedAdjust.Bool() {
   621  			// No further adjust.
   622  			break MaybeAdjustShiftLoop
   623  		}
   624  
   625  		// Adjusted again, need to re-bootstrap from the shifted series.
   626  		adjustedShiftedCtx := reflected[0].Interface().(*common.Context)
   627  		adjustedShiftedSeries, err := call.in[0].Evaluate(adjustedShiftedCtx)
   628  		if err != nil {
   629  			return reflect.Value{}, err
   630  		}
   631  
   632  		// Override previously shifted context and series fetched and re-eval.
   633  		shiftedCtx = adjustedShiftedCtx
   634  		shiftedSeries = adjustedShiftedSeries
   635  	}
   636  
   637  	// Execute the unary transformer function with the shifted series.
   638  	var (
   639  		transformerFn = contextShifter.Field(1)
   640  		ret           []reflect.Value
   641  	)
   642  	switch call.f.out {
   643  	case unaryContextShifterPtrType:
   644  		// unary function
   645  		ret = transformerFn.Call([]reflect.Value{shiftedSeries})
   646  	default:
   647  		return reflect.Value{}, fmt.Errorf("unknown context shift: %v", call.f.out)
   648  	}
   649  	if !ret[1].IsNil() {
   650  		err = ret[1].Interface().(error)
   651  	}
   652  	return ret[0], err
   653  }
   654  
   655  func (call *functionCall) Type() reflect.Type {
   656  	return reflect.ValueOf(call).Type()
   657  }
   658  
   659  // CompatibleWith checks whether the function call's return is compatible with the given reflection type
   660  func (call *functionCall) CompatibleWith(reflectType reflect.Type) bool {
   661  	if reflectType == interfaceType {
   662  		return true
   663  	}
   664  	if call.f.out == unaryContextShifterPtrType {
   665  		return reflectType == singlePathSpecType || reflectType == multiplePathSpecsType
   666  	}
   667  	return call.f.out.Kind() == reflectType.Kind()
   668  }
   669  
   670  func (call *functionCall) String() string {
   671  	var buf bytes.Buffer
   672  	buf.WriteString(call.f.name)
   673  	buf.WriteByte('(')
   674  	for i := range call.in {
   675  		if i > 0 {
   676  			buf.WriteByte(',')
   677  		}
   678  		buf.WriteString(call.in[i].String())
   679  	}
   680  
   681  	buf.WriteByte(')')
   682  	return buf.String()
   683  }
   684  
   685  // isTimeSeries checks whether the given value contains a timeseries or
   686  // timeseries list
   687  func isTimeSeries(v reflect.Value) bool {
   688  	return v.IsValid() && v.Type() == seriesListType
   689  }
   690  
   691  // getStats gets trace stats for the given timeseries argument
   692  func getStats(v reflect.Value) common.TraceStats {
   693  	if v.IsValid() && v.Type() == timeSeriesType {
   694  		return common.TraceStats{NumSeries: 1}
   695  	}
   696  
   697  	l := v.Interface().(ts.SeriesList)
   698  	return common.TraceStats{NumSeries: l.Len()}
   699  }