diff --git a/app/vmselect/promql/aggr.go b/app/vmselect/promql/aggr.go index f53fd9f4b..4ddb96c60 100644 --- a/app/vmselect/promql/aggr.go +++ b/app/vmselect/promql/aggr.go @@ -43,7 +43,7 @@ var aggrFuncs = map[string]aggrFunc{ "bottomk_max": newAggrFuncRangeTopK(maxValue, true), "bottomk_avg": newAggrFuncRangeTopK(avgValue, true), "bottomk_median": newAggrFuncRangeTopK(medianValue, true), - "any": newAggrFunc(aggrFuncAny), + "any": aggrFuncAny, "outliersk": aggrFuncOutliersK, } @@ -77,6 +77,8 @@ func removeGroupTags(metricName *storage.MetricName, modifier *metricsql.Modifie metricName.RemoveTagsOn(modifier.Args) case "without": metricName.RemoveTagsIgnoring(modifier.Args) + // Reset metric group as Prometheus does on `aggr(...) without (...)` call. + metricName.ResetMetricGroup() default: logger.Panicf("BUG: unknown group modifier: %q", groupOp) } @@ -120,8 +122,20 @@ func aggrFuncExt(afe func(tss []*timeseries) []*timeseries, argOrig []*timeserie return rvs, nil } -func aggrFuncAny(tss []*timeseries) []*timeseries { - return tss[:1] +func aggrFuncAny(afa *aggrFuncArg) ([]*timeseries, error) { + args := afa.args + if err := expectTransformArgsNum(args, 1); err != nil { + return nil, err + } + afe := func(tss []*timeseries) []*timeseries { + return tss[:1] + } + limit := afa.ae.Limit + if limit > 1 { + // Only a single time series per group must be returned + limit = 1 + } + return aggrFuncExt(afe, args[0], &afa.ae.Modifier, limit, true) } func aggrFuncSum(tss []*timeseries) []*timeseries { diff --git a/app/vmselect/promql/exec_test.go b/app/vmselect/promql/exec_test.go index 3a2032c5f..432fef6b1 100644 --- a/app/vmselect/promql/exec_test.go +++ b/app/vmselect/promql/exec_test.go @@ -3835,6 +3835,10 @@ func TestExecSuccess(t *testing.T) { Values: []float64{10, 10, 10, 10, 10, 10}, Timestamps: timestampsExpected, } + r.MetricName.Tags = []storage.Tag{{ + Key: []byte("foo"), + Value: []byte("bar"), + }} resultExpected := []netstorage.Result{r} f(q, resultExpected) }) @@ -5627,6 +5631,7 @@ func TestExecError(t *testing.T) { f(`sum()`) f(`count_values()`) f(`quantile()`) + f(`any()`) f(`topk()`) f(`topk_min()`) f(`topk_max()`) diff --git a/docs/MetricsQL.md b/docs/MetricsQL.md index 0e905fc6b..ca69567fd 100644 --- a/docs/MetricsQL.md +++ b/docs/MetricsQL.md @@ -76,7 +76,7 @@ This functionality can be tried at [an editable Grafana dashboard](http://play-g - `median_over_time(m[d])` - calculates median values for `m` over `d` time window. Shorthand to `quantile_over_time(0.5, m[d])`. - `median(q)` - median aggregate. Shorthand to `quantile(0.5, q)`. - `limitk(k, q)` - limits the number of time series returned from `q` to `k`. -- `any(q) by (x)` - returns any time series from `q` for each group in `x`. Note that `any()` removes all the labels except of those listed in `by (x)`. +- `any(q) by (x)` - returns any time series from `q` for each group in `x`. Use `limitk(1, q)` if you need retaining all the labels from `q`. - `keep_last_value(q)` - fills missing data (gaps) in `q` with the previous non-empty value. - `keep_next_value(q)` - fills missing data (gaps) in `q` with the next non-empty value.