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 }