From 60271d58bf06687f2dc23764526f4c1291cde6a6 Mon Sep 17 00:00:00 2001 From: Brian Brazil Date: Thu, 5 Feb 2015 16:06:35 +0000 Subject: [PATCH] Change the 2nd argument of round to toNearest. This is more useful if you want get a multiple of 2 or 5, while still working for .001. --- rules/ast/ast.go | 6 ++++-- rules/ast/functions.go | 22 ++++++++------------- rules/rules_test.go | 44 +++++++++++++++++++++++++++++------------- 3 files changed, 43 insertions(+), 29 deletions(-) diff --git a/rules/ast/ast.go b/rules/ast/ast.go index a58356f6c6..8797c8fdec 100644 --- a/rules/ast/ast.go +++ b/rules/ast/ast.go @@ -31,14 +31,16 @@ import ( var ( stalenessDelta = flag.Duration("query.staleness-delta", 300*time.Second, "Staleness delta allowance during expression evaluations.") - queryTimeout = flag.Duration("query.timeout", 2*time.Minute, "Maximum time a query may take before being aborted.") + queryTimeout = flag.Duration("query.timeout", 2*time.Minute, "Maximum time a query may take before being aborted.") ) type queryTimeoutError struct { timeoutAfter time.Duration } -func (e queryTimeoutError) Error() string { return fmt.Sprintf("query timeout after %v", e.timeoutAfter) } +func (e queryTimeoutError) Error() string { + return fmt.Sprintf("query timeout after %v", e.timeoutAfter) +} // ---------------------------------------------------------------------------- // Raw data value types. diff --git a/rules/ast/functions.go b/rules/ast/functions.go index 531a6f684a..dafa3c99bd 100644 --- a/rules/ast/functions.go +++ b/rules/ast/functions.go @@ -291,28 +291,22 @@ func dropCommonLabelsImpl(timestamp clientmodel.Timestamp, args []Node) interfac return vector } -// === round(vector VectorNode) Vector === +// === round(vector VectorNode, toNearest=1 Scalar) Vector === func roundImpl(timestamp clientmodel.Timestamp, args []Node) interface{} { - // round returns a number rounded to nearest integer. - // Ties are solved by rounding towards the even integer. - round := func(n float64) float64 { - if n-math.Floor(n) == 0.5 && int64(n)%2 == 0 { - n += math.Copysign(0.5, -n) - } - return math.Copysign(math.Floor(math.Abs(n)+0.5), n) - } - - places := float64(0) + // round returns a number rounded to toNearest. + // Ties are solved by rounding up. + toNearest := float64(1) if len(args) >= 2 { - places = float64(args[1].(ScalarNode).Eval(timestamp)) + toNearest = float64(args[1].(ScalarNode).Eval(timestamp)) } - pow := math.Pow(10, places) + // Invert as it seems to cause fewer floating point accuracy issues. + toNearestInverse := 1.0 / toNearest n := args[0].(VectorNode) vector := n.Eval(timestamp) for _, el := range vector { el.Metric.Delete(clientmodel.MetricNameLabel) - el.Value = clientmodel.SampleValue(round(pow*float64(el.Value)) / pow) + el.Value = clientmodel.SampleValue(math.Floor(float64(el.Value)*toNearestInverse+0.5) / toNearestInverse) } return vector } diff --git a/rules/rules_test.go b/rules/rules_test.go index 52177d190d..70ff6d8438 100644 --- a/rules/rules_test.go +++ b/rules/rules_test.go @@ -527,16 +527,16 @@ func TestExpressions(t *testing.T) { { // Round should correctly handle negative numbers. expr: `round(-1 * (0.004 * http_requests{group="production",job="api-server"}))`, output: []string{ - `{group="production", instance="0", job="api-server"} => -0 @[%v]`, + `{group="production", instance="0", job="api-server"} => 0 @[%v]`, `{group="production", instance="1", job="api-server"} => -1 @[%v]`, }, fullRanges: 0, intervalRanges: 2, }, - { // Round should round half to even. + { // Round should round half up. expr: `round(0.005 * http_requests{group="production",job="api-server"})`, output: []string{ - `{group="production", instance="0", job="api-server"} => 0 @[%v]`, + `{group="production", instance="0", job="api-server"} => 1 @[%v]`, `{group="production", instance="1", job="api-server"} => 1 @[%v]`, }, fullRanges: 0, @@ -551,7 +551,7 @@ func TestExpressions(t *testing.T) { fullRanges: 0, intervalRanges: 2, }, - { // Round should round half to even. + { expr: `round(1 + 0.005 * http_requests{group="production",job="api-server"})`, output: []string{ `{group="production", instance="0", job="api-server"} => 2 @[%v]`, @@ -563,23 +563,23 @@ func TestExpressions(t *testing.T) { { expr: `round(-1 * (1 + 0.005 * http_requests{group="production",job="api-server"}))`, output: []string{ - `{group="production", instance="0", job="api-server"} => -2 @[%v]`, + `{group="production", instance="0", job="api-server"} => -1 @[%v]`, `{group="production", instance="1", job="api-server"} => -2 @[%v]`, }, fullRanges: 0, intervalRanges: 2, }, - { // Round should accept a number of decimal places to round to. - expr: `round(0.0005 * http_requests{group="production",job="api-server"}, 1)`, + { // Round should accept the number to round nearest to. + expr: `round(0.0005 * http_requests{group="production",job="api-server"}, 0.1)`, output: []string{ - `{group="production", instance="0", job="api-server"} => 0 @[%v]`, + `{group="production", instance="0", job="api-server"} => 0.1 @[%v]`, `{group="production", instance="1", job="api-server"} => 0.1 @[%v]`, }, fullRanges: 0, intervalRanges: 2, }, - { // Round should look at last decimal place parity. - expr: `round(2.1 + 0.0005 * http_requests{group="production",job="api-server"}, 1)`, + { + expr: `round(2.1 + 0.0005 * http_requests{group="production",job="api-server"}, 0.1)`, output: []string{ `{group="production", instance="0", job="api-server"} => 2.2 @[%v]`, `{group="production", instance="1", job="api-server"} => 2.2 @[%v]`, @@ -588,16 +588,16 @@ func TestExpressions(t *testing.T) { intervalRanges: 2, }, { - expr: `round(5.2 + 0.0005 * http_requests{group="production",job="api-server"}, 1)`, + expr: `round(5.2 + 0.0005 * http_requests{group="production",job="api-server"}, 0.1)`, output: []string{ - `{group="production", instance="0", job="api-server"} => 5.2 @[%v]`, + `{group="production", instance="0", job="api-server"} => 5.3 @[%v]`, `{group="production", instance="1", job="api-server"} => 5.3 @[%v]`, }, fullRanges: 0, intervalRanges: 2, }, { // Round should work correctly with negative numbers and multiple decimal places. - expr: `round(-1 * (5.2 + 0.0005 * http_requests{group="production",job="api-server"}), 1)`, + expr: `round(-1 * (5.2 + 0.0005 * http_requests{group="production",job="api-server"}), 0.1)`, output: []string{ `{group="production", instance="0", job="api-server"} => -5.2 @[%v]`, `{group="production", instance="1", job="api-server"} => -5.3 @[%v]`, @@ -605,6 +605,24 @@ func TestExpressions(t *testing.T) { fullRanges: 0, intervalRanges: 2, }, + { // Round should work correctly with big toNearests. + expr: `round(0.025 * http_requests{group="production",job="api-server"}, 5)`, + output: []string{ + `{group="production", instance="0", job="api-server"} => 5 @[%v]`, + `{group="production", instance="1", job="api-server"} => 5 @[%v]`, + }, + fullRanges: 0, + intervalRanges: 2, + }, + { + expr: `round(0.045 * http_requests{group="production",job="api-server"}, 5)`, + output: []string{ + `{group="production", instance="0", job="api-server"} => 5 @[%v]`, + `{group="production", instance="1", job="api-server"} => 10 @[%v]`, + }, + fullRanges: 0, + intervalRanges: 2, + }, { expr: `avg_over_time(http_requests{group="production",job="api-server"}[1h])`, output: []string{