From 011d61847f7fba1a67d7d4588cb2fa4264535039 Mon Sep 17 00:00:00 2001 From: Kyle Brandt Date: Wed, 29 Jun 2016 10:42:02 -0700 Subject: [PATCH 01/15] cmd/bosun: map func for each item in series evil evil code right now, just playing --- cmd/bosun/expr/funcs.go | 1 + 1 file changed, 1 insertion(+) diff --git a/cmd/bosun/expr/funcs.go b/cmd/bosun/expr/funcs.go index fcf1f6ad49..6c4c75ff32 100644 --- a/cmd/bosun/expr/funcs.go +++ b/cmd/bosun/expr/funcs.go @@ -73,6 +73,7 @@ func tagRename(args []parse.Node) (parse.Tags, error) { return tags, nil } +<<<<<<< 45102aa71b0dcdc6da012dc484b5e3e0c682deb2 var builtins = map[string]parse.Func{ // Reduction functions From f5eb857cfcf2b1538b8478379c34ed1b74123781 Mon Sep 17 00:00:00 2001 From: Kyle Brandt Date: Wed, 29 Jun 2016 11:23:17 -0700 Subject: [PATCH 02/15] number or scalar --- cmd/bosun/expr/funcs.go | 1 - 1 file changed, 1 deletion(-) diff --git a/cmd/bosun/expr/funcs.go b/cmd/bosun/expr/funcs.go index 6c4c75ff32..fcf1f6ad49 100644 --- a/cmd/bosun/expr/funcs.go +++ b/cmd/bosun/expr/funcs.go @@ -73,7 +73,6 @@ func tagRename(args []parse.Node) (parse.Tags, error) { return tags, nil } -<<<<<<< 45102aa71b0dcdc6da012dc484b5e3e0c682deb2 var builtins = map[string]parse.Func{ // Reduction functions From 73492f0396852d05e64e1e01311bb8d6be5cc1da Mon Sep 17 00:00:00 2001 From: Kyle Brandt Date: Thu, 30 Jun 2016 12:56:42 -0700 Subject: [PATCH 03/15] experiment: map as expr type --- cmd/bosun/expr/expr.go | 24 +++++++++++++++ cmd/bosun/expr/funcs.go | 49 ++++++++++++++++++++++++++++++ cmd/bosun/expr/funcs_test.go | 50 ++++++++++++++++++++++++++++++ cmd/bosun/expr/parse/lex.go | 5 +++ cmd/bosun/expr/parse/node.go | 38 ++++++++++++++++++++++- cmd/bosun/expr/parse/parse.go | 57 ++++++++++++++++++++++++++++++++--- models/incidents.go | 1 + 7 files changed, 218 insertions(+), 6 deletions(-) diff --git a/cmd/bosun/expr/expr.go b/cmd/bosun/expr/expr.go index 9e9f627b76..2b6c53696f 100644 --- a/cmd/bosun/expr/expr.go +++ b/cmd/bosun/expr/expr.go @@ -29,6 +29,7 @@ type State struct { enableComputations bool unjoinedOk bool autods int + vValue float64 *Backends @@ -76,6 +77,7 @@ func (e *Expr) MarshalJSON() ([]byte, error) { return json.Marshal(e.String()) } +// New creates a new expression tree func New(expr string, funcs ...map[string]parse.Func) (*Expr, error) { funcs = append(funcs, builtins) t, err := parse.Parse(expr, funcs...) @@ -172,6 +174,11 @@ type String string func (s String) Type() models.FuncType { return models.TypeString } func (s String) Value() interface{} { return s } +type SubExpr Expr + +func (s SubExpr) Type() models.FuncType { return models.TypeExpr } +func (s SubExpr) Value() interface{} { return s } + //func (s String) MarshalJSON() ([]byte, error) { return json.Marshal(s) } // Series is the standard form within bosun to represent timeseries data. @@ -460,12 +467,24 @@ func (e *State) walk(node parse.Node, T miniprofiler.Timer) *Results { res = e.walkUnary(node, T) case *parse.FuncNode: res = e.walkFunc(node, T) + case *parse.ExprNode: + res = e.walkExpr(node, T) default: panic(fmt.Errorf("expr: unknown node type")) } return res } +func (e *State) walkExpr(node *parse.ExprNode, T miniprofiler.Timer) *Results { + return &Results{ + Results: ResultSlice{ + &Result{ + Value: SubExpr{node.Tree}, + }, + }, + } +} + func (e *State) walkBinary(node *parse.BinaryNode, T miniprofiler.Timer) *Results { ar := e.walk(node.Args[0], T) br := e.walk(node.Args[1], T) @@ -692,6 +711,8 @@ func (e *State) walkFunc(node *parse.FuncNode, T miniprofiler.Timer) *Results { v = extract(e.walkUnary(t, T)) case *parse.BinaryNode: v = extract(e.walkBinary(t, T)) + case *parse.ExprNode: + v = e.walkExpr(t, T) default: panic(fmt.Errorf("expr: unknown func arg type")) } @@ -741,5 +762,8 @@ func extract(res *Results) interface{} { if len(res.Results) == 1 && res.Results[0].Type() == models.TypeString { return string(res.Results[0].Value.Value().(String)) } + if len(res.Results) == 1 && res.Results[0].Type() == models.TypeExpr { + return res.Results[0].Value.Value() + } return res } diff --git a/cmd/bosun/expr/funcs.go b/cmd/bosun/expr/funcs.go index fcf1f6ad49..fe623e0a05 100644 --- a/cmd/bosun/expr/funcs.go +++ b/cmd/bosun/expr/funcs.go @@ -334,6 +334,55 @@ var builtins = map[string]parse.Func{ Tags: tagFirst, F: Tail, }, + "map": { + Args: []models.FuncType{models.TypeSeriesSet, models.TypeExpr}, + Return: models.TypeSeriesSet, + Tags: tagFirst, + F: Map, + }, + "v": { + Return: models.TypeScalar, + F: V, + MapFunc: true, + }, +} + +func V(e *State, T miniprofiler.Timer) (*Results, error) { + return &Results{ + Results: ResultSlice{ + &Result{ + Value: Scalar(e.vValue), + }, + }, + }, nil +} + +func Map(e *State, T miniprofiler.Timer, series *Results, expr *Results) (*Results, error) { + newExpr := Expr{expr.Results[0].Value.Value().(SubExpr).Tree} + for _, result := range series.Results { + newSeries := make(Series) + for t, v := range result.Value.Value().(Series) { + e.vValue = v + subResults, _, err := newExpr.ExecuteState(e, T) + if err != nil { + return series, err + } + for _, res := range subResults.Results { + var v float64 + switch res.Value.Value().(type) { + case Number: + v = float64(res.Value.Value().(Number)) + case Scalar: + v = float64(res.Value.Value().(Scalar)) + default: + return series, fmt.Errorf("wrong return type for map expr: %v", res.Type()) + } + newSeries[t] = v + } + } + result.Value = newSeries + } + return series, nil } func SeriesFunc(e *State, T miniprofiler.Timer, tags string, pairs ...float64) (*Results, error) { diff --git a/cmd/bosun/expr/funcs_test.go b/cmd/bosun/expr/funcs_test.go index cebd506a91..73fcac87ce 100644 --- a/cmd/bosun/expr/funcs_test.go +++ b/cmd/bosun/expr/funcs_test.go @@ -110,6 +110,56 @@ func TestUngroup(t *testing.T) { } } +func TestMap(t *testing.T) { + err := testExpression(exprInOut{ + `map(series("test=test", 0, 1, 1, 3), expr(v()+1))`, + Results{ + Results: ResultSlice{ + &Result{ + Value: Series{ + time.Unix(0, 0): 2, + time.Unix(1, 0): 4, + }, + Group: opentsdb.TagSet{"test": "test"}, + }, + }, + }, + }) + if err != nil { + t.Error(err) + } + + err = testExpression(exprInOut{ + `avg(map(series("test=test", 0, 1, 1, 3), expr(v()+1)))`, + Results{ + Results: ResultSlice{ + &Result{ + Value: Number(3), + Group: opentsdb.TagSet{"test": "test"}, + }, + }, + }, + }) + if err != nil { + t.Error(err) + } + + err = testExpression(exprInOut{ + `1 + avg(map(series("test=test", 0, 1, 1, 3), expr(v()+1))) + 1`, + Results{ + Results: ResultSlice{ + &Result{ + Value: Number(5), + Group: opentsdb.TagSet{"test": "test"}, + }, + }, + }, + }) + if err != nil { + t.Error(err) + } +} + func TestMerge(t *testing.T) { seriesA := `series("foo=bar", 0, 1)` seriesB := `series("foo=baz", 0, 1)` diff --git a/cmd/bosun/expr/parse/lex.go b/cmd/bosun/expr/parse/lex.go index 152ca26430..16569675e4 100644 --- a/cmd/bosun/expr/parse/lex.go +++ b/cmd/bosun/expr/parse/lex.go @@ -58,6 +58,7 @@ const ( itemFunc itemTripleQuotedString itemPow // '**' + itemExpr ) const eof = -1 @@ -278,6 +279,10 @@ func lexFunc(l *lexer) stateFn { // absorb default: l.backup() + if l.input[l.start:l.pos] == "expr" { + l.emit(itemExpr) + return lexItem + } l.emit(itemFunc) return lexItem } diff --git a/cmd/bosun/expr/parse/node.go b/cmd/bosun/expr/parse/node.go index e818d0086a..2084de1440 100644 --- a/cmd/bosun/expr/parse/node.go +++ b/cmd/bosun/expr/parse/node.go @@ -58,6 +58,7 @@ const ( NodeUnary // Unary operator: !, - NodeString // A string constant. NodeNumber // A numerical constant. + NodeExpr // A sub expression ) // Nodes. @@ -164,6 +165,41 @@ type NumberNode struct { Text string // The original textual representation from the input. } +type ExprNode struct { + NodeType + Pos + Text string + Tree *Tree +} + +func newExprNode(text string, pos Pos) (*ExprNode, error) { + return &ExprNode{ + NodeType: NodeExpr, + Text: text, + Pos: pos, + }, nil +} + +func (s *ExprNode) String() string { + return fmt.Sprintf("%v", s.Text) +} + +func (s *ExprNode) StringAST() string { + return s.String() +} + +func (s *ExprNode) Check(*Tree) error { + return nil +} + +func (s *ExprNode) Return() models.FuncType { + return models.TypeExpr +} + +func (s *ExprNode) Tags() (Tags, error) { + return nil, nil +} + func newNumber(pos Pos, text string) (*NumberNode, error) { n := &NumberNode{NodeType: NodeNumber, Pos: pos, Text: text} // Do integer test first so we get 0x123 etc. @@ -365,7 +401,7 @@ func Walk(n Node, f func(Node)) { for _, a := range n.Args { Walk(a, f) } - case *NumberNode, *StringNode: + case *NumberNode, *StringNode, *ExprNode: // Ignore. case *UnaryNode: Walk(n.Arg, f) diff --git a/cmd/bosun/expr/parse/parse.go b/cmd/bosun/expr/parse/parse.go index b354a5bdc6..b2f81daa86 100644 --- a/cmd/bosun/expr/parse/parse.go +++ b/cmd/bosun/expr/parse/parse.go @@ -22,7 +22,8 @@ type Tree struct { Text string // text parsed to create the expression. Root Node // top-level root of the tree, returns a number. - funcs []map[string]Func + funcs []map[string]Func + mapExpr bool // Parsing only; cleared after parse. lex *lexer @@ -38,6 +39,7 @@ type Func struct { VArgs bool VArgsPos int VArgsOmit bool + MapFunc bool // Func is only valid in map expressions Check func(*Tree, *FuncNode) error } @@ -95,6 +97,14 @@ func Parse(text string, funcs ...map[string]Func) (t *Tree, err error) { return } +func ParseSub(text string, funcs ...map[string]Func) (t *Tree, err error) { + t = New() + t.mapExpr = true + t.Text = text + err = t.Parse(text, funcs...) + return +} + // next returns the next token. func (t *Tree) next() item { if t.peekCount > 0 { @@ -314,17 +324,17 @@ func (t *Tree) E() Node { func (t *Tree) F() Node { switch token := t.peek(); token.typ { - case itemNumber, itemFunc: + case itemNumber, itemFunc, itemExpr: return t.v() case itemNot, itemMinus: return newUnary(t.next(), t.F()) case itemLeftParen: t.next() n := t.O() - t.expect(itemRightParen, "input") + t.expect(itemRightParen, "input: F()") return n default: - t.unexpected(token, "input") + t.unexpected(token, "input: F()") } return nil } @@ -340,8 +350,45 @@ func (t *Tree) v() Node { case itemFunc: t.backup() return t.Func() + case itemExpr: + start := t.lex.pos + t.next() + leftCount := 0 + rightCount := 0 + fmt.Println("Going over tokens") + TOKENS: + for { + switch token = t.next(); token.typ { + case itemLeftParen: + leftCount++ + case itemRightParen: + fmt.Println("right paren, yay") + rightCount++ + if rightCount > leftCount { + fmt.Println("breaking sub expression") + break TOKENS + } + case itemEOF: + t.unexpected(token, "input: v()") + default: + // continue + } + } + fmt.Println("Out of tokens loop") + n, err := newExprNode(t.lex.input[start:t.lex.pos-1], t.lex.pos-1) + if err != nil { + t.error(err) + } + fmt.Println("going to parse sub expression: ", n.Text) + // not the correct place to inject, but doing it here for now + n.Tree, err = ParseSub(n.Text, t.funcs...) + if err != nil { + t.error(err) + } + fmt.Println("returning exprNode") + return n default: - t.unexpected(token, "input") + t.unexpected(token, "input: v()") } return nil } diff --git a/models/incidents.go b/models/incidents.go index 4cb4c27dfd..fc5453346d 100644 --- a/models/incidents.go +++ b/models/incidents.go @@ -130,6 +130,7 @@ const ( TypeSeriesSet TypeESQuery TypeESIndexer + TypeExpr ) type Status int From 66a97ccbad13a8a72258857803d9eed74de496d9 Mon Sep 17 00:00:00 2001 From: Kyle Brandt Date: Thu, 30 Jun 2016 17:11:22 -0700 Subject: [PATCH 04/15] parse fixes --- cmd/bosun/expr/funcs_test.go | 51 +++++++++++++++++++++++++++++++++++ cmd/bosun/expr/parse/parse.go | 26 +++++++++++------- 2 files changed, 67 insertions(+), 10 deletions(-) diff --git a/cmd/bosun/expr/funcs_test.go b/cmd/bosun/expr/funcs_test.go index 73fcac87ce..f602dc0b8e 100644 --- a/cmd/bosun/expr/funcs_test.go +++ b/cmd/bosun/expr/funcs_test.go @@ -158,6 +158,57 @@ func TestMap(t *testing.T) { if err != nil { t.Error(err) } + + err = testExpression(exprInOut{ + `max(map(series("test=test", 0, 1, 1, 3), expr(v()+v())))`, + Results{ + Results: ResultSlice{ + &Result{ + Value: Number(6), + Group: opentsdb.TagSet{"test": "test"}, + }, + }, + }, + }) + if err != nil { + t.Error(err) + } + + err = testExpression(exprInOut{ + `map(series("test=test", 0, -2, 1, 3), expr(1+1))`, + Results{ + Results: ResultSlice{ + &Result{ + Value: Series{ + time.Unix(0, 0): 2, + time.Unix(1, 0): 2, + }, + Group: opentsdb.TagSet{"test": "test"}, + }, + }, + }, + }) + if err != nil { + t.Error(err) + } + + err = testExpression(exprInOut{ + `map(series("test=test", 0, -2, 1, 3), expr(abs(1)))`, + Results{ + Results: ResultSlice{ + &Result{ + Value: Series{ + time.Unix(0, 0): 1, + time.Unix(1, 0): 1, + }, + Group: opentsdb.TagSet{"test": "test"}, + }, + }, + }, + }) + if err != nil { + t.Error(err) + } } func TestMerge(t *testing.T) { diff --git a/cmd/bosun/expr/parse/parse.go b/cmd/bosun/expr/parse/parse.go index b2f81daa86..d5b354da63 100644 --- a/cmd/bosun/expr/parse/parse.go +++ b/cmd/bosun/expr/parse/parse.go @@ -230,7 +230,7 @@ func (t *Tree) Parse(text string, funcs ...map[string]Func) (err error) { // It runs to EOF. func (t *Tree) parse() { t.Root = t.O() - t.expect(itemEOF, "input") + t.expect(itemEOF, "root input") if err := t.Root.Check(t); err != nil { t.error(err) } @@ -351,20 +351,25 @@ func (t *Tree) v() Node { t.backup() return t.Func() case itemExpr: - start := t.lex.pos - t.next() - leftCount := 0 - rightCount := 0 + fmt.Println(token.String()) + t.expect(itemLeftParen, "v() expect left paran in itemExpr") + start := t.lex.lastPos + leftCount := 1 fmt.Println("Going over tokens") TOKENS: for { switch token = t.next(); token.typ { case itemLeftParen: + fmt.Println("left paren") leftCount++ + case itemFunc: + fmt.Println("itemFunc in subExpr") case itemRightParen: - fmt.Println("right paren, yay") - rightCount++ - if rightCount > leftCount { + fmt.Println("right paren") + leftCount-- + if leftCount == 0 { + t.expect(itemRightParen, "v() expect right paren in itemExpr") + t.backup() fmt.Println("breaking sub expression") break TOKENS } @@ -374,8 +379,9 @@ func (t *Tree) v() Node { // continue } } - fmt.Println("Out of tokens loop") - n, err := newExprNode(t.lex.input[start:t.lex.pos-1], t.lex.pos-1) + fmt.Println("Out of tokens loop: on token ", t.token[0].String()) + //t.expect(itemRightParen, "v() expect right paren in itemExpr") + n, err := newExprNode(t.lex.input[start:t.lex.lastPos], t.lex.lastPos) if err != nil { t.error(err) } From 2051762d37897b009dc64ab61fb50fc6cc232cc2 Mon Sep 17 00:00:00 2001 From: Kyle Brandt Date: Fri, 1 Jul 2016 11:34:32 -0400 Subject: [PATCH 05/15] fromScalar --- cmd/bosun/expr/funcs.go | 8 +------- cmd/bosun/expr/funcs_test.go | 6 +++--- 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/cmd/bosun/expr/funcs.go b/cmd/bosun/expr/funcs.go index fe623e0a05..ee992627b6 100644 --- a/cmd/bosun/expr/funcs.go +++ b/cmd/bosun/expr/funcs.go @@ -348,13 +348,7 @@ var builtins = map[string]parse.Func{ } func V(e *State, T miniprofiler.Timer) (*Results, error) { - return &Results{ - Results: ResultSlice{ - &Result{ - Value: Scalar(e.vValue), - }, - }, - }, nil + return fromScalar(e.vValue), nil } func Map(e *State, T miniprofiler.Timer, series *Results, expr *Results) (*Results, error) { diff --git a/cmd/bosun/expr/funcs_test.go b/cmd/bosun/expr/funcs_test.go index f602dc0b8e..1c48553b41 100644 --- a/cmd/bosun/expr/funcs_test.go +++ b/cmd/bosun/expr/funcs_test.go @@ -193,13 +193,13 @@ func TestMap(t *testing.T) { } err = testExpression(exprInOut{ - `map(series("test=test", 0, -2, 1, 3), expr(abs(1)))`, + `map(series("test=test", 0, -2, 1, 3), expr(abs(v())))`, Results{ Results: ResultSlice{ &Result{ Value: Series{ - time.Unix(0, 0): 1, - time.Unix(1, 0): 1, + time.Unix(0, 0): 2, + time.Unix(1, 0): 3, }, Group: opentsdb.TagSet{"test": "test"}, }, From c0f7de5f3621be76750c1595358bb873f9449f80 Mon Sep 17 00:00:00 2001 From: Kyle Brandt Date: Fri, 1 Jul 2016 11:43:30 -0400 Subject: [PATCH 06/15] subExpr valid only as func param --- cmd/bosun/expr/parse/parse.go | 104 +++++++++++++++++++--------------- 1 file changed, 59 insertions(+), 45 deletions(-) diff --git a/cmd/bosun/expr/parse/parse.go b/cmd/bosun/expr/parse/parse.go index d5b354da63..6a372eaf8e 100644 --- a/cmd/bosun/expr/parse/parse.go +++ b/cmd/bosun/expr/parse/parse.go @@ -246,7 +246,7 @@ E -> F {( "**" ) F} F -> v | "(" O ")" | "!" O | "-" O v -> number | func(..) Func -> name "(" param {"," param} ")" -param -> number | "string" | [query] +param -> number | "string" | subExpr | [query] */ // expr: @@ -324,7 +324,7 @@ func (t *Tree) E() Node { func (t *Tree) F() Node { switch token := t.peek(); token.typ { - case itemNumber, itemFunc, itemExpr: + case itemNumber, itemFunc: return t.v() case itemNot, itemMinus: return newUnary(t.next(), t.F()) @@ -350,49 +350,6 @@ func (t *Tree) v() Node { case itemFunc: t.backup() return t.Func() - case itemExpr: - fmt.Println(token.String()) - t.expect(itemLeftParen, "v() expect left paran in itemExpr") - start := t.lex.lastPos - leftCount := 1 - fmt.Println("Going over tokens") - TOKENS: - for { - switch token = t.next(); token.typ { - case itemLeftParen: - fmt.Println("left paren") - leftCount++ - case itemFunc: - fmt.Println("itemFunc in subExpr") - case itemRightParen: - fmt.Println("right paren") - leftCount-- - if leftCount == 0 { - t.expect(itemRightParen, "v() expect right paren in itemExpr") - t.backup() - fmt.Println("breaking sub expression") - break TOKENS - } - case itemEOF: - t.unexpected(token, "input: v()") - default: - // continue - } - } - fmt.Println("Out of tokens loop: on token ", t.token[0].String()) - //t.expect(itemRightParen, "v() expect right paren in itemExpr") - n, err := newExprNode(t.lex.input[start:t.lex.lastPos], t.lex.lastPos) - if err != nil { - t.error(err) - } - fmt.Println("going to parse sub expression: ", n.Text) - // not the correct place to inject, but doing it here for now - n.Tree, err = ParseSub(n.Text, t.funcs...) - if err != nil { - t.error(err) - } - fmt.Println("returning exprNode") - return n default: t.unexpected(token, "input: v()") } @@ -422,6 +379,49 @@ func (t *Tree) Func() (f *FuncNode) { f.append(newString(token.pos, token.val, s)) case itemRightParen: return + case itemExpr: + fmt.Println(token.String()) + t.expect(itemLeftParen, "v() expect left paran in itemExpr") + start := t.lex.lastPos + leftCount := 1 + fmt.Println("Going over tokens") + TOKENS: + for { + switch token = t.next(); token.typ { + case itemLeftParen: + fmt.Println("left paren") + leftCount++ + case itemFunc: + fmt.Println("itemFunc in subExpr") + case itemRightParen: + fmt.Println("right paren") + leftCount-- + if leftCount == 0 { + t.expect(itemRightParen, "v() expect right paren in itemExpr") + t.backup() + fmt.Println("breaking sub expression") + break TOKENS + } + case itemEOF: + t.unexpected(token, "input: v()") + default: + // continue + } + } + fmt.Println("Out of tokens loop: on token ", t.token[0].String()) + //t.expect(itemRightParen, "v() expect right paren in itemExpr") + n, err := newExprNode(t.lex.input[start:t.lex.lastPos], t.lex.lastPos) + if err != nil { + t.error(err) + } + fmt.Println("going to parse sub expression: ", n.Text) + // not the correct place to inject, but doing it here for now + n.Tree, err = ParseSub(n.Text, t.funcs...) + if err != nil { + t.error(err) + } + fmt.Println("returning exprNode") + f.append(n) } switch token = t.next(); token.typ { case itemComma: @@ -446,6 +446,20 @@ func (t *Tree) GetFunction(name string) (v Func, ok bool) { return } +func (t *Tree) SetFunction(name string, F interface{}) error { + for i, funcMap := range t.funcs { + if funcMap == nil { + continue + } + if v, ok := funcMap[name]; ok { + v.F = F + t.funcs[i][name] = v + return nil + } + } + return fmt.Errorf("can not set function, function %v not found", name) +} + func (t *Tree) String() string { return t.Root.String() } From b1e6d48ce1156ed5bc9560c479492be12ea9adb2 Mon Sep 17 00:00:00 2001 From: Kyle Brandt Date: Fri, 1 Jul 2016 11:45:56 -0400 Subject: [PATCH 07/15] restore builtins --- cmd/bosun/expr/funcs.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/cmd/bosun/expr/funcs.go b/cmd/bosun/expr/funcs.go index ee992627b6..694d387710 100644 --- a/cmd/bosun/expr/funcs.go +++ b/cmd/bosun/expr/funcs.go @@ -208,12 +208,15 @@ var builtins = map[string]parse.Func{ Tags: tagFirst, F: Abs, }, +<<<<<<< c0f7de5f3621be76750c1595358bb873f9449f80 "crop": { Args: []models.FuncType{models.TypeSeriesSet, models.TypeNumberSet, models.TypeNumberSet}, Return: models.TypeSeriesSet, Tags: tagFirst, F: Crop, }, +======= +>>>>>>> restore builtins "d": { Args: []models.FuncType{models.TypeString}, Return: models.TypeScalar, From 8e3b700da78b8a9d587a4f87f85a31e934997d26 Mon Sep 17 00:00:00 2001 From: Kyle Brandt Date: Mon, 4 Jul 2016 22:36:58 -0400 Subject: [PATCH 08/15] ensure series are not valid in map expr that expect a number as final result --- cmd/bosun/expr/expr.go | 10 ++++----- cmd/bosun/expr/expr_test.go | 3 +++ cmd/bosun/expr/funcs.go | 4 ++-- cmd/bosun/expr/funcs_test.go | 42 ++++++++++++++++++++++++++++++++++-- cmd/bosun/expr/parse/node.go | 14 +++++++++--- models/incidents.go | 8 ++++++- 6 files changed, 68 insertions(+), 13 deletions(-) diff --git a/cmd/bosun/expr/expr.go b/cmd/bosun/expr/expr.go index 2b6c53696f..e8db477078 100644 --- a/cmd/bosun/expr/expr.go +++ b/cmd/bosun/expr/expr.go @@ -174,10 +174,10 @@ type String string func (s String) Type() models.FuncType { return models.TypeString } func (s String) Value() interface{} { return s } -type SubExpr Expr +type NumberExpr Expr -func (s SubExpr) Type() models.FuncType { return models.TypeExpr } -func (s SubExpr) Value() interface{} { return s } +func (s NumberExpr) Type() models.FuncType { return models.TypeNumberExpr } +func (s NumberExpr) Value() interface{} { return s } //func (s String) MarshalJSON() ([]byte, error) { return json.Marshal(s) } @@ -479,7 +479,7 @@ func (e *State) walkExpr(node *parse.ExprNode, T miniprofiler.Timer) *Results { return &Results{ Results: ResultSlice{ &Result{ - Value: SubExpr{node.Tree}, + Value: NumberExpr{node.Tree}, }, }, } @@ -762,7 +762,7 @@ func extract(res *Results) interface{} { if len(res.Results) == 1 && res.Results[0].Type() == models.TypeString { return string(res.Results[0].Value.Value().(String)) } - if len(res.Results) == 1 && res.Results[0].Type() == models.TypeExpr { + if len(res.Results) == 1 && res.Results[0].Type() == models.TypeNumberExpr { return res.Results[0].Value.Value() } return res diff --git a/cmd/bosun/expr/expr_test.go b/cmd/bosun/expr/expr_test.go index dffe77b99a..fbbb45c7ab 100644 --- a/cmd/bosun/expr/expr_test.go +++ b/cmd/bosun/expr/expr_test.go @@ -278,6 +278,7 @@ func TestSeriesOperations(t *testing.T) { }, }, }, + false, }, { fmt.Sprintf(template, seriesA, "+", seriesC), @@ -291,6 +292,7 @@ func TestSeriesOperations(t *testing.T) { }, }, }, + false, }, { fmt.Sprintf(template, seriesA, "/", seriesB), @@ -306,6 +308,7 @@ func TestSeriesOperations(t *testing.T) { }, }, }, + false, }, } for _, test := range tests { diff --git a/cmd/bosun/expr/funcs.go b/cmd/bosun/expr/funcs.go index 694d387710..0ad169249f 100644 --- a/cmd/bosun/expr/funcs.go +++ b/cmd/bosun/expr/funcs.go @@ -338,7 +338,7 @@ var builtins = map[string]parse.Func{ F: Tail, }, "map": { - Args: []models.FuncType{models.TypeSeriesSet, models.TypeExpr}, + Args: []models.FuncType{models.TypeSeriesSet, models.TypeNumberExpr}, Return: models.TypeSeriesSet, Tags: tagFirst, F: Map, @@ -355,7 +355,7 @@ func V(e *State, T miniprofiler.Timer) (*Results, error) { } func Map(e *State, T miniprofiler.Timer, series *Results, expr *Results) (*Results, error) { - newExpr := Expr{expr.Results[0].Value.Value().(SubExpr).Tree} + newExpr := Expr{expr.Results[0].Value.Value().(NumberExpr).Tree} for _, result := range series.Results { newSeries := make(Series) for t, v := range result.Value.Value().(Series) { diff --git a/cmd/bosun/expr/funcs_test.go b/cmd/bosun/expr/funcs_test.go index 1c48553b41..53a9cdda89 100644 --- a/cmd/bosun/expr/funcs_test.go +++ b/cmd/bosun/expr/funcs_test.go @@ -11,12 +11,19 @@ import ( ) type exprInOut struct { - expr string - out Results + expr string + out Results + shouldParseErr bool } func testExpression(eio exprInOut) error { e, err := New(eio.expr, builtins) + if eio.shouldParseErr { + if err == nil { + return fmt.Errorf("no error when expected error on %v", eio.expr) + } + return nil + } if err != nil { return err } @@ -44,6 +51,7 @@ func TestDuration(t *testing.T) { }, }, }, + false, } err := testExpression(d) if err != nil { @@ -81,6 +89,7 @@ func TestToDuration(t *testing.T) { }, }, }, + false, } err := testExpression(d) if err != nil { @@ -103,6 +112,7 @@ func TestUngroup(t *testing.T) { }, }, }, + false, }) if err != nil { @@ -124,6 +134,7 @@ func TestMap(t *testing.T) { }, }, }, + false, }) if err != nil { t.Error(err) @@ -139,6 +150,7 @@ func TestMap(t *testing.T) { }, }, }, + false, }) if err != nil { t.Error(err) @@ -154,6 +166,7 @@ func TestMap(t *testing.T) { }, }, }, + false, }) if err != nil { t.Error(err) @@ -169,6 +182,7 @@ func TestMap(t *testing.T) { }, }, }, + false, }) if err != nil { t.Error(err) @@ -187,6 +201,7 @@ func TestMap(t *testing.T) { }, }, }, + false, }) if err != nil { t.Error(err) @@ -205,6 +220,26 @@ func TestMap(t *testing.T) { }, }, }, + false, + }) + if err != nil { + t.Error(err) + } + + err = testExpression(exprInOut{ + `map(series("test=test", 0, -2, 1, 3), expr(series("test=test", 0, v())))`, + Results{ + Results: ResultSlice{ + &Result{ + Value: Series{ + time.Unix(0, 0): 2, + time.Unix(1, 0): 3, + }, + Group: opentsdb.TagSet{"test": "test"}, + }, + }, + }, + true, // expect parse error here, series result not valid as TypeNumberExpr }) if err != nil { t.Error(err) @@ -232,6 +267,7 @@ func TestMerge(t *testing.T) { }, }, }, + false, }) if err != nil { t.Error(err) @@ -256,6 +292,7 @@ func TestMerge(t *testing.T) { }, }, }, + false, }) if err == nil { t.Errorf("error expected due to identical groups in merge but did not get one") @@ -292,6 +329,7 @@ func TestTimedelta(t *testing.T) { }, }, }, + false, }) if err != nil { diff --git a/cmd/bosun/expr/parse/node.go b/cmd/bosun/expr/parse/node.go index 2084de1440..1ad74f315e 100644 --- a/cmd/bosun/expr/parse/node.go +++ b/cmd/bosun/expr/parse/node.go @@ -126,6 +126,7 @@ func (f *FuncNode) Check(t *Tree) error { funcType = f.F.Args[i] } argType := arg.Return() + fmt.Printf("func tpye %v, arg type %v\n", funcType, argType) if funcType == models.TypeNumberSet && argType == models.TypeScalar { // Scalars are promoted to NumberSets during execution. } else if funcType != argType { @@ -175,8 +176,8 @@ type ExprNode struct { func newExprNode(text string, pos Pos) (*ExprNode, error) { return &ExprNode{ NodeType: NodeExpr, - Text: text, - Pos: pos, + Text: text, + Pos: pos, }, nil } @@ -193,7 +194,14 @@ func (s *ExprNode) Check(*Tree) error { } func (s *ExprNode) Return() models.FuncType { - return models.TypeExpr + switch s.Tree.Root.Return() { + case models.TypeNumberSet, models.TypeScalar: + return models.TypeNumberExpr + case models.TypeSeriesSet: + return models.TypeSeriesExpr + default: + return models.TypeUnexpected + } } func (s *ExprNode) Tags() (Tags, error) { diff --git a/models/incidents.go b/models/incidents.go index fc5453346d..26ddf8be28 100644 --- a/models/incidents.go +++ b/models/incidents.go @@ -118,6 +118,10 @@ func (f FuncType) String() string { return "esquery" case TypeESIndexer: return "esindexer" + case TypeNumberExpr: + return "numberexpr" + case TypeSeriesExpr: + return "seriesexpr" default: return "unknown" } @@ -130,7 +134,9 @@ const ( TypeSeriesSet TypeESQuery TypeESIndexer - TypeExpr + TypeNumberExpr + TypeSeriesExpr // No implmentation yet + TypeUnexpected ) type Status int From 18110d2eab8f3bf7e3236cc28546a690ea25c506 Mon Sep 17 00:00:00 2001 From: Kyle Brandt Date: Tue, 5 Jul 2016 11:27:35 -0400 Subject: [PATCH 09/15] check that funcs that should only be used in map are not used anywhere else --- cmd/bosun/expr/funcs_test.go | 21 +++++++++++++++++---- cmd/bosun/expr/parse/node.go | 3 +++ 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/cmd/bosun/expr/funcs_test.go b/cmd/bosun/expr/funcs_test.go index 53a9cdda89..c126016be0 100644 --- a/cmd/bosun/expr/funcs_test.go +++ b/cmd/bosun/expr/funcs_test.go @@ -231,10 +231,7 @@ func TestMap(t *testing.T) { Results{ Results: ResultSlice{ &Result{ - Value: Series{ - time.Unix(0, 0): 2, - time.Unix(1, 0): 3, - }, + Value: Series{}, Group: opentsdb.TagSet{"test": "test"}, }, }, @@ -244,6 +241,22 @@ func TestMap(t *testing.T) { if err != nil { t.Error(err) } + + err = testExpression(exprInOut{ + `v()`, + Results{ + Results: ResultSlice{ + &Result{ + Value: Series{}, + Group: opentsdb.TagSet{"test": "test"}, + }, + }, + }, + true, // v() is not outside a map expression + }) + if err != nil { + t.Error(err) + } } func TestMerge(t *testing.T) { diff --git a/cmd/bosun/expr/parse/node.go b/cmd/bosun/expr/parse/node.go index 1ad74f315e..49eeaca095 100644 --- a/cmd/bosun/expr/parse/node.go +++ b/cmd/bosun/expr/parse/node.go @@ -105,6 +105,9 @@ func (f *FuncNode) StringAST() string { } func (f *FuncNode) Check(t *Tree) error { + if f.F.MapFunc && !t.mapExpr { + return fmt.Errorf("%v is only valid in a map expression", f.Name) + } const errFuncType = "parse: bad argument type in %s, expected %s, got %s" // For VArgs we make sure they are all of the expected type if f.F.VArgs { From 7efba044847eee2356f3dbc4a45a38dda3556edd Mon Sep 17 00:00:00 2001 From: Kyle Brandt Date: Tue, 5 Jul 2016 11:32:36 -0400 Subject: [PATCH 10/15] move map tests to own file --- cmd/bosun/expr/funcs_test.go | 137 -------------------------------- cmd/bosun/expr/map_test.go | 147 +++++++++++++++++++++++++++++++++++ 2 files changed, 147 insertions(+), 137 deletions(-) create mode 100644 cmd/bosun/expr/map_test.go diff --git a/cmd/bosun/expr/funcs_test.go b/cmd/bosun/expr/funcs_test.go index c126016be0..7e13a5e2e4 100644 --- a/cmd/bosun/expr/funcs_test.go +++ b/cmd/bosun/expr/funcs_test.go @@ -120,144 +120,7 @@ func TestUngroup(t *testing.T) { } } -func TestMap(t *testing.T) { - err := testExpression(exprInOut{ - `map(series("test=test", 0, 1, 1, 3), expr(v()+1))`, - Results{ - Results: ResultSlice{ - &Result{ - Value: Series{ - time.Unix(0, 0): 2, - time.Unix(1, 0): 4, - }, - Group: opentsdb.TagSet{"test": "test"}, - }, - }, - }, - false, - }) - if err != nil { - t.Error(err) - } - - err = testExpression(exprInOut{ - `avg(map(series("test=test", 0, 1, 1, 3), expr(v()+1)))`, - Results{ - Results: ResultSlice{ - &Result{ - Value: Number(3), - Group: opentsdb.TagSet{"test": "test"}, - }, - }, - }, - false, - }) - if err != nil { - t.Error(err) - } - err = testExpression(exprInOut{ - `1 + avg(map(series("test=test", 0, 1, 1, 3), expr(v()+1))) + 1`, - Results{ - Results: ResultSlice{ - &Result{ - Value: Number(5), - Group: opentsdb.TagSet{"test": "test"}, - }, - }, - }, - false, - }) - if err != nil { - t.Error(err) - } - - err = testExpression(exprInOut{ - `max(map(series("test=test", 0, 1, 1, 3), expr(v()+v())))`, - Results{ - Results: ResultSlice{ - &Result{ - Value: Number(6), - Group: opentsdb.TagSet{"test": "test"}, - }, - }, - }, - false, - }) - if err != nil { - t.Error(err) - } - - err = testExpression(exprInOut{ - `map(series("test=test", 0, -2, 1, 3), expr(1+1))`, - Results{ - Results: ResultSlice{ - &Result{ - Value: Series{ - time.Unix(0, 0): 2, - time.Unix(1, 0): 2, - }, - Group: opentsdb.TagSet{"test": "test"}, - }, - }, - }, - false, - }) - if err != nil { - t.Error(err) - } - - err = testExpression(exprInOut{ - `map(series("test=test", 0, -2, 1, 3), expr(abs(v())))`, - Results{ - Results: ResultSlice{ - &Result{ - Value: Series{ - time.Unix(0, 0): 2, - time.Unix(1, 0): 3, - }, - Group: opentsdb.TagSet{"test": "test"}, - }, - }, - }, - false, - }) - if err != nil { - t.Error(err) - } - - err = testExpression(exprInOut{ - `map(series("test=test", 0, -2, 1, 3), expr(series("test=test", 0, v())))`, - Results{ - Results: ResultSlice{ - &Result{ - Value: Series{}, - Group: opentsdb.TagSet{"test": "test"}, - }, - }, - }, - true, // expect parse error here, series result not valid as TypeNumberExpr - }) - if err != nil { - t.Error(err) - } - - err = testExpression(exprInOut{ - `v()`, - Results{ - Results: ResultSlice{ - &Result{ - Value: Series{}, - Group: opentsdb.TagSet{"test": "test"}, - }, - }, - }, - true, // v() is not outside a map expression - }) - if err != nil { - t.Error(err) - } -} func TestMerge(t *testing.T) { seriesA := `series("foo=bar", 0, 1)` diff --git a/cmd/bosun/expr/map_test.go b/cmd/bosun/expr/map_test.go new file mode 100644 index 0000000000..ec4859b752 --- /dev/null +++ b/cmd/bosun/expr/map_test.go @@ -0,0 +1,147 @@ +package expr + +import ( + "testing" + "time" + + "bosun.org/opentsdb" +) + +func TestMap(t *testing.T) { + err := testExpression(exprInOut{ + `map(series("test=test", 0, 1, 1, 3), expr(v()+1))`, + Results{ + Results: ResultSlice{ + &Result{ + Value: Series{ + time.Unix(0, 0): 2, + time.Unix(1, 0): 4, + }, + Group: opentsdb.TagSet{"test": "test"}, + }, + }, + }, + false, + }) + if err != nil { + t.Error(err) + } + + err = testExpression(exprInOut{ + `avg(map(series("test=test", 0, 1, 1, 3), expr(v()+1)))`, + Results{ + Results: ResultSlice{ + &Result{ + Value: Number(3), + Group: opentsdb.TagSet{"test": "test"}, + }, + }, + }, + false, + }) + if err != nil { + t.Error(err) + } + + err = testExpression(exprInOut{ + `1 + avg(map(series("test=test", 0, 1, 1, 3), expr(v()+1))) + 1`, + Results{ + Results: ResultSlice{ + &Result{ + Value: Number(5), + Group: opentsdb.TagSet{"test": "test"}, + }, + }, + }, + false, + }) + if err != nil { + t.Error(err) + } + + err = testExpression(exprInOut{ + `max(map(series("test=test", 0, 1, 1, 3), expr(v()+v())))`, + Results{ + Results: ResultSlice{ + &Result{ + Value: Number(6), + Group: opentsdb.TagSet{"test": "test"}, + }, + }, + }, + false, + }) + if err != nil { + t.Error(err) + } + + err = testExpression(exprInOut{ + `map(series("test=test", 0, -2, 1, 3), expr(1+1))`, + Results{ + Results: ResultSlice{ + &Result{ + Value: Series{ + time.Unix(0, 0): 2, + time.Unix(1, 0): 2, + }, + Group: opentsdb.TagSet{"test": "test"}, + }, + }, + }, + false, + }) + if err != nil { + t.Error(err) + } + + err = testExpression(exprInOut{ + `map(series("test=test", 0, -2, 1, 3), expr(abs(v())))`, + Results{ + Results: ResultSlice{ + &Result{ + Value: Series{ + time.Unix(0, 0): 2, + time.Unix(1, 0): 3, + }, + Group: opentsdb.TagSet{"test": "test"}, + }, + }, + }, + false, + }) + if err != nil { + t.Error(err) + } + + err = testExpression(exprInOut{ + `map(series("test=test", 0, -2, 1, 3), expr(series("test=test", 0, v())))`, + Results{ + Results: ResultSlice{ + &Result{ + Value: Series{}, + Group: opentsdb.TagSet{"test": "test"}, + }, + }, + }, + true, // expect parse error here, series result not valid as TypeNumberExpr + }) + if err != nil { + t.Error(err) + } + + err = testExpression(exprInOut{ + `v()`, + Results{ + Results: ResultSlice{ + &Result{ + Value: Series{}, + Group: opentsdb.TagSet{"test": "test"}, + }, + }, + }, + true, // v() is not valid outside a map expression + }) + if err != nil { + t.Error(err) + } +} From 9c65d5de0e19fe8922bcaf06ae0eb5914b8e892c Mon Sep 17 00:00:00 2001 From: Kyle Brandt Date: Tue, 5 Jul 2016 11:49:01 -0400 Subject: [PATCH 11/15] cleanup --- cmd/bosun/expr/map_test.go | 19 +++++++++++++++++++ cmd/bosun/expr/parse/node.go | 1 - cmd/bosun/expr/parse/parse.go | 11 ----------- 3 files changed, 19 insertions(+), 12 deletions(-) diff --git a/cmd/bosun/expr/map_test.go b/cmd/bosun/expr/map_test.go index ec4859b752..1bb98a843c 100644 --- a/cmd/bosun/expr/map_test.go +++ b/cmd/bosun/expr/map_test.go @@ -144,4 +144,23 @@ func TestMap(t *testing.T) { if err != nil { t.Error(err) } + + err = testExpression(exprInOut{ + `map(series("test=test", 0, -2, 1, 0), expr(!v()))`, + Results{ + Results: ResultSlice{ + &Result{ + Value: Series{ + time.Unix(0, 0): 0, + time.Unix(1, 0): 1, + }, + Group: opentsdb.TagSet{"test": "test"}, + }, + }, + }, + false, + }) + if err != nil { + t.Error(err) + } } diff --git a/cmd/bosun/expr/parse/node.go b/cmd/bosun/expr/parse/node.go index 49eeaca095..a8a9b2cb4d 100644 --- a/cmd/bosun/expr/parse/node.go +++ b/cmd/bosun/expr/parse/node.go @@ -129,7 +129,6 @@ func (f *FuncNode) Check(t *Tree) error { funcType = f.F.Args[i] } argType := arg.Return() - fmt.Printf("func tpye %v, arg type %v\n", funcType, argType) if funcType == models.TypeNumberSet && argType == models.TypeScalar { // Scalars are promoted to NumberSets during execution. } else if funcType != argType { diff --git a/cmd/bosun/expr/parse/parse.go b/cmd/bosun/expr/parse/parse.go index 6a372eaf8e..b96052acab 100644 --- a/cmd/bosun/expr/parse/parse.go +++ b/cmd/bosun/expr/parse/parse.go @@ -380,26 +380,20 @@ func (t *Tree) Func() (f *FuncNode) { case itemRightParen: return case itemExpr: - fmt.Println(token.String()) t.expect(itemLeftParen, "v() expect left paran in itemExpr") start := t.lex.lastPos leftCount := 1 - fmt.Println("Going over tokens") TOKENS: for { switch token = t.next(); token.typ { case itemLeftParen: - fmt.Println("left paren") leftCount++ case itemFunc: - fmt.Println("itemFunc in subExpr") case itemRightParen: - fmt.Println("right paren") leftCount-- if leftCount == 0 { t.expect(itemRightParen, "v() expect right paren in itemExpr") t.backup() - fmt.Println("breaking sub expression") break TOKENS } case itemEOF: @@ -408,19 +402,14 @@ func (t *Tree) Func() (f *FuncNode) { // continue } } - fmt.Println("Out of tokens loop: on token ", t.token[0].String()) - //t.expect(itemRightParen, "v() expect right paren in itemExpr") n, err := newExprNode(t.lex.input[start:t.lex.lastPos], t.lex.lastPos) if err != nil { t.error(err) } - fmt.Println("going to parse sub expression: ", n.Text) - // not the correct place to inject, but doing it here for now n.Tree, err = ParseSub(n.Text, t.funcs...) if err != nil { t.error(err) } - fmt.Println("returning exprNode") f.append(n) } switch token = t.next(); token.typ { From 301a5e779148ceac21f6b154029a7715dbfd6f19 Mon Sep 17 00:00:00 2001 From: Kyle Brandt Date: Tue, 5 Jul 2016 13:52:10 -0400 Subject: [PATCH 12/15] fix tests, fmt --- cmd/bosun/expr/funcs_test.go | 2 -- cmd/bosun/expr/parse/parse_test.go | 4 ++++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/cmd/bosun/expr/funcs_test.go b/cmd/bosun/expr/funcs_test.go index 7e13a5e2e4..d50c20e5db 100644 --- a/cmd/bosun/expr/funcs_test.go +++ b/cmd/bosun/expr/funcs_test.go @@ -120,8 +120,6 @@ func TestUngroup(t *testing.T) { } } - - func TestMerge(t *testing.T) { seriesA := `series("foo=bar", 0, 1)` seriesB := `series("foo=baz", 0, 1)` diff --git a/cmd/bosun/expr/parse/parse_test.go b/cmd/bosun/expr/parse/parse_test.go index aaefac139a..8ec01e6432 100644 --- a/cmd/bosun/expr/parse/parse_test.go +++ b/cmd/bosun/expr/parse/parse_test.go @@ -159,6 +159,7 @@ var builtins = map[string]Func{ false, 0, false, + false, nil, }, "band": { @@ -169,6 +170,7 @@ var builtins = map[string]Func{ false, 0, false, + false, nil, }, "q": { @@ -179,6 +181,7 @@ var builtins = map[string]Func{ false, 0, false, + false, nil, }, "forecastlr": { @@ -189,6 +192,7 @@ var builtins = map[string]Func{ false, 0, false, + false, nil, }, } From a75f733bc5f4e61e711734cd62d6b277598028e4 Mon Sep 17 00:00:00 2001 From: Kyle Brandt Date: Tue, 5 Jul 2016 14:36:29 -0400 Subject: [PATCH 13/15] document map --- docs/expressions.md | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/docs/expressions.md b/docs/expressions.md index 1f6fcef310..34a2bc7f1f 100644 --- a/docs/expressions.md +++ b/docs/expressions.md @@ -562,6 +562,30 @@ Returns the first key from the given lookup table with matching tags, this searc Returns the first key from the given lookup table with matching tags. The first argument is a series to use from which to derive the tag information. This is good for alternative storage backends such as graphite and influxdb. +## map(series seriesSet, subExpr numberSetExpr) seriesSet + +map applies the subExpr to each value in each series in the set. A special function `v()` which is only available in a numberSetExpr and it gives you the value for each item in the series. + +For example you can do something like the following to get the absolute value for each item in the series (since the normal `abs()` function works on normal numbers, not series: + +``` +$q = q("avg:rate:os.cpu{host=*bosun*}", "5m", "") +map($q, expr(abs(v()))) +``` + +Or for another example, this would get you the absolute difference of each datapoint from the series average as a new series: + +``` +$q = q("avg:rate:os.cpu{host=*bosun*}", "5m", "") +map($q, expr(abs(v()-avg($q)))) +``` + +Since this function is not optimized for a particular operation on a seriesSet it may not be very efficent. If you find you are doing things that involve more complex expressions within the `expr(...)` inside map (for example, having query functions in there) than you may want to consider requesting a new function to be added to bosun's DSL. + +## expr(expression) + +expr takes an expression and returns either a numberSetExpr or a seriesSetExpr depending on the resulting type of the inner expression. This exists for functions like `map` - it is currently not valid in the expression language outside of function arguments. + ## month(offset scalar, startEnd string) scalar Returns the epoch of either the start or end of the month. Offset is the timezone offset from UTC that the month starts/ends at (but the returned epoch is representitive of UTC). startEnd must be either `"start"` or `"end"`. Useful for things like monthly billing, for example: From f48f6835dd464945303fbd50098ba81a4299dd50 Mon Sep 17 00:00:00 2001 From: Kyle Brandt Date: Tue, 5 Jul 2016 20:18:29 -0400 Subject: [PATCH 14/15] merge garbage --- cmd/bosun/expr/funcs.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/cmd/bosun/expr/funcs.go b/cmd/bosun/expr/funcs.go index 0ad169249f..ac044eec07 100644 --- a/cmd/bosun/expr/funcs.go +++ b/cmd/bosun/expr/funcs.go @@ -208,15 +208,12 @@ var builtins = map[string]parse.Func{ Tags: tagFirst, F: Abs, }, -<<<<<<< c0f7de5f3621be76750c1595358bb873f9449f80 "crop": { Args: []models.FuncType{models.TypeSeriesSet, models.TypeNumberSet, models.TypeNumberSet}, Return: models.TypeSeriesSet, Tags: tagFirst, F: Crop, }, -======= ->>>>>>> restore builtins "d": { Args: []models.FuncType{models.TypeString}, Return: models.TypeScalar, From 86ff7d1f5b58a44e7b57864a9d2480ee584b8b6f Mon Sep 17 00:00:00 2001 From: Kyle Brandt Date: Tue, 5 Jul 2016 20:19:49 -0400 Subject: [PATCH 15/15] fix test after merge --- cmd/bosun/expr/funcs_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/cmd/bosun/expr/funcs_test.go b/cmd/bosun/expr/funcs_test.go index d50c20e5db..e0808f63dd 100644 --- a/cmd/bosun/expr/funcs_test.go +++ b/cmd/bosun/expr/funcs_test.go @@ -249,6 +249,7 @@ func TestTail(t *testing.T) { }, }, }, + false, }) if err != nil {