Skip to content

Commit

Permalink
cmd/bosun: func to turn seconds (scalars) to duration string
Browse files Browse the repository at this point in the history
  • Loading branch information
kylebrandt committed Mar 15, 2016
1 parent 2acf8e5 commit 5621c96
Show file tree
Hide file tree
Showing 5 changed files with 149 additions and 1 deletion.
46 changes: 46 additions & 0 deletions cmd/bosun/expr/expr.go
Expand Up @@ -168,6 +168,13 @@ func (s Scalar) Type() models.FuncType { return models.TypeScalar }
func (s Scalar) Value() interface{} { return s }
func (s Scalar) MarshalJSON() ([]byte, error) { return marshalFloat(float64(s)) }

type String string

func (s String) Type() models.FuncType { return models.TypeString }
func (s String) 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.
type Series map[time.Time]float64

Expand Down Expand Up @@ -246,6 +253,42 @@ type Results struct {
NaNValue *float64
}

// Equal inspects if two results have the same content
// error will return why they are not equal if they
// are not equal
func (a *Results) Equal(b *Results) (bool, error) {
if len(a.Results) != len(b.Results) {
return false, fmt.Errorf("unequal number of results: length a: %v, length b: %v", len(a.Results), len(b.Results))
}
if a.IgnoreUnjoined != b.IgnoreUnjoined {
return false, fmt.Errorf("ignoreUnjoined flag does not match a: %v, b: %v", a.IgnoreUnjoined, b.IgnoreUnjoined)
}
if a.IgnoreOtherUnjoined != b.IgnoreOtherUnjoined {
return false, fmt.Errorf("ignoreUnjoined flag does not match a: %v, b: %v", a.IgnoreOtherUnjoined, b.IgnoreOtherUnjoined)
}
if a.NaNValue != a.NaNValue {
return false, fmt.Errorf("NaNValue does not match a: %v, b: %v", a.NaNValue, b.NaNValue)
}
sortedA := ResultSliceByGroup(a.Results)
sort.Sort(sortedA)
sortedB := ResultSliceByGroup(b.Results)
sort.Sort(sortedB)
for i, result := range sortedA {
for ic, computation := range result.Computations {
if computation != sortedB[i].Computations[ic] {
return false, fmt.Errorf("mismatched computation a: %v, b: %v", computation, sortedB[ic])
}
}
if !result.Group.Equal(sortedB[i].Group) {
return false, fmt.Errorf("mismatched groups a: %v, b: %v", result.Group, sortedB[i].Group)
}
if result.Value != sortedB[i].Value {
return false, fmt.Errorf("values do not match a: %v, b: %v", result.Value, sortedB[i].Value)
}
}
return true, nil
}

type ResultSlice []*Result

type ResultSliceByGroup ResultSlice
Expand Down Expand Up @@ -672,5 +715,8 @@ func extract(res *Results) interface{} {
if len(res.Results) == 1 && res.Results[0].Type() == models.TypeESIndexer {
return res.Results[0].Value.Value()
}
if len(res.Results) == 1 && res.Results[0].Type() == models.TypeString {
return string(res.Results[0].Value.Value().(String))
}
return res
}
14 changes: 14 additions & 0 deletions cmd/bosun/expr/funcs.go
Expand Up @@ -272,6 +272,11 @@ var builtins = map[string]parse.Func{
Return: models.TypeScalar,
F: Duration,
},
"tod": {
Args: []models.FuncType{models.TypeScalar},
Return: models.TypeString,
F: ToDuration,
},
"des": {
Args: []models.FuncType{models.TypeSeriesSet, models.TypeScalar, models.TypeScalar},
Return: models.TypeSeriesSet,
Expand Down Expand Up @@ -487,6 +492,15 @@ func Duration(e *State, T miniprofiler.Timer, d string) (*Results, error) {
}, nil
}

func ToDuration(e *State, T miniprofiler.Timer, sec float64) (*Results, error) {
d := opentsdb.Duration(time.Duration(int64(sec)) * time.Second)
return &Results{
Results: []*Result{
{Value: String(d.HumanString())},
},
}, nil
}

func DropValues(e *State, T miniprofiler.Timer, series *Results, threshold *Results, dropFunction func(float64, float64) bool) (*Results, error) {
f := func(res *Results, s *Result, floats []float64) error {
nv := make(Series)
Expand Down
62 changes: 62 additions & 0 deletions cmd/bosun/expr/funcs_test.go
@@ -0,0 +1,62 @@
package expr

import (
"testing"
"time"

"github.com/influxdata/influxdb/client"
)

type exprInOut struct {
expr string
out Results
}

func testExpression(eio exprInOut) error {
e, err := New(eio.expr, builtins)
if err != nil {
return err
}
r, _, err := e.Execute(nil, nil, nil, nil, client.Config{}, nil, nil, time.Now(), 0, false, nil, nil, nil)
if err != nil {
return err
}
if _, err := eio.out.Equal(r); err != nil {
return err
}
return nil
}

func TestDuration(t *testing.T) {
d := exprInOut{
`d("1h")`,
Results{
Results: ResultSlice{
&Result{
Value: Scalar(3600),
},
},
},
}
err := testExpression(d)
if err != nil {
t.Error(err)
}
}

func TestToDuration(t *testing.T) {
d := exprInOut{
`tod(3600*2)`,
Results{
Results: ResultSlice{
&Result{
Value: String("2h"),
},
},
},
}
err := testExpression(d)
if err != nil {
t.Error(err)
}
}
4 changes: 4 additions & 0 deletions docs/expressions.md
Expand Up @@ -452,6 +452,10 @@ Returns the absolute value of each element in the numberSet.

Returns the number of seconds of the [OpenTSDB duration string](http://opentsdb.net/docs/build/html/user_guide/query/dates.html).

## tod(scalar) string

Returns an [OpenTSDB duration string](http://opentsdb.net/docs/build/html/user_guide/query/dates.html) that represents the given number of seconds. This lets you do math on durations and then pass it to the duration arguments in functions like `q()`

## des(series, alpha scalar, beta scalar) series

Returns series smoothed using Holt-Winters double exponential smoothing. Alpha
Expand Down
24 changes: 23 additions & 1 deletion opentsdb/duration.go
Expand Up @@ -145,7 +145,29 @@ func leadingInt(s string) (x int64, rem string, err error) {
}

func (d Duration) String() string {
return fmt.Sprintf("%dms", time.Duration(d).Nanoseconds()/1e6)
return fmt.Sprintf("%dms", d/Millisecond)
}

func (d Duration) HumanString() string {
if d >= Year && d%Year == 0 {
return fmt.Sprintf("%dy", d/Year)
}
if d >= Week && d%Week == 0 {
return fmt.Sprintf("%dw", d/Week)
}
if d >= Day && d%Day == 0 {
return fmt.Sprintf("%dd", d/Day)
}
if d >= Hour && d%Hour == 0 {
return fmt.Sprintf("%dh", d/Hour)
}
if d >= Minute && d%Minute == 0 {
return fmt.Sprintf("%dh", d/Minute)
}
if d >= Second && d%Second == 0 {
return fmt.Sprintf("%ds", d/Second)
}
return fmt.Sprintf("%dms", d/Millisecond)
}

// Seconds returns the duration as a floating point number of seconds.
Expand Down

0 comments on commit 5621c96

Please sign in to comment.