diff --git a/app/vmselect/prometheus/prometheus.go b/app/vmselect/prometheus/prometheus.go index abb5a50aa..9b83cfa91 100644 --- a/app/vmselect/prometheus/prometheus.go +++ b/app/vmselect/prometheus/prometheus.go @@ -574,12 +574,47 @@ func QueryRangeHandler(w http.ResponseWriter, r *http.Request) error { result = adjustLastPoints(result) } + // Remove NaN values as Prometheus does. + // See https://github.com/VictoriaMetrics/VictoriaMetrics/issues/153 + removeNaNValuesInplace(result) + w.Header().Set("Content-Type", "application/json") WriteQueryRangeResponse(w, result) queryRangeDuration.UpdateDuration(startTime) return nil } +func removeNaNValuesInplace(tss []netstorage.Result) { + for i := range tss { + ts := &tss[i] + hasNaNs := false + for _, v := range ts.Values { + if math.IsNaN(v) { + hasNaNs = true + break + } + } + if !hasNaNs { + // Fast path: nothing to remove. + continue + } + + // Slow path: remove NaNs. + srcTimestamps := ts.Timestamps + dstValues := ts.Values[:0] + dstTimestamps := ts.Timestamps[:0] + for j, v := range ts.Values { + if math.IsNaN(v) { + continue + } + dstValues = append(dstValues, v) + dstTimestamps = append(dstTimestamps, srcTimestamps[j]) + } + ts.Values = dstValues + ts.Timestamps = dstTimestamps + } +} + var queryRangeDuration = metrics.NewSummary(`vm_request_duration_seconds{path="/api/v1/query_range"}`) // adjustLastPoints substitutes the last point values with the previous diff --git a/app/vmselect/prometheus/prometheus_test.go b/app/vmselect/prometheus/prometheus_test.go index 3bdf42d8f..69c2c1237 100644 --- a/app/vmselect/prometheus/prometheus_test.go +++ b/app/vmselect/prometheus/prometheus_test.go @@ -2,11 +2,48 @@ package prometheus import ( "fmt" + "math" "net/http" "net/url" + "reflect" "testing" + + "github.com/VictoriaMetrics/VictoriaMetrics/app/vmselect/netstorage" ) +func TestRemoveNaNValuesInplace(t *testing.T) { + f := func(tss []netstorage.Result, tssExpected []netstorage.Result) { + t.Helper() + removeNaNValuesInplace(tss) + if !reflect.DeepEqual(tss, tssExpected) { + t.Fatalf("unexpected result; got %v; want %v", tss, tssExpected) + } + } + + nan := math.NaN() + + f(nil, nil) + f([]netstorage.Result{ + { + Timestamps: []int64{100, 200, 300}, + Values: []float64{1, 2, 3}, + }, + { + Timestamps: []int64{100, 200, 300, 400}, + Values: []float64{nan, nan, 3, nan}, + }, + }, []netstorage.Result{ + { + Timestamps: []int64{100, 200, 300}, + Values: []float64{1, 2, 3}, + }, + { + Timestamps: []int64{300}, + Values: []float64{3}, + }, + }) +} + func TestGetTimeSuccess(t *testing.T) { f := func(s string, timestampExpected int64) { t.Helper()