这是indexloc提供的服务,不要输入任何密码
Skip to content
This repository was archived by the owner on Feb 13, 2025. It is now read-only.
Merged

Map #1813

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions cmd/bosun/expr/expr.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ type State struct {
enableComputations bool
unjoinedOk bool
autods int
vValue float64

*Backends

Expand Down Expand Up @@ -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...)
Expand Down Expand Up @@ -172,6 +174,11 @@ type String string
func (s String) Type() models.FuncType { return models.TypeString }
func (s String) Value() interface{} { return s }

type NumberExpr Expr

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) }

// Series is the standard form within bosun to represent timeseries data.
Expand Down Expand Up @@ -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: NumberExpr{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)
Expand Down Expand Up @@ -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"))
}
Expand Down Expand Up @@ -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.TypeNumberExpr {
return res.Results[0].Value.Value()
}
return res
}
3 changes: 3 additions & 0 deletions cmd/bosun/expr/expr_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,7 @@ func TestSeriesOperations(t *testing.T) {
},
},
},
false,
},
{
fmt.Sprintf(template, seriesA, "+", seriesC),
Expand All @@ -291,6 +292,7 @@ func TestSeriesOperations(t *testing.T) {
},
},
},
false,
},
{
fmt.Sprintf(template, seriesA, "/", seriesB),
Expand All @@ -306,6 +308,7 @@ func TestSeriesOperations(t *testing.T) {
},
},
},
false,
},
}
for _, test := range tests {
Expand Down
43 changes: 43 additions & 0 deletions cmd/bosun/expr/funcs.go
Original file line number Diff line number Diff line change
Expand Up @@ -334,6 +334,49 @@ var builtins = map[string]parse.Func{
Tags: tagFirst,
F: Tail,
},
"map": {
Args: []models.FuncType{models.TypeSeriesSet, models.TypeNumberExpr},
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 fromScalar(e.vValue), nil
}

func Map(e *State, T miniprofiler.Timer, series *Results, expr *Results) (*Results, error) {
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) {
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) {
Expand Down
18 changes: 16 additions & 2 deletions cmd/bosun/expr/funcs_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down Expand Up @@ -44,6 +51,7 @@ func TestDuration(t *testing.T) {
},
},
},
false,
}
err := testExpression(d)
if err != nil {
Expand Down Expand Up @@ -81,6 +89,7 @@ func TestToDuration(t *testing.T) {
},
},
},
false,
}
err := testExpression(d)
if err != nil {
Expand All @@ -103,6 +112,7 @@ func TestUngroup(t *testing.T) {
},
},
},
false,
})

if err != nil {
Expand Down Expand Up @@ -131,6 +141,7 @@ func TestMerge(t *testing.T) {
},
},
},
false,
})
if err != nil {
t.Error(err)
Expand All @@ -155,6 +166,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")
Expand Down Expand Up @@ -191,6 +203,7 @@ func TestTimedelta(t *testing.T) {
},
},
},
false,
})

if err != nil {
Expand Down Expand Up @@ -236,6 +249,7 @@ func TestTail(t *testing.T) {
},
},
},
false,
})

if err != nil {
Expand Down
166 changes: 166 additions & 0 deletions cmd/bosun/expr/map_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
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)
}

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)
}
}
Loading