bosun.org@v0.0.0-20210513094433-e25bc3e69a1f/cmd/bosun/expr/expr.go (about)

     1  package expr // import "bosun.org/cmd/bosun/expr"
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"math"
     7  	"reflect"
     8  	"runtime"
     9  	"runtime/debug"
    10  	"sort"
    11  	"strconv"
    12  	"strings"
    13  	"time"
    14  
    15  	"bosun.org/annotate/backend"
    16  	"bosun.org/cloudwatch"
    17  	"bosun.org/cmd/bosun/cache"
    18  	"bosun.org/cmd/bosun/expr/parse"
    19  	"bosun.org/cmd/bosun/search"
    20  	"bosun.org/collect"
    21  	"bosun.org/graphite"
    22  	"bosun.org/metadata"
    23  	"bosun.org/models"
    24  	"bosun.org/opentsdb"
    25  	"bosun.org/slog"
    26  	"github.com/MiniProfiler/go/miniprofiler"
    27  	"github.com/influxdata/influxdb/client/v2"
    28  )
    29  
    30  type State struct {
    31  	*Expr
    32  	now                time.Time
    33  	enableComputations bool
    34  	unjoinedOk         bool
    35  	autods             int
    36  	vValue             float64
    37  
    38  	// Origin allows the source of the expression to be identified for logging and debugging
    39  	Origin string
    40  
    41  	Timer miniprofiler.Timer
    42  
    43  	*Backends
    44  
    45  	// Bosun Internal
    46  	*BosunProviders
    47  
    48  	// Graphite
    49  	graphiteQueries []graphite.Request
    50  
    51  	// OpenTSDB
    52  	tsdbQueries []opentsdb.Request
    53  
    54  	// CloudWatch
    55  	cloudwatchQueries []cloudwatch.Request
    56  }
    57  
    58  type Backends struct {
    59  	TSDBContext       opentsdb.Context
    60  	GraphiteContext   graphite.Context
    61  	ElasticHosts      ElasticHosts
    62  	InfluxConfig      client.HTTPConfig
    63  	ElasticConfig     ElasticConfig
    64  	AzureMonitor      AzureMonitorClients
    65  	CloudWatchContext cloudwatch.Context
    66  	PromConfig        PromClients
    67  }
    68  
    69  type BosunProviders struct {
    70  	Squelched func(tags opentsdb.TagSet) bool
    71  	Search    *search.Search
    72  	History   AlertStatusProvider
    73  	Cache     *cache.Cache
    74  	Annotate  backend.Backend
    75  }
    76  
    77  // Alert Status Provider is used to provide information about alert results.
    78  // This facilitates alerts referencing other alerts, even when they go unknown or unevaluated.
    79  type AlertStatusProvider interface {
    80  	GetUnknownAndUnevaluatedAlertKeys(alertName string) (unknown, unevaluated []models.AlertKey)
    81  }
    82  
    83  var ErrUnknownOp = fmt.Errorf("expr: unknown op type")
    84  
    85  type Expr struct {
    86  	*parse.Tree
    87  }
    88  
    89  func (e *Expr) MarshalJSON() ([]byte, error) {
    90  	return json.Marshal(e.String())
    91  }
    92  
    93  // New creates a new expression tree
    94  func New(expr string, funcs ...map[string]parse.Func) (*Expr, error) {
    95  	funcs = append(funcs, builtins)
    96  	t, err := parse.Parse(expr, funcs...)
    97  	if err != nil {
    98  		return nil, err
    99  	}
   100  	e := &Expr{
   101  		Tree: t,
   102  	}
   103  	return e, nil
   104  }
   105  
   106  // Execute applies a parse expression to the specified OpenTSDB context, and
   107  // returns one result per group. T may be nil to ignore timings.
   108  func (e *Expr) Execute(backends *Backends, providers *BosunProviders, T miniprofiler.Timer, now time.Time, autods int, unjoinedOk bool, origin string) (r *Results, queries []opentsdb.Request, err error) {
   109  	if providers.Squelched == nil {
   110  		providers.Squelched = func(tags opentsdb.TagSet) bool {
   111  			return false
   112  		}
   113  	}
   114  	s := &State{
   115  		Expr:           e,
   116  		now:            now,
   117  		autods:         autods,
   118  		unjoinedOk:     unjoinedOk,
   119  		Origin:         origin,
   120  		Backends:       backends,
   121  		BosunProviders: providers,
   122  		Timer:          T,
   123  	}
   124  	return e.ExecuteState(s)
   125  }
   126  
   127  func (e *Expr) ExecuteState(s *State) (r *Results, queries []opentsdb.Request, err error) {
   128  	defer errRecover(&err, s)
   129  	if s.Timer == nil {
   130  		s.Timer = new(miniprofiler.Profile)
   131  	} else {
   132  		s.enableComputations = true
   133  	}
   134  	s.Timer.Step("expr execute", func(T miniprofiler.Timer) {
   135  		r = s.walk(e.Tree.Root)
   136  	})
   137  	queries = s.tsdbQueries
   138  	return
   139  }
   140  
   141  // errRecover is the handler that turns panics into returns from the top
   142  // level of Parse.
   143  func errRecover(errp *error, s *State) {
   144  	e := recover()
   145  	if e != nil {
   146  		switch err := e.(type) {
   147  		case runtime.Error:
   148  			slog.Errorf("Error: %s. Origin: %v. Expression: %s, Stack: %s", e, s.Origin, s.Expr, debug.Stack())
   149  			panic(e)
   150  		case error:
   151  			*errp = err
   152  		default:
   153  			slog.Errorf("Error: %s. Origin: %v. Expression: %s, Stack: %s", e, s.Origin, s.Expr, debug.Stack())
   154  			panic(e)
   155  		}
   156  	}
   157  }
   158  
   159  func marshalFloat(n float64) ([]byte, error) {
   160  	if math.IsNaN(n) {
   161  		return json.Marshal("NaN")
   162  	} else if math.IsInf(n, 1) {
   163  		return json.Marshal("+Inf")
   164  	} else if math.IsInf(n, -1) {
   165  		return json.Marshal("-Inf")
   166  	}
   167  	return json.Marshal(n)
   168  }
   169  
   170  type Value interface {
   171  	Type() models.FuncType
   172  	Value() interface{}
   173  }
   174  
   175  type Number float64
   176  
   177  func (n Number) Type() models.FuncType        { return models.TypeNumberSet }
   178  func (n Number) Value() interface{}           { return n }
   179  func (n Number) MarshalJSON() ([]byte, error) { return marshalFloat(float64(n)) }
   180  
   181  type Scalar float64
   182  
   183  func (s Scalar) Type() models.FuncType        { return models.TypeScalar }
   184  func (s Scalar) Value() interface{}           { return s }
   185  func (s Scalar) MarshalJSON() ([]byte, error) { return marshalFloat(float64(s)) }
   186  
   187  type String string
   188  
   189  func (s String) Type() models.FuncType { return models.TypeString }
   190  func (s String) Value() interface{}    { return s }
   191  
   192  type NumberExpr Expr
   193  
   194  func (s NumberExpr) Type() models.FuncType { return models.TypeNumberExpr }
   195  func (s NumberExpr) Value() interface{}    { return s }
   196  
   197  type Info []interface{}
   198  
   199  func (i Info) Type() models.FuncType { return models.TypeInfo }
   200  func (i Info) Value() interface{}    { return i }
   201  
   202  //func (s String) MarshalJSON() ([]byte, error) { return json.Marshal(s) }
   203  
   204  // Series is the standard form within bosun to represent timeseries data.
   205  type Series map[time.Time]float64
   206  
   207  func (s Series) Type() models.FuncType { return models.TypeSeriesSet }
   208  func (s Series) Value() interface{}    { return s }
   209  
   210  func (s Series) MarshalJSON() ([]byte, error) {
   211  	r := make(map[string]interface{}, len(s))
   212  	for k, v := range s {
   213  		r[fmt.Sprint(k.Unix())] = Scalar(v)
   214  	}
   215  	return json.Marshal(r)
   216  }
   217  
   218  func (a Series) Equal(b Series) bool {
   219  	return reflect.DeepEqual(a, b)
   220  }
   221  
   222  // See the elastic#.go files for ESQuery
   223  
   224  func (e ESQuery) Type() models.FuncType { return models.TypeESQuery }
   225  func (e ESQuery) Value() interface{}    { return e }
   226  func (e ESQuery) MarshalJSON() ([]byte, error) {
   227  	// source, err := e.Query(esV2).Source()
   228  	// if err != nil {
   229  	// 	return nil, err
   230  	// }
   231  	// return json.Marshal(source)
   232  	return json.Marshal("ESQuery")
   233  }
   234  
   235  type ESIndexer struct {
   236  	TimeField string
   237  	Generate  func(startDuration, endDuration *time.Time) []string
   238  }
   239  
   240  func (e ESIndexer) Type() models.FuncType { return models.TypeESIndexer }
   241  func (e ESIndexer) Value() interface{}    { return e }
   242  func (e ESIndexer) MarshalJSON() ([]byte, error) {
   243  	return json.Marshal("ESGenerator")
   244  }
   245  
   246  type Table struct {
   247  	Columns []string
   248  	Rows    [][]interface{}
   249  }
   250  
   251  func (t Table) Type() models.FuncType { return models.TypeTable }
   252  func (t Table) Value() interface{}    { return t }
   253  
   254  func (a AzureResources) Type() models.FuncType { return models.TypeAzureResourceList }
   255  func (a AzureResources) Value() interface{}    { return a }
   256  
   257  func (a AzureApplicationInsightsApps) Type() models.FuncType { return models.TypeAzureAIApps }
   258  func (a AzureApplicationInsightsApps) Value() interface{}    { return a }
   259  
   260  type SortablePoint struct {
   261  	T time.Time
   262  	V float64
   263  }
   264  
   265  // SortableSeries is an alternative datastructure for timeseries data,
   266  // which stores points in a time-ordered fashion instead of a map.
   267  // see discussion at https://github.com/bosun-monitor/bosun/pull/699
   268  type SortableSeries []SortablePoint
   269  
   270  func (s SortableSeries) Len() int           { return len(s) }
   271  func (s SortableSeries) Swap(i, j int)      { s[i], s[j] = s[j], s[i] }
   272  func (s SortableSeries) Less(i, j int) bool { return s[i].T.Before(s[j].T) }
   273  
   274  func NewSortedSeries(dps Series) SortableSeries {
   275  	series := make(SortableSeries, 0, len(dps))
   276  	for t, v := range dps {
   277  		series = append(series, SortablePoint{t, v})
   278  	}
   279  	sort.Sort(series)
   280  	return series
   281  }
   282  
   283  type Result struct {
   284  	models.Computations
   285  	Value
   286  	Group opentsdb.TagSet
   287  }
   288  
   289  type Results struct {
   290  	Results ResultSlice
   291  	// If true, ungrouped joins from this set will be ignored.
   292  	IgnoreUnjoined bool
   293  	// If true, ungrouped joins from the other set will be ignored.
   294  	IgnoreOtherUnjoined bool
   295  	// If non nil, will set any NaN value to it.
   296  	NaNValue *float64
   297  }
   298  
   299  // Equal inspects if two results have the same content
   300  // error will return why they are not equal if they
   301  // are not equal
   302  func (a *Results) Equal(b *Results) (bool, error) {
   303  	if len(a.Results) != len(b.Results) {
   304  		return false, fmt.Errorf("unequal number of results: length a: %v, length b: %v", len(a.Results), len(b.Results))
   305  	}
   306  	if a.IgnoreUnjoined != b.IgnoreUnjoined {
   307  		return false, fmt.Errorf("ignoreUnjoined flag does not match a: %v, b: %v", a.IgnoreUnjoined, b.IgnoreUnjoined)
   308  	}
   309  	if a.IgnoreOtherUnjoined != b.IgnoreOtherUnjoined {
   310  		return false, fmt.Errorf("ignoreUnjoined flag does not match a: %v, b: %v", a.IgnoreOtherUnjoined, b.IgnoreOtherUnjoined)
   311  	}
   312  	if a.NaNValue != b.NaNValue {
   313  		return false, fmt.Errorf("NaNValue does not match a: %v, b: %v", a.NaNValue, b.NaNValue)
   314  	}
   315  	sortedA := ResultSliceByGroup(a.Results)
   316  	sort.Sort(sortedA)
   317  	sortedB := ResultSliceByGroup(b.Results)
   318  	sort.Sort(sortedB)
   319  	for i, result := range sortedA {
   320  		for ic, computation := range result.Computations {
   321  			if computation != sortedB[i].Computations[ic] {
   322  				return false, fmt.Errorf("mismatched computation a: %v, b: %v", computation, sortedB[ic])
   323  			}
   324  		}
   325  		if !result.Group.Equal(sortedB[i].Group) {
   326  			return false, fmt.Errorf("mismatched groups a: %v, b: %v", result.Group, sortedB[i].Group)
   327  		}
   328  		switch t := result.Value.(type) {
   329  		case Number, Scalar, String:
   330  			if result.Value != sortedB[i].Value {
   331  				return false, fmt.Errorf("values do not match a: %v, b: %v", result.Value, sortedB[i].Value)
   332  			}
   333  		case Series:
   334  			if !t.Equal(sortedB[i].Value.(Series)) {
   335  				return false, fmt.Errorf("mismatched series in result (Group: %s) a: %v, b: %v", result.Group, t, sortedB[i].Value.(Series))
   336  			}
   337  		default:
   338  			panic(fmt.Sprintf("can't compare results with type %T", t))
   339  		}
   340  
   341  	}
   342  	return true, nil
   343  }
   344  
   345  type ResultSlice []*Result
   346  
   347  type ResultSliceByGroup ResultSlice
   348  
   349  type ResultSliceByValue ResultSlice
   350  
   351  func (r *Results) NaN() Number {
   352  	if r.NaNValue != nil {
   353  		return Number(*r.NaNValue)
   354  	}
   355  	return Number(math.NaN())
   356  }
   357  
   358  func (r ResultSlice) DescByValue() ResultSlice {
   359  	for _, v := range r {
   360  		if _, ok := v.Value.(Number); !ok {
   361  			return r
   362  		}
   363  	}
   364  	c := r[:]
   365  	sort.Sort(sort.Reverse(ResultSliceByValue(c)))
   366  	return c
   367  }
   368  
   369  // Filter returns a slice with only the results that have a tagset that conforms to the given key/value pair restrictions
   370  func (r ResultSlice) Filter(filter opentsdb.TagSet) ResultSlice {
   371  	output := make(ResultSlice, 0, len(r))
   372  	for _, res := range r {
   373  		if res.Group.Compatible(filter) {
   374  			output = append(output, res)
   375  		}
   376  	}
   377  	return output
   378  }
   379  
   380  func (r ResultSliceByValue) Len() int           { return len(r) }
   381  func (r ResultSliceByValue) Swap(i, j int)      { r[i], r[j] = r[j], r[i] }
   382  func (r ResultSliceByValue) Less(i, j int) bool { return r[i].Value.(Number) < r[j].Value.(Number) }
   383  
   384  func (r ResultSliceByGroup) Len() int           { return len(r) }
   385  func (r ResultSliceByGroup) Swap(i, j int)      { r[i], r[j] = r[j], r[i] }
   386  func (r ResultSliceByGroup) Less(i, j int) bool { return r[i].Group.String() < r[j].Group.String() }
   387  
   388  func (e *State) AddComputation(r *Result, text string, value interface{}) {
   389  	if !e.enableComputations {
   390  		return
   391  	}
   392  	r.Computations = append(r.Computations, models.Computation{Text: opentsdb.ReplaceTags(text, r.Group), Value: value})
   393  }
   394  
   395  type Union struct {
   396  	models.Computations
   397  	A, B  Value
   398  	Group opentsdb.TagSet
   399  }
   400  
   401  // wrap creates a new Result with a nil group and given value.
   402  func wrap(v float64) *Results {
   403  	return &Results{
   404  		Results: []*Result{
   405  			{
   406  				Value: Scalar(v),
   407  				Group: nil,
   408  			},
   409  		},
   410  	}
   411  }
   412  
   413  func (u *Union) ExtendComputations(o *Result) {
   414  	u.Computations = append(u.Computations, o.Computations...)
   415  }
   416  
   417  // union returns the combination of a and b where one is a subset of the other.
   418  func (e *State) union(a, b *Results, expression string) []*Union {
   419  	const unjoinedGroup = "unjoined group (%v)"
   420  	var us []*Union
   421  	if len(a.Results) == 0 || len(b.Results) == 0 {
   422  		return us
   423  	}
   424  	am := make(map[*Result]bool, len(a.Results))
   425  	bm := make(map[*Result]bool, len(b.Results))
   426  	for _, ra := range a.Results {
   427  		am[ra] = true
   428  	}
   429  	for _, rb := range b.Results {
   430  		bm[rb] = true
   431  	}
   432  	var group opentsdb.TagSet
   433  	for _, ra := range a.Results {
   434  		for _, rb := range b.Results {
   435  			if ra.Group.Equal(rb.Group) || len(ra.Group) == 0 || len(rb.Group) == 0 {
   436  				g := ra.Group
   437  				if len(ra.Group) == 0 {
   438  					g = rb.Group
   439  				}
   440  				group = g
   441  			} else if len(ra.Group) == len(rb.Group) {
   442  				continue
   443  			} else if ra.Group.Subset(rb.Group) {
   444  				group = ra.Group
   445  			} else if rb.Group.Subset(ra.Group) {
   446  				group = rb.Group
   447  			} else {
   448  				continue
   449  			}
   450  			delete(am, ra)
   451  			delete(bm, rb)
   452  			u := &Union{
   453  				A:     ra.Value,
   454  				B:     rb.Value,
   455  				Group: group,
   456  			}
   457  			u.ExtendComputations(ra)
   458  			u.ExtendComputations(rb)
   459  			us = append(us, u)
   460  		}
   461  	}
   462  	if !e.unjoinedOk {
   463  		if !a.IgnoreUnjoined && !b.IgnoreOtherUnjoined {
   464  			for r := range am {
   465  				u := &Union{
   466  					A:     r.Value,
   467  					B:     b.NaN(),
   468  					Group: r.Group,
   469  				}
   470  				e.AddComputation(r, expression, fmt.Sprintf(unjoinedGroup, u.B))
   471  				u.ExtendComputations(r)
   472  				us = append(us, u)
   473  			}
   474  		}
   475  		if !b.IgnoreUnjoined && !a.IgnoreOtherUnjoined {
   476  			for r := range bm {
   477  				u := &Union{
   478  					A:     a.NaN(),
   479  					B:     r.Value,
   480  					Group: r.Group,
   481  				}
   482  				e.AddComputation(r, expression, fmt.Sprintf(unjoinedGroup, u.A))
   483  				u.ExtendComputations(r)
   484  				us = append(us, u)
   485  			}
   486  		}
   487  	}
   488  	return us
   489  }
   490  
   491  func (e *State) walk(node parse.Node) *Results {
   492  	var res *Results
   493  	switch node := node.(type) {
   494  	case *parse.NumberNode:
   495  		res = wrap(node.Float64)
   496  	case *parse.BinaryNode:
   497  		res = e.walkBinary(node)
   498  	case *parse.UnaryNode:
   499  		res = e.walkUnary(node)
   500  	case *parse.FuncNode:
   501  		res = e.walkFunc(node)
   502  	case *parse.ExprNode:
   503  		res = e.walkExpr(node)
   504  	case *parse.PrefixNode:
   505  		res = e.walkPrefix(node)
   506  	default:
   507  		panic(fmt.Errorf("expr: unknown node type"))
   508  	}
   509  	return res
   510  }
   511  
   512  func (e *State) walkExpr(node *parse.ExprNode) *Results {
   513  	return &Results{
   514  		Results: ResultSlice{
   515  			&Result{
   516  				Value: NumberExpr{node.Tree},
   517  			},
   518  		},
   519  	}
   520  }
   521  
   522  func (e *State) walkBinary(node *parse.BinaryNode) *Results {
   523  	ar := e.walk(node.Args[0])
   524  	br := e.walk(node.Args[1])
   525  	res := Results{
   526  		IgnoreUnjoined:      ar.IgnoreUnjoined || br.IgnoreUnjoined,
   527  		IgnoreOtherUnjoined: ar.IgnoreOtherUnjoined || br.IgnoreOtherUnjoined,
   528  	}
   529  	e.Timer.Step("walkBinary: "+node.OpStr, func(T miniprofiler.Timer) {
   530  		u := e.union(ar, br, node.String())
   531  		for _, v := range u {
   532  			var value Value
   533  			r := &Result{
   534  				Group:        v.Group,
   535  				Computations: v.Computations,
   536  			}
   537  			switch at := v.A.(type) {
   538  			case Scalar:
   539  				switch bt := v.B.(type) {
   540  				case Scalar:
   541  					n := Scalar(operate(node.OpStr, float64(at), float64(bt)))
   542  					e.AddComputation(r, node.String(), Number(n))
   543  					value = n
   544  				case Number:
   545  					n := Number(operate(node.OpStr, float64(at), float64(bt)))
   546  					e.AddComputation(r, node.String(), n)
   547  					value = n
   548  				case Series:
   549  					s := make(Series)
   550  					for k, v := range bt {
   551  						s[k] = operate(node.OpStr, float64(at), float64(v))
   552  					}
   553  					value = s
   554  				default:
   555  					panic(ErrUnknownOp)
   556  				}
   557  			case Number:
   558  				switch bt := v.B.(type) {
   559  				case Scalar:
   560  					n := Number(operate(node.OpStr, float64(at), float64(bt)))
   561  					e.AddComputation(r, node.String(), Number(n))
   562  					value = n
   563  				case Number:
   564  					n := Number(operate(node.OpStr, float64(at), float64(bt)))
   565  					e.AddComputation(r, node.String(), n)
   566  					value = n
   567  				case Series:
   568  					s := make(Series)
   569  					for k, v := range bt {
   570  						s[k] = operate(node.OpStr, float64(at), float64(v))
   571  					}
   572  					value = s
   573  				default:
   574  					panic(ErrUnknownOp)
   575  				}
   576  			case Series:
   577  				switch bt := v.B.(type) {
   578  				case Number, Scalar:
   579  					bv := reflect.ValueOf(bt).Float()
   580  					s := make(Series)
   581  					for k, v := range at {
   582  						s[k] = operate(node.OpStr, float64(v), bv)
   583  					}
   584  					value = s
   585  				case Series:
   586  					s := make(Series)
   587  					for k, av := range at {
   588  						if bv, ok := bt[k]; ok {
   589  							s[k] = operate(node.OpStr, av, bv)
   590  						}
   591  					}
   592  					value = s
   593  				default:
   594  					panic(ErrUnknownOp)
   595  				}
   596  			default:
   597  				panic(ErrUnknownOp)
   598  			}
   599  			r.Value = value
   600  			res.Results = append(res.Results, r)
   601  		}
   602  	})
   603  	return &res
   604  }
   605  
   606  func operate(op string, a, b float64) (r float64) {
   607  	// Test short circuit before NaN.
   608  	switch op {
   609  	case "||":
   610  		if a != 0 {
   611  			return 1
   612  		}
   613  	case "&&":
   614  		if a == 0 {
   615  			return 0
   616  		}
   617  	}
   618  	if math.IsNaN(a) || math.IsNaN(b) {
   619  		return math.NaN()
   620  	}
   621  	switch op {
   622  	case "+":
   623  		r = a + b
   624  	case "*":
   625  		r = a * b
   626  	case "-":
   627  		r = a - b
   628  	case "/":
   629  		r = a / b
   630  	case "**":
   631  		r = math.Pow(a, b)
   632  	case "%":
   633  		r = math.Mod(a, b)
   634  	case "==":
   635  		if a == b {
   636  			r = 1
   637  		} else {
   638  			r = 0
   639  		}
   640  	case ">":
   641  		if a > b {
   642  			r = 1
   643  		} else {
   644  			r = 0
   645  		}
   646  	case "!=":
   647  		if a != b {
   648  			r = 1
   649  		} else {
   650  			r = 0
   651  		}
   652  	case "<":
   653  		if a < b {
   654  			r = 1
   655  		} else {
   656  			r = 0
   657  		}
   658  	case ">=":
   659  		if a >= b {
   660  			r = 1
   661  		} else {
   662  			r = 0
   663  		}
   664  	case "<=":
   665  		if a <= b {
   666  			r = 1
   667  		} else {
   668  			r = 0
   669  		}
   670  	case "||":
   671  		if a != 0 || b != 0 {
   672  			r = 1
   673  		} else {
   674  			r = 0
   675  		}
   676  	case "&&":
   677  		if a != 0 && b != 0 {
   678  			r = 1
   679  		} else {
   680  			r = 0
   681  		}
   682  	default:
   683  		panic(fmt.Errorf("expr: unknown operator %s", op))
   684  	}
   685  	return
   686  }
   687  
   688  func (e *State) walkUnary(node *parse.UnaryNode) *Results {
   689  	a := e.walk(node.Arg)
   690  	e.Timer.Step("walkUnary: "+node.OpStr, func(T miniprofiler.Timer) {
   691  		for _, r := range a.Results {
   692  			if an, aok := r.Value.(Scalar); aok && math.IsNaN(float64(an)) {
   693  				r.Value = Scalar(math.NaN())
   694  				continue
   695  			}
   696  			switch rt := r.Value.(type) {
   697  			case Scalar:
   698  				r.Value = Scalar(uoperate(node.OpStr, float64(rt)))
   699  			case Number:
   700  				r.Value = Number(uoperate(node.OpStr, float64(rt)))
   701  			case Series:
   702  				s := make(Series)
   703  				for k, v := range rt {
   704  					s[k] = uoperate(node.OpStr, float64(v))
   705  				}
   706  				r.Value = s
   707  			default:
   708  				panic(ErrUnknownOp)
   709  			}
   710  		}
   711  	})
   712  	return a
   713  }
   714  
   715  func uoperate(op string, a float64) (r float64) {
   716  	switch op {
   717  	case "!":
   718  		if a == 0 {
   719  			r = 1
   720  		} else {
   721  			r = 0
   722  		}
   723  	case "-":
   724  		r = -a
   725  	default:
   726  		panic(fmt.Errorf("expr: unknown operator %s", op))
   727  	}
   728  	return
   729  }
   730  
   731  func (e *State) walkPrefix(node *parse.PrefixNode) *Results {
   732  	key := strings.TrimPrefix(node.Text, "[")
   733  	key = strings.TrimSuffix(key, "]")
   734  	key, _ = strconv.Unquote(key)
   735  	switch node := node.Arg.(type) {
   736  	case *parse.FuncNode:
   737  		if node.F.PrefixEnabled {
   738  			node.Prefix = key
   739  			node.F.PrefixKey = true
   740  		}
   741  		return e.walk(node)
   742  	default:
   743  		panic(fmt.Errorf("expr: prefix can only be append to a FuncNode"))
   744  	}
   745  }
   746  
   747  func (e *State) walkFunc(node *parse.FuncNode) *Results {
   748  	var res *Results
   749  	e.Timer.Step("func: "+node.Name, func(T miniprofiler.Timer) {
   750  		var in []reflect.Value
   751  		for i, a := range node.Args {
   752  			var v interface{}
   753  			switch t := a.(type) {
   754  			case *parse.StringNode:
   755  				v = t.Text
   756  			case *parse.NumberNode:
   757  				v = t.Float64
   758  			case *parse.FuncNode:
   759  				v = extract(e.walkFunc(t))
   760  			case *parse.UnaryNode:
   761  				v = extract(e.walkUnary(t))
   762  			case *parse.BinaryNode:
   763  				v = extract(e.walkBinary(t))
   764  			case *parse.ExprNode:
   765  				v = e.walkExpr(t)
   766  			case *parse.PrefixNode:
   767  				v = extract(e.walkPrefix(t))
   768  			default:
   769  				panic(fmt.Errorf("expr: unknown func arg type"))
   770  			}
   771  
   772  			var argType models.FuncType
   773  			if i >= len(node.F.Args) {
   774  				if !node.F.VArgs {
   775  					panic("expr: shouldn't be here, more args then expected and not variable argument type func")
   776  				}
   777  				argType = node.F.Args[node.F.VArgsPos]
   778  			} else {
   779  				argType = node.F.Args[i]
   780  			}
   781  			if f, ok := v.(float64); ok && (argType == models.TypeNumberSet || argType == models.TypeVariantSet) {
   782  				v = fromScalar(f)
   783  			}
   784  			in = append(in, reflect.ValueOf(v))
   785  		}
   786  
   787  		f := reflect.ValueOf(node.F.F)
   788  		fr := []reflect.Value{}
   789  
   790  		if node.F.PrefixEnabled {
   791  			if !node.F.PrefixKey {
   792  				fr = f.Call(append([]reflect.Value{reflect.ValueOf("default"), reflect.ValueOf(e)}, in...))
   793  			} else {
   794  				fr = f.Call(append([]reflect.Value{reflect.ValueOf(node.Prefix), reflect.ValueOf(e)}, in...))
   795  			}
   796  		} else {
   797  			fr = f.Call(append([]reflect.Value{reflect.ValueOf(e)}, in...))
   798  		}
   799  
   800  		res = fr[0].Interface().(*Results)
   801  		if len(fr) > 1 && !fr[1].IsNil() {
   802  			err := fr[1].Interface().(error)
   803  			if err != nil {
   804  				panic(err)
   805  			}
   806  		}
   807  		if node.Return() == models.TypeNumberSet {
   808  			for _, r := range res.Results {
   809  				e.AddComputation(r, node.String(), r.Value.(Number))
   810  			}
   811  		}
   812  	})
   813  	return res
   814  }
   815  
   816  // extract will return a float64 if res contains exactly one scalar or a ESQuery if that is the type
   817  func extract(res *Results) interface{} {
   818  	if len(res.Results) == 1 && res.Results[0].Type() == models.TypeScalar {
   819  		return float64(res.Results[0].Value.Value().(Scalar))
   820  	}
   821  	if len(res.Results) == 1 && res.Results[0].Type() == models.TypeESQuery {
   822  		return res.Results[0].Value.Value()
   823  	}
   824  	if len(res.Results) == 1 && res.Results[0].Type() == models.TypeAzureResourceList {
   825  		return res.Results[0].Value.Value()
   826  	}
   827  	if len(res.Results) == 1 && res.Results[0].Type() == models.TypeAzureAIApps {
   828  		return res.Results[0].Value.Value()
   829  	}
   830  	if len(res.Results) == 1 && res.Results[0].Type() == models.TypeESIndexer {
   831  		return res.Results[0].Value.Value()
   832  	}
   833  	if len(res.Results) == 1 && res.Results[0].Type() == models.TypeString {
   834  		return string(res.Results[0].Value.Value().(String))
   835  	}
   836  	if len(res.Results) == 1 && res.Results[0].Type() == models.TypeNumberExpr {
   837  		return res.Results[0].Value.Value()
   838  	}
   839  	return res
   840  }
   841  
   842  // collectCache is a helper function for collecting metrics on
   843  // the expression cache
   844  func collectCacheHit(c *cache.Cache, qType string, hit bool) {
   845  	if c == nil {
   846  		return // if no cache
   847  	}
   848  	tags := opentsdb.TagSet{"query_type": qType, "name": c.Name}
   849  	if hit {
   850  		collect.Add("expr_cache.hit_by_type", tags, 1)
   851  		return
   852  	}
   853  	collect.Add("expr_cache.miss_by_type", tags, 1)
   854  }
   855  
   856  func init() {
   857  	metadata.AddMetricMeta("bosun.expr_cache.hit_by_type", metadata.Counter, metadata.Request,
   858  		"The number of hits to Bosun's expression query cache that resulted in a cache hit.")
   859  	metadata.AddMetricMeta("bosun.expr_cache.miss_by_type", metadata.Counter, metadata.Request,
   860  		"The number of hits to Bosun's expression query cache that resulted in a cache miss.")
   861  }