Skip to content
This repository was archived by the owner on Aug 23, 2023. It is now read-only.

Commit

Permalink
Merge pull request #1719 from bloomberg/round
Browse files Browse the repository at this point in the history
Add round function
  • Loading branch information
Dieterbe authored Mar 19, 2020
2 parents 9edaddf + 9040053 commit 3acdf79
Show file tree
Hide file tree
Showing 5 changed files with 455 additions and 3 deletions.
2 changes: 1 addition & 1 deletion docs/graphite.md
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ See also:
| removeBelowValue(seriesList, n) seriesList | | Stable |
| removeBetweenPercentile | | No |
| removeEmptySeries | | No |
| roundFunction | | No |
| round | | Stable |
| scale(seriesList, num) series | | Stable |
| scaleToSeconds(seriesList, seconds) seriesList | | Stable |
| secondYAxis | | No |
Expand Down
96 changes: 96 additions & 0 deletions expr/func_round.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
package expr

import (
"fmt"
"math"
"math/big"
"strconv"

"github.com/grafana/metrictank/api/models"
"github.com/grafana/metrictank/schema"
)

type FuncRound struct {
in GraphiteFunc
precision int64
}

func NewRound() GraphiteFunc {
return &FuncRound{}
}

func (s *FuncRound) Signature() ([]Arg, []Arg) {
return []Arg{
ArgSeriesList{val: &s.in},
ArgInt{key: "precision", opt: true, validator: []Validator{IntPositive}, val: &s.precision},
}, []Arg{
ArgSeriesList{},
}
}

func (s *FuncRound) Context(context Context) Context {
return context
}

func (s *FuncRound) Exec(dataMap DataMap) ([]models.Series, error) {
series, err := s.in.Exec(dataMap)
if err != nil {
return nil, err
}

outputs := make([]models.Series, len(series))
precisionMult := float64(math.Pow10(int(s.precision)))
for i, serie := range series {
out := pointSlicePool.Get().([]schema.Point)

for _, v := range serie.Datapoints {
out = append(out, schema.Point{Val: roundToPrecision(v.Val, precisionMult, int(s.precision)), Ts: v.Ts})
}

s := models.Series{
Target: fmt.Sprintf("round(%s,%d)", serie.Target, s.precision),
QueryPatt: fmt.Sprintf("round(%s,%d)", serie.Target, s.precision),
Tags: serie.CopyTagsWith("round", strconv.Itoa(int(s.precision))),
Datapoints: out,
Interval: serie.Interval,
Meta: serie.Meta,
QueryMDP: serie.QueryMDP,
QueryPNGroup: serie.QueryPNGroup,
}
outputs[i] = s
}
dataMap.Add(Req{}, outputs...)
return outputs, nil
}

func roundToPrecision(val float64, precisionMult float64, precision int) float64 {
if math.IsNaN(val) || math.IsInf(val, 0) {
return val
}

// Special case 1: Large negative precision, pow10 is too small to represent
if precisionMult == 0 {
return 0
}
// Special case 2: Large positive precision, pow10 is too large to represent
if math.IsInf(precisionMult, 0) {
return val
}

alignedVal := val * precisionMult
// Special case 3: underflow or overflow, need to do expensive "Big" version
if math.IsInf(alignedVal, 0) {
return roundToPrecisionSlow(val, precision)
}
return math.Round(alignedVal) / precisionMult
}

func roundToPrecisionSlow(val float64, precision int) float64 {
f := big.Rat{}
f.SetFloat64(val)
ret, err := strconv.ParseFloat(f.FloatString(precision), 64)
if err != nil {
return math.NaN()
}
return ret
}
Loading

0 comments on commit 3acdf79

Please sign in to comment.