From b2651027fcb5b713e9d79ce52b0eee82674431a0 Mon Sep 17 00:00:00 2001 From: Julius Volz Date: Mon, 16 Mar 2015 14:23:03 +0100 Subject: [PATCH 1/3] Fix special value handling in division and modulo. This fixes https://github.com/prometheus/prometheus/issues/597 --- rules/ast/ast.go | 14 ++++---------- rules/rules_test.go | 40 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+), 10 deletions(-) diff --git a/rules/ast/ast.go b/rules/ast/ast.go index 0eeb3aae4..49d1e54e9 100644 --- a/rules/ast/ast.go +++ b/rules/ast/ast.go @@ -637,15 +637,12 @@ func evalScalarBinop(opType BinOpType, case Mul: return lhs * rhs case Div: - if rhs != 0 { - return lhs / rhs - } - return clientmodel.SampleValue(math.Inf(int(rhs))) + return lhs / rhs case Mod: if rhs != 0 { return clientmodel.SampleValue(int(lhs) % int(rhs)) } - return clientmodel.SampleValue(math.Inf(int(rhs))) + return clientmodel.SampleValue(math.NaN()) case EQ: if lhs == rhs { return 1 @@ -691,15 +688,12 @@ func evalVectorBinop(opType BinOpType, case Mul: return lhs * rhs, true case Div: - if rhs != 0 { - return lhs / rhs, true - } - return clientmodel.SampleValue(math.Inf(int(rhs))), true + return lhs / rhs, true case Mod: if rhs != 0 { return clientmodel.SampleValue(int(lhs) % int(rhs)), true } - return clientmodel.SampleValue(math.Inf(int(rhs))), true + return clientmodel.SampleValue(math.NaN()), true case EQ: if lhs == rhs { return lhs, true diff --git a/rules/rules_test.go b/rules/rules_test.go index 14fc46161..baecc50b7 100644 --- a/rules/rules_test.go +++ b/rules/rules_test.go @@ -1157,6 +1157,46 @@ func TestExpressions(t *testing.T) { expr: `999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999`, output: []string{`scalar: +Inf @[%v]`}, }, + { + expr: `1 / 0`, + output: []string{`scalar: +Inf @[%v]`}, + }, + { + expr: `-1 / 0`, + output: []string{`scalar: -Inf @[%v]`}, + }, + { + expr: `0 / 0`, + output: []string{`scalar: NaN @[%v]`}, + }, + { + expr: `1 % 0`, + output: []string{`scalar: NaN @[%v]`}, + }, + { + expr: `http_requests{group="canary", instance="0", job="api-server"} / 0`, + output: []string{ + `{group="canary", instance="0", job="api-server"} => +Inf @[%v]`, + }, + }, + { + expr: `-1 * http_requests{group="canary", instance="0", job="api-server"} / 0`, + output: []string{ + `{group="canary", instance="0", job="api-server"} => -Inf @[%v]`, + }, + }, + { + expr: `0 * http_requests{group="canary", instance="0", job="api-server"} / 0`, + output: []string{ + `{group="canary", instance="0", job="api-server"} => NaN @[%v]`, + }, + }, + { + expr: `0 * http_requests{group="canary", instance="0", job="api-server"} % 0`, + output: []string{ + `{group="canary", instance="0", job="api-server"} => NaN @[%v]`, + }, + }, } storage, closer := newTestStorage(t) From 624f27f4b6f10baa88aa7c78a3ddd35fd2cd8717 Mon Sep 17 00:00:00 2001 From: Fabian Reinartz Date: Mon, 16 Mar 2015 15:57:47 +0100 Subject: [PATCH 2/3] Add ln, log2, log10 and exp functions to the query language. --- rules/ast/functions.go | 68 ++++++++++++++++++++++++++++++ rules/rules_test.go | 93 +++++++++++++++++++++++++++++++++++++++++- 2 files changed, 160 insertions(+), 1 deletion(-) diff --git a/rules/ast/functions.go b/rules/ast/functions.go index 79908cd5b..ea95a1966 100644 --- a/rules/ast/functions.go +++ b/rules/ast/functions.go @@ -456,6 +456,50 @@ func ceilImpl(timestamp clientmodel.Timestamp, args []Node) interface{} { return vector } +// === exp(vector VectorNode) Vector === +func expImpl(timestamp clientmodel.Timestamp, args []Node) interface{} { + n := args[0].(VectorNode) + vector := n.Eval(timestamp) + for _, el := range vector { + el.Metric.Delete(clientmodel.MetricNameLabel) + el.Value = clientmodel.SampleValue(math.Exp(float64(el.Value))) + } + return vector +} + +// === ln(vector VectorNode) Vector === +func lnImpl(timestamp clientmodel.Timestamp, args []Node) interface{} { + n := args[0].(VectorNode) + vector := n.Eval(timestamp) + for _, el := range vector { + el.Metric.Delete(clientmodel.MetricNameLabel) + el.Value = clientmodel.SampleValue(math.Log(float64(el.Value))) + } + return vector +} + +// === log2(vector VectorNode) Vector === +func log2Impl(timestamp clientmodel.Timestamp, args []Node) interface{} { + n := args[0].(VectorNode) + vector := n.Eval(timestamp) + for _, el := range vector { + el.Metric.Delete(clientmodel.MetricNameLabel) + el.Value = clientmodel.SampleValue(math.Log2(float64(el.Value))) + } + return vector +} + +// === log10(vector VectorNode) Vector === +func log10Impl(timestamp clientmodel.Timestamp, args []Node) interface{} { + n := args[0].(VectorNode) + vector := n.Eval(timestamp) + for _, el := range vector { + el.Metric.Delete(clientmodel.MetricNameLabel) + el.Value = clientmodel.SampleValue(math.Log10(float64(el.Value))) + } + return vector +} + // === deriv(node MatrixNode) Vector === func derivImpl(timestamp clientmodel.Timestamp, args []Node) interface{} { matrixNode := args[0].(MatrixNode) @@ -598,6 +642,12 @@ var functions = map[string]*Function{ returnType: VectorType, callFn: dropCommonLabelsImpl, }, + "exp": { + name: "exp", + argTypes: []ExprType{VectorType}, + returnType: VectorType, + callFn: expImpl, + }, "floor": { name: "floor", argTypes: []ExprType{VectorType}, @@ -610,6 +660,24 @@ var functions = map[string]*Function{ returnType: VectorType, callFn: histogramQuantileImpl, }, + "ln": { + name: "ln", + argTypes: []ExprType{VectorType}, + returnType: VectorType, + callFn: lnImpl, + }, + "log10": { + name: "log10", + argTypes: []ExprType{VectorType}, + returnType: VectorType, + callFn: log10Impl, + }, + "log2": { + name: "log2", + argTypes: []ExprType{VectorType}, + returnType: VectorType, + callFn: log2Impl, + }, "max_over_time": { name: "max_over_time", argTypes: []ExprType{MatrixType}, diff --git a/rules/rules_test.go b/rules/rules_test.go index baecc50b7..5966c21ec 100644 --- a/rules/rules_test.go +++ b/rules/rules_test.go @@ -36,7 +36,7 @@ var ( testEvalTime = testStartTime.Add(testSampleInterval * 10) fixturesPath = "fixtures" - reSample = regexp.MustCompile(`^(.*)(?: \=\>|:) (\-?\d+\.?\d*e?\d*|[+-]Inf|NaN) \@\[(\d+)\]$`) + reSample = regexp.MustCompile(`^(.*)(?: \=\>|:) (\-?\d+\.?\d*(?:e-?\d*)?|[+-]Inf|NaN) \@\[(\d+)\]$`) minNormal = math.Float64frombits(0x0010000000000000) // The smallest positive normal value of type float64. ) @@ -1197,6 +1197,97 @@ func TestExpressions(t *testing.T) { `{group="canary", instance="0", job="api-server"} => NaN @[%v]`, }, }, + { + expr: `exp(vector_matching_a)`, + output: []string{ + `{l="x"} => 22026.465794806718 @[%v]`, + `{l="y"} => 485165195.4097903 @[%v]`, + }, + }, + { + expr: `exp(vector_matching_a - 10)`, + output: []string{ + `{l="y"} => 22026.465794806718 @[%v]`, + `{l="x"} => 1 @[%v]`, + }, + }, + { + expr: `exp(vector_matching_a - 20)`, + output: []string{ + `{l="x"} => 4.5399929762484854e-05 @[%v]`, + `{l="y"} => 1 @[%v]`, + }, + }, + { + expr: `ln(vector_matching_a)`, + output: []string{ + `{l="x"} => 2.302585092994046 @[%v]`, + `{l="y"} => 2.995732273553991 @[%v]`, + }, + }, + { + expr: `ln(vector_matching_a - 10)`, + output: []string{ + `{l="y"} => 2.302585092994046 @[%v]`, + `{l="x"} => -Inf @[%v]`, + }, + }, + { + expr: `ln(vector_matching_a - 20)`, + output: []string{ + `{l="y"} => -Inf @[%v]`, + `{l="x"} => NaN @[%v]`, + }, + }, + { + expr: `exp(ln(vector_matching_a))`, + output: []string{ + `{l="y"} => 20 @[%v]`, + `{l="x"} => 10 @[%v]`, + }, + }, + { + expr: `log2(vector_matching_a)`, + output: []string{ + `{l="x"} => 3.3219280948873626 @[%v]`, + `{l="y"} => 4.321928094887363 @[%v]`, + }, + }, + { + expr: `log2(vector_matching_a - 10)`, + output: []string{ + `{l="y"} => 3.3219280948873626 @[%v]`, + `{l="x"} => -Inf @[%v]`, + }, + }, + { + expr: `log2(vector_matching_a - 20)`, + output: []string{ + `{l="x"} => NaN @[%v]`, + `{l="y"} => -Inf @[%v]`, + }, + }, + { + expr: `log10(vector_matching_a)`, + output: []string{ + `{l="x"} => 1 @[%v]`, + `{l="y"} => 1.301029995663981 @[%v]`, + }, + }, + { + expr: `log10(vector_matching_a - 10)`, + output: []string{ + `{l="y"} => 1 @[%v]`, + `{l="x"} => -Inf @[%v]`, + }, + }, + { + expr: `log10(vector_matching_a - 20)`, + output: []string{ + `{l="x"} => NaN @[%v]`, + `{l="y"} => -Inf @[%v]`, + }, + }, } storage, closer := newTestStorage(t) From 1d8fc7d56f0d947efda5cf20c98dd1f535aa0ac8 Mon Sep 17 00:00:00 2001 From: beorn7 Date: Wed, 18 Mar 2015 19:09:07 +0100 Subject: [PATCH 3/3] Change minor things after code review. --- storage/local/crashrecovery.go | 4 +++- storage/local/persistence.go | 2 +- storage/local/series.go | 5 ++--- storage/local/storage.go | 4 ++-- 4 files changed, 8 insertions(+), 7 deletions(-) diff --git a/storage/local/crashrecovery.go b/storage/local/crashrecovery.go index 247d839da..f988bcb6c 100644 --- a/storage/local/crashrecovery.go +++ b/storage/local/crashrecovery.go @@ -225,7 +225,9 @@ func (p *persistence) sanitizeSeries( // Everything is consistent. We are good. return fp, true } - // If we are here, something's fishy. + // If we are here, we cannot be sure the series file is + // consistent with the checkpoint, so we have to take a closer + // look. if s.headChunkClosed { // This is the easy case as we don't have any chunks in // heads.db. Treat this series as a freshly unarchived diff --git a/storage/local/persistence.go b/storage/local/persistence.go index a3859e1bc..45bb63777 100644 --- a/storage/local/persistence.go +++ b/storage/local/persistence.go @@ -911,7 +911,7 @@ func (p *persistence) dropAndPersistChunks( // Otherwise, seek backwards to the beginning of its header and start // copying everything from there into a new file. Then append the chunks // to the new file. - _, err = f.Seek(-(chunkHeaderLen), os.SEEK_CUR) + _, err = f.Seek(-chunkHeaderLen, os.SEEK_CUR) if err != nil { return } diff --git a/storage/local/series.go b/storage/local/series.go index 3381d9132..3b67e424b 100644 --- a/storage/local/series.go +++ b/storage/local/series.go @@ -32,7 +32,6 @@ const ( chunkDescEvictionFactor = 10 headChunkTimeout = time.Hour // Close head chunk if not touched for that long. - ) // fingerprintSeriesPair pairs a fingerprint with a memorySeries pointer. @@ -168,7 +167,7 @@ type memorySeries struct { // appended. headChunkUsedByIterator bool // Whether the series is inconsistent with the last checkpoint in a way - // that would require a desk seek during crash recovery. + // that would require a disk seek during crash recovery. dirty bool } @@ -233,7 +232,7 @@ func (s *memorySeries) add(v *metric.SamplePair) int { } // maybeCloseHeadChunk closes the head chunk if it has not been touched for the -// duration of headChunkTimeout. It returns wether the head chunk was closed. +// duration of headChunkTimeout. It returns whether the head chunk was closed. // If the head chunk is already closed, the method is a no-op and returns false. // // The caller must have locked the fingerprint of the series. diff --git a/storage/local/storage.go b/storage/local/storage.go index db14aa840..b60685bfd 100644 --- a/storage/local/storage.go +++ b/storage/local/storage.go @@ -803,8 +803,8 @@ func (s *memorySeriesStorage) maintainMemorySeries( } // writeMemorySeries (re-)writes a memory series file. While doing so, it drops -// chunks older than beforeTime from both, the series file (if it exists) as -// well as from memory. The provided chunksToPersist are appended to the newly +// chunks older than beforeTime from both the series file (if it exists) as well +// as from memory. The provided chunksToPersist are appended to the newly // written series file. If no chunks need to be purged, but chunksToPersist is // not empty, those chunks are simply appended to the series file. If the series // contains no chunks after dropping old chunks, it is purged entirely. In that