Merge remote-tracking branch 'upstream/main' into codesome/syncprom

This commit is contained in:
Ganesh Vernekar 2021-09-21 17:38:40 +05:30
commit 0af335cfe8
No known key found for this signature in database
GPG key ID: 0F8729A5EB59B965
8 changed files with 108 additions and 69 deletions

View file

@ -55,7 +55,7 @@ Prometheus will now be reachable at http://localhost:9090/.
### Building from source ### Building from source
To build Prometheus from source code, first ensure that have a working To build Prometheus from source code, first ensure that you have a working
Go environment with [version 1.14 or greater installed](https://golang.org/doc/install). Go environment with [version 1.14 or greater installed](https://golang.org/doc/install).
You also need [Node.js](https://nodejs.org/) and [npm](https://www.npmjs.com/) You also need [Node.js](https://nodejs.org/) and [npm](https://www.npmjs.com/)
installed in order to build the frontend assets. installed in order to build the frontend assets.

View file

@ -25,7 +25,7 @@ Here is a table comparing our two generic Service Discovery implementations.
## Requirements of HTTP SD endpoints ## Requirements of HTTP SD endpoints
If you implement an HTTP SD endpoint, here is a few requirements you should be If you implement an HTTP SD endpoint, here are a few requirements you should be
aware of. aware of.
The response is consumed as is, unmodified. On each refresh interval (default: 1 The response is consumed as is, unmodified. On each refresh interval (default: 1
@ -47,7 +47,7 @@ for incremental updates. A Prometheus instance does not send its hostname and it
is not possible for a SD endpoint to know if the SD requests is the first one is not possible for a SD endpoint to know if the SD requests is the first one
after a restart or not. after a restart or not.
The URL to the HTTP SD is not considered secret. The authentication, and any API The URL to the HTTP SD is not considered secret. The authentication and any API
keys should be passed with the appropriate authentication mechanisms. Prometheus keys should be passed with the appropriate authentication mechanisms. Prometheus
supports TLS authentication, basic authentication, OAuth2, and authorization supports TLS authentication, basic authentication, OAuth2, and authorization
headers. headers.

2
go.mod
View file

@ -8,7 +8,7 @@ require (
github.com/Azure/go-autorest/autorest/adal v0.9.15 github.com/Azure/go-autorest/autorest/adal v0.9.15
github.com/Azure/go-autorest/autorest/to v0.4.0 // indirect github.com/Azure/go-autorest/autorest/to v0.4.0 // indirect
github.com/Azure/go-autorest/autorest/validation v0.3.1 // indirect github.com/Azure/go-autorest/autorest/validation v0.3.1 // indirect
github.com/alecthomas/units v0.0.0-20210208195552-ff826a37aa15 github.com/alecthomas/units v0.0.0-20210912230133-d1bdfacee922
github.com/aws/aws-sdk-go v1.40.37 github.com/aws/aws-sdk-go v1.40.37
github.com/cespare/xxhash/v2 v2.1.2 github.com/cespare/xxhash/v2 v2.1.2
github.com/containerd/containerd v1.5.4 // indirect github.com/containerd/containerd v1.5.4 // indirect

3
go.sum
View file

@ -151,8 +151,9 @@ github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuy
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
github.com/alecthomas/units v0.0.0-20210208195552-ff826a37aa15 h1:AUNCr9CiJuwrRYS3XieqF+Z9B9gNxo/eANAJCF2eiN4=
github.com/alecthomas/units v0.0.0-20210208195552-ff826a37aa15/go.mod h1:OMCwj8VM1Kc9e19TLln2VL61YJF0x1XFtfdL4JdbSyE= github.com/alecthomas/units v0.0.0-20210208195552-ff826a37aa15/go.mod h1:OMCwj8VM1Kc9e19TLln2VL61YJF0x1XFtfdL4JdbSyE=
github.com/alecthomas/units v0.0.0-20210912230133-d1bdfacee922 h1:8ypNbf5sd3Sm3cKJ9waOGoQv6dKAFiFty9L6NP1AqJ4=
github.com/alecthomas/units v0.0.0-20210912230133-d1bdfacee922/go.mod h1:OMCwj8VM1Kc9e19TLln2VL61YJF0x1XFtfdL4JdbSyE=
github.com/alexflint/go-filemutex v0.0.0-20171022225611-72bdc8eae2ae/go.mod h1:CgnQgUtFrFz9mxFNtED3jI5tLDjKlOM+oUF/sTk6ps0= github.com/alexflint/go-filemutex v0.0.0-20171022225611-72bdc8eae2ae/go.mod h1:CgnQgUtFrFz9mxFNtED3jI5tLDjKlOM+oUF/sTk6ps0=
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8=
github.com/antihax/optional v0.0.0-20180407024304-ca021399b1a6/go.mod h1:V8iCPQYkqmusNa815XgQio277wI47sdRh1dUOLdyC6Q= github.com/antihax/optional v0.0.0-20180407024304-ca021399b1a6/go.mod h1:V8iCPQYkqmusNa815XgQio277wI47sdRh1dUOLdyC6Q=

View file

@ -71,7 +71,7 @@ func BenchmarkRangeQuery(b *testing.B) {
a := storage.Appender(context.Background()) a := storage.Appender(context.Background())
ts := int64(s * 10000) // 10s interval. ts := int64(s * 10000) // 10s interval.
for i, metric := range metrics { for i, metric := range metrics {
ref, _ := a.Append(refs[i], metric, ts, float64(s)) ref, _ := a.Append(refs[i], metric, ts, float64(s)+float64(i)/float64(len(metrics)))
refs[i] = ref refs[i] = ref
} }
if err := a.Commit(); err != nil { if err := a.Commit(); err != nil {
@ -130,6 +130,9 @@ func BenchmarkRangeQuery(b *testing.B) {
{ {
expr: "a_X unless b_X{l=~'.*[0-4]$'}", expr: "a_X unless b_X{l=~'.*[0-4]$'}",
}, },
{
expr: "a_X and b_X{l='notfound'}",
},
// Simple functions. // Simple functions.
{ {
expr: "abs(a_X)", expr: "abs(a_X)",
@ -159,6 +162,9 @@ func BenchmarkRangeQuery(b *testing.B) {
{ {
expr: "count_values('value', h_X)", expr: "count_values('value', h_X)",
}, },
{
expr: "topk(1, a_X)",
},
// Combinations. // Combinations.
{ {
expr: "rate(a_X[1m]) + rate(b_X[1m])", expr: "rate(a_X[1m]) + rate(b_X[1m])",
@ -172,6 +178,10 @@ func BenchmarkRangeQuery(b *testing.B) {
{ {
expr: "histogram_quantile(0.9, rate(h_X[5m]))", expr: "histogram_quantile(0.9, rate(h_X[5m]))",
}, },
// Many-to-one join.
{
expr: "a_X + on(l) group_right a_one",
},
} }
// X in an expr will be replaced by different metric sizes. // X in an expr will be replaced by different metric sizes.

View file

@ -913,6 +913,8 @@ func (ev *evaluator) Eval(expr parser.Expr) (v parser.Value, ws storage.Warnings
type EvalSeriesHelper struct { type EvalSeriesHelper struct {
// The grouping key used by aggregation. // The grouping key used by aggregation.
groupingKey uint64 groupingKey uint64
// Used to map left-hand to right-hand in binary operations.
signature string
} }
// EvalNodeHelper stores extra information and caches for evaluating a single node across steps. // EvalNodeHelper stores extra information and caches for evaluating a single node across steps.
@ -925,8 +927,6 @@ type EvalNodeHelper struct {
// Caches. // Caches.
// DropMetricName and label_*. // DropMetricName and label_*.
Dmn map[uint64]labels.Labels Dmn map[uint64]labels.Labels
// signatureFunc.
sigf map[string]string
// funcHistogramQuantile. // funcHistogramQuantile.
signatureToMetricWithBuckets map[string]*metricWithBuckets signatureToMetricWithBuckets map[string]*metricWithBuckets
// label_replace. // label_replace.
@ -957,23 +957,6 @@ func (enh *EvalNodeHelper) DropMetricName(l labels.Labels) labels.Labels {
return ret return ret
} }
func (enh *EvalNodeHelper) signatureFunc(on bool, names ...string) func(labels.Labels) string {
if enh.sigf == nil {
enh.sigf = make(map[string]string, len(enh.Out))
}
f := signatureFunc(on, enh.lblBuf, names...)
return func(l labels.Labels) string {
enh.lblBuf = l.Bytes(enh.lblBuf)
ret, ok := enh.sigf[string(enh.lblBuf)]
if ok {
return ret
}
ret = f(l)
enh.sigf[string(enh.lblBuf)] = ret
return ret
}
}
// rangeEval evaluates the given expressions, and then for each step calls // rangeEval evaluates the given expressions, and then for each step calls
// the given funcCall with the values computed for each expression at that // the given funcCall with the values computed for each expression at that
// step. The return value is the combination into time series of all the // step. The return value is the combination into time series of all the
@ -1432,22 +1415,28 @@ func (ev *evaluator) eval(expr parser.Expr) (parser.Value, storage.Warnings) {
return append(enh.Out, Sample{Point: Point{V: val}}), nil return append(enh.Out, Sample{Point: Point{V: val}}), nil
}, e.LHS, e.RHS) }, e.LHS, e.RHS)
case lt == parser.ValueTypeVector && rt == parser.ValueTypeVector: case lt == parser.ValueTypeVector && rt == parser.ValueTypeVector:
// Function to compute the join signature for each series.
buf := make([]byte, 0, 1024)
sigf := signatureFunc(e.VectorMatching.On, buf, e.VectorMatching.MatchingLabels...)
initSignatures := func(series labels.Labels, h *EvalSeriesHelper) {
h.signature = sigf(series)
}
switch e.Op { switch e.Op {
case parser.LAND: case parser.LAND:
return ev.rangeEval(nil, func(v []parser.Value, _ [][]EvalSeriesHelper, enh *EvalNodeHelper) (Vector, storage.Warnings) { return ev.rangeEval(initSignatures, func(v []parser.Value, sh [][]EvalSeriesHelper, enh *EvalNodeHelper) (Vector, storage.Warnings) {
return ev.VectorAnd(v[0].(Vector), v[1].(Vector), e.VectorMatching, enh), nil return ev.VectorAnd(v[0].(Vector), v[1].(Vector), e.VectorMatching, sh[0], sh[1], enh), nil
}, e.LHS, e.RHS) }, e.LHS, e.RHS)
case parser.LOR: case parser.LOR:
return ev.rangeEval(nil, func(v []parser.Value, _ [][]EvalSeriesHelper, enh *EvalNodeHelper) (Vector, storage.Warnings) { return ev.rangeEval(initSignatures, func(v []parser.Value, sh [][]EvalSeriesHelper, enh *EvalNodeHelper) (Vector, storage.Warnings) {
return ev.VectorOr(v[0].(Vector), v[1].(Vector), e.VectorMatching, enh), nil return ev.VectorOr(v[0].(Vector), v[1].(Vector), e.VectorMatching, sh[0], sh[1], enh), nil
}, e.LHS, e.RHS) }, e.LHS, e.RHS)
case parser.LUNLESS: case parser.LUNLESS:
return ev.rangeEval(nil, func(v []parser.Value, _ [][]EvalSeriesHelper, enh *EvalNodeHelper) (Vector, storage.Warnings) { return ev.rangeEval(initSignatures, func(v []parser.Value, sh [][]EvalSeriesHelper, enh *EvalNodeHelper) (Vector, storage.Warnings) {
return ev.VectorUnless(v[0].(Vector), v[1].(Vector), e.VectorMatching, enh), nil return ev.VectorUnless(v[0].(Vector), v[1].(Vector), e.VectorMatching, sh[0], sh[1], enh), nil
}, e.LHS, e.RHS) }, e.LHS, e.RHS)
default: default:
return ev.rangeEval(nil, func(v []parser.Value, _ [][]EvalSeriesHelper, enh *EvalNodeHelper) (Vector, storage.Warnings) { return ev.rangeEval(initSignatures, func(v []parser.Value, sh [][]EvalSeriesHelper, enh *EvalNodeHelper) (Vector, storage.Warnings) {
return ev.VectorBinop(e.Op, v[0].(Vector), v[1].(Vector), e.VectorMatching, e.ReturnBool, enh), nil return ev.VectorBinop(e.Op, v[0].(Vector), v[1].(Vector), e.VectorMatching, e.ReturnBool, sh[0], sh[1], enh), nil
}, e.LHS, e.RHS) }, e.LHS, e.RHS)
} }
@ -1774,62 +1763,72 @@ func (ev *evaluator) matrixIterSlice(it *storage.BufferedSeriesIterator, mint, m
return out return out
} }
func (ev *evaluator) VectorAnd(lhs, rhs Vector, matching *parser.VectorMatching, enh *EvalNodeHelper) Vector { func (ev *evaluator) VectorAnd(lhs, rhs Vector, matching *parser.VectorMatching, lhsh, rhsh []EvalSeriesHelper, enh *EvalNodeHelper) Vector {
if matching.Card != parser.CardManyToMany { if matching.Card != parser.CardManyToMany {
panic("set operations must only use many-to-many matching") panic("set operations must only use many-to-many matching")
} }
sigf := enh.signatureFunc(matching.On, matching.MatchingLabels...) if len(lhs) == 0 || len(rhs) == 0 {
return nil // Short-circuit: AND with nothing is nothing.
}
// The set of signatures for the right-hand side Vector. // The set of signatures for the right-hand side Vector.
rightSigs := map[string]struct{}{} rightSigs := map[string]struct{}{}
// Add all rhs samples to a map so we can easily find matches later. // Add all rhs samples to a map so we can easily find matches later.
for _, rs := range rhs { for _, sh := range rhsh {
rightSigs[sigf(rs.Metric)] = struct{}{} rightSigs[sh.signature] = struct{}{}
} }
for _, ls := range lhs { for i, ls := range lhs {
// If there's a matching entry in the right-hand side Vector, add the sample. // If there's a matching entry in the right-hand side Vector, add the sample.
if _, ok := rightSigs[sigf(ls.Metric)]; ok { if _, ok := rightSigs[lhsh[i].signature]; ok {
enh.Out = append(enh.Out, ls) enh.Out = append(enh.Out, ls)
} }
} }
return enh.Out return enh.Out
} }
func (ev *evaluator) VectorOr(lhs, rhs Vector, matching *parser.VectorMatching, enh *EvalNodeHelper) Vector { func (ev *evaluator) VectorOr(lhs, rhs Vector, matching *parser.VectorMatching, lhsh, rhsh []EvalSeriesHelper, enh *EvalNodeHelper) Vector {
if matching.Card != parser.CardManyToMany { if matching.Card != parser.CardManyToMany {
panic("set operations must only use many-to-many matching") panic("set operations must only use many-to-many matching")
} }
sigf := enh.signatureFunc(matching.On, matching.MatchingLabels...) if len(lhs) == 0 { // Short-circuit.
return rhs
} else if len(rhs) == 0 {
return lhs
}
leftSigs := map[string]struct{}{} leftSigs := map[string]struct{}{}
// Add everything from the left-hand-side Vector. // Add everything from the left-hand-side Vector.
for _, ls := range lhs { for i, ls := range lhs {
leftSigs[sigf(ls.Metric)] = struct{}{} leftSigs[lhsh[i].signature] = struct{}{}
enh.Out = append(enh.Out, ls) enh.Out = append(enh.Out, ls)
} }
// Add all right-hand side elements which have not been added from the left-hand side. // Add all right-hand side elements which have not been added from the left-hand side.
for _, rs := range rhs { for j, rs := range rhs {
if _, ok := leftSigs[sigf(rs.Metric)]; !ok { if _, ok := leftSigs[rhsh[j].signature]; !ok {
enh.Out = append(enh.Out, rs) enh.Out = append(enh.Out, rs)
} }
} }
return enh.Out return enh.Out
} }
func (ev *evaluator) VectorUnless(lhs, rhs Vector, matching *parser.VectorMatching, enh *EvalNodeHelper) Vector { func (ev *evaluator) VectorUnless(lhs, rhs Vector, matching *parser.VectorMatching, lhsh, rhsh []EvalSeriesHelper, enh *EvalNodeHelper) Vector {
if matching.Card != parser.CardManyToMany { if matching.Card != parser.CardManyToMany {
panic("set operations must only use many-to-many matching") panic("set operations must only use many-to-many matching")
} }
sigf := enh.signatureFunc(matching.On, matching.MatchingLabels...) // Short-circuit: empty rhs means we will return everything in lhs;
// empty lhs means we will return empty - don't need to build a map.
rightSigs := map[string]struct{}{} if len(lhs) == 0 || len(rhs) == 0 {
for _, rs := range rhs { return lhs
rightSigs[sigf(rs.Metric)] = struct{}{}
} }
for _, ls := range lhs { rightSigs := map[string]struct{}{}
if _, ok := rightSigs[sigf(ls.Metric)]; !ok { for _, sh := range rhsh {
rightSigs[sh.signature] = struct{}{}
}
for i, ls := range lhs {
if _, ok := rightSigs[lhsh[i].signature]; !ok {
enh.Out = append(enh.Out, ls) enh.Out = append(enh.Out, ls)
} }
} }
@ -1837,17 +1836,20 @@ func (ev *evaluator) VectorUnless(lhs, rhs Vector, matching *parser.VectorMatchi
} }
// VectorBinop evaluates a binary operation between two Vectors, excluding set operators. // VectorBinop evaluates a binary operation between two Vectors, excluding set operators.
func (ev *evaluator) VectorBinop(op parser.ItemType, lhs, rhs Vector, matching *parser.VectorMatching, returnBool bool, enh *EvalNodeHelper) Vector { func (ev *evaluator) VectorBinop(op parser.ItemType, lhs, rhs Vector, matching *parser.VectorMatching, returnBool bool, lhsh, rhsh []EvalSeriesHelper, enh *EvalNodeHelper) Vector {
if matching.Card == parser.CardManyToMany { if matching.Card == parser.CardManyToMany {
panic("many-to-many only allowed for set operators") panic("many-to-many only allowed for set operators")
} }
sigf := enh.signatureFunc(matching.On, matching.MatchingLabels...) if len(lhs) == 0 || len(rhs) == 0 {
return nil // Short-circuit: nothing is going to match.
}
// The control flow below handles one-to-one or many-to-one matching. // The control flow below handles one-to-one or many-to-one matching.
// For one-to-many, swap sidedness and account for the swap when calculating // For one-to-many, swap sidedness and account for the swap when calculating
// values. // values.
if matching.Card == parser.CardOneToMany { if matching.Card == parser.CardOneToMany {
lhs, rhs = rhs, lhs lhs, rhs = rhs, lhs
lhsh, rhsh = rhsh, lhsh
} }
// All samples from the rhs hashed by the matching label/values. // All samples from the rhs hashed by the matching label/values.
@ -1861,8 +1863,8 @@ func (ev *evaluator) VectorBinop(op parser.ItemType, lhs, rhs Vector, matching *
rightSigs := enh.rightSigs rightSigs := enh.rightSigs
// Add all rhs samples to a map so we can easily find matches later. // Add all rhs samples to a map so we can easily find matches later.
for _, rs := range rhs { for i, rs := range rhs {
sig := sigf(rs.Metric) sig := rhsh[i].signature
// The rhs is guaranteed to be the 'one' side. Having multiple samples // The rhs is guaranteed to be the 'one' side. Having multiple samples
// with the same signature means that the matching is many-to-many. // with the same signature means that the matching is many-to-many.
if duplSample, found := rightSigs[sig]; found { if duplSample, found := rightSigs[sig]; found {
@ -1892,8 +1894,8 @@ func (ev *evaluator) VectorBinop(op parser.ItemType, lhs, rhs Vector, matching *
// For all lhs samples find a respective rhs sample and perform // For all lhs samples find a respective rhs sample and perform
// the binary operation. // the binary operation.
for _, ls := range lhs { for i, ls := range lhs {
sig := sigf(ls.Metric) sig := lhsh[i].signature
rs, found := rightSigs[sig] // Look for a match in the rhs Vector. rs, found := rightSigs[sig] // Look for a match in the rhs Vector.
if !found { if !found {
@ -2210,22 +2212,24 @@ func (ev *evaluator) aggregation(op parser.ItemType, grouping []string, without
resultSize := k resultSize := k
if k > inputVecLen { if k > inputVecLen {
resultSize = inputVecLen resultSize = inputVecLen
} else if k == 0 {
resultSize = 1
} }
switch op { switch op {
case parser.STDVAR, parser.STDDEV: case parser.STDVAR, parser.STDDEV:
result[groupingKey].value = 0 result[groupingKey].value = 0
case parser.TOPK, parser.QUANTILE: case parser.TOPK, parser.QUANTILE:
result[groupingKey].heap = make(vectorByValueHeap, 0, resultSize) result[groupingKey].heap = make(vectorByValueHeap, 1, resultSize)
heap.Push(&result[groupingKey].heap, &Sample{ result[groupingKey].heap[0] = Sample{
Point: Point{V: s.V}, Point: Point{V: s.V},
Metric: s.Metric, Metric: s.Metric,
}) }
case parser.BOTTOMK: case parser.BOTTOMK:
result[groupingKey].reverseHeap = make(vectorByReverseValueHeap, 0, resultSize) result[groupingKey].reverseHeap = make(vectorByReverseValueHeap, 1, resultSize)
heap.Push(&result[groupingKey].reverseHeap, &Sample{ result[groupingKey].reverseHeap[0] = Sample{
Point: Point{V: s.V}, Point: Point{V: s.V},
Metric: s.Metric, Metric: s.Metric,
}) }
case parser.GROUP: case parser.GROUP:
result[groupingKey].value = 1 result[groupingKey].value = 1
} }
@ -2283,6 +2287,13 @@ func (ev *evaluator) aggregation(op parser.ItemType, grouping []string, without
case parser.TOPK: case parser.TOPK:
if int64(len(group.heap)) < k || group.heap[0].V < s.V || math.IsNaN(group.heap[0].V) { if int64(len(group.heap)) < k || group.heap[0].V < s.V || math.IsNaN(group.heap[0].V) {
if int64(len(group.heap)) == k { if int64(len(group.heap)) == k {
if k == 1 { // For k==1 we can replace in-situ.
group.heap[0] = Sample{
Point: Point{V: s.V},
Metric: s.Metric,
}
break
}
heap.Pop(&group.heap) heap.Pop(&group.heap)
} }
heap.Push(&group.heap, &Sample{ heap.Push(&group.heap, &Sample{
@ -2294,6 +2305,13 @@ func (ev *evaluator) aggregation(op parser.ItemType, grouping []string, without
case parser.BOTTOMK: case parser.BOTTOMK:
if int64(len(group.reverseHeap)) < k || group.reverseHeap[0].V > s.V || math.IsNaN(group.reverseHeap[0].V) { if int64(len(group.reverseHeap)) < k || group.reverseHeap[0].V > s.V || math.IsNaN(group.reverseHeap[0].V) {
if int64(len(group.reverseHeap)) == k { if int64(len(group.reverseHeap)) == k {
if k == 1 { // For k==1 we can replace in-situ.
group.reverseHeap[0] = Sample{
Point: Point{V: s.V},
Metric: s.Metric,
}
break
}
heap.Pop(&group.reverseHeap) heap.Pop(&group.reverseHeap)
} }
heap.Push(&group.reverseHeap, &Sample{ heap.Push(&group.reverseHeap, &Sample{
@ -2327,7 +2345,9 @@ func (ev *evaluator) aggregation(op parser.ItemType, grouping []string, without
case parser.TOPK: case parser.TOPK:
// The heap keeps the lowest value on top, so reverse it. // The heap keeps the lowest value on top, so reverse it.
sort.Sort(sort.Reverse(aggr.heap)) if len(aggr.heap) > 1 {
sort.Sort(sort.Reverse(aggr.heap))
}
for _, v := range aggr.heap { for _, v := range aggr.heap {
enh.Out = append(enh.Out, Sample{ enh.Out = append(enh.Out, Sample{
Metric: v.Metric, Metric: v.Metric,
@ -2338,7 +2358,9 @@ func (ev *evaluator) aggregation(op parser.ItemType, grouping []string, without
case parser.BOTTOMK: case parser.BOTTOMK:
// The heap keeps the highest value on top, so reverse it. // The heap keeps the highest value on top, so reverse it.
sort.Sort(sort.Reverse(aggr.reverseHeap)) if len(aggr.reverseHeap) > 1 {
sort.Sort(sort.Reverse(aggr.reverseHeap))
}
for _, v := range aggr.reverseHeap { for _, v := range aggr.reverseHeap {
enh.Out = append(enh.Out, Sample{ enh.Out = append(enh.Out, Sample{
Metric: v.Metric, Metric: v.Metric,

View file

@ -8,7 +8,10 @@
"workspaces": [ "workspaces": [
"react-app", "react-app",
"module/*" "module/*"
] ],
"engines": {
"npm": ">=7.0.0"
}
}, },
"module/codemirror-promql": { "module/codemirror-promql": {
"version": "0.18.0", "version": "0.18.0",

View file

@ -12,5 +12,8 @@
"workspaces": [ "workspaces": [
"react-app", "react-app",
"module/*" "module/*"
] ],
"engines": {
"npm": ">=7.0.0"
}
} }