diff --git a/docs/querying/examples.md b/docs/querying/examples.md index 957c2f5f9..8287ff6f6 100644 --- a/docs/querying/examples.md +++ b/docs/querying/examples.md @@ -95,3 +95,13 @@ Assuming this metric contains one time series per running instance, you could count the number of running instances per application like this: count by (app) (instance_cpu_time_ns) + +If we are exploring some metrics for their labels, to e.g. be able to aggregate +over some of them, we could use the following: + + limitk(10, app_foo_metric_bar) + +Alternatively, if we wanted the returned timeseries to be more evenly sampled, +we could use the following to get approximately 10% of them: + + limit_ratio(0.1, app_foo_metric_bar) diff --git a/docs/querying/operators.md b/docs/querying/operators.md index b92bdd94a..f5f217ff6 100644 --- a/docs/querying/operators.md +++ b/docs/querying/operators.md @@ -230,6 +230,8 @@ vector of fewer elements with aggregated values: * `bottomk` (smallest k elements by sample value) * `topk` (largest k elements by sample value) * `quantile` (calculate φ-quantile (0 ≤ φ ≤ 1) over dimensions) +* `limitk` (sample n elements) +* `limit_ratio` (sample elements with approximately 𝑟 ratio if `𝑟 > 0`, and the complement of such samples if `𝑟 = -(1.0 - 𝑟)`) These operators can either be used to aggregate over **all** label dimensions or preserve distinct dimensions by including a `without` or `by` clause. These @@ -249,8 +251,8 @@ all other labels are preserved in the output. `by` does the opposite and drops labels that are not listed in the `by` clause, even if their label values are identical between all elements of the vector. -`parameter` is only required for `count_values`, `quantile`, `topk` and -`bottomk`. +`parameter` is only required for `count_values`, `quantile`, `topk`, +`bottomk`, `limitk` and `limit_ratio`. `count_values` outputs one time series per unique sample value. Each series has an additional label. The name of that label is given by the aggregation @@ -261,11 +263,16 @@ time series is the number of times that sample value was present. the input samples, including the original labels, are returned in the result vector. `by` and `without` are only used to bucket the input vector. +`limitk` and `limit_ratio` also return a subset of the input samples, +including the original labels in the result vector, these are experimental +operators that must be enabled with `--enable-feature=promql-experimental-functions`. + `quantile` calculates the φ-quantile, the value that ranks at number φ*N among the N metric values of the dimensions aggregated over. φ is provided as the aggregation parameter. For example, `quantile(0.5, ...)` calculates the median, `quantile(0.95, ...)` the 95th percentile. For φ = `NaN`, `NaN` is returned. For φ < 0, `-Inf` is returned. For φ > 1, `+Inf` is returned. + Example: If the metric `http_requests_total` had time series that fan out by @@ -291,6 +298,33 @@ To get the 5 largest HTTP requests counts across all instances we could write: topk(5, http_requests_total) +To sample 10 timeseries, for example to inspect labels and their values, we +could write: + + limitk(10, http_requests_total) + +To deterministically sample approximately 10% of timeseries we could write: + + limit_ratio(0.1, http_requests_total) + +Given that `limit_ratio()` implements a deterministic sampling algorithm (based +on labels' hash), you can get the _complement_ of the above samples, i.e. +approximately 90%, but precisely those not returned by `limit_ratio(0.1, ...)` +with: + + limit_ratio(-0.9, http_requests_total) + +You can also use this feature to e.g. verify that `avg()` is a representative +aggregation for your samples' values, by checking that the difference between +averaging two samples' subsets is "small" when compared to the standard +deviation. + + abs( + avg(limit_ratio(0.5, http_requests_total)) + - + avg(limit_ratio(-0.5, http_requests_total)) + ) <= bool stddev(http_requests_total) + ## Binary operator precedence The following list shows the precedence of binary operators in Prometheus, from diff --git a/promql/bench_test.go b/promql/bench_test.go index fb3b6ac74..bd6728029 100644 --- a/promql/bench_test.go +++ b/promql/bench_test.go @@ -187,6 +187,21 @@ func rangeQueryCases() []benchCase { { expr: "topk(5, a_X)", }, + { + expr: "limitk(1, a_X)", + }, + { + expr: "limitk(5, a_X)", + }, + { + expr: "limit_ratio(0.1, a_X)", + }, + { + expr: "limit_ratio(0.5, a_X)", + }, + { + expr: "limit_ratio(-0.5, a_X)", + }, // Combinations. { expr: "rate(a_X[1m]) + rate(b_X[1m])", diff --git a/promql/engine.go b/promql/engine.go index a5377d164..bf19aac8b 100644 --- a/promql/engine.go +++ b/promql/engine.go @@ -1318,7 +1318,7 @@ func (ev *evaluator) rangeEvalAgg(aggExpr *parser.AggregateExpr, sortedGrouping index, ok := groupToResultIndex[groupingKey] // Add a new group if it doesn't exist. if !ok { - if aggExpr.Op != parser.TOPK && aggExpr.Op != parser.BOTTOMK { + if aggExpr.Op != parser.TOPK && aggExpr.Op != parser.BOTTOMK && aggExpr.Op != parser.LIMITK && aggExpr.Op != parser.LIMIT_RATIO { m := generateGroupingLabels(enh, series.Metric, aggExpr.Without, sortedGrouping) result = append(result, Series{Metric: m}) } @@ -1331,9 +1331,10 @@ func (ev *evaluator) rangeEvalAgg(aggExpr *parser.AggregateExpr, sortedGrouping groups := make([]groupedAggregation, groupCount) var k int + var ratio float64 var seriess map[uint64]Series switch aggExpr.Op { - case parser.TOPK, parser.BOTTOMK: + case parser.TOPK, parser.BOTTOMK, parser.LIMITK: if !convertibleToInt64(param) { ev.errorf("Scalar value %v overflows int64", param) } @@ -1345,6 +1346,23 @@ func (ev *evaluator) rangeEvalAgg(aggExpr *parser.AggregateExpr, sortedGrouping return nil, warnings } seriess = make(map[uint64]Series, len(inputMatrix)) // Output series by series hash. + case parser.LIMIT_RATIO: + if math.IsNaN(param) { + ev.errorf("Ratio value %v is NaN", param) + } + switch { + case param == 0: + return nil, warnings + case param < -1.0: + ratio = -1.0 + warnings.Add(annotations.NewInvalidRatioWarning(param, ratio, aggExpr.Param.PositionRange())) + case param > 1.0: + ratio = 1.0 + warnings.Add(annotations.NewInvalidRatioWarning(param, ratio, aggExpr.Param.PositionRange())) + default: + ratio = param + } + seriess = make(map[uint64]Series, len(inputMatrix)) // Output series by series hash. case parser.QUANTILE: if math.IsNaN(param) || param < 0 || param > 1 { warnings.Add(annotations.NewInvalidQuantileWarning(param, aggExpr.Param.PositionRange())) @@ -1362,11 +1380,12 @@ func (ev *evaluator) rangeEvalAgg(aggExpr *parser.AggregateExpr, sortedGrouping enh.Ts = ts var ws annotations.Annotations switch aggExpr.Op { - case parser.TOPK, parser.BOTTOMK: - result, ws = ev.aggregationK(aggExpr, k, inputMatrix, seriesToResult, groups, enh, seriess) + case parser.TOPK, parser.BOTTOMK, parser.LIMITK, parser.LIMIT_RATIO: + result, ws = ev.aggregationK(aggExpr, k, ratio, inputMatrix, seriesToResult, groups, enh, seriess) // If this could be an instant query, shortcut so as not to change sort order. if ev.endTimestamp == ev.startTimestamp { - return result, ws + warnings.Merge(ws) + return result, warnings } default: ws = ev.aggregation(aggExpr, param, inputMatrix, result, seriesToResult, groups, enh) @@ -1381,7 +1400,7 @@ func (ev *evaluator) rangeEvalAgg(aggExpr *parser.AggregateExpr, sortedGrouping // Assemble the output matrix. By the time we get here we know we don't have too many samples. switch aggExpr.Op { - case parser.TOPK, parser.BOTTOMK: + case parser.TOPK, parser.BOTTOMK, parser.LIMITK, parser.LIMIT_RATIO: result = make(Matrix, 0, len(seriess)) for _, ss := range seriess { result = append(result, ss) @@ -2754,14 +2773,15 @@ func vectorElemBinop(op parser.ItemType, lhs, rhs float64, hlhs, hrhs *histogram } type groupedAggregation struct { - seen bool // Was this output groups seen in the input at this timestamp. - hasFloat bool // Has at least 1 float64 sample aggregated. - hasHistogram bool // Has at least 1 histogram sample aggregated. - floatValue float64 - histogramValue *histogram.FloatHistogram - floatMean float64 // Mean, or "compensating value" for Kahan summation. - groupCount int - heap vectorByValueHeap + seen bool // Was this output groups seen in the input at this timestamp. + hasFloat bool // Has at least 1 float64 sample aggregated. + hasHistogram bool // Has at least 1 histogram sample aggregated. + floatValue float64 + histogramValue *histogram.FloatHistogram + floatMean float64 // Mean, or "compensating value" for Kahan summation. + groupCount int + groupAggrComplete bool // Used by LIMITK to short-cut series loop when we've reached K elem on every group + heap vectorByValueHeap } // aggregation evaluates sum, avg, count, stdvar, stddev or quantile at one timestep on inputMatrix. @@ -2958,19 +2978,22 @@ func (ev *evaluator) aggregation(e *parser.AggregateExpr, q float64, inputMatrix return annos } -// aggregationK evaluates topk or bottomk at one timestep on inputMatrix. +// aggregationK evaluates topk, bottomk, limitk, or limit_ratio at one timestep on inputMatrix. // Output that has the same labels as the input, but just k of them per group. // seriesToResult maps inputMatrix indexes to groups indexes. -// For an instant query, returns a Matrix in descending order for topk or ascending for bottomk. +// For an instant query, returns a Matrix in descending order for topk or ascending for bottomk, or without any order for limitk / limit_ratio. // For a range query, aggregates output in the seriess map. -func (ev *evaluator) aggregationK(e *parser.AggregateExpr, k int, inputMatrix Matrix, seriesToResult []int, groups []groupedAggregation, enh *EvalNodeHelper, seriess map[uint64]Series) (Matrix, annotations.Annotations) { +func (ev *evaluator) aggregationK(e *parser.AggregateExpr, k int, r float64, inputMatrix Matrix, seriesToResult []int, groups []groupedAggregation, enh *EvalNodeHelper, seriess map[uint64]Series) (Matrix, annotations.Annotations) { op := e.Op var s Sample var annos annotations.Annotations + // Used to short-cut the loop for LIMITK if we already collected k elements for every group + groupsRemaining := len(groups) for i := range groups { groups[i].seen = false } +seriesLoop: for si := range inputMatrix { f, _, ok := ev.nextValues(enh.Ts, &inputMatrix[si]) if !ok { @@ -2981,11 +3004,23 @@ func (ev *evaluator) aggregationK(e *parser.AggregateExpr, k int, inputMatrix Ma group := &groups[seriesToResult[si]] // Initialize this group if it's the first time we've seen it. if !group.seen { - *group = groupedAggregation{ - seen: true, - heap: make(vectorByValueHeap, 1, k), + // LIMIT_RATIO is a special case, as we may not add this very sample to the heap, + // while we also don't know the final size of it. + if op == parser.LIMIT_RATIO { + *group = groupedAggregation{ + seen: true, + heap: make(vectorByValueHeap, 0), + } + if ratiosampler.AddRatioSample(r, &s) { + heap.Push(&group.heap, &s) + } + } else { + *group = groupedAggregation{ + seen: true, + heap: make(vectorByValueHeap, 1, k), + } + group.heap[0] = s } - group.heap[0] = s continue } @@ -3016,6 +3051,26 @@ func (ev *evaluator) aggregationK(e *parser.AggregateExpr, k int, inputMatrix Ma } } + case parser.LIMITK: + if len(group.heap) < k { + heap.Push(&group.heap, &s) + } + // LIMITK optimization: early break if we've added K elem to _every_ group, + // especially useful for large timeseries where the user is exploring labels via e.g. + // limitk(10, my_metric) + if !group.groupAggrComplete && len(group.heap) == k { + group.groupAggrComplete = true + groupsRemaining-- + if groupsRemaining == 0 { + break seriesLoop + } + } + + case parser.LIMIT_RATIO: + if ratiosampler.AddRatioSample(r, &s) { + heap.Push(&group.heap, &s) + } + default: panic(fmt.Errorf("expected aggregation operator but got %q", op)) } @@ -3065,6 +3120,11 @@ func (ev *evaluator) aggregationK(e *parser.AggregateExpr, k int, inputMatrix Ma for _, v := range aggr.heap { add(v.Metric, v.F) } + + case parser.LIMITK, parser.LIMIT_RATIO: + for _, v := range aggr.heap { + add(v.Metric, v.F) + } } } @@ -3419,6 +3479,56 @@ func makeInt64Pointer(val int64) *int64 { return valp } +// Add RatioSampler interface to allow unit-testing (previously: Randomizer). +type RatioSampler interface { + // Return this sample "offset" between [0.0, 1.0] + sampleOffset(ts int64, sample *Sample) float64 + AddRatioSample(r float64, sample *Sample) bool +} + +// Use Hash(labels.String()) / maxUint64 as a "deterministic" +// value in [0.0, 1.0]. +type HashRatioSampler struct{} + +var ratiosampler RatioSampler = NewHashRatioSampler() + +func NewHashRatioSampler() *HashRatioSampler { + return &HashRatioSampler{} +} + +func (s *HashRatioSampler) sampleOffset(ts int64, sample *Sample) float64 { + const ( + float64MaxUint64 = float64(math.MaxUint64) + ) + return float64(sample.Metric.Hash()) / float64MaxUint64 +} + +func (s *HashRatioSampler) AddRatioSample(ratioLimit float64, sample *Sample) bool { + // If ratioLimit >= 0: add sample if sampleOffset is lesser than ratioLimit + // + // 0.0 ratioLimit 1.0 + // [---------|--------------------------] + // [#########...........................] + // + // e.g.: + // sampleOffset==0.3 && ratioLimit==0.4 + // 0.3 < 0.4 ? --> add sample + // + // Else if ratioLimit < 0: add sample if rand() return the "complement" of ratioLimit>=0 case + // (loosely similar behavior to negative array index in other programming languages) + // + // 0.0 1+ratioLimit 1.0 + // [---------|--------------------------] + // [.........###########################] + // + // e.g.: + // sampleOffset==0.3 && ratioLimit==-0.6 + // 0.3 >= 0.4 ? --> don't add sample + sampleOffset := s.sampleOffset(sample.T, sample) + return (ratioLimit >= 0 && sampleOffset < ratioLimit) || + (ratioLimit < 0 && sampleOffset >= (1.0+ratioLimit)) +} + type histogramStatsSeries struct { storage.Series } diff --git a/promql/engine_test.go b/promql/engine_test.go index b144c1174..99f3fc5bd 100644 --- a/promql/engine_test.go +++ b/promql/engine_test.go @@ -49,6 +49,8 @@ const ( ) func TestMain(m *testing.M) { + // Enable experimental functions testing + parser.EnableExperimentalFunctions = true goleak.VerifyTestMain(m) } diff --git a/promql/parser/generated_parser.y b/promql/parser/generated_parser.y index b39c1150a..d84acc37c 100644 --- a/promql/parser/generated_parser.y +++ b/promql/parser/generated_parser.y @@ -126,6 +126,8 @@ STDDEV STDVAR SUM TOPK +LIMITK +LIMIT_RATIO %token aggregatorsEnd // Keywords. @@ -609,7 +611,7 @@ metric : metric_identifier label_set ; -metric_identifier: AVG | BOTTOMK | BY | COUNT | COUNT_VALUES | GROUP | IDENTIFIER | LAND | LOR | LUNLESS | MAX | METRIC_IDENTIFIER | MIN | OFFSET | QUANTILE | STDDEV | STDVAR | SUM | TOPK | WITHOUT | START | END; +metric_identifier: AVG | BOTTOMK | BY | COUNT | COUNT_VALUES | GROUP | IDENTIFIER | LAND | LOR | LUNLESS | MAX | METRIC_IDENTIFIER | MIN | OFFSET | QUANTILE | STDDEV | STDVAR | SUM | TOPK | WITHOUT | START | END | LIMITK | LIMIT_RATIO; label_set : LEFT_BRACE label_set_list RIGHT_BRACE { $$ = labels.New($2...) } @@ -851,10 +853,10 @@ bucket_set_list : bucket_set_list SPACE number * Keyword lists. */ -aggregate_op : AVG | BOTTOMK | COUNT | COUNT_VALUES | GROUP | MAX | MIN | QUANTILE | STDDEV | STDVAR | SUM | TOPK ; +aggregate_op : AVG | BOTTOMK | COUNT | COUNT_VALUES | GROUP | MAX | MIN | QUANTILE | STDDEV | STDVAR | SUM | TOPK | LIMITK | LIMIT_RATIO; // Inside of grouping options label names can be recognized as keywords by the lexer. This is a list of keywords that could also be a label name. -maybe_label : AVG | BOOL | BOTTOMK | BY | COUNT | COUNT_VALUES | GROUP | GROUP_LEFT | GROUP_RIGHT | IDENTIFIER | IGNORING | LAND | LOR | LUNLESS | MAX | METRIC_IDENTIFIER | MIN | OFFSET | ON | QUANTILE | STDDEV | STDVAR | SUM | TOPK | START | END | ATAN2; +maybe_label : AVG | BOOL | BOTTOMK | BY | COUNT | COUNT_VALUES | GROUP | GROUP_LEFT | GROUP_RIGHT | IDENTIFIER | IGNORING | LAND | LOR | LUNLESS | MAX | METRIC_IDENTIFIER | MIN | OFFSET | ON | QUANTILE | STDDEV | STDVAR | SUM | TOPK | START | END | ATAN2 | LIMITK | LIMIT_RATIO; unary_op : ADD | SUB; diff --git a/promql/parser/generated_parser.y.go b/promql/parser/generated_parser.y.go index d9a312a13..07899c0a0 100644 --- a/promql/parser/generated_parser.y.go +++ b/promql/parser/generated_parser.y.go @@ -103,27 +103,29 @@ const STDDEV = 57411 const STDVAR = 57412 const SUM = 57413 const TOPK = 57414 -const aggregatorsEnd = 57415 -const keywordsStart = 57416 -const BOOL = 57417 -const BY = 57418 -const GROUP_LEFT = 57419 -const GROUP_RIGHT = 57420 -const IGNORING = 57421 -const OFFSET = 57422 -const ON = 57423 -const WITHOUT = 57424 -const keywordsEnd = 57425 -const preprocessorStart = 57426 -const START = 57427 -const END = 57428 -const preprocessorEnd = 57429 -const startSymbolsStart = 57430 -const START_METRIC = 57431 -const START_SERIES_DESCRIPTION = 57432 -const START_EXPRESSION = 57433 -const START_METRIC_SELECTOR = 57434 -const startSymbolsEnd = 57435 +const LIMITK = 57415 +const LIMIT_RATIO = 57416 +const aggregatorsEnd = 57417 +const keywordsStart = 57418 +const BOOL = 57419 +const BY = 57420 +const GROUP_LEFT = 57421 +const GROUP_RIGHT = 57422 +const IGNORING = 57423 +const OFFSET = 57424 +const ON = 57425 +const WITHOUT = 57426 +const keywordsEnd = 57427 +const preprocessorStart = 57428 +const START = 57429 +const END = 57430 +const preprocessorEnd = 57431 +const startSymbolsStart = 57432 +const START_METRIC = 57433 +const START_SERIES_DESCRIPTION = 57434 +const START_EXPRESSION = 57435 +const START_METRIC_SELECTOR = 57436 +const startSymbolsEnd = 57437 var yyToknames = [...]string{ "$end", @@ -198,6 +200,8 @@ var yyToknames = [...]string{ "STDVAR", "SUM", "TOPK", + "LIMITK", + "LIMIT_RATIO", "aggregatorsEnd", "keywordsStart", "BOOL", @@ -231,279 +235,298 @@ var yyExca = [...]int16{ -1, 1, 1, -1, -2, 0, - -1, 35, - 1, 134, - 10, 134, - 24, 134, + -1, 37, + 1, 136, + 10, 136, + 24, 136, -2, 0, - -1, 58, - 2, 172, - 15, 172, - 76, 172, - 82, 172, - -2, 100, - -1, 59, - 2, 173, - 15, 173, - 76, 173, - 82, 173, - -2, 101, -1, 60, 2, 174, 15, 174, - 76, 174, - 82, 174, - -2, 103, + 78, 174, + 84, 174, + -2, 100, -1, 61, 2, 175, 15, 175, - 76, 175, - 82, 175, - -2, 104, + 78, 175, + 84, 175, + -2, 101, -1, 62, 2, 176, 15, 176, - 76, 176, - 82, 176, - -2, 105, + 78, 176, + 84, 176, + -2, 103, -1, 63, 2, 177, 15, 177, - 76, 177, - 82, 177, - -2, 110, + 78, 177, + 84, 177, + -2, 104, -1, 64, 2, 178, 15, 178, - 76, 178, - 82, 178, - -2, 112, + 78, 178, + 84, 178, + -2, 105, -1, 65, 2, 179, 15, 179, - 76, 179, - 82, 179, - -2, 114, + 78, 179, + 84, 179, + -2, 110, -1, 66, 2, 180, 15, 180, - 76, 180, - 82, 180, - -2, 115, + 78, 180, + 84, 180, + -2, 112, -1, 67, 2, 181, 15, 181, - 76, 181, - 82, 181, - -2, 116, + 78, 181, + 84, 181, + -2, 114, -1, 68, 2, 182, 15, 182, - 76, 182, - 82, 182, - -2, 117, + 78, 182, + 84, 182, + -2, 115, -1, 69, 2, 183, 15, 183, - 76, 183, - 82, 183, + 78, 183, + 84, 183, + -2, 116, + -1, 70, + 2, 184, + 15, 184, + 78, 184, + 84, 184, + -2, 117, + -1, 71, + 2, 185, + 15, 185, + 78, 185, + 84, 185, -2, 118, - -1, 195, - 12, 231, - 13, 231, - 18, 231, - 19, 231, - 25, 231, - 40, 231, - 46, 231, - 47, 231, - 50, 231, - 56, 231, - 61, 231, - 62, 231, - 63, 231, - 64, 231, - 65, 231, - 66, 231, - 67, 231, - 68, 231, - 69, 231, - 70, 231, - 71, 231, - 72, 231, - 76, 231, - 80, 231, - 82, 231, - 85, 231, - 86, 231, + -1, 72, + 2, 186, + 15, 186, + 78, 186, + 84, 186, + -2, 122, + -1, 73, + 2, 187, + 15, 187, + 78, 187, + 84, 187, + -2, 123, + -1, 199, + 12, 237, + 13, 237, + 18, 237, + 19, 237, + 25, 237, + 40, 237, + 46, 237, + 47, 237, + 50, 237, + 56, 237, + 61, 237, + 62, 237, + 63, 237, + 64, 237, + 65, 237, + 66, 237, + 67, 237, + 68, 237, + 69, 237, + 70, 237, + 71, 237, + 72, 237, + 73, 237, + 74, 237, + 78, 237, + 82, 237, + 84, 237, + 87, 237, + 88, 237, -2, 0, - -1, 196, - 12, 231, - 13, 231, - 18, 231, - 19, 231, - 25, 231, - 40, 231, - 46, 231, - 47, 231, - 50, 231, - 56, 231, - 61, 231, - 62, 231, - 63, 231, - 64, 231, - 65, 231, - 66, 231, - 67, 231, - 68, 231, - 69, 231, - 70, 231, - 71, 231, - 72, 231, - 76, 231, - 80, 231, - 82, 231, - 85, 231, - 86, 231, + -1, 200, + 12, 237, + 13, 237, + 18, 237, + 19, 237, + 25, 237, + 40, 237, + 46, 237, + 47, 237, + 50, 237, + 56, 237, + 61, 237, + 62, 237, + 63, 237, + 64, 237, + 65, 237, + 66, 237, + 67, 237, + 68, 237, + 69, 237, + 70, 237, + 71, 237, + 72, 237, + 73, 237, + 74, 237, + 78, 237, + 82, 237, + 84, 237, + 87, 237, + 88, 237, -2, 0, - -1, 217, - 21, 229, + -1, 221, + 21, 235, -2, 0, - -1, 286, - 21, 230, + -1, 292, + 21, 236, -2, 0, } const yyPrivate = 57344 -const yyLast = 778 +const yyLast = 793 var yyAct = [...]int16{ - 151, 324, 322, 268, 329, 148, 221, 37, 187, 144, - 282, 281, 152, 113, 77, 173, 104, 102, 101, 6, - 223, 193, 105, 194, 195, 196, 128, 262, 260, 155, - 233, 103, 342, 293, 100, 319, 239, 116, 146, 318, - 315, 263, 156, 123, 106, 147, 284, 114, 295, 116, - 156, 341, 175, 259, 340, 253, 57, 264, 157, 114, - 117, 108, 313, 109, 235, 236, 157, 112, 237, 107, - 323, 174, 117, 175, 155, 96, 250, 99, 293, 224, - 226, 228, 229, 230, 238, 240, 243, 244, 245, 246, - 247, 177, 145, 225, 227, 231, 232, 234, 241, 242, - 98, 176, 178, 248, 249, 104, 2, 3, 4, 5, - 158, 105, 177, 110, 168, 162, 165, 302, 150, 160, - 191, 161, 176, 178, 189, 155, 213, 343, 106, 330, - 72, 179, 192, 33, 181, 155, 190, 197, 198, 199, - 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, - 210, 211, 185, 301, 258, 212, 156, 214, 215, 188, - 256, 183, 290, 191, 252, 164, 155, 289, 300, 218, - 223, 79, 157, 217, 7, 299, 312, 257, 163, 251, - 233, 78, 288, 255, 182, 254, 239, 156, 216, 180, - 220, 124, 172, 120, 147, 311, 314, 171, 119, 261, - 287, 153, 154, 157, 279, 280, 79, 147, 283, 310, - 170, 118, 159, 10, 235, 236, 78, 309, 237, 147, - 308, 307, 306, 74, 76, 305, 250, 286, 304, 224, - 226, 228, 229, 230, 238, 240, 243, 244, 245, 246, - 247, 303, 81, 225, 227, 231, 232, 234, 241, 242, - 48, 34, 1, 248, 249, 122, 73, 121, 285, 47, - 291, 292, 294, 56, 296, 8, 9, 9, 46, 35, - 45, 44, 297, 298, 127, 129, 130, 131, 132, 133, - 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, - 43, 42, 41, 125, 166, 40, 316, 317, 126, 39, - 38, 49, 186, 321, 338, 265, 326, 327, 328, 80, - 325, 184, 219, 332, 331, 334, 333, 75, 115, 149, - 335, 336, 100, 51, 72, 337, 53, 55, 222, 22, - 52, 339, 50, 167, 111, 0, 54, 0, 0, 0, - 0, 344, 0, 0, 0, 0, 0, 0, 82, 84, - 0, 70, 0, 0, 0, 0, 0, 18, 19, 93, - 94, 20, 0, 96, 97, 99, 83, 71, 0, 0, - 0, 0, 58, 59, 60, 61, 62, 63, 64, 65, - 66, 67, 68, 69, 0, 0, 0, 13, 98, 0, - 0, 24, 0, 30, 0, 0, 31, 32, 36, 100, - 51, 72, 0, 53, 267, 0, 22, 52, 0, 0, - 0, 266, 0, 54, 0, 270, 271, 269, 276, 278, - 275, 277, 272, 273, 274, 0, 84, 0, 70, 0, - 0, 0, 0, 0, 18, 19, 93, 94, 20, 0, - 96, 0, 99, 83, 71, 0, 0, 0, 0, 58, - 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, - 69, 0, 0, 0, 13, 98, 0, 0, 24, 0, - 30, 0, 0, 31, 32, 51, 72, 0, 53, 320, - 0, 22, 52, 0, 0, 0, 0, 0, 54, 0, - 270, 271, 269, 276, 278, 275, 277, 272, 273, 274, - 0, 0, 0, 70, 0, 0, 17, 72, 0, 18, - 19, 0, 22, 20, 0, 0, 0, 0, 0, 71, - 0, 0, 0, 0, 58, 59, 60, 61, 62, 63, - 64, 65, 66, 67, 68, 69, 0, 0, 0, 13, - 18, 19, 0, 24, 20, 30, 0, 0, 31, 32, - 0, 0, 0, 0, 0, 11, 12, 14, 15, 16, - 21, 23, 25, 26, 27, 28, 29, 17, 33, 0, - 13, 0, 0, 22, 24, 0, 30, 0, 0, 31, - 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 155, 330, 328, 274, 335, 152, 225, 39, 191, 148, + 288, 287, 156, 117, 81, 177, 227, 106, 105, 6, + 154, 108, 107, 197, 132, 198, 237, 109, 199, 200, + 159, 59, 243, 325, 324, 110, 321, 159, 189, 268, + 348, 301, 265, 127, 159, 192, 349, 264, 290, 195, + 176, 160, 159, 269, 308, 175, 319, 195, 160, 347, + 239, 240, 346, 112, 241, 113, 299, 161, 174, 270, + 263, 111, 254, 160, 161, 228, 230, 232, 233, 234, + 242, 244, 247, 248, 249, 250, 251, 255, 256, 161, + 114, 229, 231, 235, 236, 238, 245, 246, 108, 266, + 258, 252, 253, 329, 109, 157, 158, 159, 2, 3, + 4, 5, 307, 160, 162, 257, 262, 299, 172, 166, + 169, 217, 104, 164, 110, 165, 150, 306, 193, 161, + 178, 104, 179, 151, 305, 183, 196, 179, 185, 261, + 194, 201, 202, 203, 204, 205, 206, 207, 208, 209, + 210, 211, 212, 213, 214, 215, 128, 227, 88, 216, + 120, 218, 219, 100, 336, 103, 168, 237, 97, 98, + 118, 181, 100, 243, 103, 87, 181, 224, 259, 167, + 149, 180, 182, 121, 187, 76, 180, 182, 120, 260, + 102, 35, 124, 7, 10, 296, 151, 123, 118, 102, + 295, 239, 240, 267, 78, 241, 116, 186, 285, 286, + 122, 121, 289, 254, 318, 294, 228, 230, 232, 233, + 234, 242, 244, 247, 248, 249, 250, 251, 255, 256, + 317, 292, 229, 231, 235, 236, 238, 245, 246, 316, + 315, 314, 252, 253, 133, 134, 135, 136, 137, 138, + 139, 140, 141, 142, 143, 144, 145, 146, 147, 313, + 312, 311, 310, 309, 320, 293, 297, 298, 300, 273, + 302, 222, 151, 8, 85, 221, 272, 37, 303, 304, + 276, 277, 275, 282, 284, 281, 283, 278, 279, 280, + 220, 163, 126, 50, 125, 36, 1, 291, 151, 77, + 83, 49, 322, 323, 48, 83, 47, 104, 46, 327, + 82, 131, 332, 333, 334, 82, 331, 45, 184, 338, + 337, 340, 339, 80, 44, 43, 341, 342, 129, 53, + 76, 343, 55, 86, 88, 22, 54, 345, 170, 171, + 42, 130, 56, 41, 97, 98, 40, 350, 100, 101, + 103, 87, 58, 51, 190, 9, 9, 74, 344, 271, + 84, 188, 223, 18, 19, 79, 119, 20, 153, 57, + 226, 52, 115, 75, 0, 102, 0, 0, 60, 61, + 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, + 72, 73, 0, 0, 0, 13, 0, 0, 0, 24, + 0, 30, 0, 0, 31, 32, 38, 0, 53, 76, + 0, 55, 326, 0, 22, 54, 0, 0, 0, 0, + 0, 56, 0, 276, 277, 275, 282, 284, 281, 283, + 278, 279, 280, 0, 0, 0, 74, 0, 0, 0, + 0, 0, 18, 19, 0, 0, 20, 0, 0, 0, + 0, 0, 75, 0, 0, 0, 0, 60, 61, 62, + 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, + 73, 0, 0, 0, 13, 0, 0, 0, 24, 0, + 30, 0, 0, 31, 32, 53, 76, 0, 55, 0, + 0, 22, 54, 0, 0, 0, 0, 0, 56, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 18, 19, 0, 0, 20, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 11, 12, 14, 15, - 16, 21, 23, 25, 26, 27, 28, 29, 100, 0, - 0, 13, 0, 0, 0, 24, 169, 30, 0, 0, - 31, 32, 0, 0, 0, 0, 0, 100, 0, 0, - 0, 0, 0, 0, 82, 84, 85, 0, 86, 87, - 88, 89, 90, 91, 92, 93, 94, 95, 0, 96, - 97, 99, 83, 82, 84, 85, 0, 86, 87, 88, - 89, 90, 91, 92, 93, 94, 95, 0, 96, 97, - 99, 83, 100, 0, 98, 0, 0, 0, 0, 0, + 0, 0, 0, 74, 0, 17, 76, 0, 0, 18, + 19, 22, 0, 20, 0, 0, 0, 0, 0, 75, + 0, 0, 0, 0, 60, 61, 62, 63, 64, 65, + 66, 67, 68, 69, 70, 71, 72, 73, 0, 18, + 19, 13, 0, 20, 0, 24, 0, 30, 0, 0, + 31, 32, 0, 0, 11, 12, 14, 15, 16, 21, + 23, 25, 26, 27, 28, 29, 33, 34, 17, 35, + 0, 13, 0, 0, 22, 24, 0, 30, 0, 0, + 31, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 100, 0, 98, 0, 0, 0, 0, 82, 84, - 85, 0, 86, 87, 88, 0, 90, 91, 92, 93, - 94, 95, 0, 96, 97, 99, 83, 82, 84, 85, - 0, 86, 87, 0, 0, 90, 91, 0, 93, 94, - 95, 0, 96, 97, 99, 83, 0, 0, 98, 0, + 0, 0, 18, 19, 0, 0, 20, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 11, 12, 14, + 15, 16, 21, 23, 25, 26, 27, 28, 29, 33, + 34, 104, 0, 0, 13, 0, 0, 0, 24, 173, + 30, 0, 0, 31, 32, 0, 0, 0, 0, 0, + 104, 0, 0, 0, 0, 0, 0, 86, 88, 89, + 0, 90, 91, 92, 93, 94, 95, 96, 97, 98, + 99, 0, 100, 101, 103, 87, 86, 88, 89, 0, + 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, + 0, 100, 101, 103, 87, 104, 0, 0, 0, 102, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 98, + 0, 0, 0, 0, 104, 0, 0, 0, 102, 0, + 0, 86, 88, 89, 0, 90, 91, 92, 0, 94, + 95, 96, 97, 98, 99, 0, 100, 101, 103, 87, + 86, 88, 89, 0, 90, 91, 0, 0, 94, 95, + 0, 97, 98, 99, 0, 100, 101, 103, 87, 0, + 0, 0, 0, 102, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 102, } var yyPact = [...]int16{ - 17, 164, 555, 555, 388, 494, -1000, -1000, -1000, 120, + 17, 183, 566, 566, 396, 503, -1000, -1000, -1000, 178, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, - -1000, -1000, -1000, 204, -1000, 240, -1000, 633, -1000, -1000, + -1000, -1000, -1000, -1000, -1000, 303, -1000, 272, -1000, 646, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, - 29, 113, -1000, 463, -1000, 463, 117, -1000, -1000, -1000, + -1000, -1000, 20, 109, -1000, 473, -1000, 473, 172, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, - -1000, -1000, 47, -1000, -1000, 191, -1000, -1000, 253, -1000, - 19, -1000, -49, -49, -49, -49, -49, -49, -49, -49, - -49, -49, -49, -49, -49, -49, -49, -49, 36, 116, - 210, 113, -60, -1000, 163, 163, 311, -1000, 614, 20, - -1000, 190, -1000, -1000, 69, 48, -1000, -1000, -1000, 169, - -1000, 159, -1000, 147, 463, -1000, -58, -53, -1000, 463, - 463, 463, 463, 463, 463, 463, 463, 463, 463, 463, - 463, 463, 463, 463, -1000, 185, -1000, -1000, -1000, 111, - -1000, -1000, -1000, -1000, -1000, -1000, 55, 55, 167, -1000, - -1000, -1000, -1000, 168, -1000, -1000, 157, -1000, 633, -1000, - -1000, 35, -1000, 158, -1000, -1000, -1000, -1000, -1000, 152, - -1000, -1000, -1000, -1000, -1000, 27, 2, 1, -1000, -1000, - -1000, 387, 385, 163, 163, 163, 163, 20, 20, 308, - 308, 308, 697, 678, 308, 308, 697, 20, 20, 308, - 20, 385, -1000, 24, -1000, -1000, -1000, 198, -1000, 160, + -1000, -1000, -1000, -1000, -1000, -1000, 186, -1000, -1000, 190, + -1000, -1000, 290, -1000, 19, -1000, -53, -53, -53, -53, + -53, -53, -53, -53, -53, -53, -53, -53, -53, -53, + -53, -53, 124, 18, 289, 109, -57, -1000, 164, 164, + 317, -1000, 627, 108, -1000, 48, -1000, -1000, 128, 133, + -1000, -1000, -1000, 298, -1000, 182, -1000, 33, 473, -1000, + -58, -51, -1000, 473, 473, 473, 473, 473, 473, 473, + 473, 473, 473, 473, 473, 473, 473, 473, -1000, 187, + -1000, -1000, -1000, 106, -1000, -1000, -1000, -1000, -1000, -1000, + 88, 88, 269, -1000, -1000, -1000, -1000, 155, -1000, -1000, + 93, -1000, 646, -1000, -1000, 158, -1000, 114, -1000, -1000, + -1000, -1000, -1000, 45, -1000, -1000, -1000, -1000, -1000, 16, + 73, 13, -1000, -1000, -1000, 252, 117, 164, 164, 164, + 164, 108, 108, 293, 293, 293, 710, 691, 293, 293, + 710, 108, 108, 293, 108, 117, -1000, 26, -1000, -1000, + -1000, 263, -1000, 193, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, - -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, - -1000, -1000, 463, -1000, -1000, -1000, -1000, -1000, -1000, 59, - 59, 22, 59, 104, 104, 151, 100, -1000, -1000, 235, - 222, 219, 216, 215, 214, 211, 203, 189, 170, -1000, - -1000, -1000, -1000, -1000, -1000, 41, 194, -1000, -1000, 18, - -1000, 633, -1000, -1000, -1000, 59, -1000, 13, 9, 462, - -1000, -1000, -1000, 14, 10, 55, 55, 55, 115, 115, - 14, 115, 14, -1000, -1000, -1000, -1000, -1000, 59, 59, - -1000, -1000, -1000, 59, -1000, -1000, -1000, -1000, -1000, -1000, - 55, -1000, -1000, -1000, -1000, -1000, -1000, -1000, 30, -1000, - 106, -1000, -1000, -1000, -1000, + -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, 473, -1000, + -1000, -1000, -1000, -1000, -1000, 98, 98, 15, 98, 41, + 41, 110, 37, -1000, -1000, 257, 256, 255, 254, 253, + 235, 234, 233, 224, 208, -1000, -1000, -1000, -1000, -1000, + -1000, 35, 262, -1000, -1000, 14, -1000, 646, -1000, -1000, + -1000, 98, -1000, 8, 7, 395, -1000, -1000, -1000, 47, + 11, 88, 88, 88, 150, 150, 47, 150, 47, -1000, + -1000, -1000, -1000, -1000, 98, 98, -1000, -1000, -1000, 98, + -1000, -1000, -1000, -1000, -1000, -1000, 88, -1000, -1000, -1000, + -1000, -1000, -1000, -1000, 38, -1000, 25, -1000, -1000, -1000, + -1000, } var yyPgo = [...]int16{ - 0, 334, 13, 332, 6, 15, 328, 263, 327, 319, - 318, 213, 265, 317, 14, 312, 10, 11, 311, 309, - 8, 305, 3, 4, 304, 2, 1, 0, 302, 12, - 5, 301, 300, 18, 191, 299, 298, 7, 295, 294, - 17, 293, 56, 292, 291, 290, 274, 271, 270, 268, - 259, 250, 9, 258, 252, 251, + 0, 372, 13, 371, 6, 15, 370, 352, 369, 368, + 366, 194, 273, 365, 14, 362, 10, 11, 361, 360, + 8, 359, 3, 4, 358, 2, 1, 0, 354, 12, + 5, 353, 346, 18, 156, 343, 341, 7, 340, 338, + 17, 328, 31, 325, 324, 317, 311, 308, 306, 304, + 301, 293, 9, 297, 296, 295, } var yyR1 = [...]int8{ @@ -519,18 +542,18 @@ var yyR1 = [...]int8{ 1, 2, 2, 2, 2, 2, 2, 2, 12, 12, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, - 7, 7, 11, 11, 11, 11, 13, 13, 13, 14, - 14, 14, 14, 55, 19, 19, 19, 19, 18, 18, - 18, 18, 18, 18, 18, 18, 18, 28, 28, 28, - 20, 20, 20, 20, 21, 21, 21, 22, 22, 22, - 22, 22, 22, 22, 22, 22, 22, 23, 23, 24, - 24, 24, 3, 3, 3, 3, 3, 3, 3, 3, - 3, 3, 3, 3, 6, 6, 6, 6, 6, 6, + 7, 7, 7, 7, 11, 11, 11, 11, 13, 13, + 13, 14, 14, 14, 14, 55, 19, 19, 19, 19, + 18, 18, 18, 18, 18, 18, 18, 18, 18, 28, + 28, 28, 20, 20, 20, 20, 21, 21, 21, 22, + 22, 22, 22, 22, 22, 22, 22, 22, 22, 23, + 23, 24, 24, 24, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, - 6, 8, 8, 5, 5, 5, 5, 44, 27, 29, - 29, 30, 30, 26, 25, 25, 52, 48, 10, 53, - 53, 17, 17, + 6, 6, 6, 6, 6, 6, 6, 8, 8, 5, + 5, 5, 5, 44, 27, 29, 29, 30, 30, 26, + 25, 25, 52, 48, 10, 53, 53, 17, 17, } var yyR2 = [...]int8{ @@ -546,94 +569,96 @@ var yyR2 = [...]int8{ 2, 3, 3, 1, 3, 3, 2, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 3, 4, 2, 0, 3, 1, 2, 3, - 3, 2, 1, 2, 0, 3, 2, 1, 1, 3, - 1, 3, 4, 1, 3, 5, 5, 1, 1, 1, - 4, 3, 3, 2, 3, 1, 2, 3, 3, 3, - 3, 3, 3, 3, 3, 3, 3, 4, 3, 3, - 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 3, 4, 2, 0, 3, 1, + 2, 3, 3, 2, 1, 2, 0, 3, 2, 1, + 1, 3, 1, 3, 4, 1, 3, 5, 5, 1, + 1, 1, 4, 3, 3, 2, 3, 1, 2, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, + 3, 3, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, - 2, 1, 1, 1, 2, 1, 1, 1, 1, 0, - 1, 0, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 2, 2, 1, 1, 1, + 2, 1, 1, 1, 1, 0, 1, 0, 1, } var yyChk = [...]int16{ - -1000, -54, 89, 90, 91, 92, 2, 10, -12, -7, - -11, 61, 62, 76, 63, 64, 65, 12, 46, 47, - 50, 66, 18, 67, 80, 68, 69, 70, 71, 72, - 82, 85, 86, 13, -55, -12, 10, -37, -32, -35, - -38, -43, -44, -45, -47, -48, -49, -50, -51, -31, - -3, 12, 19, 15, 25, -8, -7, -42, 61, 62, - 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, - 40, 56, 13, -51, -11, -13, 20, -14, 12, 2, - -19, 2, 40, 58, 41, 42, 44, 45, 46, 47, - 48, 49, 50, 51, 52, 53, 55, 56, 80, 57, - 14, -33, -40, 2, 76, 82, 15, -40, -37, -37, - -42, -1, 20, -2, 12, -10, 2, 25, 20, 7, - 2, 4, 2, 24, -34, -41, -36, -46, 75, -34, - -34, -34, -34, -34, -34, -34, -34, -34, -34, -34, - -34, -34, -34, -34, -52, 56, 2, 9, -30, -9, - 2, -27, -29, 85, 86, 19, 40, 56, -52, 2, - -40, -33, -16, 15, 2, -16, -39, 22, -37, 22, - 20, 7, 2, -5, 2, 4, 53, 43, 54, -5, - 20, -14, 25, 2, -18, 5, -28, -20, 12, -27, - -29, 16, -37, 79, 81, 77, 78, -37, -37, -37, - -37, -37, -37, -37, -37, -37, -37, -37, -37, -37, - -37, -37, -52, 15, -27, -27, 21, 6, 2, -15, - 22, -4, -6, 2, 61, 75, 62, 76, 63, 64, - 65, 77, 78, 12, 79, 46, 47, 50, 66, 18, - 67, 80, 81, 68, 69, 70, 71, 72, 85, 86, - 58, 22, 7, 20, -2, 25, 2, 25, 2, 26, - 26, -29, 26, 40, 56, -21, 24, 17, -22, 30, - 28, 29, 35, 36, 37, 33, 31, 34, 32, -16, - -16, -17, -16, -17, 22, -53, -52, 2, 22, 7, - 2, -37, -26, 19, -26, 26, -26, -20, -20, 24, - 17, 2, 17, 6, 6, 6, 6, 6, 6, 6, - 6, 6, 6, 21, 2, 22, -4, -26, 26, 26, - 17, -22, -25, 56, -26, -30, -27, -27, -27, -23, - 14, -23, -25, -23, -25, -26, -26, -26, -24, -27, - 24, 21, 2, 21, -27, + -1000, -54, 91, 92, 93, 94, 2, 10, -12, -7, + -11, 61, 62, 78, 63, 64, 65, 12, 46, 47, + 50, 66, 18, 67, 82, 68, 69, 70, 71, 72, + 84, 87, 88, 73, 74, 13, -55, -12, 10, -37, + -32, -35, -38, -43, -44, -45, -47, -48, -49, -50, + -51, -31, -3, 12, 19, 15, 25, -8, -7, -42, + 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, + 71, 72, 73, 74, 40, 56, 13, -51, -11, -13, + 20, -14, 12, 2, -19, 2, 40, 58, 41, 42, + 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, + 55, 56, 82, 57, 14, -33, -40, 2, 78, 84, + 15, -40, -37, -37, -42, -1, 20, -2, 12, -10, + 2, 25, 20, 7, 2, 4, 2, 24, -34, -41, + -36, -46, 77, -34, -34, -34, -34, -34, -34, -34, + -34, -34, -34, -34, -34, -34, -34, -34, -52, 56, + 2, 9, -30, -9, 2, -27, -29, 87, 88, 19, + 40, 56, -52, 2, -40, -33, -16, 15, 2, -16, + -39, 22, -37, 22, 20, 7, 2, -5, 2, 4, + 53, 43, 54, -5, 20, -14, 25, 2, -18, 5, + -28, -20, 12, -27, -29, 16, -37, 81, 83, 79, + 80, -37, -37, -37, -37, -37, -37, -37, -37, -37, + -37, -37, -37, -37, -37, -37, -52, 15, -27, -27, + 21, 6, 2, -15, 22, -4, -6, 2, 61, 77, + 62, 78, 63, 64, 65, 79, 80, 12, 81, 46, + 47, 50, 66, 18, 67, 82, 83, 68, 69, 70, + 71, 72, 87, 88, 58, 73, 74, 22, 7, 20, + -2, 25, 2, 25, 2, 26, 26, -29, 26, 40, + 56, -21, 24, 17, -22, 30, 28, 29, 35, 36, + 37, 33, 31, 34, 32, -16, -16, -17, -16, -17, + 22, -53, -52, 2, 22, 7, 2, -37, -26, 19, + -26, 26, -26, -20, -20, 24, 17, 2, 17, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 21, + 2, 22, -4, -26, 26, 26, 17, -22, -25, 56, + -26, -30, -27, -27, -27, -23, 14, -23, -25, -23, + -25, -26, -26, -26, -24, -27, 24, 21, 2, 21, + -27, } var yyDef = [...]int16{ - 0, -2, 125, 125, 0, 0, 7, 6, 1, 125, + 0, -2, 127, 127, 0, 0, 7, 6, 1, 127, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, - 119, 120, 121, 0, 2, -2, 3, 4, 8, 9, - 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, - 0, 106, 217, 0, 227, 0, 83, 84, -2, -2, + 119, 120, 121, 122, 123, 0, 2, -2, 3, 4, + 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, + 18, 19, 0, 106, 223, 0, 233, 0, 83, 84, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, - 211, 212, 0, 5, 98, 0, 124, 127, 0, 132, - 133, 137, 43, 43, 43, 43, 43, 43, 43, 43, - 43, 43, 43, 43, 43, 43, 43, 43, 0, 0, - 0, 0, 22, 23, 0, 0, 0, 60, 0, 81, - 82, 0, 87, 89, 0, 93, 97, 228, 122, 0, - 128, 0, 131, 136, 0, 42, 47, 48, 44, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 67, 0, 69, 226, 70, 0, - 72, 221, 222, 73, 74, 218, 0, 0, 0, 80, - 20, 21, 24, 0, 54, 25, 0, 62, 64, 66, - 85, 0, 90, 0, 96, 213, 214, 215, 216, 0, - 123, 126, 129, 130, 135, 138, 140, 143, 147, 148, - 149, 0, 26, 0, 0, -2, -2, 27, 28, 29, - 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, - 40, 41, 68, 0, 219, 220, 75, -2, 79, 0, - 53, 56, 58, 59, 184, 185, 186, 187, 188, 189, + -2, -2, -2, -2, 217, 218, 0, 5, 98, 0, + 126, 129, 0, 134, 135, 139, 43, 43, 43, 43, + 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, + 43, 43, 0, 0, 0, 0, 22, 23, 0, 0, + 0, 60, 0, 81, 82, 0, 87, 89, 0, 93, + 97, 234, 124, 0, 130, 0, 133, 138, 0, 42, + 47, 48, 44, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 67, 0, + 69, 232, 70, 0, 72, 227, 228, 73, 74, 224, + 0, 0, 0, 80, 20, 21, 24, 0, 54, 25, + 0, 62, 64, 66, 85, 0, 90, 0, 96, 219, + 220, 221, 222, 0, 125, 128, 131, 132, 137, 140, + 142, 145, 149, 150, 151, 0, 26, 0, 0, -2, + -2, 27, 28, 29, 30, 31, 32, 33, 34, 35, + 36, 37, 38, 39, 40, 41, 68, 0, 225, 226, + 75, -2, 79, 0, 53, 56, 58, 59, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, - 210, 61, 65, 86, 88, 91, 95, 92, 94, 0, - 0, 0, 0, 0, 0, 0, 0, 153, 155, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 45, - 46, 49, 232, 50, 71, 0, -2, 78, 51, 0, - 57, 63, 139, 223, 141, 0, 144, 0, 0, 0, - 151, 156, 152, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 76, 77, 52, 55, 142, 0, 0, - 150, 154, 157, 0, 225, 158, 159, 160, 161, 162, - 0, 163, 164, 165, 166, 145, 146, 224, 0, 170, - 0, 168, 171, 167, 169, + 210, 211, 212, 213, 214, 215, 216, 61, 65, 86, + 88, 91, 95, 92, 94, 0, 0, 0, 0, 0, + 0, 0, 0, 155, 157, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 45, 46, 49, 238, 50, + 71, 0, -2, 78, 51, 0, 57, 63, 141, 229, + 143, 0, 146, 0, 0, 0, 153, 158, 154, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 76, + 77, 52, 55, 144, 0, 0, 152, 156, 159, 0, + 231, 160, 161, 162, 163, 164, 0, 165, 166, 167, + 168, 147, 148, 230, 0, 172, 0, 170, 173, 169, + 171, } var yyTok1 = [...]int8{ @@ -650,7 +675,7 @@ var yyTok2 = [...]int8{ 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, - 92, 93, + 92, 93, 94, 95, } var yyTok3 = [...]int8{ @@ -1506,66 +1531,66 @@ yydefault: { yyVAL.labels = yyDollar[1].labels } - case 122: + case 124: yyDollar = yyS[yypt-3 : yypt+1] { yyVAL.labels = labels.New(yyDollar[2].lblList...) } - case 123: + case 125: yyDollar = yyS[yypt-4 : yypt+1] { yyVAL.labels = labels.New(yyDollar[2].lblList...) } - case 124: + case 126: yyDollar = yyS[yypt-2 : yypt+1] { yyVAL.labels = labels.New() } - case 125: + case 127: yyDollar = yyS[yypt-0 : yypt+1] { yyVAL.labels = labels.New() } - case 126: + case 128: yyDollar = yyS[yypt-3 : yypt+1] { yyVAL.lblList = append(yyDollar[1].lblList, yyDollar[3].label) } - case 127: + case 129: yyDollar = yyS[yypt-1 : yypt+1] { yyVAL.lblList = []labels.Label{yyDollar[1].label} } - case 128: + case 130: yyDollar = yyS[yypt-2 : yypt+1] { yylex.(*parser).unexpected("label set", "\",\" or \"}\"") yyVAL.lblList = yyDollar[1].lblList } - case 129: + case 131: yyDollar = yyS[yypt-3 : yypt+1] { yyVAL.label = labels.Label{Name: yyDollar[1].item.Val, Value: yylex.(*parser).unquoteString(yyDollar[3].item.Val)} } - case 130: + case 132: yyDollar = yyS[yypt-3 : yypt+1] { yylex.(*parser).unexpected("label set", "string") yyVAL.label = labels.Label{} } - case 131: + case 133: yyDollar = yyS[yypt-2 : yypt+1] { yylex.(*parser).unexpected("label set", "\"=\"") yyVAL.label = labels.Label{} } - case 132: + case 134: yyDollar = yyS[yypt-1 : yypt+1] { yylex.(*parser).unexpected("label set", "identifier or \"}\"") yyVAL.label = labels.Label{} } - case 133: + case 135: yyDollar = yyS[yypt-2 : yypt+1] { yylex.(*parser).generatedParserResult = &seriesDescription{ @@ -1573,33 +1598,33 @@ yydefault: values: yyDollar[2].series, } } - case 134: + case 136: yyDollar = yyS[yypt-0 : yypt+1] { yyVAL.series = []SequenceValue{} } - case 135: + case 137: yyDollar = yyS[yypt-3 : yypt+1] { yyVAL.series = append(yyDollar[1].series, yyDollar[3].series...) } - case 136: + case 138: yyDollar = yyS[yypt-2 : yypt+1] { yyVAL.series = yyDollar[1].series } - case 137: + case 139: yyDollar = yyS[yypt-1 : yypt+1] { yylex.(*parser).unexpected("series values", "") yyVAL.series = nil } - case 138: + case 140: yyDollar = yyS[yypt-1 : yypt+1] { yyVAL.series = []SequenceValue{{Omitted: true}} } - case 139: + case 141: yyDollar = yyS[yypt-3 : yypt+1] { yyVAL.series = []SequenceValue{} @@ -1607,12 +1632,12 @@ yydefault: yyVAL.series = append(yyVAL.series, SequenceValue{Omitted: true}) } } - case 140: + case 142: yyDollar = yyS[yypt-1 : yypt+1] { yyVAL.series = []SequenceValue{{Value: yyDollar[1].float}} } - case 141: + case 143: yyDollar = yyS[yypt-3 : yypt+1] { yyVAL.series = []SequenceValue{} @@ -1621,7 +1646,7 @@ yydefault: yyVAL.series = append(yyVAL.series, SequenceValue{Value: yyDollar[1].float}) } } - case 142: + case 144: yyDollar = yyS[yypt-4 : yypt+1] { yyVAL.series = []SequenceValue{} @@ -1631,12 +1656,12 @@ yydefault: yyDollar[1].float += yyDollar[2].float } } - case 143: + case 145: yyDollar = yyS[yypt-1 : yypt+1] { yyVAL.series = []SequenceValue{{Histogram: yyDollar[1].histogram}} } - case 144: + case 146: yyDollar = yyS[yypt-3 : yypt+1] { yyVAL.series = []SequenceValue{} @@ -1646,7 +1671,7 @@ yydefault: //$1 += $2 } } - case 145: + case 147: yyDollar = yyS[yypt-5 : yypt+1] { val, err := yylex.(*parser).histogramsIncreaseSeries(yyDollar[1].histogram, yyDollar[3].histogram, yyDollar[5].uint) @@ -1655,7 +1680,7 @@ yydefault: } yyVAL.series = val } - case 146: + case 148: yyDollar = yyS[yypt-5 : yypt+1] { val, err := yylex.(*parser).histogramsDecreaseSeries(yyDollar[1].histogram, yyDollar[3].histogram, yyDollar[5].uint) @@ -1664,7 +1689,7 @@ yydefault: } yyVAL.series = val } - case 147: + case 149: yyDollar = yyS[yypt-1 : yypt+1] { if yyDollar[1].item.Val != "stale" { @@ -1672,124 +1697,124 @@ yydefault: } yyVAL.float = math.Float64frombits(value.StaleNaN) } - case 150: + case 152: yyDollar = yyS[yypt-4 : yypt+1] { yyVAL.histogram = yylex.(*parser).buildHistogramFromMap(&yyDollar[2].descriptors) } - case 151: + case 153: yyDollar = yyS[yypt-3 : yypt+1] { yyVAL.histogram = yylex.(*parser).buildHistogramFromMap(&yyDollar[2].descriptors) } - case 152: + case 154: yyDollar = yyS[yypt-3 : yypt+1] { m := yylex.(*parser).newMap() yyVAL.histogram = yylex.(*parser).buildHistogramFromMap(&m) } - case 153: + case 155: yyDollar = yyS[yypt-2 : yypt+1] { m := yylex.(*parser).newMap() yyVAL.histogram = yylex.(*parser).buildHistogramFromMap(&m) } - case 154: + case 156: yyDollar = yyS[yypt-3 : yypt+1] { yyVAL.descriptors = *(yylex.(*parser).mergeMaps(&yyDollar[1].descriptors, &yyDollar[3].descriptors)) } - case 155: + case 157: yyDollar = yyS[yypt-1 : yypt+1] { yyVAL.descriptors = yyDollar[1].descriptors } - case 156: + case 158: yyDollar = yyS[yypt-2 : yypt+1] { yylex.(*parser).unexpected("histogram description", "histogram description key, e.g. buckets:[5 10 7]") } - case 157: - yyDollar = yyS[yypt-3 : yypt+1] - { - yyVAL.descriptors = yylex.(*parser).newMap() - yyVAL.descriptors["schema"] = yyDollar[3].int - } - case 158: - yyDollar = yyS[yypt-3 : yypt+1] - { - yyVAL.descriptors = yylex.(*parser).newMap() - yyVAL.descriptors["sum"] = yyDollar[3].float - } case 159: yyDollar = yyS[yypt-3 : yypt+1] { yyVAL.descriptors = yylex.(*parser).newMap() - yyVAL.descriptors["count"] = yyDollar[3].float + yyVAL.descriptors["schema"] = yyDollar[3].int } case 160: yyDollar = yyS[yypt-3 : yypt+1] { yyVAL.descriptors = yylex.(*parser).newMap() - yyVAL.descriptors["z_bucket"] = yyDollar[3].float + yyVAL.descriptors["sum"] = yyDollar[3].float } case 161: yyDollar = yyS[yypt-3 : yypt+1] { yyVAL.descriptors = yylex.(*parser).newMap() - yyVAL.descriptors["z_bucket_w"] = yyDollar[3].float + yyVAL.descriptors["count"] = yyDollar[3].float } case 162: yyDollar = yyS[yypt-3 : yypt+1] { yyVAL.descriptors = yylex.(*parser).newMap() - yyVAL.descriptors["custom_values"] = yyDollar[3].bucket_set + yyVAL.descriptors["z_bucket"] = yyDollar[3].float } case 163: yyDollar = yyS[yypt-3 : yypt+1] { yyVAL.descriptors = yylex.(*parser).newMap() - yyVAL.descriptors["buckets"] = yyDollar[3].bucket_set + yyVAL.descriptors["z_bucket_w"] = yyDollar[3].float } case 164: yyDollar = yyS[yypt-3 : yypt+1] { yyVAL.descriptors = yylex.(*parser).newMap() - yyVAL.descriptors["offset"] = yyDollar[3].int + yyVAL.descriptors["custom_values"] = yyDollar[3].bucket_set } case 165: yyDollar = yyS[yypt-3 : yypt+1] { yyVAL.descriptors = yylex.(*parser).newMap() - yyVAL.descriptors["n_buckets"] = yyDollar[3].bucket_set + yyVAL.descriptors["buckets"] = yyDollar[3].bucket_set } case 166: yyDollar = yyS[yypt-3 : yypt+1] { yyVAL.descriptors = yylex.(*parser).newMap() - yyVAL.descriptors["n_offset"] = yyDollar[3].int + yyVAL.descriptors["offset"] = yyDollar[3].int } case 167: - yyDollar = yyS[yypt-4 : yypt+1] + yyDollar = yyS[yypt-3 : yypt+1] { - yyVAL.bucket_set = yyDollar[2].bucket_set + yyVAL.descriptors = yylex.(*parser).newMap() + yyVAL.descriptors["n_buckets"] = yyDollar[3].bucket_set } case 168: yyDollar = yyS[yypt-3 : yypt+1] { - yyVAL.bucket_set = yyDollar[2].bucket_set + yyVAL.descriptors = yylex.(*parser).newMap() + yyVAL.descriptors["n_offset"] = yyDollar[3].int } case 169: + yyDollar = yyS[yypt-4 : yypt+1] + { + yyVAL.bucket_set = yyDollar[2].bucket_set + } + case 170: + yyDollar = yyS[yypt-3 : yypt+1] + { + yyVAL.bucket_set = yyDollar[2].bucket_set + } + case 171: yyDollar = yyS[yypt-3 : yypt+1] { yyVAL.bucket_set = append(yyDollar[1].bucket_set, yyDollar[3].float) } - case 170: + case 172: yyDollar = yyS[yypt-1 : yypt+1] { yyVAL.bucket_set = []float64{yyDollar[1].float} } - case 217: + case 223: yyDollar = yyS[yypt-1 : yypt+1] { yyVAL.node = &NumberLiteral{ @@ -1797,22 +1822,22 @@ yydefault: PosRange: yyDollar[1].item.PositionRange(), } } - case 218: + case 224: yyDollar = yyS[yypt-1 : yypt+1] { yyVAL.float = yylex.(*parser).number(yyDollar[1].item.Val) } - case 219: + case 225: yyDollar = yyS[yypt-2 : yypt+1] { yyVAL.float = yyDollar[2].float } - case 220: + case 226: yyDollar = yyS[yypt-2 : yypt+1] { yyVAL.float = -yyDollar[2].float } - case 223: + case 229: yyDollar = yyS[yypt-1 : yypt+1] { var err error @@ -1821,17 +1846,17 @@ yydefault: yylex.(*parser).addParseErrf(yyDollar[1].item.PositionRange(), "invalid repetition in series values: %s", err) } } - case 224: + case 230: yyDollar = yyS[yypt-2 : yypt+1] { yyVAL.int = -int64(yyDollar[2].uint) } - case 225: + case 231: yyDollar = yyS[yypt-1 : yypt+1] { yyVAL.int = int64(yyDollar[1].uint) } - case 226: + case 232: yyDollar = yyS[yypt-1 : yypt+1] { var err error @@ -1840,7 +1865,7 @@ yydefault: yylex.(*parser).addParseErr(yyDollar[1].item.PositionRange(), err) } } - case 227: + case 233: yyDollar = yyS[yypt-1 : yypt+1] { yyVAL.node = &StringLiteral{ @@ -1848,7 +1873,7 @@ yydefault: PosRange: yyDollar[1].item.PositionRange(), } } - case 228: + case 234: yyDollar = yyS[yypt-1 : yypt+1] { yyVAL.item = Item{ @@ -1857,12 +1882,12 @@ yydefault: Val: yylex.(*parser).unquoteString(yyDollar[1].item.Val), } } - case 229: + case 235: yyDollar = yyS[yypt-0 : yypt+1] { yyVAL.duration = 0 } - case 231: + case 237: yyDollar = yyS[yypt-0 : yypt+1] { yyVAL.strings = nil diff --git a/promql/parser/lex.go b/promql/parser/lex.go index c8ea4c46e..8c7fbb89b 100644 --- a/promql/parser/lex.go +++ b/promql/parser/lex.go @@ -65,7 +65,7 @@ func (i ItemType) IsAggregator() bool { return i > aggregatorsStart && i < aggre // IsAggregatorWithParam returns true if the Item is an aggregator that takes a parameter. // Returns false otherwise. func (i ItemType) IsAggregatorWithParam() bool { - return i == TOPK || i == BOTTOMK || i == COUNT_VALUES || i == QUANTILE + return i == TOPK || i == BOTTOMK || i == COUNT_VALUES || i == QUANTILE || i == LIMITK || i == LIMIT_RATIO } // IsKeyword returns true if the Item corresponds to a keyword. @@ -118,6 +118,8 @@ var key = map[string]ItemType{ "bottomk": BOTTOMK, "count_values": COUNT_VALUES, "quantile": QUANTILE, + "limitk": LIMITK, + "limit_ratio": LIMIT_RATIO, // Keywords. "offset": OFFSET, diff --git a/promql/parser/parse.go b/promql/parser/parse.go index f3fa27f84..6f73e2427 100644 --- a/promql/parser/parse.go +++ b/promql/parser/parse.go @@ -447,6 +447,10 @@ func (p *parser) newAggregateExpr(op Item, modifier, args Node) (ret *AggregateE desiredArgs := 1 if ret.Op.IsAggregatorWithParam() { + if !EnableExperimentalFunctions && (ret.Op == LIMITK || ret.Op == LIMIT_RATIO) { + p.addParseErrf(ret.PositionRange(), "limitk() and limit_ratio() are experimental and must be enabled with --enable-feature=promql-experimental-functions") + return + } desiredArgs = 2 ret.Param = arguments[0] @@ -672,7 +676,7 @@ func (p *parser) checkAST(node Node) (typ ValueType) { p.addParseErrf(n.PositionRange(), "aggregation operator expected in aggregation expression but got %q", n.Op) } p.expectType(n.Expr, ValueTypeVector, "aggregation expression") - if n.Op == TOPK || n.Op == BOTTOMK || n.Op == QUANTILE { + if n.Op == TOPK || n.Op == BOTTOMK || n.Op == QUANTILE || n.Op == LIMITK || n.Op == LIMIT_RATIO { p.expectType(n.Param, ValueTypeScalar, "aggregation parameter") } if n.Op == COUNT_VALUES { diff --git a/promql/promql_test.go b/promql/promql_test.go index 7bafc02e3..a423f90ee 100644 --- a/promql/promql_test.go +++ b/promql/promql_test.go @@ -23,6 +23,7 @@ import ( "golang.org/x/sync/errgroup" "github.com/prometheus/prometheus/promql" + "github.com/prometheus/prometheus/promql/parser" "github.com/prometheus/prometheus/promql/promqltest" "github.com/prometheus/prometheus/util/teststorage" ) @@ -45,6 +46,8 @@ func TestConcurrentRangeQueries(t *testing.T) { MaxSamples: 50000000, Timeout: 100 * time.Second, } + // Enable experimental functions testing + parser.EnableExperimentalFunctions = true engine := promql.NewEngine(opts) const interval = 10000 // 10s interval. diff --git a/promql/promqltest/testdata/limit.test b/promql/promqltest/testdata/limit.test new file mode 100644 index 000000000..0ab363f9a --- /dev/null +++ b/promql/promqltest/testdata/limit.test @@ -0,0 +1,119 @@ +# Tests for limitk +# +# NB: those many `and http_requests` are to ensure that the series _are_ indeed +# a subset of the original series. +load 5m + http_requests{job="api-server", instance="0", group="production"} 0+10x10 + http_requests{job="api-server", instance="1", group="production"} 0+20x10 + http_requests{job="api-server", instance="0", group="canary"} 0+30x10 + http_requests{job="api-server", instance="1", group="canary"} 0+40x10 + http_requests{job="api-server", instance="2", group="canary"} 0+50x10 + http_requests{job="api-server", instance="3", group="canary"} 0+60x10 + +eval instant at 50m count(limitk by (group) (0, http_requests)) +# empty + +eval instant at 50m count(limitk by (group) (-1, http_requests)) +# empty + +# Exercise k==1 special case (as sample is added before the main series loop +eval instant at 50m count(limitk by (group) (1, http_requests) and http_requests) + {} 2 + +eval instant at 50m count(limitk by (group) (2, http_requests) and http_requests) + {} 4 + +eval instant at 50m count(limitk(100, http_requests) and http_requests) + {} 6 + +# Exercise k==1 special case (as sample is added before the main series loop +eval instant at 50m count(limitk by (group) (1, http_requests) and http_requests) + {} 2 + +eval instant at 50m count(limitk by (group) (2, http_requests) and http_requests) + {} 4 + +eval instant at 50m count(limitk(100, http_requests) and http_requests) + {} 6 + +# limit_ratio +eval range from 0 to 50m step 5m count(limit_ratio(0.0, http_requests)) +# empty + +# limitk(2, ...) should always return a 2-count subset of the timeseries (hence the AND'ing) +eval range from 0 to 50m step 5m count(limitk(2, http_requests) and http_requests) + {} 2+0x10 + +# Tests for limit_ratio +# +# NB: below 0.5 ratio will depend on some hashing "luck" (also there's no guarantee that +# an integer comes from: total number of series * ratio), as it depends on: +# +# * ratioLimit = [0.0, 1.0]: +# float64(sample.Metric.Hash()) / float64MaxUint64 < Ratio ? +# * ratioLimit = [-1.0, 1.0): +# float64(sample.Metric.Hash()) / float64MaxUint64 >= (1.0 + Ratio) ? +# +# See `AddRatioSample()` in promql/engine.go for more details. + +# Half~ish samples: verify we get "near" 3 (of 0.5 * 6) +eval range from 0 to 50m step 5m count(limit_ratio(0.5, http_requests) and http_requests) <= bool (3+1) + {} 1+0x10 + +eval range from 0 to 50m step 5m count(limit_ratio(0.5, http_requests) and http_requests) >= bool (3-1) + {} 1+0x10 + +# All samples +eval range from 0 to 50m step 5m count(limit_ratio(1.0, http_requests) and http_requests) + {} 6+0x10 + +# All samples +eval range from 0 to 50m step 5m count(limit_ratio(-1.0, http_requests) and http_requests) + {} 6+0x10 + +# Capped to 1.0 -> all samples +eval_warn range from 0 to 50m step 5m count(limit_ratio(1.1, http_requests) and http_requests) + {} 6+0x10 + +# Capped to -1.0 -> all samples +eval_warn range from 0 to 50m step 5m count(limit_ratio(-1.1, http_requests) and http_requests) + {} 6+0x10 + +# Verify that limit_ratio(value) and limit_ratio(1.0-value) return the "complement" of each other +# Complement below for [0.2, -0.8] +# +# Complement 1of2: `or` should return all samples +eval range from 0 to 50m step 5m count(limit_ratio(0.2, http_requests) or limit_ratio(-0.8, http_requests)) + {} 6+0x10 + +# Complement 2of2: `and` should return no samples +eval range from 0 to 50m step 5m count(limit_ratio(0.2, http_requests) and limit_ratio(-0.8, http_requests)) +# empty + +# Complement below for [0.5, -0.5] +eval range from 0 to 50m step 5m count(limit_ratio(0.5, http_requests) or limit_ratio(-0.5, http_requests)) + {} 6+0x10 + +eval range from 0 to 50m step 5m count(limit_ratio(0.5, http_requests) and limit_ratio(-0.5, http_requests)) +# empty + +# Complement below for [0.8, -0.2] +eval range from 0 to 50m step 5m count(limit_ratio(0.8, http_requests) or limit_ratio(-0.2, http_requests)) + {} 6+0x10 + +eval range from 0 to 50m step 5m count(limit_ratio(0.8, http_requests) and limit_ratio(-0.2, http_requests)) +# empty + +# Complement below for [some_ratio, 1.0 - some_ratio], some_ratio derived from time(), +# using a small prime number to avoid rounded ratio values, and a small set of them. +eval range from 0 to 50m step 5m count(limit_ratio(time() % 17/17, http_requests) or limit_ratio(1.0 - (time() % 17/17), http_requests)) + {} 6+0x10 + +eval range from 0 to 50m step 5m count(limit_ratio(time() % 17/17, http_requests) and limit_ratio(1.0 - (time() % 17/17), http_requests)) +# empty + +# Poor man's normality check: ok (loaded samples follow a nice linearity over labels and time) +# The check giving: 1 (i.e. true) +eval range from 0 to 50m step 5m abs(avg(limit_ratio(0.5, http_requests)) - avg(limit_ratio(-0.5, http_requests))) <= bool stddev(http_requests) + {} 1+0x10 + diff --git a/util/annotations/annotations.go b/util/annotations/annotations.go index 6415f4474..40a20e4b9 100644 --- a/util/annotations/annotations.go +++ b/util/annotations/annotations.go @@ -116,6 +116,7 @@ var ( PromQLInfo = errors.New("PromQL info") PromQLWarning = errors.New("PromQL warning") + InvalidRatioWarning = fmt.Errorf("%w: ratio value should be between -1 and 1", PromQLWarning) InvalidQuantileWarning = fmt.Errorf("%w: quantile value should be between 0 and 1", PromQLWarning) BadBucketLabelWarning = fmt.Errorf("%w: bucket label %q is missing or has a malformed value", PromQLWarning, model.BucketLabel) MixedFloatsHistogramsWarning = fmt.Errorf("%w: encountered a mix of histograms and floats for", PromQLWarning) @@ -155,6 +156,15 @@ func NewInvalidQuantileWarning(q float64, pos posrange.PositionRange) error { } } +// NewInvalidQuantileWarning is used when the user specifies an invalid ratio +// value, i.e. a float that is outside the range [-1, 1] or NaN. +func NewInvalidRatioWarning(q, to float64, pos posrange.PositionRange) error { + return annoErr{ + PositionRange: pos, + Err: fmt.Errorf("%w, got %g, capping to %g", InvalidRatioWarning, q, to), + } +} + // NewBadBucketLabelWarning is used when there is an error parsing the bucket label // of a classic histogram. func NewBadBucketLabelWarning(metricName, label string, pos posrange.PositionRange) error { diff --git a/web/ui/module/codemirror-promql/src/complete/promql.terms.ts b/web/ui/module/codemirror-promql/src/complete/promql.terms.ts index 9d5d55f60..f4f934f50 100644 --- a/web/ui/module/codemirror-promql/src/complete/promql.terms.ts +++ b/web/ui/module/codemirror-promql/src/complete/promql.terms.ts @@ -544,6 +544,18 @@ export const aggregateOpTerms = [ info: 'Group series, while setting the sample value to 1', type: 'keyword', }, + { + label: 'limitk', + detail: 'aggregation', + info: 'Sample k elements', + type: 'keyword', + }, + { + label: 'limit_ratio', + detail: 'aggregation', + info: 'Sample given ratio of elements', + type: 'keyword', + }, { label: 'max', detail: 'aggregation', diff --git a/web/ui/module/codemirror-promql/src/parser/parser.ts b/web/ui/module/codemirror-promql/src/parser/parser.ts index fba7b7b6b..351183d6b 100644 --- a/web/ui/module/codemirror-promql/src/parser/parser.ts +++ b/web/ui/module/codemirror-promql/src/parser/parser.ts @@ -28,6 +28,8 @@ import { Gtr, Identifier, LabelMatchers, + LimitK, + LimitRatio, Lss, Lte, MatrixSelector, @@ -167,7 +169,13 @@ export class Parser { } this.expectType(params[params.length - 1], ValueType.vector, 'aggregation expression'); // get the parameter of the aggregation operator - if (aggregateOp.type.id === Topk || aggregateOp.type.id === Bottomk || aggregateOp.type.id === Quantile) { + if ( + aggregateOp.type.id === Topk || + aggregateOp.type.id === Bottomk || + aggregateOp.type.id === LimitK || + aggregateOp.type.id === LimitRatio || + aggregateOp.type.id === Quantile + ) { this.expectType(params[0], ValueType.scalar, 'aggregation parameter'); } if (aggregateOp.type.id === CountValues) { diff --git a/web/ui/module/lezer-promql/src/highlight.js b/web/ui/module/lezer-promql/src/highlight.js index 53321c75e..b8bdab76d 100644 --- a/web/ui/module/lezer-promql/src/highlight.js +++ b/web/ui/module/lezer-promql/src/highlight.js @@ -22,7 +22,7 @@ export const promQLHighLight = styleTags({ Identifier: tags.variableName, 'Abs Absent AbsentOverTime Acos Acosh Asin Asinh Atan Atanh AvgOverTime Ceil Changes Clamp ClampMax ClampMin Cos Cosh CountOverTime DaysInMonth DayOfMonth DayOfWeek DayOfYear Deg Delta Deriv Exp Floor HistogramAvg HistogramCount HistogramFraction HistogramQuantile HistogramSum HoltWinters Hour Idelta Increase Irate LabelReplace LabelJoin LastOverTime Ln Log10 Log2 MaxOverTime MinOverTime Minute Month Pi PredictLinear PresentOverTime QuantileOverTime Rad Rate Resets Round Scalar Sgn Sin Sinh Sort SortDesc SortByLabel SortByLabelDesc Sqrt StddevOverTime StdvarOverTime SumOverTime Tan Tanh Time Timestamp Vector Year': tags.function(tags.variableName), - 'Avg Bottomk Count Count_values Group Max Min Quantile Stddev Stdvar Sum Topk': tags.operatorKeyword, + 'Avg Bottomk Count Count_values Group LimitK LimitRatio Max Min Quantile Stddev Stdvar Sum Topk': tags.operatorKeyword, 'By Without Bool On Ignoring GroupLeft GroupRight Offset Start End': tags.modifier, 'And Unless Or': tags.logicOperator, 'Sub Add Mul Mod Div Atan2 Eql Neq Lte Lss Gte Gtr EqlRegex EqlSingle NeqRegex Pow At': tags.operator, diff --git a/web/ui/module/lezer-promql/src/promql.grammar b/web/ui/module/lezer-promql/src/promql.grammar index 89aa23c79..735fede24 100644 --- a/web/ui/module/lezer-promql/src/promql.grammar +++ b/web/ui/module/lezer-promql/src/promql.grammar @@ -54,6 +54,8 @@ AggregateOp { Max | Min | Quantile | + LimitK | + LimitRatio | Stddev | Stdvar | Sum | @@ -330,6 +332,8 @@ NumberLiteral { Max, Min, Quantile, + LimitK, + LimitRatio, Stddev, Stdvar, Sum, diff --git a/web/ui/module/lezer-promql/src/tokens.js b/web/ui/module/lezer-promql/src/tokens.js index 551040969..d9e7b1d9b 100644 --- a/web/ui/module/lezer-promql/src/tokens.js +++ b/web/ui/module/lezer-promql/src/tokens.js @@ -33,6 +33,8 @@ import { On, Or, Quantile, + LimitK, + LimitRatio, Start, Stddev, Stdvar, @@ -67,6 +69,8 @@ const contextualKeywordTokens = { max: Max, min: Min, quantile: Quantile, + limitk: LimitK, + limit_ratio: LimitRatio, stddev: Stddev, stdvar: Stdvar, sum: Sum,