Use timestamp of a sample in deriv() to avoid FP issues (#2958)

With the squaring of the timestamp, we run into the
limitations of the 53bit mantissa for a 64bit float.

By subtracting away a timestamp of one of the samples (which is how the
intercept is used) we avoid this issue in practice as it's unlikely
that it is used over a very long time range.

Fixes #2674
This commit is contained in:
Brian Brazil 2017-08-07 17:15:38 +01:00 committed by GitHub
parent 1bf3b91ae0
commit 4c8173acac
2 changed files with 41 additions and 2 deletions

View File

@ -678,7 +678,10 @@ func funcDeriv(ev *evaluator, args Expressions) model.Value {
if len(samples.Values) < 2 {
continue
}
slope, _ := linearRegression(samples.Values, 0)
// We pass in an arbitrary timestamp that is near the values in use
// to avoid floating point accuracy issues, see
// https://github.com/prometheus/prometheus/issues/2674
slope, _ := linearRegression(samples.Values, samples.Values[0].Timestamp)
resultSample := &sample{
Metric: samples.Metric,
Value: slope,

View File

@ -13,7 +13,13 @@
package promql
import "testing"
import (
"context"
"testing"
"github.com/prometheus/common/model"
"github.com/prometheus/prometheus/storage/local"
)
func BenchmarkHoltWinters4Week5Min(b *testing.B) {
input := `
@ -71,3 +77,33 @@ eval instant at 1d changes(http_requests[1d])
bench := NewBenchmark(b, input)
bench.Run()
}
func TestDeriv(t *testing.T) {
// https://github.com/prometheus/prometheus/issues/2674#issuecomment-315439393
// This requires more precision than the usual test system offers,
// so we test it by hand.
storage, closer := local.NewTestStorage(t, 2)
defer closer.Close()
engine := NewEngine(storage, nil)
metric := model.Metric{model.MetricNameLabel: model.LabelValue("foo")}
storage.Append(&model.Sample{Metric: metric, Timestamp: 1493712816939, Value: 1.0})
storage.Append(&model.Sample{Metric: metric, Timestamp: 1493712846939, Value: 1.0})
storage.WaitForIndexing()
query, err := engine.NewInstantQuery("deriv(foo[30m])", 1493712846939)
if err != nil {
t.Fatalf("Error parsing query: %s", err)
}
result := query.Exec(context.Background())
if result.Err != nil {
t.Fatalf("Error running query: %s", result.Err)
}
vec, _ := result.Vector()
if vec.Len() != 1 {
t.Fatalf("Expected 1 result, got %d", vec.Len())
}
if vec[0].Value != 0.0 {
t.Fatalf("Expected 0.0 as value, got %f", vec[0].Value)
}
}