app/vmselect/promql: return at most one time series from absent_over_time() in the same way as Prometheus does

See https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2130
This commit is contained in:
Aliaksandr Valialkin 2022-02-12 15:45:06 +02:00
parent 70cfb87c0e
commit 989668beba
No known key found for this signature in database
GPG key ID: A72BEC6CD3D0DED1
3 changed files with 51 additions and 19 deletions

View file

@ -650,6 +650,9 @@ func evalRollupFuncWithoutAt(ec *EvalConfig, funcName string, rf rollupFunc, exp
if err != nil {
return nil, err
}
if funcName == "absent_over_time" {
rvs = aggregateAbsentOverTime(ec, re.Expr, rvs)
}
ec.updateIsPartialResponse(ecNew.IsPartialResponse)
if offset != 0 && len(rvs) > 0 {
// Make a copy of timestamps, since they may be used in other values.
@ -665,6 +668,27 @@ func evalRollupFuncWithoutAt(ec *EvalConfig, funcName string, rf rollupFunc, exp
return rvs, nil
}
// aggregateAbsentOverTime collapses tss to a single time series with 1 and nan values.
//
// Values for returned series are set to nan if at least a single tss series contains nan at that point.
// This means that tss contains a series with non-empty results at that point.
// This follows Prometheus logic - see https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2130
func aggregateAbsentOverTime(ec *EvalConfig, expr metricsql.Expr, tss []*timeseries) []*timeseries {
rvs := getAbsentTimeseries(ec, expr)
if len(tss) == 0 {
return rvs
}
for i := range tss[0].Values {
for _, ts := range tss {
if math.IsNaN(ts.Values[i]) {
rvs[0].Values[i] = nan
break
}
}
}
return rvs
}
func evalRollupFuncWithSubquery(ec *EvalConfig, funcName string, rf rollupFunc, expr metricsql.Expr, re *metricsql.RollupExpr) ([]*timeseries, error) {
// TODO: determine whether to use rollupResultCacheV here.
step := re.Step.Duration(ec.Step)
@ -688,10 +712,6 @@ func evalRollupFuncWithSubquery(ec *EvalConfig, funcName string, rf rollupFunc,
}
ec.updateIsPartialResponse(ecSQ.IsPartialResponse)
if len(tssSQ) == 0 {
if funcName == "absent_over_time" {
tss := evalNumber(ec, 1)
return tss, nil
}
return nil, nil
}
sharedTimestamps := getTimestamps(ec.Start, ec.End, ec.Step)
@ -842,14 +862,7 @@ func evalRollupFuncWithMetricExpr(ec *EvalConfig, funcName string, rf rollupFunc
rssLen := rss.Len()
if rssLen == 0 {
rss.Cancel()
var tss []*timeseries
if funcName == "absent_over_time" {
tss = getAbsentTimeseries(ec, me)
}
// Add missing points until ec.End.
// Do not cache the result, since missing points
// may be backfilled in the future.
tss = mergeTimeseries(tssCached, tss, start, ec)
tss := mergeTimeseries(tssCached, nil, start, ec)
return tss, nil
}

View file

@ -877,19 +877,37 @@ func TestExecSuccess(t *testing.T) {
resultExpected := []netstorage.Result{r}
f(q, resultExpected)
})
t.Run(`absent_over_time(scalar(multi-timeseries))`, func(t *testing.T) {
t.Run(`absent_over_time(non-nan)`, func(t *testing.T) {
t.Parallel()
q := `
absent_over_time(label_set(scalar(1 or label_set(2, "xx", "foo")), "yy", "foo"))`
absent_over_time(time())`
resultExpected := []netstorage.Result{}
f(q, resultExpected)
})
t.Run(`absent_over_time(nan)`, func(t *testing.T) {
t.Parallel()
q := `
absent_over_time((time() < 1500)[300s:])`
r := netstorage.Result{
MetricName: metricNameExpected,
Values: []float64{1, 1, 1, 1, 1, 1},
Values: []float64{nan, nan, nan, nan, 1, 1},
Timestamps: timestampsExpected,
}
resultExpected := []netstorage.Result{r}
f(q, resultExpected)
})
t.Run(`absent_over_time(multi-ts)`, func(t *testing.T) {
t.Parallel()
q := `
absent_over_time((
alias((time() < 1400)[200s:], "one"),
alias((time() > 1600)[200s:], "two"),
))`
r := netstorage.Result{
MetricName: metricNameExpected,
Values: []float64{nan, nan, nan, 1, nan, nan},
Timestamps: timestampsExpected,
}
r.MetricName.Tags = []storage.Tag{{
Key: []byte("yy"),
Value: []byte("foo"),
}}
resultExpected := []netstorage.Result{r}
f(q, resultExpected)
})

View file

@ -34,6 +34,7 @@ The following tip changes can be tested by building VictoriaMetrics components f
* FEATURE: add `__meta_kubernetes_endpointslice_label*` and `__meta_kubernetes_endpointslice_annotation*` labels for `role: endpointslice` targets in [kubernetes_sd_config](https://prometheus.io/docs/prometheus/latest/configuration/configuration/#kubernetes_sd_config) to be consistent with other `role` values. See [this issue](https://github.com/prometheus/prometheus/issues/10284).
* FEATURE: vmagent: support Prometheus-like durations in `-promscrape.config`. See [this comment](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/817#issuecomment-1033384766).
* BUGFIX: calculate [absent_over_time()](https://docs.victoriametrics.com/MetricsQL.html#absent_over_time) in the same way as Prometheus does. Previously it could return multiple time series instead of at most one time series like Prometheus does. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2130).
* BUGFIX: return proper results from `highestMax()` function at [Graphite render API](https://docs.victoriametrics.com/#graphite-render-api-usage). Previously it was incorrectly returning timeseries with min peaks instead of max peaks.
* BUGFIX: properly limit indexdb cache sizes. Previously they could exceed values set via `-memory.allowedPercent` and/or `-memory.allowedBytes` when `indexdb` contained many data parts. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2007).
* BUGFIX: [vmui](https://docs.victoriametrics.com/#vmui): fix a bug, which could break time range picker when editing `From` or `To` input fields. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2080).