diff --git a/RELEASE.md b/RELEASE.md index ff89dad1e7..bdba363771 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -37,7 +37,9 @@ Release cadence of first pre-releases being cut is 6 weeks. | v2.30 | 2021-09-08 | Ganesh Vernekar (GitHub: @codesome) | | v2.31 | 2021-10-20 | Julien Pivotto (GitHub: @roidelapluie) | | v2.32 | 2021-12-01 | Julius Volz (GitHub: @juliusv) | -| v2.33 | 2022-01-12 | **searching for volunteer** | +| v2.33 | 2022-01-12 | Björn Rabenstein (GitHub: @beorn7) | +| v2.34 | 2022-02-23 | Chris Marchbanks (GitHub: @csmarchbanks) | +| v2.35 | 2022-04-06 | **searching for volunteer** | If you are interested in volunteering please create a pull request against the [prometheus/prometheus](https://github.com/prometheus/prometheus) repository and propose yourself for the release series of your choice. @@ -70,7 +72,7 @@ If a bug fix got accidentally merged into main after non-bug-fix changes in main Maintaining the release branches for older minor releases happens on a best effort basis. -### 0. Updating dependencies +### 0. Updating dependencies and promoting/demoting experimental features A few days before a major or minor release, consider updating the dependencies. @@ -85,6 +87,10 @@ you can skip the dependency update or only update select dependencies. In such a case, you have to create an issue or pull request in the GitHub project for later follow-up. +This is also a good time to consider any experimental features and feature +flags for promotion to stable or for deprecation or ultimately removal. Do any +of these in pull requests, one per feature. + #### Updating Go dependencies ``` @@ -155,3 +161,5 @@ For release candidate versions (`v2.16.0-rc.0`), run the benchmark for 3 days us If the release has happened in the latest release branch, merge the changes into main. Once the binaries have been uploaded, announce the release on `prometheus-announce@googlegroups.com`. (Please do not use `prometheus-users@googlegroups.com` for announcements anymore.) Check out previous announcement mails for inspiration. + +Finally, in case there is no release shepherd listed for the next release yet, find a volunteer. diff --git a/discovery/legacymanager/manager_test.go b/discovery/legacymanager/manager_test.go index ce2278d231..57c82b72a8 100644 --- a/discovery/legacymanager/manager_test.go +++ b/discovery/legacymanager/manager_test.go @@ -668,13 +668,16 @@ func TestTargetUpdatesOrder(t *testing.T) { discoveryManager.updatert = 100 * time.Millisecond var totalUpdatesCount int - provUpdates := make(chan []*targetgroup.Group) for _, up := range tc.updates { - go newMockDiscoveryProvider(up...).Run(ctx, provUpdates) if len(up) > 0 { totalUpdatesCount += len(up) } } + provUpdates := make(chan []*targetgroup.Group, totalUpdatesCount) + + for _, up := range tc.updates { + go newMockDiscoveryProvider(up...).Run(ctx, provUpdates) + } for x := 0; x < totalUpdatesCount; x++ { select { diff --git a/discovery/manager_test.go b/discovery/manager_test.go index 80ea1008e4..970168b0f5 100644 --- a/discovery/manager_test.go +++ b/discovery/manager_test.go @@ -668,13 +668,16 @@ func TestTargetUpdatesOrder(t *testing.T) { discoveryManager.updatert = 100 * time.Millisecond var totalUpdatesCount int - provUpdates := make(chan []*targetgroup.Group) for _, up := range tc.updates { - go newMockDiscoveryProvider(up...).Run(ctx, provUpdates) if len(up) > 0 { totalUpdatesCount += len(up) } } + provUpdates := make(chan []*targetgroup.Group, totalUpdatesCount) + + for _, up := range tc.updates { + go newMockDiscoveryProvider(up...).Run(ctx, provUpdates) + } for x := 0; x < totalUpdatesCount; x++ { select { diff --git a/go.mod b/go.mod index 2c3227a5f5..5b9095633e 100644 --- a/go.mod +++ b/go.mod @@ -3,13 +3,13 @@ module github.com/prometheus/prometheus go 1.16 require ( - github.com/Azure/azure-sdk-for-go v60.3.0+incompatible + github.com/Azure/azure-sdk-for-go v61.1.0+incompatible github.com/Azure/go-autorest/autorest v0.11.23 github.com/Azure/go-autorest/autorest/adal v0.9.18 github.com/Azure/go-autorest/autorest/to v0.4.0 // indirect github.com/Azure/go-autorest/autorest/validation v0.3.1 // indirect - github.com/alecthomas/units v0.0.0-20210927113745-59d0afb8317a - github.com/aws/aws-sdk-go v1.42.25 + github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137 + github.com/aws/aws-sdk-go v1.42.28 github.com/cespare/xxhash/v2 v2.1.2 github.com/containerd/containerd v1.5.7 // indirect github.com/dennwc/varint v1.0.0 @@ -27,7 +27,7 @@ require ( github.com/go-zookeeper/zk v1.0.2 github.com/gogo/protobuf v1.3.2 github.com/golang/snappy v0.0.4 - github.com/google/pprof v0.0.0-20211122183932-1daafda22083 + github.com/google/pprof v0.0.0-20211214055906-6f57359322fd github.com/gophercloud/gophercloud v0.24.0 github.com/grpc-ecosystem/grpc-gateway v1.16.0 github.com/hashicorp/consul/api v1.12.0 @@ -59,14 +59,14 @@ require ( github.com/uber/jaeger-lib v2.4.1+incompatible go.uber.org/atomic v1.9.0 go.uber.org/goleak v1.1.12 - golang.org/x/net v0.0.0-20211209124913-491a49abca63 + golang.org/x/net v0.0.0-20220105145211-5b0dc2dfae98 golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 golang.org/x/sync v0.0.0-20210220032951-036812b2e83c golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e golang.org/x/time v0.0.0-20211116232009-f0f3c7e86c11 golang.org/x/tools v0.1.8 google.golang.org/api v0.63.0 - google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa + google.golang.org/genproto v0.0.0-20211223182754-3ac035c7e7cb google.golang.org/protobuf v1.27.1 gopkg.in/alecthomas/kingpin.v2 v2.2.6 gopkg.in/yaml.v2 v2.4.0 @@ -75,7 +75,7 @@ require ( k8s.io/apimachinery v0.22.4 k8s.io/client-go v0.22.4 k8s.io/klog v1.0.0 - k8s.io/klog/v2 v2.30.0 + k8s.io/klog/v2 v2.40.1 ) replace ( diff --git a/go.sum b/go.sum index 85508d00ab..fec2cfed30 100644 --- a/go.sum +++ b/go.sum @@ -52,8 +52,8 @@ collectd.org v0.3.0/go.mod h1:A/8DzQBkF6abtvrT2j/AU/4tiBgJWYyh0y/oB/4MlWE= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/Azure/azure-sdk-for-go v16.2.1+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= github.com/Azure/azure-sdk-for-go v41.3.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= -github.com/Azure/azure-sdk-for-go v60.3.0+incompatible h1:u6EXgnASaUOh38GXCwEpRs+u2bbfJpJpXeB42kU2cjg= -github.com/Azure/azure-sdk-for-go v60.3.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= +github.com/Azure/azure-sdk-for-go v61.1.0+incompatible h1:Qbz3jdfkXIPjZECEuk2E7i3iLhC9Ul74pG5mQRQC+z4= +github.com/Azure/azure-sdk-for-go v61.1.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8= github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= @@ -154,8 +154,8 @@ github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRF 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-20210208195552-ff826a37aa15/go.mod h1:OMCwj8VM1Kc9e19TLln2VL61YJF0x1XFtfdL4JdbSyE= -github.com/alecthomas/units v0.0.0-20210927113745-59d0afb8317a h1:E/8AP5dFtMhl5KPJz66Kt9G0n+7Sn41Fy1wv9/jHOrc= -github.com/alecthomas/units v0.0.0-20210927113745-59d0afb8317a/go.mod h1:OMCwj8VM1Kc9e19TLln2VL61YJF0x1XFtfdL4JdbSyE= +github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137 h1:s6gZFSlWYmbqAuRjVTiNNhvNRfY2Wxp9nhfyel4rklc= +github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137/go.mod h1:OMCwj8VM1Kc9e19TLln2VL61YJF0x1XFtfdL4JdbSyE= 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/antihax/optional v0.0.0-20180407024304-ca021399b1a6/go.mod h1:V8iCPQYkqmusNa815XgQio277wI47sdRh1dUOLdyC6Q= @@ -188,8 +188,8 @@ github.com/aws/aws-sdk-go v1.30.12/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZve github.com/aws/aws-sdk-go v1.34.28/go.mod h1:H7NKnBqNVzoTJpGfLrQkkD+ytBA93eiDYi/+8rV9s48= github.com/aws/aws-sdk-go v1.38.35/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro= github.com/aws/aws-sdk-go v1.40.11/go.mod h1:585smgzpB/KqRA+K3y/NL/oYRqQvpNJYvLm+LY1U59Q= -github.com/aws/aws-sdk-go v1.42.25 h1:BbdvHAi+t9LRiaYUyd53noq9jcaAcfzOhSVbKfr6Avs= -github.com/aws/aws-sdk-go v1.42.25/go.mod h1:gyRszuZ/icHmHAVE4gc/r+cfCmhA1AD+vqfWbgI+eHs= +github.com/aws/aws-sdk-go v1.42.28 h1:YJtgL7IGSN41saY4JLW08jya5nU0vGcuAeAa1OL2M6c= +github.com/aws/aws-sdk-go v1.42.28/go.mod h1:OGr6lGMAKGlG9CVrYnWYDKIyb829c6EVBRjxqjmPepc= github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g= github.com/benbjohnson/immutable v0.2.1/go.mod h1:uc6OHo6PN2++n98KHLxW8ef4W42ylHiQSENghE1ezxI= github.com/benbjohnson/tmpl v1.0.0/go.mod h1:igT620JFIi44B6awvU9IsDhR77IXWtFigTLil/RPdps= @@ -719,8 +719,8 @@ github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLe github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20211122183932-1daafda22083 h1:c8EUapQFi+kjzedr4c6WqbwMdmB95+oDBWZ5XFHFYxY= -github.com/google/pprof v0.0.0-20211122183932-1daafda22083/go.mod h1:KgnwoLYCZ8IQu3XUZ8Nc/bM9CCZFOyjUNOSygVozoDg= +github.com/google/pprof v0.0.0-20211214055906-6f57359322fd h1:1FjCyPC+syAzJ5/2S8fqdZK1R22vvA0J7JZKcuOIQ7Y= +github.com/google/pprof v0.0.0-20211214055906-6f57359322fd/go.mod h1:KgnwoLYCZ8IQu3XUZ8Nc/bM9CCZFOyjUNOSygVozoDg= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= @@ -1534,8 +1534,9 @@ golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qx golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20211209124913-491a49abca63 h1:iocB37TsdFuN6IBRZ+ry36wrkoV51/tl5vOWqkcPGvY= -golang.org/x/net v0.0.0-20211209124913-491a49abca63/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211216030914-fe4d6282115f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220105145211-5b0dc2dfae98 h1:+6WJMRLHlD7X7frgp7TUZ36RnQzSf9wVVTNakEp+nqY= +golang.org/x/net v0.0.0-20220105145211-5b0dc2dfae98/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -1906,8 +1907,9 @@ google.golang.org/genproto v0.0.0-20210909211513-a8c4777a87af/go.mod h1:eFjDcFEc google.golang.org/genproto v0.0.0-20210924002016-3dee208752a0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/genproto v0.0.0-20211206160659-862468c7d6e0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa h1:I0YcKz0I7OAhddo7ya8kMnvprhcWM045PmkBdMO9zN0= google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211223182754-3ac035c7e7cb h1:ZrsicilzPCS/Xr8qtBZZLpy4P9TYXAfl49ctG1/5tgw= +google.golang.org/genproto v0.0.0-20211223182754-3ac035c7e7cb/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/grpc v0.0.0-20160317175043-d3ddb4469d5a/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= diff --git a/storage/remote/queue_manager.go b/storage/remote/queue_manager.go index 49de2c8a35..e4a2b5399c 100644 --- a/storage/remote/queue_manager.go +++ b/storage/remote/queue_manager.go @@ -15,6 +15,7 @@ package remote import ( "context" + "errors" "math" "strconv" "sync" @@ -1311,12 +1312,16 @@ func (s *shards) sendSamplesWithBackoff(ctx context.Context, samples []prompb.Ti } err = sendWriteRequestWithBackoff(ctx, s.qm.cfg, s.qm.logger, attemptStore, onRetry) - if err != nil { + if errors.Is(err, context.Canceled) { + // When there is resharding, we cancel the context for this queue, which means the data is not sent. + // So we exit early to not update the metrics. return err } + s.qm.metrics.sentBytesTotal.Add(float64(reqSize)) s.qm.metrics.highestSentTimestamp.Set(float64(highest / 1000)) - return nil + + return err } func sendWriteRequestWithBackoff(ctx context.Context, cfg config.QueueConfig, l log.Logger, attempt func(int) error, onRetry func()) error { diff --git a/tsdb/exemplar.go b/tsdb/exemplar.go index 516b538e18..3718d9591d 100644 --- a/tsdb/exemplar.go +++ b/tsdb/exemplar.go @@ -27,8 +27,12 @@ import ( "github.com/prometheus/prometheus/storage" ) -// Indicates that there is no index entry for an exmplar. -const noExemplar = -1 +const ( + // Indicates that there is no index entry for an exmplar. + noExemplar = -1 + // Estimated number of exemplars per series, for sizing the index. + estimatedExemplarsPerSeries = 16 +) type CircularExemplarStorage struct { lock sync.RWMutex @@ -117,7 +121,7 @@ func NewCircularExemplarStorage(len int64, m *ExemplarMetrics) (ExemplarStorage, } c := &CircularExemplarStorage{ exemplars: make([]*circularBufferEntry, len), - index: make(map[string]*indexEntry), + index: make(map[string]*indexEntry, len/estimatedExemplarsPerSeries), metrics: m, } @@ -202,7 +206,8 @@ Outer: } func (ce *CircularExemplarStorage) ValidateExemplar(l labels.Labels, e exemplar.Exemplar) error { - seriesLabels := l.String() + var buf [1024]byte + seriesLabels := l.Bytes(buf[:]) // TODO(bwplotka): This lock can lock all scrapers, there might high contention on this on scale. // Optimize by moving the lock to be per series (& benchmark it). @@ -213,7 +218,7 @@ func (ce *CircularExemplarStorage) ValidateExemplar(l labels.Labels, e exemplar. // Not thread safe. The append parameters tells us whether this is an external validation, or internal // as a result of an AddExemplar call, in which case we should update any relevant metrics. -func (ce *CircularExemplarStorage) validateExemplar(l string, e exemplar.Exemplar, append bool) error { +func (ce *CircularExemplarStorage) validateExemplar(key []byte, e exemplar.Exemplar, append bool) error { if len(ce.exemplars) <= 0 { return storage.ErrExemplarsDisabled } @@ -230,7 +235,7 @@ func (ce *CircularExemplarStorage) validateExemplar(l string, e exemplar.Exempla } } - idx, ok := ce.index[l] + idx, ok := ce.index[string(key)] if !ok { return nil } @@ -269,7 +274,7 @@ func (ce *CircularExemplarStorage) Resize(l int64) int { oldNextIndex := int64(ce.nextIndex) ce.exemplars = make([]*circularBufferEntry, l) - ce.index = make(map[string]*indexEntry) + ce.index = make(map[string]*indexEntry, l/estimatedExemplarsPerSeries) ce.nextIndex = 0 // Replay as many entries as needed, starting with oldest first. @@ -305,13 +310,14 @@ func (ce *CircularExemplarStorage) Resize(l int64) int { // migrate is like AddExemplar but reuses existing structs. Expected to be called in batch and requires // external lock and does not compute metrics. func (ce *CircularExemplarStorage) migrate(entry *circularBufferEntry) { - seriesLabels := entry.ref.seriesLabels.String() + var buf [1024]byte + seriesLabels := entry.ref.seriesLabels.Bytes(buf[:]) - idx, ok := ce.index[seriesLabels] + idx, ok := ce.index[string(seriesLabels)] if !ok { idx = entry.ref idx.oldest = ce.nextIndex - ce.index[seriesLabels] = idx + ce.index[string(seriesLabels)] = idx } else { entry.ref = idx ce.exemplars[idx.newest].next = ce.nextIndex @@ -329,7 +335,8 @@ func (ce *CircularExemplarStorage) AddExemplar(l labels.Labels, e exemplar.Exemp return storage.ErrExemplarsDisabled } - seriesLabels := l.String() + var buf [1024]byte + seriesLabels := l.Bytes(buf[:]) // TODO(bwplotka): This lock can lock all scrapers, there might high contention on this on scale. // Optimize by moving the lock to be per series (& benchmark it). @@ -345,11 +352,11 @@ func (ce *CircularExemplarStorage) AddExemplar(l labels.Labels, e exemplar.Exemp return err } - _, ok := ce.index[seriesLabels] + _, ok := ce.index[string(seriesLabels)] if !ok { - ce.index[seriesLabels] = &indexEntry{oldest: ce.nextIndex, seriesLabels: l} + ce.index[string(seriesLabels)] = &indexEntry{oldest: ce.nextIndex, seriesLabels: l} } else { - ce.exemplars[ce.index[seriesLabels].newest].next = ce.nextIndex + ce.exemplars[ce.index[string(seriesLabels)].newest].next = ce.nextIndex } if prev := ce.exemplars[ce.nextIndex]; prev == nil { @@ -357,12 +364,13 @@ func (ce *CircularExemplarStorage) AddExemplar(l labels.Labels, e exemplar.Exemp } else { // There exists exemplar already on this ce.nextIndex entry, drop it, to make place // for others. - prevLabels := prev.ref.seriesLabels.String() + var buf [1024]byte + prevLabels := prev.ref.seriesLabels.Bytes(buf[:]) if prev.next == noExemplar { // Last item for this series, remove index entry. - delete(ce.index, prevLabels) + delete(ce.index, string(prevLabels)) } else { - ce.index[prevLabels].oldest = prev.next + ce.index[string(prevLabels)].oldest = prev.next } } @@ -370,8 +378,8 @@ func (ce *CircularExemplarStorage) AddExemplar(l labels.Labels, e exemplar.Exemp // since this is the first exemplar stored for this series. ce.exemplars[ce.nextIndex].next = noExemplar ce.exemplars[ce.nextIndex].exemplar = e - ce.exemplars[ce.nextIndex].ref = ce.index[seriesLabels] - ce.index[seriesLabels].newest = ce.nextIndex + ce.exemplars[ce.nextIndex].ref = ce.index[string(seriesLabels)] + ce.index[string(seriesLabels)].newest = ce.nextIndex ce.nextIndex = (ce.nextIndex + 1) % len(ce.exemplars) diff --git a/tsdb/exemplar_test.go b/tsdb/exemplar_test.go index 1418dcca98..30c497426f 100644 --- a/tsdb/exemplar_test.go +++ b/tsdb/exemplar_test.go @@ -112,7 +112,7 @@ func TestAddExemplar(t *testing.T) { } require.NoError(t, es.AddExemplar(l, e)) - require.Equal(t, es.index[l.String()].newest, 0, "exemplar was not stored correctly") + require.Equal(t, es.index[string(l.Bytes(nil))].newest, 0, "exemplar was not stored correctly") e2 := exemplar.Exemplar{ Labels: labels.Labels{ @@ -126,8 +126,8 @@ func TestAddExemplar(t *testing.T) { } require.NoError(t, es.AddExemplar(l, e2)) - require.Equal(t, es.index[l.String()].newest, 1, "exemplar was not stored correctly, location of newest exemplar for series in index did not update") - require.True(t, es.exemplars[es.index[l.String()].newest].exemplar.Equals(e2), "exemplar was not stored correctly, expected %+v got: %+v", e2, es.exemplars[es.index[l.String()].newest].exemplar) + require.Equal(t, es.index[string(l.Bytes(nil))].newest, 1, "exemplar was not stored correctly, location of newest exemplar for series in index did not update") + require.True(t, es.exemplars[es.index[string(l.Bytes(nil))].newest].exemplar.Equals(e2), "exemplar was not stored correctly, expected %+v got: %+v", e2, es.exemplars[es.index[string(l.Bytes(nil))].newest].exemplar) require.NoError(t, es.AddExemplar(l, e2), "no error is expected attempting to add duplicate exemplar") @@ -300,7 +300,7 @@ func TestSelectExemplar_TimeRange(t *testing.T) { Ts: int64(101 + i), }) require.NoError(t, err) - require.Equal(t, es.index[l.String()].newest, i, "exemplar was not stored correctly") + require.Equal(t, es.index[string(l.Bytes(nil))].newest, i, "exemplar was not stored correctly") } m, err := labels.NewMatcher(labels.MatchEqual, l[0].Name, l[0].Value) @@ -376,14 +376,14 @@ func TestIndexOverwrite(t *testing.T) { // Ensure index GC'ing is taking place, there should no longer be any // index entry for series l1 since we just wrote two exemplars for series l2. - _, ok := es.index[l1.String()] + _, ok := es.index[string(l1.Bytes(nil))] require.False(t, ok) - require.Equal(t, &indexEntry{1, 0, l2}, es.index[l2.String()]) + require.Equal(t, &indexEntry{1, 0, l2}, es.index[string(l2.Bytes(nil))]) err = es.AddExemplar(l1, exemplar.Exemplar{Value: 4, Ts: 4}) require.NoError(t, err) - i := es.index[l2.String()] + i := es.index[string(l2.Bytes(nil))] require.Equal(t, &indexEntry{0, 0, l2}, i) } @@ -492,18 +492,23 @@ func BenchmarkAddExemplar(b *testing.B) { for _, n := range []int{10000, 100000, 1000000} { b.Run(fmt.Sprintf("%d", n), func(b *testing.B) { - exs, err := NewCircularExemplarStorage(int64(n), eMetrics) - require.NoError(b, err) - es := exs.(*CircularExemplarStorage) - - b.ResetTimer() - l := labels.Labels{{Name: "service", Value: strconv.Itoa(0)}} - for i := 0; i < n; i++ { - if i%100 == 0 { - l = labels.Labels{{Name: "service", Value: strconv.Itoa(i)}} - } - err = es.AddExemplar(l, exemplar.Exemplar{Value: float64(i), Ts: int64(i), Labels: exLabels}) + for j := 0; j < b.N; j++ { + b.StopTimer() + exs, err := NewCircularExemplarStorage(int64(n), eMetrics) require.NoError(b, err) + es := exs.(*CircularExemplarStorage) + l := labels.Labels{{Name: "service", Value: strconv.Itoa(0)}} + b.StartTimer() + + for i := 0; i < n; i++ { + if i%100 == 0 { + l = labels.Labels{{Name: "service", Value: strconv.Itoa(i)}} + } + err = es.AddExemplar(l, exemplar.Exemplar{Value: float64(i), Ts: int64(i), Labels: exLabels}) + if err != nil { + require.NoError(b, err) + } + } } }) } @@ -543,24 +548,24 @@ func BenchmarkResizeExemplars(b *testing.B) { } for _, tc := range testCases { - exs, err := NewCircularExemplarStorage(tc.startSize, eMetrics) - require.NoError(b, err) - es := exs.(*CircularExemplarStorage) + b.Run(fmt.Sprintf("%s-%d-to-%d", tc.name, tc.startSize, tc.endSize), func(b *testing.B) { + for j := 0; j < b.N; j++ { + b.StopTimer() + exs, err := NewCircularExemplarStorage(tc.startSize, eMetrics) + require.NoError(b, err) + es := exs.(*CircularExemplarStorage) - for i := 0; i < int(float64(tc.startSize)*float64(1.5)); i++ { - l := labels.FromStrings("service", strconv.Itoa(i)) + for i := 0; i < int(float64(tc.startSize)*float64(1.5)); i++ { + l := labels.FromStrings("service", strconv.Itoa(i)) - err = es.AddExemplar(l, exemplar.Exemplar{Value: float64(i), Ts: int64(i)}) - require.NoError(b, err) - } - saveIndex := es.index - saveExemplars := es.exemplars - - b.Run(fmt.Sprintf("%s-%d-to-%d", tc.name, tc.startSize, tc.endSize), func(t *testing.B) { - es.index = saveIndex - es.exemplars = saveExemplars - b.ResetTimer() - es.Resize(tc.endSize) + err = es.AddExemplar(l, exemplar.Exemplar{Value: float64(i), Ts: int64(i)}) + if err != nil { + require.NoError(b, err) + } + } + b.StartTimer() + es.Resize(tc.endSize) + } }) } } diff --git a/tsdb/index/postings.go b/tsdb/index/postings.go index 33f07bc97d..bcee2607da 100644 --- a/tsdb/index/postings.go +++ b/tsdb/index/postings.go @@ -868,19 +868,18 @@ func FindIntersectingPostings(p Postings, candidates []Postings) (indexes []int, return nil, it.Err() } } - if len(h) == 0 { + if h.empty() { return nil, nil } - h.Init() + heap.Init(&h) - for len(h) > 0 { - if !p.Seek(h.At()) { + for !h.empty() { + if !p.Seek(h.at()) { return indexes, p.Err() } - if p.At() == h.At() { - found := heap.Pop(&h).(postingsWithIndex) - indexes = append(indexes, found.index) - } else if err := h.Next(); err != nil { + if p.At() == h.at() { + indexes = append(indexes, h.popIndex()) + } else if err := h.next(); err != nil { return nil, err } } @@ -888,16 +887,46 @@ func FindIntersectingPostings(p Postings, candidates []Postings) (indexes []int, return indexes, nil } +// postingsWithIndex is used as postingsWithIndexHeap elements by FindIntersectingPostings, +// keeping track of the original index of each postings while they move inside the heap. type postingsWithIndex struct { index int p Postings + // popped means that these postings shouldn't be considered anymore. + // See popIndex() comment to understand why we need this. + popped bool } +// postingsWithIndexHeap implements heap.Interface, +// with root always pointing to the postings with minimum Postings.At() value. +// It also implements a special way of removing elements that marks them as popped and moves them to the bottom of the +// heap instead of actually removing them, see popIndex() for more details. type postingsWithIndexHeap []postingsWithIndex -func (h *postingsWithIndexHeap) Init() { heap.Init(h) } -func (h postingsWithIndexHeap) At() storage.SeriesRef { return h[0].p.At() } -func (h *postingsWithIndexHeap) Next() error { +// empty checks whether the heap is empty, which is true if it has no elements, of if the smallest element is popped. +func (h *postingsWithIndexHeap) empty() bool { + return len(*h) == 0 || (*h)[0].popped +} + +// popIndex pops the smallest heap element and returns its index. +// In our implementation we don't actually do heap.Pop(), instead we mark the element as `popped` and fix its position, which +// should be after all the non-popped elements according to our sorting strategy. +// By skipping the `heap.Pop()` call we avoid an extra allocation in this heap's Pop() implementation which returns an interface{}. +func (h *postingsWithIndexHeap) popIndex() int { + index := (*h)[0].index + (*h)[0].popped = true + heap.Fix(h, 0) + return index +} + +// at provides the storage.SeriesRef where root Postings is pointing at this moment. +func (h postingsWithIndexHeap) at() storage.SeriesRef { return h[0].p.At() } + +// next performs the Postings.Next() operation on the root of the heap, performing the related operation on the heap +// and conveniently returning the result of calling Postings.Err() if the result of calling Next() was false. +// If Next() succeeds, heap is fixed to move the root to its new position, according to its Postings.At() value. +// If Next() returns fails and there's no error reported by Postings.Err(), then root is marked as removed and heap is fixed. +func (h *postingsWithIndexHeap) next() error { pi := (*h)[0] next := pi.p.Next() if next { @@ -908,21 +937,35 @@ func (h *postingsWithIndexHeap) Next() error { if err := pi.p.Err(); err != nil { return errors.Wrapf(err, "postings %d", pi.index) } - - heap.Pop(h) + h.popIndex() return nil } -func (h postingsWithIndexHeap) Len() int { return len(h) } -func (h postingsWithIndexHeap) Less(i, j int) bool { return h[i].p.At() < h[j].p.At() } -func (h *postingsWithIndexHeap) Swap(i, j int) { (*h)[i], (*h)[j] = (*h)[j], (*h)[i] } +// Len implements heap.Interface. +// Notice that Len() > 0 does not imply that heap is not empty as elements are not removed from this heap. +// Use empty() to check whether heap is empty or not. +func (h postingsWithIndexHeap) Len() int { return len(h) } +// Less implements heap.Interface, it puts all the popped elements at the bottom, +// and then sorts by Postings.At() property of each node. +func (h postingsWithIndexHeap) Less(i, j int) bool { + if h[i].popped != h[j].popped { + return h[j].popped + } + return h[i].p.At() < h[j].p.At() +} + +// Swap implements heap.Interface. +func (h *postingsWithIndexHeap) Swap(i, j int) { (*h)[i], (*h)[j] = (*h)[j], (*h)[i] } + +// Push implements heap.Interface. func (h *postingsWithIndexHeap) Push(x interface{}) { *h = append(*h, x.(postingsWithIndex)) } // Pop implements heap.Interface and pops the last element, which is NOT the min element, // so this doesn't return the same heap.Pop() +// Although this method is implemented for correctness, we don't expect it to be used, see popIndex() method for details. func (h *postingsWithIndexHeap) Pop() interface{} { old := *h n := len(old) diff --git a/tsdb/index/postings_test.go b/tsdb/index/postings_test.go index 45a1268472..b8b6dba96a 100644 --- a/tsdb/index/postings_test.go +++ b/tsdb/index/postings_test.go @@ -1164,13 +1164,13 @@ func TestPostingsWithIndexHeap(t *testing.T) { for _, node := range h { node.p.Next() } - h.Init() + heap.Init(&h) for _, expected := range []storage.SeriesRef{1, 5, 10, 20, 25, 30, 50} { - require.Equal(t, expected, h.At()) - require.NoError(t, h.Next()) + require.Equal(t, expected, h.at()) + require.NoError(t, h.next()) } - require.Empty(t, h) + require.True(t, h.empty()) }) t.Run("pop", func(t *testing.T) { @@ -1182,13 +1182,13 @@ func TestPostingsWithIndexHeap(t *testing.T) { for _, node := range h { node.p.Next() } - h.Init() + heap.Init(&h) for _, expected := range []storage.SeriesRef{1, 5, 10, 20} { - require.Equal(t, expected, h.At()) - require.NoError(t, h.Next()) + require.Equal(t, expected, h.at()) + require.NoError(t, h.next()) } - require.Equal(t, storage.SeriesRef(25), h.At()) + require.Equal(t, storage.SeriesRef(25), h.at()) node := heap.Pop(&h).(postingsWithIndex) require.Equal(t, 2, node.index) require.Equal(t, storage.SeriesRef(25), node.p.At()) diff --git a/web/ui/react-app/src/pages/alerts/AlertContents.tsx b/web/ui/react-app/src/pages/alerts/AlertContents.tsx index 5165eae5b4..f96ae75232 100644 --- a/web/ui/react-app/src/pages/alerts/AlertContents.tsx +++ b/web/ui/react-app/src/pages/alerts/AlertContents.tsx @@ -63,7 +63,7 @@ const AlertsContent: FC = ({ groups = [], statsCount }) => { return ( @@ -35,7 +35,7 @@ exports[`AlertsContent matches a snapshot 1`] = ` onChange={[Function]} wrapperStyles={ Object { - "marginRight": 10, + "marginRight": 20, } } > @@ -58,7 +58,7 @@ exports[`AlertsContent matches a snapshot 1`] = ` onChange={[Function]} wrapperStyles={ Object { - "marginRight": 10, + "marginRight": 20, } } >