From 39d008f94fe44b3b2efe2ac9bbd2a1198e67cdf8 Mon Sep 17 00:00:00 2001 From: "m.nabokikh" Date: Tue, 11 Jul 2023 23:44:23 +0200 Subject: [PATCH 001/237] Route different alerts to different alertmanagers Signed-off-by: m.nabokikh --- config/config.go | 8 ++ notifier/notifier.go | 44 +++++++---- notifier/notifier_test.go | 157 +++++++++++++++++++++++++++++--------- 3 files changed, 158 insertions(+), 51 deletions(-) diff --git a/config/config.go b/config/config.go index d32fcc33c9..b77bbd6917 100644 --- a/config/config.go +++ b/config/config.go @@ -823,6 +823,8 @@ type AlertmanagerConfig struct { // List of Alertmanager relabel configurations. RelabelConfigs []*relabel.Config `yaml:"relabel_configs,omitempty"` + // Relabel alerts before sending to the specific alertmanager. + AlertRelabelConfigs []*relabel.Config `yaml:"alert_relabel_configs,omitempty"` } // SetDirectory joins any relative file paths with dir. @@ -858,6 +860,12 @@ func (c *AlertmanagerConfig) UnmarshalYAML(unmarshal func(interface{}) error) er } } + for _, rlcfg := range c.AlertRelabelConfigs { + if rlcfg == nil { + return errors.New("empty or null Alertmanager alert relabeling rule") + } + } + return nil } diff --git a/notifier/notifier.go b/notifier/notifier.go index 891372c43e..630cbdf1e5 100644 --- a/notifier/notifier.go +++ b/notifier/notifier.go @@ -349,7 +349,7 @@ func (n *Manager) Send(alerts ...*Alert) { n.mtx.Lock() defer n.mtx.Unlock() - alerts = n.relabelAlerts(alerts) + alerts = relabelAlerts(n.opts.RelabelConfigs, n.opts.ExternalLabels, alerts) if len(alerts) == 0 { return } @@ -377,20 +377,21 @@ func (n *Manager) Send(alerts ...*Alert) { n.setMore() } -// Attach external labels and process relabelling rules. -func (n *Manager) relabelAlerts(alerts []*Alert) []*Alert { +func relabelAlerts(relabelConfigs []*relabel.Config, externalLabels labels.Labels, alerts []*Alert) []*Alert { lb := labels.NewBuilder(labels.EmptyLabels()) var relabeledAlerts []*Alert for _, a := range alerts { lb.Reset(a.Labels) - n.opts.ExternalLabels.Range(func(l labels.Label) { - if a.Labels.Get(l.Name) == "" { - lb.Set(l.Name, l.Value) - } - }) + if externalLabels.Len() > 0 { + externalLabels.Range(func(l labels.Label) { + if a.Labels.Get(l.Name) == "" { + lb.Set(l.Name, l.Value) + } + }) + } - keep := relabel.ProcessBuilder(lb, n.opts.RelabelConfigs...) + keep := relabel.ProcessBuilder(lb, relabelConfigs...) if !keep { continue } @@ -472,17 +473,30 @@ func (n *Manager) sendAll(alerts ...*Alert) bool { ) for _, ams := range amSets { var ( - payload []byte - err error + payload []byte + err error + amAlerts = alerts ) ams.mtx.RLock() + if len(ams.cfg.AlertRelabelConfigs) > 0 { + amAlerts = relabelAlerts(ams.cfg.AlertRelabelConfigs, labels.Labels{}, alerts) + // TODO(nabokihms): figure out the right way to cache marshalled alerts. + // Now it works well only for happy cases. + v1Payload = nil + v2Payload = nil + + if len(amAlerts) == 0 { + continue + } + } + switch ams.cfg.APIVersion { case config.AlertmanagerAPIVersionV1: { if v1Payload == nil { - v1Payload, err = json.Marshal(alerts) + v1Payload, err = json.Marshal(amAlerts) if err != nil { level.Error(n.logger).Log("msg", "Encoding alerts for Alertmanager API v1 failed", "err", err) ams.mtx.RUnlock() @@ -495,7 +509,7 @@ func (n *Manager) sendAll(alerts ...*Alert) bool { case config.AlertmanagerAPIVersionV2: { if v2Payload == nil { - openAPIAlerts := alertsToOpenAPIAlerts(alerts) + openAPIAlerts := alertsToOpenAPIAlerts(amAlerts) v2Payload, err = json.Marshal(openAPIAlerts) if err != nil { @@ -526,13 +540,13 @@ func (n *Manager) sendAll(alerts ...*Alert) bool { go func(client *http.Client, url string) { if err := n.sendOne(ctx, client, url, payload); err != nil { - level.Error(n.logger).Log("alertmanager", url, "count", len(alerts), "msg", "Error sending alert", "err", err) + level.Error(n.logger).Log("alertmanager", url, "count", len(amAlerts), "msg", "Error sending alert", "err", err) n.metrics.errors.WithLabelValues(url).Inc() } else { numSuccess.Inc() } n.metrics.latency.WithLabelValues(url).Observe(time.Since(begin).Seconds()) - n.metrics.sent.WithLabelValues(url).Add(float64(len(alerts))) + n.metrics.sent.WithLabelValues(url).Add(float64(len(amAlerts))) wg.Done() }(ams.client, am.url().String()) diff --git a/notifier/notifier_test.go b/notifier/notifier_test.go index 66ee45c6e8..79a3157d79 100644 --- a/notifier/notifier_test.go +++ b/notifier/notifier_test.go @@ -98,6 +98,41 @@ func alertsEqual(a, b []*Alert) error { return nil } +func newTestHTTPServerBuilder(expected *[]*Alert, errc chan<- error, u, p string, status *atomic.Int32) *httptest.Server { + return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + var err error + defer func() { + if err == nil { + return + } + select { + case errc <- err: + default: + } + }() + user, pass, _ := r.BasicAuth() + if user != u || pass != p { + err = fmt.Errorf("unexpected user/password: %s/%s != %s/%s", user, pass, u, p) + w.WriteHeader(http.StatusInternalServerError) + return + } + + b, err := io.ReadAll(r.Body) + if err != nil { + err = fmt.Errorf("error reading body: %v", err) + w.WriteHeader(http.StatusInternalServerError) + return + } + + var alerts []*Alert + err = json.Unmarshal(b, &alerts) + if err == nil { + err = alertsEqual(*expected, alerts) + } + w.WriteHeader(int(status.Load())) + })) +} + func TestHandlerSendAll(t *testing.T) { var ( errc = make(chan error, 1) @@ -107,42 +142,8 @@ func TestHandlerSendAll(t *testing.T) { status1.Store(int32(http.StatusOK)) status2.Store(int32(http.StatusOK)) - newHTTPServer := func(u, p string, status *atomic.Int32) *httptest.Server { - return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - var err error - defer func() { - if err == nil { - return - } - select { - case errc <- err: - default: - } - }() - user, pass, _ := r.BasicAuth() - if user != u || pass != p { - err = fmt.Errorf("unexpected user/password: %s/%s != %s/%s", user, pass, u, p) - w.WriteHeader(http.StatusInternalServerError) - return - } - - b, err := io.ReadAll(r.Body) - if err != nil { - err = fmt.Errorf("error reading body: %w", err) - w.WriteHeader(http.StatusInternalServerError) - return - } - - var alerts []*Alert - err = json.Unmarshal(b, &alerts) - if err == nil { - err = alertsEqual(expected, alerts) - } - w.WriteHeader(int(status.Load())) - })) - } - server1 := newHTTPServer("prometheus", "testing_password", &status1) - server2 := newHTTPServer("", "", &status2) + server1 := newTestHTTPServerBuilder(&expected, errc, "prometheus", "testing_password", &status1) + server2 := newTestHTTPServerBuilder(&expected, errc, "", "", &status2) defer server1.Close() defer server2.Close() @@ -213,6 +214,90 @@ func TestHandlerSendAll(t *testing.T) { checkNoErr() } +func TestHandlerSendAllRemapPerAm(t *testing.T) { + var ( + errc = make(chan error, 1) + expected1 = make([]*Alert, 0, maxBatchSize) + expected2 = make([]*Alert, 0, maxBatchSize) + + status1, status2 atomic.Int32 + ) + status1.Store(int32(http.StatusOK)) + status2.Store(int32(http.StatusOK)) + + server1 := newTestHTTPServerBuilder(&expected1, errc, "", "", &status1) + server2 := newTestHTTPServerBuilder(&expected2, errc, "", "", &status2) + + defer server1.Close() + defer server2.Close() + + h := NewManager(&Options{}, nil) + h.alertmanagers = make(map[string]*alertmanagerSet) + + am1Cfg := config.DefaultAlertmanagerConfig + am1Cfg.Timeout = model.Duration(time.Second) + + am2Cfg := config.DefaultAlertmanagerConfig + am2Cfg.Timeout = model.Duration(time.Second) + am2Cfg.AlertRelabelConfigs = []*relabel.Config{ + { + SourceLabels: model.LabelNames{"alertnamedrop"}, + Action: "drop", + Regex: relabel.MustNewRegexp(".+"), + }, + } + + h.alertmanagers["1"] = &alertmanagerSet{ + ams: []alertmanager{ + alertmanagerMock{ + urlf: func() string { return server1.URL }, + }, + }, + cfg: &am1Cfg, + } + + h.alertmanagers["2"] = &alertmanagerSet{ + ams: []alertmanager{ + alertmanagerMock{ + urlf: func() string { return server2.URL }, + }, + }, + cfg: &am2Cfg, + } + + for i := range make([]struct{}, maxBatchSize/2) { + h.queue = append(h.queue, &Alert{ + Labels: labels.FromStrings("alertname", fmt.Sprintf("%d", i)), + }) + h.queue = append(h.queue, &Alert{ + Labels: labels.FromStrings("alertnamedrop", fmt.Sprintf("%d", i)), + }) + + expected1 = append(expected1, &Alert{ + Labels: labels.FromStrings("alertname", fmt.Sprintf("%d", i)), + }) + expected1 = append(expected1, &Alert{ + Labels: labels.FromStrings("alertnamedrop", fmt.Sprintf("%d", i)), + }) + + expected2 = append(expected2, &Alert{ + Labels: labels.FromStrings("alertname", fmt.Sprintf("%d", i)), + }) + } + + checkNoErr := func() { + t.Helper() + select { + case err := <-errc: + require.NoError(t, err) + default: + } + } + + require.True(t, h.sendAll(h.queue...), "all sends failed unexpectedly") + checkNoErr() +} + func TestCustomDo(t *testing.T) { const testURL = "http://testurl.com/" const testBody = "testbody" From 9d8463339d70f8d39314b11615ae4350e246dc63 Mon Sep 17 00:00:00 2001 From: "m.nabokikh" Date: Sun, 23 Jul 2023 00:37:30 +0200 Subject: [PATCH 002/237] Fixes according to the code review Signed-off-by: m.nabokikh --- docs/configuration/configuration.md | 4 ++++ notifier/notifier.go | 12 +++++------- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/docs/configuration/configuration.md b/docs/configuration/configuration.md index 30bb07a8c1..0efded225d 100644 --- a/docs/configuration/configuration.md +++ b/docs/configuration/configuration.md @@ -3415,6 +3415,10 @@ static_configs: # List of Alertmanager relabel configurations. relabel_configs: [ - ... ] + +# List of alert relabel configurations. +alert_relabel_configs: + [ - ... ] ``` ### `` diff --git a/notifier/notifier.go b/notifier/notifier.go index 630cbdf1e5..0f93c25a35 100644 --- a/notifier/notifier.go +++ b/notifier/notifier.go @@ -383,13 +383,11 @@ func relabelAlerts(relabelConfigs []*relabel.Config, externalLabels labels.Label for _, a := range alerts { lb.Reset(a.Labels) - if externalLabels.Len() > 0 { - externalLabels.Range(func(l labels.Label) { - if a.Labels.Get(l.Name) == "" { - lb.Set(l.Name, l.Value) - } - }) - } + externalLabels.Range(func(l labels.Label) { + if a.Labels.Get(l.Name) == "" { + lb.Set(l.Name, l.Value) + } + }) keep := relabel.ProcessBuilder(lb, relabelConfigs...) if !keep { From 6fe8217ce4ab9a161aeba0c375a26bc2102beed0 Mon Sep 17 00:00:00 2001 From: Bryan Boreham Date: Sat, 21 Oct 2023 12:38:01 +0000 Subject: [PATCH 003/237] tsdb: shrink txRing with smaller integers 4 billion active transactions ought to be enough for anyone. Signed-off-by: Bryan Boreham --- tsdb/head_read.go | 2 +- tsdb/head_test.go | 2 +- tsdb/isolation.go | 16 ++++++++-------- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/tsdb/head_read.go b/tsdb/head_read.go index 6964d5472b..6e74b74a6a 100644 --- a/tsdb/head_read.go +++ b/tsdb/head_read.go @@ -724,7 +724,7 @@ func (s *memSeries) iterator(id chunks.HeadChunkID, c chunkenc.Chunk, isoState * // Removing the extra transactionIDs that are relevant for samples that // come after this chunk, from the total transactionIDs. - appendIDsToConsider := s.txs.txIDCount - (totalSamples - (previousSamples + numSamples)) + appendIDsToConsider := int(s.txs.txIDCount) - (totalSamples - (previousSamples + numSamples)) // Iterate over the appendIDs, find the first one that the isolation state says not // to return. diff --git a/tsdb/head_test.go b/tsdb/head_test.go index 54fd469a3b..616270100a 100644 --- a/tsdb/head_test.go +++ b/tsdb/head_test.go @@ -2550,7 +2550,7 @@ func TestIsolationAppendIDZeroIsNoop(t *testing.T) { ok, _ := s.append(0, 0, 0, cOpts) require.True(t, ok, "Series append failed.") - require.Equal(t, 0, s.txs.txIDCount, "Series should not have an appendID after append with appendID=0.") + require.Equal(t, 0, int(s.txs.txIDCount), "Series should not have an appendID after append with appendID=0.") } func TestHeadSeriesChunkRace(t *testing.T) { diff --git a/tsdb/isolation.go b/tsdb/isolation.go index e436884a8d..19eba9340c 100644 --- a/tsdb/isolation.go +++ b/tsdb/isolation.go @@ -240,8 +240,8 @@ func (i *isolation) closeAppend(appendID uint64) { // The transactionID ring buffer. type txRing struct { txIDs []uint64 - txIDFirst int // Position of the first id in the ring. - txIDCount int // How many ids in the ring. + txIDFirst uint32 // Position of the first id in the ring. + txIDCount uint32 // How many ids in the ring. } func newTxRing(capacity int) *txRing { @@ -251,7 +251,7 @@ func newTxRing(capacity int) *txRing { } func (txr *txRing) add(appendID uint64) { - if txr.txIDCount == len(txr.txIDs) { + if int(txr.txIDCount) == len(txr.txIDs) { // Ring buffer is full, expand by doubling. newRing := make([]uint64, txr.txIDCount*2) idx := copy(newRing, txr.txIDs[txr.txIDFirst:]) @@ -260,12 +260,12 @@ func (txr *txRing) add(appendID uint64) { txr.txIDFirst = 0 } - txr.txIDs[(txr.txIDFirst+txr.txIDCount)%len(txr.txIDs)] = appendID + txr.txIDs[int(txr.txIDFirst+txr.txIDCount)%len(txr.txIDs)] = appendID txr.txIDCount++ } func (txr *txRing) cleanupAppendIDsBelow(bound uint64) { - pos := txr.txIDFirst + pos := int(txr.txIDFirst) for txr.txIDCount > 0 { if txr.txIDs[pos] < bound { @@ -281,7 +281,7 @@ func (txr *txRing) cleanupAppendIDsBelow(bound uint64) { } } - txr.txIDFirst %= len(txr.txIDs) + txr.txIDFirst %= uint32(len(txr.txIDs)) } func (txr *txRing) iterator() *txRingIterator { @@ -296,7 +296,7 @@ func (txr *txRing) iterator() *txRingIterator { type txRingIterator struct { ids []uint64 - pos int + pos uint32 } func (it *txRingIterator) At() uint64 { @@ -305,7 +305,7 @@ func (it *txRingIterator) At() uint64 { func (it *txRingIterator) Next() { it.pos++ - if it.pos == len(it.ids) { + if int(it.pos) == len(it.ids) { it.pos = 0 } } From 90e98e0235522451bf35d054173702c8837f97b1 Mon Sep 17 00:00:00 2001 From: Bryan Boreham Date: Sat, 21 Oct 2023 13:45:47 +0000 Subject: [PATCH 004/237] tsdb: create isolation transaction slice on demand When Prometheus restarts it creates every series read in from the WAL, but many of those series will be finished, and never receive any more samples. By defering allocation of the txRing slice to when it is first needed, we save 32 bytes per stale series. Signed-off-by: Bryan Boreham --- tsdb/head.go | 2 +- tsdb/isolation.go | 9 ++++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/tsdb/head.go b/tsdb/head.go index 0ea2b83853..4a6e6ac739 100644 --- a/tsdb/head.go +++ b/tsdb/head.go @@ -2011,7 +2011,7 @@ func newMemSeries(lset labels.Labels, id chunks.HeadSeriesRef, isolationDisabled nextAt: math.MinInt64, } if !isolationDisabled { - s.txs = newTxRing(4) + s.txs = newTxRing(0) } return s } diff --git a/tsdb/isolation.go b/tsdb/isolation.go index 19eba9340c..86330f36e4 100644 --- a/tsdb/isolation.go +++ b/tsdb/isolation.go @@ -253,7 +253,11 @@ func newTxRing(capacity int) *txRing { func (txr *txRing) add(appendID uint64) { if int(txr.txIDCount) == len(txr.txIDs) { // Ring buffer is full, expand by doubling. - newRing := make([]uint64, txr.txIDCount*2) + newLen := txr.txIDCount * 2 + if newLen == 0 { + newLen = 4 + } + newRing := make([]uint64, newLen) idx := copy(newRing, txr.txIDs[txr.txIDFirst:]) copy(newRing[idx:], txr.txIDs[:txr.txIDFirst]) txr.txIDs = newRing @@ -265,6 +269,9 @@ func (txr *txRing) add(appendID uint64) { } func (txr *txRing) cleanupAppendIDsBelow(bound uint64) { + if len(txr.txIDs) == 0 { + return + } pos := int(txr.txIDFirst) for txr.txIDCount > 0 { From adde7db85f0c8ef9b147deb563ad00297b584f28 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 1 Nov 2023 23:32:17 +0000 Subject: [PATCH 005/237] build(deps): bump bufbuild/buf-lint-action from 1.0.3 to 1.1.0 Bumps [bufbuild/buf-lint-action](https://github.com/bufbuild/buf-lint-action) from 1.0.3 to 1.1.0. - [Release notes](https://github.com/bufbuild/buf-lint-action/releases) - [Commits](https://github.com/bufbuild/buf-lint-action/compare/bd48f53224baaaf0fc55de9a913e7680ca6dbea4...044d13acb1f155179c606aaa2e53aea304d22058) --- updated-dependencies: - dependency-name: bufbuild/buf-lint-action dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- .github/workflows/buf-lint.yml | 2 +- .github/workflows/buf.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/buf-lint.yml b/.github/workflows/buf-lint.yml index b44ba05118..df5fba02c9 100644 --- a/.github/workflows/buf-lint.yml +++ b/.github/workflows/buf-lint.yml @@ -16,7 +16,7 @@ jobs: - uses: bufbuild/buf-setup-action@eb60cd0de4f14f1f57cf346916b8cd69a9e7ed0b # v1.26.1 with: github_token: ${{ secrets.GITHUB_TOKEN }} - - uses: bufbuild/buf-lint-action@bd48f53224baaaf0fc55de9a913e7680ca6dbea4 # v1.0.3 + - uses: bufbuild/buf-lint-action@044d13acb1f155179c606aaa2e53aea304d22058 # v1.1.0 with: input: 'prompb' - uses: bufbuild/buf-breaking-action@f47418c81c00bfd65394628385593542f64db477 # v1.1.2 diff --git a/.github/workflows/buf.yml b/.github/workflows/buf.yml index 58c1bc1989..213b4c2b63 100644 --- a/.github/workflows/buf.yml +++ b/.github/workflows/buf.yml @@ -16,7 +16,7 @@ jobs: - uses: bufbuild/buf-setup-action@eb60cd0de4f14f1f57cf346916b8cd69a9e7ed0b # v1.26.1 with: github_token: ${{ secrets.GITHUB_TOKEN }} - - uses: bufbuild/buf-lint-action@bd48f53224baaaf0fc55de9a913e7680ca6dbea4 # v1.0.3 + - uses: bufbuild/buf-lint-action@044d13acb1f155179c606aaa2e53aea304d22058 # v1.1.0 with: input: 'prompb' - uses: bufbuild/buf-breaking-action@f47418c81c00bfd65394628385593542f64db477 # v1.1.2 From e61e5938d6573aae660bb84592075ad67fd66804 Mon Sep 17 00:00:00 2001 From: Kumar Kalpadiptya Roy Date: Wed, 20 Dec 2023 00:28:59 +0530 Subject: [PATCH 006/237] Issue #13268: fix quality value in accept header Signed-off-by: Kumar Kalpadiptya Roy --- scrape/scrape.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scrape/scrape.go b/scrape/scrape.go index 9a0ba1d009..29f98979bb 100644 --- a/scrape/scrape.go +++ b/scrape/scrape.go @@ -672,7 +672,7 @@ func acceptHeader(sps []config.ScrapeProtocol) string { weight-- } // Default match anything. - vals = append(vals, fmt.Sprintf("*/*;q=%d", weight)) + vals = append(vals, fmt.Sprintf("*/*;q=0.%d", weight)) return strings.Join(vals, ",") } From 65a101cdc287cf5d167267fa5cfadc9ef5588e8f Mon Sep 17 00:00:00 2001 From: bwplotka Date: Mon, 15 Jan 2024 16:18:18 +0000 Subject: [PATCH 007/237] Cut 2.49.1 with scrape q= bugfix. Signed-off-by: bwplotka --- CHANGELOG.md | 4 ++++ VERSION | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 116a2594bb..1f71eb49ba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## 2.49.1 / 2024-01-15 + +* [BUGFIX] TSDB: Fixed a wrong `q=` value in scrape accept header #13313 + ## 2.49.0 / 2024-01-15 * [FEATURE] Promtool: Add `--run` flag promtool test rules command. #12206 diff --git a/VERSION b/VERSION index 132775c22c..f5518081bd 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -2.49.0 +2.49.1 From 216b5edb4193846b2c7707969fbcee00daee2334 Mon Sep 17 00:00:00 2001 From: bwplotka Date: Mon, 15 Jan 2024 16:44:33 +0000 Subject: [PATCH 008/237] Cut 2.49.1 web package. Signed-off-by: bwplotka --- web/ui/module/codemirror-promql/package.json | 4 ++-- web/ui/module/lezer-promql/package.json | 2 +- web/ui/package-lock.json | 14 +++++++------- web/ui/package.json | 2 +- web/ui/react-app/package.json | 4 ++-- 5 files changed, 13 insertions(+), 13 deletions(-) diff --git a/web/ui/module/codemirror-promql/package.json b/web/ui/module/codemirror-promql/package.json index fe6b9315e7..fb13605825 100644 --- a/web/ui/module/codemirror-promql/package.json +++ b/web/ui/module/codemirror-promql/package.json @@ -1,6 +1,6 @@ { "name": "@prometheus-io/codemirror-promql", - "version": "0.49.0", + "version": "0.49.1", "description": "a CodeMirror mode for the PromQL language", "types": "dist/esm/index.d.ts", "module": "dist/esm/index.js", @@ -29,7 +29,7 @@ }, "homepage": "https://github.com/prometheus/prometheus/blob/main/web/ui/module/codemirror-promql/README.md", "dependencies": { - "@prometheus-io/lezer-promql": "0.49.0", + "@prometheus-io/lezer-promql": "0.49.1", "lru-cache": "^7.18.3" }, "devDependencies": { diff --git a/web/ui/module/lezer-promql/package.json b/web/ui/module/lezer-promql/package.json index 8f4af19367..a6bb3f3eb8 100644 --- a/web/ui/module/lezer-promql/package.json +++ b/web/ui/module/lezer-promql/package.json @@ -1,6 +1,6 @@ { "name": "@prometheus-io/lezer-promql", - "version": "0.49.0", + "version": "0.49.1", "description": "lezer-based PromQL grammar", "main": "dist/index.cjs", "type": "module", diff --git a/web/ui/package-lock.json b/web/ui/package-lock.json index 0fe4edafe7..5f1db6ce0a 100644 --- a/web/ui/package-lock.json +++ b/web/ui/package-lock.json @@ -1,12 +1,12 @@ { "name": "prometheus-io", - "version": "0.49.0", + "version": "0.49.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "prometheus-io", - "version": "0.49.0", + "version": "0.49.1", "workspaces": [ "react-app", "module/*" @@ -30,10 +30,10 @@ }, "module/codemirror-promql": { "name": "@prometheus-io/codemirror-promql", - "version": "0.49.0", + "version": "0.49.1", "license": "Apache-2.0", "dependencies": { - "@prometheus-io/lezer-promql": "0.49.0", + "@prometheus-io/lezer-promql": "0.49.1", "lru-cache": "^7.18.3" }, "devDependencies": { @@ -69,7 +69,7 @@ }, "module/lezer-promql": { "name": "@prometheus-io/lezer-promql", - "version": "0.49.0", + "version": "0.49.1", "license": "Apache-2.0", "devDependencies": { "@lezer/generator": "^1.5.1", @@ -19233,7 +19233,7 @@ }, "react-app": { "name": "@prometheus-io/app", - "version": "0.49.0", + "version": "0.49.1", "dependencies": { "@codemirror/autocomplete": "^6.11.1", "@codemirror/commands": "^6.3.2", @@ -19251,7 +19251,7 @@ "@lezer/lr": "^1.3.14", "@nexucis/fuzzy": "^0.4.1", "@nexucis/kvsearch": "^0.8.1", - "@prometheus-io/codemirror-promql": "0.49.0", + "@prometheus-io/codemirror-promql": "0.49.1", "bootstrap": "^4.6.2", "css.escape": "^1.5.1", "downshift": "^7.6.2", diff --git a/web/ui/package.json b/web/ui/package.json index 1a6820abd0..026c1663b3 100644 --- a/web/ui/package.json +++ b/web/ui/package.json @@ -28,5 +28,5 @@ "ts-jest": "^29.1.1", "typescript": "^4.9.5" }, - "version": "0.49.0" + "version": "0.49.1" } diff --git a/web/ui/react-app/package.json b/web/ui/react-app/package.json index 2a1d569488..33fccf5c7c 100644 --- a/web/ui/react-app/package.json +++ b/web/ui/react-app/package.json @@ -1,6 +1,6 @@ { "name": "@prometheus-io/app", - "version": "0.49.0", + "version": "0.49.1", "private": true, "dependencies": { "@codemirror/autocomplete": "^6.11.1", @@ -19,7 +19,7 @@ "@lezer/lr": "^1.3.14", "@nexucis/fuzzy": "^0.4.1", "@nexucis/kvsearch": "^0.8.1", - "@prometheus-io/codemirror-promql": "0.49.0", + "@prometheus-io/codemirror-promql": "0.49.1", "bootstrap": "^4.6.2", "css.escape": "^1.5.1", "downshift": "^7.6.2", From 53589fde4fb80f462fb545137b36415f08c58a35 Mon Sep 17 00:00:00 2001 From: Paulin Todev Date: Tue, 23 Jan 2024 19:24:13 +0000 Subject: [PATCH 009/237] Change metric label for Puppetdb from 'http' to 'puppetdb' Signed-off-by: Paulin Todev --- discovery/puppetdb/puppetdb.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/discovery/puppetdb/puppetdb.go b/discovery/puppetdb/puppetdb.go index 4355a7adea..3f9ad1f113 100644 --- a/discovery/puppetdb/puppetdb.go +++ b/discovery/puppetdb/puppetdb.go @@ -171,7 +171,7 @@ func NewDiscovery(conf *SDConfig, logger log.Logger, metrics discovery.Discovere d.Discovery = refresh.NewDiscovery( refresh.Options{ Logger: logger, - Mech: "http", + Mech: "puppetdb", Interval: time.Duration(conf.RefreshInterval), RefreshF: d.refresh, MetricsInstantiator: m.refreshMetrics, From 033c9b229df1dbe04d4d548305befe022743b602 Mon Sep 17 00:00:00 2001 From: Ziqi Zhao Date: Wed, 24 Jan 2024 14:01:23 +0800 Subject: [PATCH 010/237] mirror metrics.proto change & generate code Signed-off-by: Ziqi Zhao --- prompb/io/prometheus/client/metrics.pb.go | 200 ++++++++++++++-------- prompb/io/prometheus/client/metrics.proto | 3 + 2 files changed, 136 insertions(+), 67 deletions(-) diff --git a/prompb/io/prometheus/client/metrics.pb.go b/prompb/io/prometheus/client/metrics.pb.go index 702ee62fce..17cab6081e 100644 --- a/prompb/io/prometheus/client/metrics.pb.go +++ b/prompb/io/prometheus/client/metrics.pb.go @@ -438,11 +438,13 @@ type Histogram struct { // Use either "positive_delta" or "positive_count", the former for // regular histograms with integer counts, the latter for float // histograms. - PositiveDelta []int64 `protobuf:"zigzag64,13,rep,packed,name=positive_delta,json=positiveDelta,proto3" json:"positive_delta,omitempty"` - PositiveCount []float64 `protobuf:"fixed64,14,rep,packed,name=positive_count,json=positiveCount,proto3" json:"positive_count,omitempty"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` + PositiveDelta []int64 `protobuf:"zigzag64,13,rep,packed,name=positive_delta,json=positiveDelta,proto3" json:"positive_delta,omitempty"` + PositiveCount []float64 `protobuf:"fixed64,14,rep,packed,name=positive_count,json=positiveCount,proto3" json:"positive_count,omitempty"` + // Only used for native histograms. These exemplars MUST have a timestamp. + Exemplars []*Exemplar `protobuf:"bytes,16,rep,name=exemplars,proto3" json:"exemplars,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } func (m *Histogram) Reset() { *m = Histogram{} } @@ -583,6 +585,13 @@ func (m *Histogram) GetPositiveCount() []float64 { return nil } +func (m *Histogram) GetExemplars() []*Exemplar { + if m != nil { + return m.Exemplars + } + return nil +} + type Bucket struct { CumulativeCount uint64 `protobuf:"varint,1,opt,name=cumulative_count,json=cumulativeCount,proto3" json:"cumulative_count,omitempty"` CumulativeCountFloat float64 `protobuf:"fixed64,4,opt,name=cumulative_count_float,json=cumulativeCountFloat,proto3" json:"cumulative_count_float,omitempty"` @@ -973,68 +982,69 @@ func init() { } var fileDescriptor_d1e5ddb18987a258 = []byte{ - // 969 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xa4, 0x56, 0xdd, 0x8e, 0xdb, 0x44, - 0x14, 0xae, 0x9b, 0x5f, 0x9f, 0x6c, 0x76, 0xbd, 0x43, 0x54, 0x59, 0x0b, 0xbb, 0x09, 0x96, 0x90, - 0x16, 0x84, 0x12, 0x01, 0x45, 0xa0, 0x52, 0x24, 0x76, 0xdb, 0x6d, 0x8a, 0x4a, 0xda, 0x32, 0x49, - 0x2e, 0xca, 0x8d, 0x35, 0x49, 0x66, 0x1d, 0x0b, 0xdb, 0x63, 0xec, 0x71, 0xc5, 0x72, 0xcf, 0x33, - 0xf0, 0x02, 0x3c, 0x06, 0xe2, 0x12, 0xf5, 0x92, 0x2b, 0x2e, 0x11, 0xda, 0x27, 0x41, 0xf3, 0x63, - 0x3b, 0x5b, 0x39, 0x0b, 0x0b, 0x77, 0x33, 0x5f, 0xbe, 0x73, 0xe6, 0x3b, 0xdf, 0x4c, 0xce, 0x31, - 0x38, 0x3e, 0x1b, 0xc5, 0x09, 0x0b, 0x29, 0x5f, 0xd3, 0x2c, 0x1d, 0x2d, 0x03, 0x9f, 0x46, 0x7c, - 0x14, 0x52, 0x9e, 0xf8, 0xcb, 0x74, 0x18, 0x27, 0x8c, 0x33, 0xd4, 0xf3, 0xd9, 0xb0, 0xe4, 0x0c, - 0x15, 0xe7, 0xa0, 0xe7, 0x31, 0x8f, 0x49, 0xc2, 0x48, 0xac, 0x14, 0xf7, 0xa0, 0xef, 0x31, 0xe6, - 0x05, 0x74, 0x24, 0x77, 0x8b, 0xec, 0x7c, 0xc4, 0xfd, 0x90, 0xa6, 0x9c, 0x84, 0xb1, 0x22, 0x38, - 0x1f, 0x83, 0xf9, 0x15, 0x59, 0xd0, 0xe0, 0x39, 0xf1, 0x13, 0x84, 0xa0, 0x1e, 0x91, 0x90, 0xda, - 0xc6, 0xc0, 0x38, 0x36, 0xb1, 0x5c, 0xa3, 0x1e, 0x34, 0x5e, 0x92, 0x20, 0xa3, 0xf6, 0x6d, 0x09, - 0xaa, 0x8d, 0x73, 0x08, 0x8d, 0x31, 0xc9, 0xbc, 0x8d, 0x9f, 0x45, 0x8c, 0x91, 0xff, 0xfc, 0xb3, - 0x01, 0xad, 0x07, 0x2c, 0x8b, 0x38, 0x4d, 0xaa, 0x19, 0xe8, 0x1e, 0xb4, 0xe9, 0xf7, 0x34, 0x8c, - 0x03, 0x92, 0xc8, 0xcc, 0x9d, 0x0f, 0x8f, 0x86, 0x55, 0x75, 0x0d, 0xcf, 0x34, 0x0b, 0x17, 0x7c, - 0x34, 0x86, 0xfd, 0x65, 0x42, 0x09, 0xa7, 0x2b, 0xb7, 0x28, 0xc7, 0xae, 0xc9, 0x24, 0x07, 0x43, - 0x55, 0xf0, 0x30, 0x2f, 0x78, 0x38, 0xcb, 0x19, 0xd8, 0xd2, 0x41, 0x05, 0xe2, 0xdc, 0x87, 0xf6, - 0xd7, 0x19, 0x89, 0xb8, 0x1f, 0x50, 0x74, 0x00, 0xed, 0xef, 0xf4, 0x5a, 0x2b, 0x2d, 0xf6, 0x57, - 0x3d, 0x28, 0x8a, 0xfc, 0xc3, 0x80, 0xd6, 0x34, 0x0b, 0x43, 0x92, 0x5c, 0xa0, 0xb7, 0x61, 0x27, - 0x25, 0x61, 0x1c, 0x50, 0x77, 0x29, 0xca, 0x96, 0x19, 0xea, 0xb8, 0xa3, 0x30, 0xe9, 0x04, 0x3a, - 0x04, 0xd0, 0x94, 0x34, 0x0b, 0x75, 0x26, 0x53, 0x21, 0xd3, 0x2c, 0x44, 0x5f, 0x6c, 0x9c, 0x5f, - 0x1b, 0xd4, 0xb6, 0x1b, 0x92, 0x2b, 0x3e, 0xad, 0xbf, 0xfa, 0xb3, 0x7f, 0x6b, 0x43, 0x65, 0xa5, - 0x2d, 0xf5, 0xff, 0x60, 0x4b, 0x1f, 0x5a, 0xf3, 0x88, 0x5f, 0xc4, 0x74, 0xb5, 0xe5, 0x7a, 0x7f, - 0x6d, 0x80, 0xf9, 0xd8, 0x4f, 0x39, 0xf3, 0x12, 0x12, 0xfe, 0x9b, 0xda, 0xdf, 0x07, 0xb4, 0x49, - 0x71, 0xcf, 0x03, 0x46, 0xb8, 0xd4, 0x66, 0x60, 0x6b, 0x83, 0xf8, 0x48, 0xe0, 0xff, 0xe4, 0xd4, - 0x3d, 0x68, 0x2e, 0xb2, 0xe5, 0xb7, 0x94, 0x6b, 0x9f, 0xde, 0xaa, 0xf6, 0xe9, 0x54, 0x72, 0xb4, - 0x4b, 0x3a, 0xa2, 0xda, 0xa3, 0xbd, 0x9b, 0x7b, 0x84, 0xee, 0x40, 0x33, 0x5d, 0xae, 0x69, 0x48, - 0xec, 0xc6, 0xc0, 0x38, 0xde, 0xc7, 0x7a, 0x87, 0xde, 0x81, 0xdd, 0x1f, 0x68, 0xc2, 0x5c, 0xbe, - 0x4e, 0x68, 0xba, 0x66, 0xc1, 0xca, 0x6e, 0x4a, 0xfd, 0x5d, 0x81, 0xce, 0x72, 0x50, 0x94, 0x28, - 0x69, 0xca, 0xb1, 0x96, 0x74, 0xcc, 0x14, 0x88, 0xf2, 0xeb, 0x18, 0xac, 0xf2, 0x67, 0xed, 0x56, - 0x5b, 0xe6, 0xd9, 0x2d, 0x48, 0xca, 0xab, 0x27, 0xd0, 0x8d, 0xa8, 0x47, 0xb8, 0xff, 0x92, 0xba, - 0x69, 0x4c, 0x22, 0xdb, 0x94, 0x9e, 0x0c, 0xae, 0xf3, 0x64, 0x1a, 0x93, 0x48, 0xfb, 0xb2, 0x93, - 0x07, 0x0b, 0x4c, 0x88, 0x2f, 0x92, 0xad, 0x68, 0xc0, 0x89, 0x0d, 0x83, 0xda, 0x31, 0xc2, 0xc5, - 0x11, 0x0f, 0x05, 0x78, 0x85, 0xa6, 0x0a, 0xe8, 0x0c, 0x6a, 0xa2, 0xc6, 0x1c, 0x55, 0x45, 0x3c, - 0x81, 0x6e, 0xcc, 0x52, 0xbf, 0x94, 0xb6, 0x73, 0x33, 0x69, 0x79, 0x70, 0x2e, 0xad, 0x48, 0xa6, - 0xa4, 0x75, 0x95, 0xb4, 0x1c, 0x2d, 0xa4, 0x15, 0x34, 0x25, 0x6d, 0x57, 0x49, 0xcb, 0x51, 0x29, - 0xcd, 0xf9, 0xcd, 0x80, 0xa6, 0x3a, 0x10, 0xbd, 0x0b, 0xd6, 0x32, 0x0b, 0xb3, 0x60, 0xb3, 0x1c, - 0xf5, 0x82, 0xf7, 0x4a, 0x5c, 0x15, 0x74, 0x17, 0xee, 0xbc, 0x4e, 0xbd, 0xf2, 0x92, 0x7b, 0xaf, - 0x05, 0xa8, 0x1b, 0xea, 0x43, 0x27, 0x8b, 0x63, 0x9a, 0xb8, 0x0b, 0x96, 0x45, 0x2b, 0xfd, 0x9c, - 0x41, 0x42, 0xa7, 0x02, 0xb9, 0xd2, 0x0a, 0x6b, 0x37, 0x6b, 0x85, 0xce, 0x7d, 0x80, 0xd2, 0x38, - 0xf1, 0x28, 0xd9, 0xf9, 0x79, 0x4a, 0x55, 0x05, 0xfb, 0x58, 0xef, 0x04, 0x1e, 0xd0, 0xc8, 0xe3, - 0x6b, 0x79, 0x7a, 0x17, 0xeb, 0x9d, 0xf3, 0x93, 0x01, 0xed, 0x3c, 0x29, 0xfa, 0x0c, 0x1a, 0x81, - 0x98, 0x04, 0xb6, 0x21, 0xaf, 0xa9, 0x5f, 0xad, 0xa1, 0x18, 0x16, 0xfa, 0x96, 0x54, 0x4c, 0x75, - 0x87, 0x44, 0x9f, 0x82, 0x79, 0x93, 0x06, 0x5d, 0x92, 0x9d, 0x1f, 0x6b, 0xd0, 0x9c, 0xc8, 0xa9, - 0xf7, 0xff, 0x74, 0x7d, 0x00, 0x0d, 0x4f, 0xcc, 0x29, 0x3d, 0x63, 0xde, 0xac, 0x0e, 0x96, 0xa3, - 0x0c, 0x2b, 0x26, 0xfa, 0x04, 0x5a, 0x4b, 0x35, 0xba, 0xb4, 0xe4, 0xc3, 0xea, 0x20, 0x3d, 0xdf, - 0x70, 0xce, 0x16, 0x81, 0xa9, 0x1a, 0x07, 0xba, 0xeb, 0x6e, 0x09, 0xd4, 0x33, 0x03, 0xe7, 0x6c, - 0x11, 0x98, 0xa9, 0x7e, 0x2b, 0x9b, 0xc9, 0xd6, 0x40, 0xdd, 0x94, 0x71, 0xce, 0x46, 0x9f, 0x83, - 0xb9, 0xce, 0xdb, 0xb0, 0x6c, 0x22, 0x5b, 0xed, 0x29, 0xba, 0x35, 0x2e, 0x23, 0x44, 0xe3, 0x2e, - 0x1c, 0x77, 0xc3, 0x54, 0x76, 0xaa, 0x1a, 0xee, 0x14, 0xd8, 0x24, 0x75, 0x7e, 0x31, 0x60, 0x47, - 0xdd, 0xc3, 0x23, 0x12, 0xfa, 0xc1, 0x45, 0xe5, 0x27, 0x02, 0x82, 0xfa, 0x9a, 0x06, 0xb1, 0xfe, - 0x42, 0x90, 0x6b, 0x74, 0x17, 0xea, 0x42, 0xa3, 0xb4, 0x70, 0x77, 0xdb, 0x7f, 0x5e, 0x65, 0x9e, - 0x5d, 0xc4, 0x14, 0x4b, 0xb6, 0x68, 0xed, 0xea, 0x5b, 0xc7, 0xae, 0x5f, 0xd7, 0xda, 0x55, 0x5c, - 0xde, 0xda, 0x55, 0x84, 0x50, 0x91, 0x45, 0x3e, 0x97, 0x16, 0x9a, 0x58, 0xae, 0xdf, 0x5b, 0x00, - 0x94, 0x67, 0xa0, 0x0e, 0xb4, 0x1e, 0x3c, 0x9b, 0x3f, 0x9d, 0x9d, 0x61, 0xeb, 0x16, 0x32, 0xa1, - 0x31, 0x3e, 0x99, 0x8f, 0xcf, 0x2c, 0x43, 0xe0, 0xd3, 0xf9, 0x64, 0x72, 0x82, 0x5f, 0x58, 0xb7, - 0xc5, 0x66, 0xfe, 0x74, 0xf6, 0xe2, 0xf9, 0xd9, 0x43, 0xab, 0x86, 0xba, 0x60, 0x3e, 0xfe, 0x72, - 0x3a, 0x7b, 0x36, 0xc6, 0x27, 0x13, 0xab, 0x8e, 0xde, 0x80, 0x3d, 0x19, 0xe3, 0x96, 0x60, 0xe3, - 0xd4, 0x79, 0x75, 0x79, 0x64, 0xfc, 0x7e, 0x79, 0x64, 0xfc, 0x75, 0x79, 0x64, 0x7c, 0xd3, 0xf3, - 0x99, 0x5b, 0x0a, 0x76, 0x95, 0xe0, 0x45, 0x53, 0xbe, 0xf6, 0x8f, 0xfe, 0x0e, 0x00, 0x00, 0xff, - 0xff, 0x6d, 0x53, 0xc5, 0x1e, 0xdf, 0x09, 0x00, 0x00, + // 982 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xa4, 0x56, 0x4d, 0x8f, 0xdb, 0x44, + 0x18, 0xae, 0x9b, 0x4f, 0xbf, 0xd9, 0x6c, 0xbd, 0x43, 0x54, 0x59, 0x0b, 0xbb, 0x09, 0x96, 0x90, + 0x16, 0x84, 0x12, 0x01, 0x45, 0xa0, 0xb2, 0x48, 0xec, 0xb6, 0xdb, 0x14, 0x95, 0xb4, 0x65, 0x92, + 0x1c, 0xca, 0xc5, 0x9a, 0x24, 0xb3, 0x8e, 0x85, 0xbf, 0xb0, 0xc7, 0x15, 0xcb, 0x9d, 0xdf, 0xc0, + 0x1f, 0xe0, 0x67, 0x70, 0x46, 0x3d, 0x72, 0xe2, 0x88, 0xd0, 0xfe, 0x0e, 0x0e, 0x68, 0xbe, 0xec, + 0x6c, 0xe5, 0x2c, 0x2c, 0xdc, 0x3c, 0x8f, 0x9f, 0x67, 0xe6, 0x79, 0x1f, 0xdb, 0xef, 0x6b, 0x70, + 0xfc, 0x78, 0x94, 0xa4, 0x71, 0x48, 0xd9, 0x9a, 0xe6, 0xd9, 0x68, 0x19, 0xf8, 0x34, 0x62, 0xa3, + 0x90, 0xb2, 0xd4, 0x5f, 0x66, 0xc3, 0x24, 0x8d, 0x59, 0x8c, 0x7a, 0x7e, 0x3c, 0x2c, 0x39, 0x43, + 0xc9, 0xd9, 0xef, 0x79, 0xb1, 0x17, 0x0b, 0xc2, 0x88, 0x5f, 0x49, 0xee, 0x7e, 0xdf, 0x8b, 0x63, + 0x2f, 0xa0, 0x23, 0xb1, 0x5a, 0xe4, 0xe7, 0x23, 0xe6, 0x87, 0x34, 0x63, 0x24, 0x4c, 0x24, 0xc1, + 0xf9, 0x18, 0xcc, 0xaf, 0xc8, 0x82, 0x06, 0xcf, 0x89, 0x9f, 0x22, 0x04, 0xf5, 0x88, 0x84, 0xd4, + 0x36, 0x06, 0xc6, 0x91, 0x89, 0xc5, 0x35, 0xea, 0x41, 0xe3, 0x25, 0x09, 0x72, 0x6a, 0xdf, 0x16, + 0xa0, 0x5c, 0x38, 0x07, 0xd0, 0x18, 0x93, 0xdc, 0xdb, 0xb8, 0xcd, 0x35, 0x86, 0xbe, 0xfd, 0xb3, + 0x01, 0xad, 0x07, 0x71, 0x1e, 0x31, 0x9a, 0x56, 0x33, 0xd0, 0x7d, 0x68, 0xd3, 0xef, 0x69, 0x98, + 0x04, 0x24, 0x15, 0x3b, 0x77, 0x3e, 0x3c, 0x1c, 0x56, 0xd5, 0x35, 0x3c, 0x53, 0x2c, 0x5c, 0xf0, + 0xd1, 0x18, 0xf6, 0x96, 0x29, 0x25, 0x8c, 0xae, 0xdc, 0xa2, 0x1c, 0xbb, 0x26, 0x36, 0xd9, 0x1f, + 0xca, 0x82, 0x87, 0xba, 0xe0, 0xe1, 0x4c, 0x33, 0xb0, 0xa5, 0x44, 0x05, 0xe2, 0x1c, 0x43, 0xfb, + 0xeb, 0x9c, 0x44, 0xcc, 0x0f, 0x28, 0xda, 0x87, 0xf6, 0x77, 0xea, 0x5a, 0x39, 0x2d, 0xd6, 0x57, + 0x33, 0x28, 0x8a, 0xfc, 0xdd, 0x80, 0xd6, 0x34, 0x0f, 0x43, 0x92, 0x5e, 0xa0, 0xb7, 0x61, 0x27, + 0x23, 0x61, 0x12, 0x50, 0x77, 0xc9, 0xcb, 0x16, 0x3b, 0xd4, 0x71, 0x47, 0x62, 0x22, 0x09, 0x74, + 0x00, 0xa0, 0x28, 0x59, 0x1e, 0xaa, 0x9d, 0x4c, 0x89, 0x4c, 0xf3, 0x10, 0x7d, 0xb1, 0x71, 0x7e, + 0x6d, 0x50, 0xdb, 0x1e, 0x88, 0x76, 0x7c, 0x5a, 0x7f, 0xf5, 0x47, 0xff, 0xd6, 0x86, 0xcb, 0xca, + 0x58, 0xea, 0xff, 0x21, 0x96, 0x3e, 0xb4, 0xe6, 0x11, 0xbb, 0x48, 0xe8, 0x6a, 0xcb, 0xe3, 0xfd, + 0xab, 0x01, 0xe6, 0x63, 0x3f, 0x63, 0xb1, 0x97, 0x92, 0xf0, 0xdf, 0xd4, 0xfe, 0x3e, 0xa0, 0x4d, + 0x8a, 0x7b, 0x1e, 0xc4, 0x84, 0x09, 0x6f, 0x06, 0xb6, 0x36, 0x88, 0x8f, 0x38, 0xfe, 0x4f, 0x49, + 0xdd, 0x87, 0xe6, 0x22, 0x5f, 0x7e, 0x4b, 0x99, 0xca, 0xe9, 0xad, 0xea, 0x9c, 0x4e, 0x05, 0x47, + 0xa5, 0xa4, 0x14, 0xd5, 0x19, 0xdd, 0xb9, 0x79, 0x46, 0xe8, 0x2e, 0x34, 0xb3, 0xe5, 0x9a, 0x86, + 0xc4, 0x6e, 0x0c, 0x8c, 0xa3, 0x3d, 0xac, 0x56, 0xe8, 0x1d, 0xd8, 0xfd, 0x81, 0xa6, 0xb1, 0xcb, + 0xd6, 0x29, 0xcd, 0xd6, 0x71, 0xb0, 0xb2, 0x9b, 0xc2, 0x7f, 0x97, 0xa3, 0x33, 0x0d, 0xf2, 0x12, + 0x05, 0x4d, 0x26, 0xd6, 0x12, 0x89, 0x99, 0x1c, 0x91, 0x79, 0x1d, 0x81, 0x55, 0xde, 0x56, 0x69, + 0xb5, 0xc5, 0x3e, 0xbb, 0x05, 0x49, 0x66, 0xf5, 0x04, 0xba, 0x11, 0xf5, 0x08, 0xf3, 0x5f, 0x52, + 0x37, 0x4b, 0x48, 0x64, 0x9b, 0x22, 0x93, 0xc1, 0x75, 0x99, 0x4c, 0x13, 0x12, 0xa9, 0x5c, 0x76, + 0xb4, 0x98, 0x63, 0xdc, 0x7c, 0xb1, 0xd9, 0x8a, 0x06, 0x8c, 0xd8, 0x30, 0xa8, 0x1d, 0x21, 0x5c, + 0x1c, 0xf1, 0x90, 0x83, 0x57, 0x68, 0xb2, 0x80, 0xce, 0xa0, 0xc6, 0x6b, 0xd4, 0xa8, 0x2c, 0xe2, + 0x09, 0x74, 0x93, 0x38, 0xf3, 0x4b, 0x6b, 0x3b, 0x37, 0xb3, 0xa6, 0xc5, 0xda, 0x5a, 0xb1, 0x99, + 0xb4, 0xd6, 0x95, 0xd6, 0x34, 0x5a, 0x58, 0x2b, 0x68, 0xd2, 0xda, 0xae, 0xb4, 0xa6, 0x51, 0x69, + 0xed, 0x18, 0x4c, 0xdd, 0x4d, 0x32, 0xdb, 0xba, 0xee, 0x6b, 0x2b, 0xda, 0x4f, 0x29, 0x70, 0x7e, + 0x35, 0xa0, 0x29, 0xed, 0xa2, 0x77, 0xc1, 0x5a, 0xe6, 0x61, 0x1e, 0x6c, 0x86, 0x21, 0xdf, 0xff, + 0x3b, 0x25, 0x2e, 0xcf, 0xbc, 0x07, 0x77, 0x5f, 0xa7, 0x5e, 0xf9, 0x0e, 0x7a, 0xaf, 0x09, 0xe4, + 0xf3, 0xed, 0x43, 0x27, 0x4f, 0x12, 0x9a, 0xba, 0x8b, 0x38, 0x8f, 0x56, 0xea, 0x63, 0x00, 0x01, + 0x9d, 0x72, 0xe4, 0x4a, 0x23, 0xad, 0xdd, 0xac, 0x91, 0x3a, 0xc7, 0x00, 0x65, 0xec, 0xfc, 0x95, + 0x8e, 0xcf, 0xcf, 0x33, 0x2a, 0x2b, 0xd8, 0xc3, 0x6a, 0xc5, 0xf1, 0x80, 0x46, 0x1e, 0x5b, 0x8b, + 0xd3, 0xbb, 0x58, 0xad, 0x9c, 0x9f, 0x0c, 0x68, 0xeb, 0x4d, 0xd1, 0x67, 0xd0, 0x08, 0xf8, 0x1c, + 0xb1, 0x0d, 0x91, 0x66, 0xbf, 0xda, 0x43, 0x31, 0x6a, 0xd4, 0x33, 0x96, 0x9a, 0xea, 0xfe, 0x8a, + 0x3e, 0x05, 0xf3, 0x26, 0xed, 0xbd, 0x24, 0x3b, 0x3f, 0xd6, 0xa0, 0x39, 0x11, 0x33, 0xf3, 0xff, + 0xf9, 0xfa, 0x00, 0x1a, 0x1e, 0x9f, 0x72, 0x6a, 0x42, 0xbd, 0x59, 0x2d, 0x16, 0x83, 0x10, 0x4b, + 0x26, 0xfa, 0x04, 0x5a, 0x4b, 0x39, 0xf8, 0x94, 0xe5, 0x83, 0x6a, 0x91, 0x9a, 0x8e, 0x58, 0xb3, + 0xb9, 0x30, 0x93, 0xc3, 0x44, 0xf5, 0xec, 0x2d, 0x42, 0x35, 0x71, 0xb0, 0x66, 0x73, 0x61, 0x2e, + 0xbb, 0xb5, 0x68, 0x45, 0x5b, 0x85, 0xaa, 0xa5, 0x63, 0xcd, 0x46, 0x9f, 0x83, 0xb9, 0xd6, 0x4d, + 0x5c, 0xb4, 0xa0, 0xad, 0xf1, 0x14, 0xbd, 0x1e, 0x97, 0x0a, 0xde, 0xf6, 0x8b, 0xc4, 0xdd, 0x30, + 0x13, 0x7d, 0xae, 0x86, 0x3b, 0x05, 0x36, 0xc9, 0x9c, 0x5f, 0x0c, 0xd8, 0x91, 0xcf, 0xe1, 0x11, + 0x09, 0xfd, 0xe0, 0xa2, 0xf2, 0x07, 0x03, 0x41, 0x7d, 0x4d, 0x83, 0x44, 0xfd, 0x5f, 0x88, 0x6b, + 0x74, 0x0f, 0xea, 0xdc, 0xa3, 0x88, 0x70, 0x77, 0x5b, 0xc7, 0x90, 0x3b, 0xcf, 0x2e, 0x12, 0x8a, + 0x05, 0x9b, 0x0f, 0x06, 0xf9, 0xa7, 0x64, 0xd7, 0xaf, 0x1b, 0x0c, 0x52, 0xa7, 0x07, 0x83, 0x54, + 0x70, 0x17, 0x79, 0xe4, 0x33, 0x11, 0xa1, 0x89, 0xc5, 0xf5, 0x7b, 0x0b, 0x80, 0xf2, 0x0c, 0xd4, + 0x81, 0xd6, 0x83, 0x67, 0xf3, 0xa7, 0xb3, 0x33, 0x6c, 0xdd, 0x42, 0x26, 0x34, 0xc6, 0x27, 0xf3, + 0xf1, 0x99, 0x65, 0x70, 0x7c, 0x3a, 0x9f, 0x4c, 0x4e, 0xf0, 0x0b, 0xeb, 0x36, 0x5f, 0xcc, 0x9f, + 0xce, 0x5e, 0x3c, 0x3f, 0x7b, 0x68, 0xd5, 0x50, 0x17, 0xcc, 0xc7, 0x5f, 0x4e, 0x67, 0xcf, 0xc6, + 0xf8, 0x64, 0x62, 0xd5, 0xd1, 0x1b, 0x70, 0x47, 0x68, 0xdc, 0x12, 0x6c, 0x9c, 0x3a, 0xaf, 0x2e, + 0x0f, 0x8d, 0xdf, 0x2e, 0x0f, 0x8d, 0x3f, 0x2f, 0x0f, 0x8d, 0x6f, 0x7a, 0x7e, 0xec, 0x96, 0x86, + 0x5d, 0x69, 0x78, 0xd1, 0x14, 0x6f, 0xfb, 0x47, 0x7f, 0x07, 0x00, 0x00, 0xff, 0xff, 0x1c, 0xe1, + 0xcf, 0xb8, 0x1d, 0x0a, 0x00, 0x00, } func (m *LabelPair) Marshal() (dAtA []byte, err error) { @@ -1328,6 +1338,22 @@ func (m *Histogram) MarshalToSizedBuffer(dAtA []byte) (int, error) { i -= len(m.XXX_unrecognized) copy(dAtA[i:], m.XXX_unrecognized) } + if len(m.Exemplars) > 0 { + for iNdEx := len(m.Exemplars) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.Exemplars[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintMetrics(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x1 + i-- + dAtA[i] = 0x82 + } + } if m.CreatedTimestamp != nil { { size, err := m.CreatedTimestamp.MarshalToSizedBuffer(dAtA[:i]) @@ -2006,6 +2032,12 @@ func (m *Histogram) Size() (n int) { l = m.CreatedTimestamp.Size() n += 1 + l + sovMetrics(uint64(l)) } + if len(m.Exemplars) > 0 { + for _, e := range m.Exemplars { + l = e.Size() + n += 2 + l + sovMetrics(uint64(l)) + } + } if m.XXX_unrecognized != nil { n += len(m.XXX_unrecognized) } @@ -3291,6 +3323,40 @@ func (m *Histogram) Unmarshal(dAtA []byte) error { return err } iNdEx = postIndex + case 16: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Exemplars", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowMetrics + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthMetrics + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthMetrics + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Exemplars = append(m.Exemplars, &Exemplar{}) + if err := m.Exemplars[len(m.Exemplars)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipMetrics(dAtA[iNdEx:]) diff --git a/prompb/io/prometheus/client/metrics.proto b/prompb/io/prometheus/client/metrics.proto index 06dcc34afb..fe55638bb7 100644 --- a/prompb/io/prometheus/client/metrics.proto +++ b/prompb/io/prometheus/client/metrics.proto @@ -112,6 +112,9 @@ message Histogram { // histograms. repeated sint64 positive_delta = 13; // Count delta of each bucket compared to previous one (or to zero for 1st bucket). repeated double positive_count = 14; // Absolute count of each bucket. + + // Only used for native histograms. These exemplars MUST have a timestamp. + repeated Exemplar exemplars = 16; } message Bucket { From 94ced726e27629d25e5668db7eb51ba2b3aad923 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 25 Jan 2024 10:20:14 +0100 Subject: [PATCH 011/237] build(deps): bump the k8s-io group with 2 updates (#13454) Bumps the k8s-io group with 2 updates: [k8s.io/api](https://github.com/kubernetes/api) and [k8s.io/client-go](https://github.com/kubernetes/client-go). Updates `k8s.io/api` from 0.28.4 to 0.29.1 - [Commits](https://github.com/kubernetes/api/compare/v0.28.4...v0.29.1) Updates `k8s.io/client-go` from 0.28.4 to 0.29.1 - [Changelog](https://github.com/kubernetes/client-go/blob/master/CHANGELOG.md) - [Commits](https://github.com/kubernetes/client-go/compare/v0.28.4...v0.29.1) --- updated-dependencies: - dependency-name: k8s.io/api dependency-type: direct:production update-type: version-update:semver-minor dependency-group: k8s-io - dependency-name: k8s.io/client-go dependency-type: direct:production update-type: version-update:semver-minor dependency-group: k8s-io ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 6 +++--- go.sum | 20 ++++++++++---------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/go.mod b/go.mod index 5147eaa9ec..c3c7facb5f 100644 --- a/go.mod +++ b/go.mod @@ -86,9 +86,9 @@ require ( google.golang.org/protobuf v1.32.0 gopkg.in/yaml.v2 v2.4.0 gopkg.in/yaml.v3 v3.0.1 - k8s.io/api v0.28.4 - k8s.io/apimachinery v0.28.4 - k8s.io/client-go v0.28.4 + k8s.io/api v0.29.1 + k8s.io/apimachinery v0.29.1 + k8s.io/client-go v0.29.1 k8s.io/klog v1.0.0 k8s.io/klog/v2 v2.120.1 ) diff --git a/go.sum b/go.sum index 209f58cbb4..f221d99d22 100644 --- a/go.sum +++ b/go.sum @@ -614,11 +614,11 @@ github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:v github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.7.0 h1:WSHQ+IS43OoUrWtD1/bbclrwK8TTH5hzp+umCiuxHgs= github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo/v2 v2.9.4 h1:xR7vG4IXt5RWx6FfIjyAtsoMAtnc3C/rFXBBd2AjZwE= -github.com/onsi/ginkgo/v2 v2.9.4/go.mod h1:gCQYp2Q+kSoIj7ykSVb9nskRSsR6PUj4AiLywzIhbKM= +github.com/onsi/ginkgo/v2 v2.13.0 h1:0jY9lJquiL8fcf3M4LAXN5aMlS/b2BV86HFFPCPMgE4= +github.com/onsi/ginkgo/v2 v2.13.0/go.mod h1:TE309ZR8s5FsKKpuB1YAQYBzCaAfUgatB/xlT/ETL/o= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= -github.com/onsi/gomega v1.27.6 h1:ENqfyGeS5AX/rlXDd/ETokDz93u0YufY1Pgxuy/PvWE= -github.com/onsi/gomega v1.27.6/go.mod h1:PIQNjfQwkP3aQAH7lf7j87O/5FiNr+ZR8+ipb+qQlhg= +github.com/onsi/gomega v1.29.0 h1:KIA/t2t5UBzoirT4H9tsML45GEbo3ouUnBHsCfD2tVg= +github.com/onsi/gomega v1.29.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ= github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= @@ -1262,12 +1262,12 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -k8s.io/api v0.28.4 h1:8ZBrLjwosLl/NYgv1P7EQLqoO8MGQApnbgH8tu3BMzY= -k8s.io/api v0.28.4/go.mod h1:axWTGrY88s/5YE+JSt4uUi6NMM+gur1en2REMR7IRj0= -k8s.io/apimachinery v0.28.4 h1:zOSJe1mc+GxuMnFzD4Z/U1wst50X28ZNsn5bhgIIao8= -k8s.io/apimachinery v0.28.4/go.mod h1:wI37ncBvfAoswfq626yPTe6Bz1c22L7uaJ8dho83mgg= -k8s.io/client-go v0.28.4 h1:Np5ocjlZcTrkyRJ3+T3PkXDpe4UpatQxj85+xjaD2wY= -k8s.io/client-go v0.28.4/go.mod h1:0VDZFpgoZfelyP5Wqu0/r/TRYcLYuJ2U1KEeoaPa1N4= +k8s.io/api v0.29.1 h1:DAjwWX/9YT7NQD4INu49ROJuZAAAP/Ijki48GUPzxqw= +k8s.io/api v0.29.1/go.mod h1:7Kl10vBRUXhnQQI8YR/R327zXC8eJ7887/+Ybta+RoQ= +k8s.io/apimachinery v0.29.1 h1:KY4/E6km/wLBguvCZv8cKTeOwwOBqFNjwJIdMkMbbRc= +k8s.io/apimachinery v0.29.1/go.mod h1:6HVkd1FwxIagpYrHSwJlQqZI3G9LfYWRPAkUvLnXTKU= +k8s.io/client-go v0.29.1 h1:19B/+2NGEwnFLzt0uB5kNJnfTsbV8w6TgQRz9l7ti7A= +k8s.io/client-go v0.29.1/go.mod h1:TDG/psL9hdet0TI9mGyHJSgRkW3H9JZk2dNEUS7bRks= k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00 h1:aVUu9fTY98ivBPKR9Y5w/AuzbMm96cd3YHRTU83I780= k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00/go.mod h1:AsvuZPBlUDVuCdzJ87iajxtXuR9oktsTctW/R9wwouA= k8s.io/utils v0.0.0-20230726121419-3b25d923346b h1:sgn3ZU783SCgtaSJjpcVVlRqd6GSnlTLKgpAAttJvpI= From 7f837694d9c3e9f308f87892a27bdcfd8e1a5bc0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 25 Jan 2024 10:20:35 +0100 Subject: [PATCH 012/237] build(deps): bump the go-opentelemetry-io group with 1 update (#13453) Bumps the go-opentelemetry-io group with 1 update: [go.opentelemetry.io/collector/semconv](https://github.com/open-telemetry/opentelemetry-collector). Updates `go.opentelemetry.io/collector/semconv` from 0.92.0 to 0.93.0 - [Release notes](https://github.com/open-telemetry/opentelemetry-collector/releases) - [Changelog](https://github.com/open-telemetry/opentelemetry-collector/blob/main/CHANGELOG-API.md) - [Commits](https://github.com/open-telemetry/opentelemetry-collector/compare/v0.92.0...v0.93.0) --- updated-dependencies: - dependency-name: go.opentelemetry.io/collector/semconv dependency-type: direct:production update-type: version-update:semver-minor dependency-group: go-opentelemetry-io ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index c3c7facb5f..3c0d01e413 100644 --- a/go.mod +++ b/go.mod @@ -61,7 +61,7 @@ require ( github.com/vultr/govultr/v2 v2.17.2 go.opentelemetry.io/collector/featuregate v1.0.1 go.opentelemetry.io/collector/pdata v1.0.1 - go.opentelemetry.io/collector/semconv v0.92.0 + go.opentelemetry.io/collector/semconv v0.93.0 go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.47.0 go.opentelemetry.io/otel v1.22.0 go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.22.0 diff --git a/go.sum b/go.sum index f221d99d22..c7850fd7bb 100644 --- a/go.sum +++ b/go.sum @@ -805,8 +805,8 @@ go.opentelemetry.io/collector/featuregate v1.0.1 h1:ok//hLSXttBbyu4sSV1pTx1nKdr5 go.opentelemetry.io/collector/featuregate v1.0.1/go.mod h1:QQXjP4etmJQhkQ20j4P/rapWuItYxoFozg/iIwuKnYg= go.opentelemetry.io/collector/pdata v1.0.1 h1:dGX2h7maA6zHbl5D3AsMnF1c3Nn+3EUftbVCLzeyNvA= go.opentelemetry.io/collector/pdata v1.0.1/go.mod h1:jutXeu0QOXYY8wcZ/hege+YAnSBP3+jpTqYU1+JTI5Y= -go.opentelemetry.io/collector/semconv v0.92.0 h1:3+OGPPuVu4rtrz8qGbpbiw7eKKULj4iJaSDTV52HM40= -go.opentelemetry.io/collector/semconv v0.92.0/go.mod h1:gZ0uzkXsN+J5NpiRcdp9xOhNGQDDui8Y62p15sKrlzo= +go.opentelemetry.io/collector/semconv v0.93.0 h1:eBlMcVNTwYYsVdAsCVDs4wvVYs75K1xcIDpqj16PG4c= +go.opentelemetry.io/collector/semconv v0.93.0/go.mod h1:gZ0uzkXsN+J5NpiRcdp9xOhNGQDDui8Y62p15sKrlzo= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.47.0 h1:sv9kVfal0MK0wBMCOGr+HeJm9v803BkJxGrk2au7j08= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.47.0/go.mod h1:SK2UL73Zy1quvRPonmOmRDiWk1KBV3LyIeeIxcEApWw= go.opentelemetry.io/otel v1.22.0 h1:xS7Ku+7yTFvDfDraDIJVpw7XPyuHlB9MCiqqX5mcJ6Y= From 97c186e2c883f5d3c56277fd0f5b2c7d613661ef Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 25 Jan 2024 10:24:14 +0100 Subject: [PATCH 013/237] build(deps): bump actions/upload-artifact from 3.1.3 to 4.0.0 (#13355) Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 3.1.3 to 4.0.0. - [Release notes](https://github.com/actions/upload-artifact/releases) - [Commits](https://github.com/actions/upload-artifact/compare/a8a3f3ad30e3422c9c7b888a15615d19a852ae32...c7d193f32edcb7bfad88892161225aeda64e9392) --- updated-dependencies: - dependency-name: actions/upload-artifact dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/fuzzing.yml | 2 +- .github/workflows/scorecards.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/fuzzing.yml b/.github/workflows/fuzzing.yml index 13f04f772e..5997570607 100644 --- a/.github/workflows/fuzzing.yml +++ b/.github/workflows/fuzzing.yml @@ -21,7 +21,7 @@ jobs: fuzz-seconds: 600 dry-run: false - name: Upload Crash - uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3 + uses: actions/upload-artifact@c7d193f32edcb7bfad88892161225aeda64e9392 # v4.0.0 if: failure() && steps.build.outcome == 'success' with: name: artifacts diff --git a/.github/workflows/scorecards.yml b/.github/workflows/scorecards.yml index 636dd1bd1a..a668a4ceb0 100644 --- a/.github/workflows/scorecards.yml +++ b/.github/workflows/scorecards.yml @@ -37,7 +37,7 @@ jobs: # Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF # format to the repository Actions tab. - name: "Upload artifact" - uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # tag=v3.1.3 + uses: actions/upload-artifact@c7d193f32edcb7bfad88892161225aeda64e9392 # tag=v4.0.0 with: name: SARIF file path: results.sarif From 1069c1f4950214f348f1bb2f6f4db7a2b1de41bf Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 25 Jan 2024 10:24:37 +0100 Subject: [PATCH 014/237] build(deps): bump bufbuild/buf-push-action (#13357) Bumps [bufbuild/buf-push-action](https://github.com/bufbuild/buf-push-action) from 342fc4cdcf29115a01cf12a2c6dd6aac68dc51e1 to a654ff18effe4641ebea4a4ce242c49800728459. - [Release notes](https://github.com/bufbuild/buf-push-action/releases) - [Commits](https://github.com/bufbuild/buf-push-action/compare/342fc4cdcf29115a01cf12a2c6dd6aac68dc51e1...a654ff18effe4641ebea4a4ce242c49800728459) --- updated-dependencies: - dependency-name: bufbuild/buf-push-action dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/buf.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/buf.yml b/.github/workflows/buf.yml index f6d5c9191a..dc0694394b 100644 --- a/.github/workflows/buf.yml +++ b/.github/workflows/buf.yml @@ -23,7 +23,7 @@ jobs: with: input: 'prompb' against: 'https://github.com/prometheus/prometheus.git#branch=main,ref=HEAD~1,subdir=prompb' - - uses: bufbuild/buf-push-action@342fc4cdcf29115a01cf12a2c6dd6aac68dc51e1 # v1.1.1 + - uses: bufbuild/buf-push-action@a654ff18effe4641ebea4a4ce242c49800728459 # v1.1.1 with: input: 'prompb' buf_token: ${{ secrets.BUF_TOKEN }} From 74b73d1e2cd7ae97f08974234b25425c4241b1d5 Mon Sep 17 00:00:00 2001 From: Bryan Boreham Date: Thu, 25 Jan 2024 10:48:49 +0000 Subject: [PATCH 015/237] Labels: Add DropMetricName function, used in PromQL (#13446) This function is called very frequently when executing PromQL functions, and we can do it much more efficiently inside Labels. In the common case that `__name__` comes first in the labels, we simply re-point to start at the next label, which is nearly free. `DropMetricName` is now so cheap I removed the cache - benchmarks show everything still goes faster. Signed-off-by: Bryan Boreham --- model/labels/labels.go | 13 +++++++++++++ model/labels/labels_stringlabels.go | 21 +++++++++++++++++++++ model/labels/labels_test.go | 6 ++++++ promql/engine.go | 29 +++++------------------------ promql/functions.go | 26 +++++++++++++------------- 5 files changed, 58 insertions(+), 37 deletions(-) diff --git a/model/labels/labels.go b/model/labels/labels.go index bf67224bba..b6663dad24 100644 --- a/model/labels/labels.go +++ b/model/labels/labels.go @@ -342,6 +342,19 @@ func (ls Labels) Validate(f func(l Label) error) error { return nil } +// DropMetricName returns Labels with "__name__" removed. +func (ls Labels) DropMetricName() Labels { + for i, l := range ls { + if l.Name == MetricName { + if i == 0 { // Make common case fast with no allocations. + return ls[1:] + } + return append(ls[:i], ls[i+1:]...) + } + } + return ls +} + // InternStrings calls intern on every string value inside ls, replacing them with what it returns. func (ls *Labels) InternStrings(intern func(string) string) { for i, l := range *ls { diff --git a/model/labels/labels_stringlabels.go b/model/labels/labels_stringlabels.go index d79a836796..f53c3b2d05 100644 --- a/model/labels/labels_stringlabels.go +++ b/model/labels/labels_stringlabels.go @@ -429,6 +429,27 @@ func (ls Labels) Validate(f func(l Label) error) error { return nil } +// DropMetricName returns Labels with "__name__" removed. +func (ls Labels) DropMetricName() Labels { + for i := 0; i < len(ls.data); { + lName, i2 := decodeString(ls.data, i) + size, i2 := decodeSize(ls.data, i2) + i2 += size + if lName == MetricName { + if i == 0 { // Make common case fast with no allocations. + ls.data = ls.data[i2:] + } else { + ls.data = ls.data[:i] + ls.data[i2:] + } + break + } else if lName[0] > MetricName[0] { // Stop looking if we've gone past. + break + } + i = i2 + } + return ls +} + // InternStrings calls intern on every string value inside ls, replacing them with what it returns. func (ls *Labels) InternStrings(intern func(string) string) { ls.data = intern(ls.data) diff --git a/model/labels/labels_test.go b/model/labels/labels_test.go index 44369278df..c497851d1b 100644 --- a/model/labels/labels_test.go +++ b/model/labels/labels_test.go @@ -447,6 +447,12 @@ func TestLabels_Get(t *testing.T) { require.Equal(t, "222", FromStrings("aaaa", "111", "bbb", "222").Get("bbb")) } +func TestLabels_DropMetricName(t *testing.T) { + require.True(t, Equal(FromStrings("aaa", "111", "bbb", "222"), FromStrings("aaa", "111", "bbb", "222").DropMetricName())) + require.True(t, Equal(FromStrings("aaa", "111"), FromStrings(MetricName, "myname", "aaa", "111").DropMetricName())) + require.True(t, Equal(FromStrings("__aaa__", "111", "bbb", "222"), FromStrings("__aaa__", "111", MetricName, "myname", "bbb", "222").DropMetricName())) +} + // BenchmarkLabels_Get was written to check whether a binary search can improve the performance vs the linear search implementation // The results have shown that binary search would only be better when searching last labels in scenarios with more than 10 labels. // In the following list, `old` is the linear search while `new` is the binary search implementation (without calling sort.Search, which performs even worse here) diff --git a/promql/engine.go b/promql/engine.go index 8c8afd181b..786b76e1f4 100644 --- a/promql/engine.go +++ b/promql/engine.go @@ -1076,7 +1076,7 @@ type EvalNodeHelper struct { Out Vector // Caches. - // DropMetricName and label_*. + // label_*. Dmn map[uint64]labels.Labels // funcHistogramQuantile for classic histograms. signatureToMetricWithBuckets map[string]*metricWithBuckets @@ -1101,21 +1101,6 @@ func (enh *EvalNodeHelper) resetBuilder(lbls labels.Labels) { } } -// DropMetricName is a cached version of DropMetricName. -func (enh *EvalNodeHelper) DropMetricName(l labels.Labels) labels.Labels { - if enh.Dmn == nil { - enh.Dmn = make(map[uint64]labels.Labels, len(enh.Out)) - } - h := l.Hash() - ret, ok := enh.Dmn[h] - if ok { - return ret - } - ret = dropMetricName(l) - enh.Dmn[h] = ret - return ret -} - // rangeEval evaluates the given expressions, and then for each step calls // 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 @@ -1492,7 +1477,7 @@ func (ev *evaluator) eval(expr parser.Expr) (parser.Value, annotations.Annotatio // vector functions, the only change needed is to drop the // metric name in the output. if e.Func.Name != "last_over_time" { - metric = dropMetricName(metric) + metric = metric.DropMetricName() } ss := Series{ Metric: metric, @@ -1624,7 +1609,7 @@ func (ev *evaluator) eval(expr parser.Expr) (parser.Value, annotations.Annotatio mat := val.(Matrix) if e.Op == parser.SUB { for i := range mat { - mat[i].Metric = dropMetricName(mat[i].Metric) + mat[i].Metric = mat[i].Metric.DropMetricName() for j := range mat[i].Floats { mat[i].Floats[j].F = -mat[i].Floats[j].F } @@ -2370,7 +2355,7 @@ func (ev *evaluator) VectorBinop(op parser.ItemType, lhs, rhs Vector, matching * } metric := resultMetric(ls.Metric, rs.Metric, op, matching, enh) if returnBool { - metric = enh.DropMetricName(metric) + metric = metric.DropMetricName() } insertedSigs, exists := matchedSigs[sig] if matching.Card == parser.CardOneToOne { @@ -2492,7 +2477,7 @@ func (ev *evaluator) VectorscalarBinop(op parser.ItemType, lhs Vector, rhs Scala lhsSample.F = float lhsSample.H = histogram if shouldDropMetricName(op) || returnBool { - lhsSample.Metric = enh.DropMetricName(lhsSample.Metric) + lhsSample.Metric = lhsSample.Metric.DropMetricName() } enh.Out = append(enh.Out, lhsSample) } @@ -2500,10 +2485,6 @@ func (ev *evaluator) VectorscalarBinop(op parser.ItemType, lhs Vector, rhs Scala return enh.Out } -func dropMetricName(l labels.Labels) labels.Labels { - return labels.NewBuilder(l).Del(labels.MetricName).Labels() -} - // scalarBinop evaluates a binary operation between two Scalars. func scalarBinop(op parser.ItemType, lhs, rhs float64) float64 { switch op { diff --git a/promql/functions.go b/promql/functions.go index 64cbd8e0fc..aef73e7f2b 100644 --- a/promql/functions.go +++ b/promql/functions.go @@ -445,7 +445,7 @@ func funcClamp(vals []parser.Value, args parser.Expressions, enh *EvalNodeHelper } for _, el := range vec { enh.Out = append(enh.Out, Sample{ - Metric: enh.DropMetricName(el.Metric), + Metric: el.Metric.DropMetricName(), F: math.Max(min, math.Min(max, el.F)), }) } @@ -458,7 +458,7 @@ func funcClampMax(vals []parser.Value, args parser.Expressions, enh *EvalNodeHel max := vals[1].(Vector)[0].F for _, el := range vec { enh.Out = append(enh.Out, Sample{ - Metric: enh.DropMetricName(el.Metric), + Metric: el.Metric.DropMetricName(), F: math.Min(max, el.F), }) } @@ -471,7 +471,7 @@ func funcClampMin(vals []parser.Value, args parser.Expressions, enh *EvalNodeHel min := vals[1].(Vector)[0].F for _, el := range vec { enh.Out = append(enh.Out, Sample{ - Metric: enh.DropMetricName(el.Metric), + Metric: el.Metric.DropMetricName(), F: math.Max(min, el.F), }) } @@ -493,7 +493,7 @@ func funcRound(vals []parser.Value, args parser.Expressions, enh *EvalNodeHelper for _, el := range vec { f := math.Floor(el.F*toNearestInverse+0.5) / toNearestInverse enh.Out = append(enh.Out, Sample{ - Metric: enh.DropMetricName(el.Metric), + Metric: el.Metric.DropMetricName(), F: f, }) } @@ -797,7 +797,7 @@ func simpleFunc(vals []parser.Value, enh *EvalNodeHelper, f func(float64) float6 for _, el := range vals[0].(Vector) { if el.H == nil { // Process only float samples. enh.Out = append(enh.Out, Sample{ - Metric: enh.DropMetricName(el.Metric), + Metric: el.Metric.DropMetricName(), F: f(el.F), }) } @@ -943,7 +943,7 @@ func funcTimestamp(vals []parser.Value, args parser.Expressions, enh *EvalNodeHe vec := vals[0].(Vector) for _, el := range vec { enh.Out = append(enh.Out, Sample{ - Metric: enh.DropMetricName(el.Metric), + Metric: el.Metric.DropMetricName(), F: float64(el.T) / 1000, }) } @@ -1057,7 +1057,7 @@ func funcHistogramCount(vals []parser.Value, args parser.Expressions, enh *EvalN continue } enh.Out = append(enh.Out, Sample{ - Metric: enh.DropMetricName(sample.Metric), + Metric: sample.Metric.DropMetricName(), F: sample.H.Count, }) } @@ -1074,7 +1074,7 @@ func funcHistogramSum(vals []parser.Value, args parser.Expressions, enh *EvalNod continue } enh.Out = append(enh.Out, Sample{ - Metric: enh.DropMetricName(sample.Metric), + Metric: sample.Metric.DropMetricName(), F: sample.H.Sum, }) } @@ -1107,7 +1107,7 @@ func funcHistogramStdDev(vals []parser.Value, args parser.Expressions, enh *Eval variance += cVariance variance /= sample.H.Count enh.Out = append(enh.Out, Sample{ - Metric: enh.DropMetricName(sample.Metric), + Metric: sample.Metric.DropMetricName(), F: math.Sqrt(variance), }) } @@ -1140,7 +1140,7 @@ func funcHistogramStdVar(vals []parser.Value, args parser.Expressions, enh *Eval variance += cVariance variance /= sample.H.Count enh.Out = append(enh.Out, Sample{ - Metric: enh.DropMetricName(sample.Metric), + Metric: sample.Metric.DropMetricName(), F: variance, }) } @@ -1159,7 +1159,7 @@ func funcHistogramFraction(vals []parser.Value, args parser.Expressions, enh *Ev continue } enh.Out = append(enh.Out, Sample{ - Metric: enh.DropMetricName(sample.Metric), + Metric: sample.Metric.DropMetricName(), F: histogramFraction(lower, upper, sample.H), }) } @@ -1230,7 +1230,7 @@ func funcHistogramQuantile(vals []parser.Value, args parser.Expressions, enh *Ev } enh.Out = append(enh.Out, Sample{ - Metric: enh.DropMetricName(sample.Metric), + Metric: sample.Metric.DropMetricName(), F: histogramQuantile(q, sample.H), }) } @@ -1440,7 +1440,7 @@ func dateWrapper(vals []parser.Value, enh *EvalNodeHelper, f func(time.Time) flo for _, el := range vals[0].(Vector) { t := time.Unix(int64(el.F), 0).UTC() enh.Out = append(enh.Out, Sample{ - Metric: enh.DropMetricName(el.Metric), + Metric: el.Metric.DropMetricName(), F: f(t), }) } From b9eab6e4b8436650559ff8ecf2a77cc31d24fc31 Mon Sep 17 00:00:00 2001 From: Bryan Boreham Date: Thu, 25 Jan 2024 10:57:54 +0000 Subject: [PATCH 016/237] tsdb: simplify internal series delete function (#13261) Lifting an optimisation from Agent code, `seriesHashmap.del` can use the unique series reference, doesn't need to check Labels. Also streamline the logic for deleting from `unique` and `conflicts` maps, and add some comments to help the next person. Signed-off-by: Bryan Boreham --- tsdb/head.go | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/tsdb/head.go b/tsdb/head.go index 692f7f77ac..cdcd3ea568 100644 --- a/tsdb/head.go +++ b/tsdb/head.go @@ -1751,32 +1751,31 @@ func (m *seriesHashmap) set(hash uint64, s *memSeries) { m.conflicts[hash] = append(l, s) } -func (m *seriesHashmap) del(hash uint64, lset labels.Labels) { +func (m *seriesHashmap) del(hash uint64, ref chunks.HeadSeriesRef) { var rem []*memSeries unique, found := m.unique[hash] switch { - case !found: + case !found: // Supplied hash is not stored. return - case labels.Equal(unique.lset, lset): + case unique.ref == ref: conflicts := m.conflicts[hash] - if len(conflicts) == 0 { + if len(conflicts) == 0 { // Exactly one series with this hash was stored delete(m.unique, hash) return } - rem = conflicts - default: - rem = append(rem, unique) + m.unique[hash] = conflicts[0] // First remaining series goes in 'unique'. + rem = conflicts[1:] // Keep the rest. + default: // The series to delete is somewhere in 'conflicts'. Keep all the ones that don't match. for _, s := range m.conflicts[hash] { - if !labels.Equal(s.lset, lset) { + if s.ref != ref { rem = append(rem, s) } } } - m.unique[hash] = rem[0] - if len(rem) == 1 { + if len(rem) == 0 { delete(m.conflicts, hash) } else { - m.conflicts[hash] = rem[1:] + m.conflicts[hash] = rem } } @@ -1891,7 +1890,7 @@ func (s *stripeSeries) gc(mint int64, minOOOMmapRef chunks.ChunkDiskMapperRef) ( } deleted[storage.SeriesRef(series.ref)] = struct{}{} - s.hashes[hashShard].del(hash, series.lset) + s.hashes[hashShard].del(hash, series.ref) delete(s.series[refShard], series.ref) deletedForCallback[series.ref] = series.lset } From 660df3488dc9fc43dcd6c80caa9c82334942c0c5 Mon Sep 17 00:00:00 2001 From: Arve Knudsen Date: Thu, 25 Jan 2024 13:48:21 +0100 Subject: [PATCH 017/237] otlptranslator/update-copy.sh: Fix sed command lines Signed-off-by: Arve Knudsen --- storage/remote/otlptranslator/update-copy.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/storage/remote/otlptranslator/update-copy.sh b/storage/remote/otlptranslator/update-copy.sh index 44b3a3245c..ee7198706a 100755 --- a/storage/remote/otlptranslator/update-copy.sh +++ b/storage/remote/otlptranslator/update-copy.sh @@ -18,5 +18,5 @@ rm -rf ./prometheus/*_test.go rm -rf ./tmp -sed -i '' 's#github.com/open-telemetry/opentelemetry-collector-contrib/pkg/translator/prometheus#github.com/prometheus/prometheus/storage/remote/otlptranslator/prometheus#g' ./prometheusremotewrite/*.go -sed -i '' '1s#^#// DO NOT EDIT. COPIED AS-IS. SEE ../README.md\n\n#g' ./prometheusremotewrite/*.go ./prometheus/*.go +sed -i 's#github.com/open-telemetry/opentelemetry-collector-contrib/pkg/translator/prometheus#github.com/prometheus/prometheus/storage/remote/otlptranslator/prometheus#g' ./prometheusremotewrite/*.go +sed -i '1s#^#// DO NOT EDIT. COPIED AS-IS. SEE ../README.md\n\n#g' ./prometheusremotewrite/*.go ./prometheus/*.go From c3b8ef1694ffd1ef4325eace32c6e3dddca8e710 Mon Sep 17 00:00:00 2001 From: Ben Kochie Date: Thu, 25 Jan 2024 14:18:47 +0100 Subject: [PATCH 018/237] Rollback k8s.io requirements (#13462) Rollback k8s.io Go modules to v0.28.6 to avoid forcing upgrade of Go to 1.21. This allows us to keep compatibility with the currently supported upstream Go releases. Signed-off-by: SuperQ --- documentation/examples/remote_storage/go.mod | 2 +- documentation/examples/remote_storage/go.sum | 82 -------------------- go.mod | 8 +- go.sum | 35 ++------- 4 files changed, 13 insertions(+), 114 deletions(-) diff --git a/documentation/examples/remote_storage/go.mod b/documentation/examples/remote_storage/go.mod index e7b5bb8be5..6faa5efc27 100644 --- a/documentation/examples/remote_storage/go.mod +++ b/documentation/examples/remote_storage/go.mod @@ -1,6 +1,6 @@ module github.com/prometheus/prometheus/documentation/examples/remote_storage -go 1.21 +go 1.20 require ( github.com/alecthomas/kingpin/v2 v2.4.0 diff --git a/documentation/examples/remote_storage/go.sum b/documentation/examples/remote_storage/go.sum index 9b8208baa2..da503b74aa 100644 --- a/documentation/examples/remote_storage/go.sum +++ b/documentation/examples/remote_storage/go.sum @@ -5,14 +5,11 @@ github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.4.0/go.mod h1:1fXstnBMas5kzG github.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0 h1:sXr+ck84g/ZlZUOZiNELInmMgOsuGwdjjVkEIde0OtY= github.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0/go.mod h1:okt5dMMTOFjX/aovMlrjvvXoPMBVSPzk9185BT0+eZM= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute/v4 v4.2.1 h1:UPeCRD+XY7QlaGQte2EVI2iOcWvUYA2XY8w5T/8v0NQ= -github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute/v4 v4.2.1/go.mod h1:oGV6NlB0cvi1ZbYRR2UN44QHxWFyGk+iylgD0qaMXjA= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork v1.1.0 h1:QM6sE5k2ZT/vI5BEe0r7mqjsUSnhVBFbOsVkEuaEfiA= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork/v2 v2.2.1 h1:bWh0Z2rOEDfB/ywv/l0iHN1JgyazE6kW/aIA89+CEK0= -github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork/v2 v2.2.1/go.mod h1:Bzf34hhAE9NSxailk8xVeLEZbUjOXcC+GnU1mMKdhLw= github.com/AzureAD/microsoft-authentication-library-for-go v1.1.1 h1:WpB/QDNLpMw72xHJc34BNNykqSOeEJDAWkhf0u12/Jk= github.com/AzureAD/microsoft-authentication-library-for-go v1.1.1/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI= github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= -github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= github.com/alecthomas/kingpin/v2 v2.4.0 h1:f48lwail6p8zpO1bC4TxtqACaGqHYA22qkHjHpqDjYY= github.com/alecthomas/kingpin/v2 v2.4.0/go.mod h1:0gyi0zQnjuFk8xrkNKamJoyUo382HRL7ATRpFZCw6tE= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= @@ -23,7 +20,6 @@ github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk5 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/armon/go-metrics v0.4.1 h1:hR91U9KYmb6bLBYLQjyM+3j+rcd/UhE+G78SFnF8gJA= -github.com/armon/go-metrics v0.4.1/go.mod h1:E6amYzXo6aW1tqzoZGT755KkbgrJsSdpwZ+3JqfkOG4= github.com/aws/aws-sdk-go v1.38.35/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro= github.com/aws/aws-sdk-go v1.45.25 h1:c4fLlh5sLdK2DCRTY1z0hyuJZU4ygxX8m1FswL6/nF4= github.com/aws/aws-sdk-go v1.45.25/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= @@ -35,7 +31,6 @@ github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XL github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4 h1:/inchEIKaYC1Akx+H+gqO04wryn5h75LSazbRlnya1k= -github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -44,31 +39,19 @@ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8Yc github.com/dennwc/varint v1.0.0 h1:kGNFFSSw8ToIy3obO/kKr8U9GZYUAxQEVuix4zfDWzE= github.com/dennwc/varint v1.0.0/go.mod h1:hnItb35rvZvJrbTALZtY/iQfDs48JKRG1RPpgziApxA= github.com/digitalocean/godo v1.104.1 h1:SZNxjAsskM/su0YW9P8Wx3gU0W1Z13b6tZlYNpl5BnA= -github.com/digitalocean/godo v1.104.1/go.mod h1:VAI/L5YDzMuPRU01lEEUSQ/sp5Z//1HnnFv/RBTEdbg= github.com/dnaeon/go-vcr v1.2.0 h1:zHCHvJYTMh1N7xnV7zf1m1GPBF9Ad0Jk/whtQ1663qI= -github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ= github.com/docker/distribution v2.8.2+incompatible h1:T3de5rq0dB1j30rp0sA2rER+m322EBzniBPB6ZIzuh8= -github.com/docker/distribution v2.8.2+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= github.com/docker/docker v24.0.6+incompatible h1:hceabKCtUgDqPu+qm0NgsaXf28Ljf4/pWFL7xjWWDgE= -github.com/docker/docker v24.0.6+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= -github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= -github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/edsrzf/mmap-go v1.1.0 h1:6EUwBLQ/Mcr1EYLE4Tn1VdW1A4ckqCQWZBw8Hr0kjpQ= -github.com/edsrzf/mmap-go v1.1.0/go.mod h1:19H/e8pUPLicwkyNgOykDXkJ9F0MHE+Z52B8EIth78Q= github.com/emicklei/go-restful/v3 v3.10.2 h1:hIovbnmBTLjHXkqEBUz3HGpXZdM7ZrE9fJIZIqlJLqE= -github.com/emicklei/go-restful/v3 v3.10.2/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/envoyproxy/go-control-plane v0.11.1 h1:wSUXTlLfiAQRWs2F+p+EKOY9rUyis1MyGqJ2DIk5HpM= -github.com/envoyproxy/go-control-plane v0.11.1/go.mod h1:uhMcXKCQMEJHiAb0w+YGefQLaTEw+YhGluxZkrTmD0g= github.com/envoyproxy/protoc-gen-validate v1.0.2 h1:QkIBuU5k+x7/QXPvPPnWXWlCdaBFApVqftFV6k087DA= -github.com/envoyproxy/protoc-gen-validate v1.0.2/go.mod h1:GpiZQP3dDbg4JouG/NNS7QWXpgx6x8QiMKdmN72jogE= github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs= -github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw= github.com/felixge/httpsnoop v1.0.3 h1:s/nj+GCswXYzN5v2DpNMuMQYe+0DDwt5WVCU6CWBdXk= github.com/felixge/httpsnoop v1.0.3/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= -github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= @@ -85,16 +68,11 @@ github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbV github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-openapi/jsonpointer v0.20.0 h1:ESKJdU9ASRfaPNOPRx12IUyA1vn3R9GiE3KYD14BXdQ= -github.com/go-openapi/jsonpointer v0.20.0/go.mod h1:6PGzBjjIIumbLYysB73Klnms1mwnU4G3YHOECG3CedA= github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE= -github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= github.com/go-openapi/swag v0.22.4 h1:QLMzNJnMGPRNDCbySlcj1x01tzU8/9LTTL9hZZZogBU= -github.com/go-openapi/swag v0.22.4/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= github.com/go-resty/resty/v2 v2.7.0 h1:me+K9p3uhSmXtrBZ4k9jcEAfJmuC8IivWHwaLZwPrFY= -github.com/go-resty/resty/v2 v2.7.0/go.mod h1:9PWDzw47qPphMRFfhsyk0NnSgvluHcljSMVIq3w7q0I= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-zookeeper/zk v1.0.3 h1:7M2kwOsc//9VeeFiPtf+uSJlVpU66x9Ba5+8XK7/TDg= -github.com/go-zookeeper/zk v1.0.3/go.mod h1:nOB03cncLtlp4t+UAkGSV+9beXP/akpekBwL+UX1Qcw= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= @@ -116,65 +94,43 @@ github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiu github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I= -github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= -github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= -github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= -github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4= github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gophercloud/gophercloud v1.7.0 h1:fyJGKh0LBvIZKLvBWvQdIgkaV5yTM3Jh9EYUh+UNCAs= -github.com/gophercloud/gophercloud v1.7.0/go.mod h1:aAVqcocTSXh2vYFZ1JTvx4EQmfgzxRcNupUfxZbBNDM= github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= -github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/grafana/regexp v0.0.0-20221122212121-6b5c0a4cb7fd h1:PpuIBO5P3e9hpqBD0O/HjhShYuM6XE0i/lbE6J94kww= github.com/grafana/regexp v0.0.0-20221122212121-6b5c0a4cb7fd/go.mod h1:M5qHK+eWfAv8VR/265dIuEpL3fNfeC21tXXp9itM24A= github.com/hashicorp/consul/api v1.25.1 h1:CqrdhYzc8XZuPnhIYZWH45toM0LB9ZeYr/gvpLVI3PE= -github.com/hashicorp/consul/api v1.25.1/go.mod h1:iiLVwR/htV7mas/sy0O+XSuEnrdBUUydemjxcUrAt4g= github.com/hashicorp/cronexpr v1.1.2 h1:wG/ZYIKT+RT3QkOdgYc+xsKWVRgnxJ1OJtjjy84fJ9A= -github.com/hashicorp/cronexpr v1.1.2/go.mod h1:P4wA0KBl9C5q2hABiMO7cp6jcIg96CDh1Efb3g1PWA4= github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= -github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= -github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= github.com/hashicorp/go-hclog v1.5.0 h1:bI2ocEMgcVlz55Oj1xZNBsVi900c7II+fWDyV9o+13c= -github.com/hashicorp/go-hclog v1.5.0/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= github.com/hashicorp/go-immutable-radix v1.3.1 h1:DKHmCUm2hRBK510BaiZlwvpD40f8bJFeZnpfm2KLowc= -github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= -github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= github.com/hashicorp/go-retryablehttp v0.7.4 h1:ZQgVdpTdAL7WpMIwLzCfbalOcSUdkDZnpUv3/+BxzFA= -github.com/hashicorp/go-retryablehttp v0.7.4/go.mod h1:Jy/gPYAdjqffZ/yFGCFV2doI5wjtH1ewM9u8iYVjtX8= github.com/hashicorp/go-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5Oi2viEzc= -github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= github.com/hashicorp/golang-lru v0.6.0 h1:uL2shRDx7RTrOrTCUZEGP/wJUFiUI8QT6E7z5o8jga4= -github.com/hashicorp/golang-lru v0.6.0/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/hashicorp/nomad/api v0.0.0-20230721134942-515895c7690c h1:Nc3Mt2BAnq0/VoLEntF/nipX+K1S7pG+RgwiitSv6v0= -github.com/hashicorp/nomad/api v0.0.0-20230721134942-515895c7690c/go.mod h1:O23qLAZuCx4htdY9zBaO4cJPXgleSFEdq6D/sezGgYE= github.com/hashicorp/serf v0.10.1 h1:Z1H2J60yRKvfDYAOZLd2MU0ND4AH/WDz7xYHDWQsIPY= -github.com/hashicorp/serf v0.10.1/go.mod h1:yL2t6BqATOLGc5HF7qbFkTfXoPIY0WZdWHfEvMqbG+4= github.com/hetznercloud/hcloud-go/v2 v2.4.0 h1:MqlAE+w125PLvJRCpAJmEwrIxoVdUdOyuFUhE/Ukbok= -github.com/hetznercloud/hcloud-go/v2 v2.4.0/go.mod h1:l7fA5xsncFBzQTyw29/dw5Yr88yEGKKdc6BHf24ONS0= github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4= -github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= github.com/influxdata/influxdb v1.11.4 h1:H3pVW+/tWQ4lkHhZxVQ13Ov1hmhHYaAzz8L5aq3ZNtw= github.com/influxdata/influxdb v1.11.4/go.mod h1:VO6X2zlamfmEf+Esc9dR+7UQhdE/krspWNEZPwxCrp0= github.com/ionos-cloud/sdk-go/v6 v6.1.9 h1:Iq3VIXzeEbc8EbButuACgfLMiY5TPVWUPNrF+Vsddo4= -github.com/ionos-cloud/sdk-go/v6 v6.1.9/go.mod h1:EzEgRIDxBELvfoa/uBN0kOQaqovLjUWEB7iW4/Q+t4k= github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= -github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA= github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= @@ -189,13 +145,11 @@ github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+o github.com/klauspost/compress v1.17.1 h1:NE3C767s2ak2bweCZo3+rdP4U/HoyVXLv/X9f2gPS5g= github.com/klauspost/compress v1.17.1/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= github.com/kolo/xmlrpc v0.0.0-20220921171641-a4b6fa1dd06b h1:udzkj9S/zlT5X367kqJis0QP7YMxobob6zhzq6Yre00= -github.com/kolo/xmlrpc v0.0.0-20220921171641-a4b6fa1dd06b/go.mod h1:pcaDhQK0/NJZEvtCO0qQPPropqV0sJOJ6YW7X+9kRwM= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= -github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= @@ -203,20 +157,13 @@ github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/linode/linodego v1.23.0 h1:s0ReCZtuN9Z1IoUN9w1RLeYO1dMZUGPwOQ/IBFsBHtU= -github.com/linode/linodego v1.23.0/go.mod h1:0U7wj/UQOqBNbKv1FYTXiBUXueR8DY4HvIotwE0ENgg= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= -github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= -github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= -github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/miekg/dns v1.1.56 h1:5imZaSeoRNvpM9SzWNhEcP9QliKiz20/dA2QabIGVnE= -github.com/miekg/dns v1.1.56/go.mod h1:cRm6Oo2C8TY9ZS/TqsSrseAcncm74lfK5G+ikN2SWWY= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= -github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= -github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -225,18 +172,13 @@ github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3Rllmb github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= -github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f h1:KUppIJq7/+SVif2QVs3tOP0zanoHgBEVAwHxUSIzRqU= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4= -github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= -github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.0.2 h1:9yCKha/T5XdGtO0q9Q9a6T5NUCsTn/DrBg0D7ufOcFM= -github.com/opencontainers/image-spec v1.0.2/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= github.com/ovh/go-ovh v1.4.3 h1:Gs3V823zwTFpzgGLZNI6ILS4rmxZgJwJCz54Er9LwD0= -github.com/ovh/go-ovh v1.4.3/go.mod h1:AkPXVtgwB6xlKblMjRKJJmjRp+ogrE7fz2lVgcQY8SY= github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 h1:KoWmjvw+nsYOo29YJK9vDA65RGE3NrOnUtO7a+RF9HU= github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -274,18 +216,14 @@ github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3c github.com/prometheus/prometheus v0.48.1 h1:CTszphSNTXkuCG6O0IfpKdHcJkvvnAAE1GbELKS+NFk= github.com/prometheus/prometheus v0.48.1/go.mod h1:SRw624aMAxTfryAcP8rOjg4S/sHHaetx2lyJJ2nM83g= github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= -github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= github.com/scaleway/scaleway-sdk-go v1.0.0-beta.21 h1:yWfiTPwYxB0l5fGMhl/G+liULugVIHD9AU77iNLrURQ= -github.com/scaleway/scaleway-sdk-go v1.0.0-beta.21/go.mod h1:fCa7OJZ/9DRTnOKmxvT6pn+LPWUptQAmHF/SBJUGEcg= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= -github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= -github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= @@ -293,7 +231,6 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/vultr/govultr/v2 v2.17.2 h1:gej/rwr91Puc/tgh+j33p/BLR16UrIPnSr+AIwYWZQs= -github.com/vultr/govultr/v2 v2.17.2/go.mod h1:ZFOKGWmgjytfyjeyAdhQlSWwTjh2ig+X49cAp50dzXI= github.com/xhit/go-str2duration/v2 v2.1.0 h1:lxklc02Drh6ynqX+DdPyp5pCKLUQpRT8bp8Ydu2Bstc= github.com/xhit/go-str2duration/v2 v2.1.0/go.mod h1:ohY8p+0f07DiV6Em5LKB0s2YpLtXVyJfNt1+BlmyAsU= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= @@ -314,7 +251,6 @@ go.opentelemetry.io/otel/trace v1.19.0/go.mod h1:mfaSyvGyEJEI0nyV2I4qhNQnbBOUUmY go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A= -go.uber.org/goleak v1.2.1/go.mod h1:qlT2yGI9QafXHhZZLxlSuNsMw3FFLxBr+tBRlmO1xH4= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= @@ -330,7 +266,6 @@ golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.13.0 h1:I/DsJXRlw/8l/0c24sM9yb0T4z9liZTduXvdAWYiysY= -golang.org/x/mod v0.13.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -361,7 +296,6 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.4.0 h1:zxkM55ReGkDlKSM+Fu41A+zmbZuaPVbGMzvvdUPznYQ= -golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -387,7 +321,6 @@ golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9sn golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.16.0 h1:m+B6fahuftsE9qjo0VWp2FW0mB3MTJvR0BaMQrq0pmE= -golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= @@ -404,7 +337,6 @@ golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roY golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.14.0 h1:jvNa2pY0M4r62jkRQ6RwEZZyPcymeL9XZMLBbV7U2nc= -golang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -414,9 +346,7 @@ google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCID google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/genproto v0.0.0-20231002182017-d307bd883b97 h1:SeZZZx0cP0fqUyA+oRzP9k7cSwJlvDFiROO72uwD6i0= -google.golang.org/genproto v0.0.0-20231002182017-d307bd883b97/go.mod h1:t1VqOqqvce95G3hIDCT5FeO3YUc6Q4Oe24L/+rNMxRk= google.golang.org/genproto/googleapis/api v0.0.0-20231012201019-e917dd12ba7a h1:myvhA4is3vrit1a6NZCWBIwN0kNEnX21DJOJX/NvIfI= -google.golang.org/genproto/googleapis/api v0.0.0-20231012201019-e917dd12ba7a/go.mod h1:SUBoKXbI1Efip18FClrQVGjWcyd0QZd8KkvdP34t7ww= google.golang.org/genproto/googleapis/rpc v0.0.0-20231009173412-8bfb1ae86b6c h1:jHkCUWkseRf+W+edG5hMzr/Uh1xkDREY4caybAq4dpY= google.golang.org/genproto/googleapis/rpc v0.0.0-20231009173412-8bfb1ae86b6c/go.mod h1:4cYg8o5yUbm77w8ZX00LhMVNl/YVBFJRYWDc0uYWMs0= google.golang.org/grpc v1.58.3 h1:BjnpXut1btbtgN/6sp+brB2Kbm2LjNXnidYujAVbSoQ= @@ -435,11 +365,8 @@ gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLks gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= -gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= -gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= @@ -452,21 +379,12 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= k8s.io/api v0.28.2 h1:9mpl5mOb6vXZvqbQmankOfPIGiudghwCoLl1EYfUZbw= -k8s.io/api v0.28.2/go.mod h1:RVnJBsjU8tcMq7C3iaRSGMeaKt2TWEUXcpIt/90fjEg= k8s.io/apimachinery v0.28.2 h1:KCOJLrc6gu+wV1BYgwik4AF4vXOlVJPdiqn0yAWWwXQ= -k8s.io/apimachinery v0.28.2/go.mod h1:RdzF87y/ngqk9H4z3EL2Rppv5jj95vGS/HaFXrLDApU= k8s.io/client-go v0.28.2 h1:DNoYI1vGq0slMBN/SWKMZMw0Rq+0EQW6/AK4v9+3VeY= -k8s.io/client-go v0.28.2/go.mod h1:sMkApowspLuc7omj1FOSUxSoqjr+d5Q0Yc0LOFnYFJY= k8s.io/klog v1.0.0 h1:Pt+yjF5aB1xDSVbau4VsWe+dQNzA0qv1LlXdC2dF6Q8= k8s.io/klog/v2 v2.100.1 h1:7WCHKK6K8fNhTqfBhISHQ97KrnJNFZMcQvKp7gP/tmg= -k8s.io/klog/v2 v2.100.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= k8s.io/kube-openapi v0.0.0-20230717233707-2695361300d9 h1:LyMgNKD2P8Wn1iAwQU5OhxCKlKJy0sHc+PcDwFB24dQ= -k8s.io/kube-openapi v0.0.0-20230717233707-2695361300d9/go.mod h1:wZK2AVp1uHCp4VamDVgBP2COHZjqD1T68Rf0CM3YjSM= k8s.io/utils v0.0.0-20230711102312-30195339c3c7 h1:ZgnF1KZsYxWIifwSNZFZgNtWE89WI5yiP5WwlfDoIyc= -k8s.io/utils v0.0.0-20230711102312-30195339c3c7/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= -sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= sigs.k8s.io/structured-merge-diff/v4 v4.3.0 h1:UZbZAZfX0wV2zr7YZorDz6GXROfDFj6LvqCRm4VUVKk= -sigs.k8s.io/structured-merge-diff/v4 v4.3.0/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08= sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= -sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= diff --git a/go.mod b/go.mod index 3c0d01e413..7e586580e6 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/prometheus/prometheus -go 1.21 +go 1.20 require ( github.com/Azure/azure-sdk-for-go/sdk/azcore v1.9.1 @@ -86,9 +86,9 @@ require ( google.golang.org/protobuf v1.32.0 gopkg.in/yaml.v2 v2.4.0 gopkg.in/yaml.v3 v3.0.1 - k8s.io/api v0.29.1 - k8s.io/apimachinery v0.29.1 - k8s.io/client-go v0.29.1 + k8s.io/api v0.28.6 + k8s.io/apimachinery v0.28.6 + k8s.io/client-go v0.28.6 k8s.io/klog v1.0.0 k8s.io/klog/v2 v2.120.1 ) diff --git a/go.sum b/go.sum index c7850fd7bb..fd9c104ca1 100644 --- a/go.sum +++ b/go.sum @@ -43,11 +43,9 @@ github.com/Azure/azure-sdk-for-go/sdk/internal v1.5.1/go.mod h1:s4kgfzA0covAXNic github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute/v5 v5.4.0 h1:QfV5XZt6iNa2aWMAt96CZEbfJ7kgG/qYIpq465Shr5E= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute/v5 v5.4.0/go.mod h1:uYt4CfhkJA9o0FN7jfE5minm/i4nUE4MjGUJkzB6Zs8= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/internal/v2 v2.0.0 h1:PTFGRSlMKCQelWwxUyYVEUqseBJVemLyqWJjvMyt0do= -github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/internal/v2 v2.0.0/go.mod h1:LRr2FzBTQlONPPa5HREE5+RjSCTXl7BwOvYOaWTqCaI= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork/v4 v4.3.0 h1:bXwSugBiSbgtz7rOtbfGf+woewp4f06orW9OP5BjHLA= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork/v4 v4.3.0/go.mod h1:Y/HgrePTmGy9HjdSGTqZNa+apUpTVIEVKXJyARP2lrk= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources v1.1.1 h1:7CBQ+Ei8SP2c6ydQTGCCrS35bDxgTMfoP2miAwK++OU= -github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources v1.1.1/go.mod h1:c/wcGeGx5FUPbM/JltUYHZcKmigwyVLJlDq+4HdtXaw= 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= github.com/AzureAD/microsoft-authentication-library-for-go v1.2.1 h1:DzHpqpoJVaCgOUdVHxE8QB52S6NiVdDQvGlny1qvPqA= @@ -150,7 +148,6 @@ github.com/digitalocean/godo v1.108.0/go.mod h1:R6EmmWI8CT1+fCtjWY9UCB+L5uufuZH1 github.com/distribution/reference v0.5.0 h1:/FUIFXtfc/x2gpa5/VGfiGLuOIdYa1t65IKK2OFGvA0= github.com/distribution/reference v0.5.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= github.com/dnaeon/go-vcr v1.2.0 h1:zHCHvJYTMh1N7xnV7zf1m1GPBF9Ad0Jk/whtQ1663qI= -github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ= github.com/docker/docker v25.0.0+incompatible h1:g9b6wZTblhMgzOT2tspESstfw6ySZ9kdm94BLDKaZac= github.com/docker/docker v25.0.0+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= @@ -189,7 +186,6 @@ github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSw github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4= github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20= github.com/frankban/quicktest v1.14.5 h1:dfYrrRyLtiqT9GyKXgdh+k4inNeTvmGbuSgZ3lx3GhA= -github.com/frankban/quicktest v1.14.5/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= @@ -258,7 +254,6 @@ github.com/go-resty/resty/v2 v2.11.0/go.mod h1:iiP/OpA0CkcL3IGt1O0+/SIItFUbkkyw5 github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= -github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= github.com/go-zookeeper/zk v1.0.3 h1:7M2kwOsc//9VeeFiPtf+uSJlVpU66x9Ba5+8XK7/TDg= github.com/go-zookeeper/zk v1.0.3/go.mod h1:nOB03cncLtlp4t+UAkGSV+9beXP/akpekBwL+UX1Qcw= github.com/gobuffalo/attrs v0.0.0-20190224210810-a9411de4debd/go.mod h1:4duuawTqi2wkkpB4ePgWMaai6/Kc6WEz83bhFwpHzj0= @@ -336,7 +331,6 @@ github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEW github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4= -github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA= github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I= github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= @@ -406,7 +400,6 @@ github.com/hashicorp/consul/api v1.27.0 h1:gmJ6DPKQog1426xsdmgk5iqDyoRiNc+ipBdJO github.com/hashicorp/consul/api v1.27.0/go.mod h1:JkekNRSou9lANFdt+4IKx3Za7XY0JzzpQjEb4Ivo1c8= github.com/hashicorp/consul/sdk v0.3.0/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= github.com/hashicorp/consul/sdk v0.15.1 h1:kKIGxc7CZtflcF5DLfHeq7rOQmRq3vk7kwISN9bif8Q= -github.com/hashicorp/consul/sdk v0.15.1/go.mod h1:7pxqqhqoaPqnBnzXD1StKed62LqJeClzVsUEy85Zr0A= github.com/hashicorp/cronexpr v1.1.2 h1:wG/ZYIKT+RT3QkOdgYc+xsKWVRgnxJ1OJtjjy84fJ9A= github.com/hashicorp/cronexpr v1.1.2/go.mod h1:P4wA0KBl9C5q2hABiMO7cp6jcIg96CDh1Efb3g1PWA4= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= @@ -424,7 +417,6 @@ github.com/hashicorp/go-immutable-radix v1.3.1 h1:DKHmCUm2hRBK510BaiZlwvpD40f8bJ github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= github.com/hashicorp/go-msgpack v0.5.5 h1:i9R9JSrqIz0QVLz3sz+i3YJdT7TTSLcfLLzJi9aZTuI= -github.com/hashicorp/go-msgpack v0.5.5/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA= github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= @@ -437,12 +429,10 @@ github.com/hashicorp/go-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5O github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= github.com/hashicorp/go-sockaddr v1.0.2 h1:ztczhD1jLxIRjVejw8gFomI1BQZOe2WoVOu0SyteCQc= -github.com/hashicorp/go-sockaddr v1.0.2/go.mod h1:rB4wwRAUzs07qva3c5SdrY/NEtAUjGlgmH/UkBUC97A= github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8= -github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek= github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= @@ -474,7 +464,6 @@ github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod github.com/ionos-cloud/sdk-go/v6 v6.1.11 h1:J/uRN4UWO3wCyGOeDdMKv8LWRzKu6UIkLEaes38Kzh8= github.com/ionos-cloud/sdk-go/v6 v6.1.11/go.mod h1:EzEgRIDxBELvfoa/uBN0kOQaqovLjUWEB7iW4/Q+t4k= github.com/jarcoal/httpmock v1.3.0 h1:2RJ8GP0IIaWwcC9Fp2BmVi8Kog3v2Hn7VXM3fTd+nuc= -github.com/jarcoal/httpmock v1.3.0/go.mod h1:3yb8rc4BI7TCBhFY8ng0gjuLKJNquuDNiPaZjnENuYg= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= @@ -517,7 +506,6 @@ github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFB github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= -github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= @@ -555,7 +543,6 @@ github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/maxatome/go-testdeep v1.12.0 h1:Ql7Go8Tg0C1D/uMMX59LAoYK7LffeJQ6X2T04nTH68g= -github.com/maxatome/go-testdeep v1.12.0/go.mod h1:lPZc/HAcJMP92l7yI6TRz1aZN5URwUBUAfUNvrclaNM= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso= github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI= @@ -568,7 +555,6 @@ github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= github.com/mitchellh/go-testing-interface v1.14.1 h1:jrgshOhYAUVNMAJiKbEu7EqAwgJJ2JqpQmpLJOu07cU= -github.com/mitchellh/go-testing-interface v1.14.1/go.mod h1:gfgS7OtZj6MA4U1UrDRp04twqAjfvlZyCfX3sDjEym8= github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= @@ -614,11 +600,9 @@ github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:v github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.7.0 h1:WSHQ+IS43OoUrWtD1/bbclrwK8TTH5hzp+umCiuxHgs= github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo/v2 v2.13.0 h1:0jY9lJquiL8fcf3M4LAXN5aMlS/b2BV86HFFPCPMgE4= -github.com/onsi/ginkgo/v2 v2.13.0/go.mod h1:TE309ZR8s5FsKKpuB1YAQYBzCaAfUgatB/xlT/ETL/o= +github.com/onsi/ginkgo/v2 v2.9.4 h1:xR7vG4IXt5RWx6FfIjyAtsoMAtnc3C/rFXBBd2AjZwE= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= -github.com/onsi/gomega v1.29.0 h1:KIA/t2t5UBzoirT4H9tsML45GEbo3ouUnBHsCfD2tVg= -github.com/onsi/gomega v1.29.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ= +github.com/onsi/gomega v1.27.6 h1:ENqfyGeS5AX/rlXDd/ETokDz93u0YufY1Pgxuy/PvWE= github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= @@ -660,7 +644,6 @@ github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s= github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g= -github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U= github.com/prometheus/alertmanager v0.26.0 h1:uOMJWfIwJguc3NaM3appWNbbrh6G/OjvaHMk22aBBYc= github.com/prometheus/alertmanager v0.26.0/go.mod h1:rVcnARltVjavgVaNnmevxK7kOn7IZavyf0KNgHkbEpU= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= @@ -710,7 +693,6 @@ github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFR github.com/rogpeppe/go-internal v1.2.2/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= -github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E= @@ -719,7 +701,6 @@ github.com/scaleway/scaleway-sdk-go v1.0.0-beta.22/go.mod h1:fCa7OJZ/9DRTnOKmxvT github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 h1:nn5Wsu0esKSJiIVhscUtVbo7ada43DJhG55ua/hjS5I= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/shoenig/test v0.6.6 h1:Oe8TPH9wAbv++YPNDKJWUnI8Q4PPWCx3UbOfH+FxiMU= -github.com/shoenig/test v0.6.6/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k= github.com/shurcooL/httpfs v0.0.0-20230704072500-f1e31cf0ba5c h1:aqg5Vm5dwtvL+YgDpBcK1ITf3o96N/K7/wsRXQnUTEs= github.com/shurcooL/httpfs v0.0.0-20230704072500-f1e31cf0ba5c/go.mod h1:owqhoLW1qZoYLZzLnBw+QkPP9WZnjlSWihhxAJC1+/M= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= @@ -1262,12 +1243,12 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -k8s.io/api v0.29.1 h1:DAjwWX/9YT7NQD4INu49ROJuZAAAP/Ijki48GUPzxqw= -k8s.io/api v0.29.1/go.mod h1:7Kl10vBRUXhnQQI8YR/R327zXC8eJ7887/+Ybta+RoQ= -k8s.io/apimachinery v0.29.1 h1:KY4/E6km/wLBguvCZv8cKTeOwwOBqFNjwJIdMkMbbRc= -k8s.io/apimachinery v0.29.1/go.mod h1:6HVkd1FwxIagpYrHSwJlQqZI3G9LfYWRPAkUvLnXTKU= -k8s.io/client-go v0.29.1 h1:19B/+2NGEwnFLzt0uB5kNJnfTsbV8w6TgQRz9l7ti7A= -k8s.io/client-go v0.29.1/go.mod h1:TDG/psL9hdet0TI9mGyHJSgRkW3H9JZk2dNEUS7bRks= +k8s.io/api v0.28.6 h1:yy6u9CuIhmg55YvF/BavPBBXB+5QicB64njJXxVnzLo= +k8s.io/api v0.28.6/go.mod h1:AM6Ys6g9MY3dl/XNaNfg/GePI0FT7WBGu8efU/lirAo= +k8s.io/apimachinery v0.28.6 h1:RsTeR4z6S07srPg6XYrwXpTJVMXsjPXn0ODakMytSW0= +k8s.io/apimachinery v0.28.6/go.mod h1:QFNX/kCl/EMT2WTSz8k4WLCv2XnkOLMaL8GAVRMdpsA= +k8s.io/client-go v0.28.6 h1:Gge6ziyIdafRchfoBKcpaARuz7jfrK1R1azuwORIsQI= +k8s.io/client-go v0.28.6/go.mod h1:+nu0Yp21Oeo/cBCsprNVXB2BfJTV51lFfe5tXl2rUL8= k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00 h1:aVUu9fTY98ivBPKR9Y5w/AuzbMm96cd3YHRTU83I780= k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00/go.mod h1:AsvuZPBlUDVuCdzJ87iajxtXuR9oktsTctW/R9wwouA= k8s.io/utils v0.0.0-20230726121419-3b25d923346b h1:sgn3ZU783SCgtaSJjpcVVlRqd6GSnlTLKgpAAttJvpI= From de284944344a9746cf4d7465e3b23a2018a2ecba Mon Sep 17 00:00:00 2001 From: Arve Knudsen Date: Thu, 25 Jan 2024 14:27:43 +0100 Subject: [PATCH 019/237] Make update-copy.sh work for both OSX and GNU sed Signed-off-by: Arve Knudsen --- storage/remote/otlptranslator/update-copy.sh | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/storage/remote/otlptranslator/update-copy.sh b/storage/remote/otlptranslator/update-copy.sh index ee7198706a..10dc194d04 100755 --- a/storage/remote/otlptranslator/update-copy.sh +++ b/storage/remote/otlptranslator/update-copy.sh @@ -18,5 +18,10 @@ rm -rf ./prometheus/*_test.go rm -rf ./tmp -sed -i 's#github.com/open-telemetry/opentelemetry-collector-contrib/pkg/translator/prometheus#github.com/prometheus/prometheus/storage/remote/otlptranslator/prometheus#g' ./prometheusremotewrite/*.go -sed -i '1s#^#// DO NOT EDIT. COPIED AS-IS. SEE ../README.md\n\n#g' ./prometheusremotewrite/*.go ./prometheus/*.go +case $(sed --help 2>&1) in + *GNU*) set sed -i;; + *) set sed -i '';; +esac + +"$@" -e 's#github.com/open-telemetry/opentelemetry-collector-contrib/pkg/translator/prometheus#github.com/prometheus/prometheus/storage/remote/otlptranslator/prometheus#g' ./prometheusremotewrite/*.go +"$@" -e '1s#^#// DO NOT EDIT. COPIED AS-IS. SEE ../README.md\n\n#g' ./prometheusremotewrite/*.go ./prometheus/*.go From fe35f2dac3aabdaf18efbadbd3fb09a8fa48a4fd Mon Sep 17 00:00:00 2001 From: beorn7 Date: Thu, 25 Jan 2024 15:16:10 +0100 Subject: [PATCH 020/237] Name @beorn7 and @krajorama as maintainers for native histograms I have been the de-facto maintainer for native histograms from the beginning. So let's put this into MAINTAINERS.md. In addition, I hereby proposose George Krajcsovits AKA Krajo as a co-maintainer. He has contributed a lot of native histogram code, but more importantly, he has contributed substantially to reviewing other contributors' native histogram code, up to a point where I was merely rubberstamping the PRs he had already reviewed. I'm confident that he is ready to to be granted commit rights as outlined in the "Maintainers" section of the governance: https://prometheus.io/governance/#maintainers According to the same section of the governance, I will announce the proposed change on the developers mailing list and will give some time for lazy consensus before merging this PR. Signed-off-by: beorn7 --- MAINTAINERS.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/MAINTAINERS.md b/MAINTAINERS.md index e2fa9c2949..a776eb3594 100644 --- a/MAINTAINERS.md +++ b/MAINTAINERS.md @@ -8,6 +8,8 @@ Julien Pivotto ( / @roidelapluie) and Levi Harrison * `k8s`: Frederic Branczyk ( / @brancz) * `documentation` * `prometheus-mixin`: Matthias Loibl ( / @metalmatze) +* `model/histogram` and other code related to native histograms: Björn Rabenstein ( / @beorn7), +George Krajcsovits ( / @krajorama) * `storage` * `remote`: Callum Styan ( / @cstyan), Bartłomiej Płotka ( / @bwplotka), Tom Wilkie ( / @tomwilkie) * `tsdb`: Ganesh Vernekar ( / @codesome), Bartłomiej Płotka ( / @bwplotka), Jesús Vázquez ( / @jesusvazquez) @@ -17,6 +19,7 @@ Julien Pivotto ( / @roidelapluie) and Levi Harrison * `module`: Augustin Husson ( @nexucis) * `Makefile` and related build configuration: Simon Pasquier ( / @simonpasquier), Ben Kochie ( / @SuperQ) + For the sake of brevity, not all subtrees are explicitly listed. Due to the size of this repository, the natural changes in focus of maintainers over time, and nuances of where particular features live, this list will always be From a598ddfbc491a79d1c09964b51bb947ff198d660 Mon Sep 17 00:00:00 2001 From: Yury Molodov Date: Fri, 26 Jan 2024 09:25:23 +0100 Subject: [PATCH 021/237] ui/fix: correct url handling for stacked graphs (#13460) Signed-off-by: Yury Moladau --- web/ui/react-app/src/pages/graph/Panel.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/web/ui/react-app/src/pages/graph/Panel.tsx b/web/ui/react-app/src/pages/graph/Panel.tsx index 501ab67c94..f60812a503 100644 --- a/web/ui/react-app/src/pages/graph/Panel.tsx +++ b/web/ui/react-app/src/pages/graph/Panel.tsx @@ -194,7 +194,8 @@ class Panel extends Component { } const isHeatmap = isHeatmapData(query.data); - if (!isHeatmap) { + const isHeatmapDisplayMode = this.props.options.displayMode === GraphDisplayMode.Heatmap; + if (!isHeatmap && isHeatmapDisplayMode) { this.setOptions({ displayMode: GraphDisplayMode.Lines }); } From 66237c1996518375001849952f2bf3c1dac423e6 Mon Sep 17 00:00:00 2001 From: Bryan Boreham Date: Fri, 26 Jan 2024 11:01:39 +0000 Subject: [PATCH 022/237] tsdb: use cheaper Mutex on series Mutex is 8 bytes; RWMutex is 24 bytes and much more complicated. Since `RLock` is only used in two places, `UpdateMetadata` and `Delete`, neither of which are hotspots, we should use the cheaper one. Signed-off-by: Bryan Boreham --- tsdb/head.go | 6 +++--- tsdb/head_append.go | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/tsdb/head.go b/tsdb/head.go index cdcd3ea568..14985d2772 100644 --- a/tsdb/head.go +++ b/tsdb/head.go @@ -1484,9 +1484,9 @@ func (h *Head) Delete(ctx context.Context, mint, maxt int64, ms ...*labels.Match continue } - series.RLock() + series.Lock() t0, t1 := series.minTime(), series.maxTime() - series.RUnlock() + series.Unlock() if t0 == math.MinInt64 || t1 == math.MinInt64 { continue } @@ -2016,7 +2016,7 @@ func (s sample) Type() chunkenc.ValueType { // memSeries is the in-memory representation of a series. None of its methods // are goroutine safe and it is the caller's responsibility to lock it. type memSeries struct { - sync.RWMutex + sync.Mutex ref chunks.HeadSeriesRef lset labels.Labels diff --git a/tsdb/head_append.go b/tsdb/head_append.go index f112ffa3ae..801beb3777 100644 --- a/tsdb/head_append.go +++ b/tsdb/head_append.go @@ -684,9 +684,9 @@ func (a *headAppender) UpdateMetadata(ref storage.SeriesRef, lset labels.Labels, return 0, fmt.Errorf("unknown series when trying to add metadata with HeadSeriesRef: %d and labels: %s", ref, lset) } - s.RLock() + s.Lock() hasNewMetadata := s.meta == nil || *s.meta != meta - s.RUnlock() + s.Unlock() if hasNewMetadata { a.metadata = append(a.metadata, record.RefMetadata{ From a577a0a542c01e863e6d53effd23457031b97e47 Mon Sep 17 00:00:00 2001 From: Filip Petkovski Date: Fri, 26 Jan 2024 10:04:02 +0100 Subject: [PATCH 023/237] Fix last_over_time for native histograms The last_over_time retains a histogram sample without making a copy. This sample is now coming from the buffered iterator used for windowing functions, and can be reused for reading subsequent samples as the iterator progresses. I would propose copying the sample in the last_over_time function, similar to how it is done for rate, sum_over_time and others. Signed-off-by: Filip Petkovski --- promql/functions.go | 2 +- promql/testdata/native_histograms.test | 11 +++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/promql/functions.go b/promql/functions.go index aef73e7f2b..a0a118c114 100644 --- a/promql/functions.go +++ b/promql/functions.go @@ -603,7 +603,7 @@ func funcLastOverTime(vals []parser.Value, args parser.Expressions, enh *EvalNod } return append(enh.Out, Sample{ Metric: el.Metric, - H: h.H, + H: h.H.Copy(), }), nil } diff --git a/promql/testdata/native_histograms.test b/promql/testdata/native_histograms.test index b74886f73e..5f0945d32f 100644 --- a/promql/testdata/native_histograms.test +++ b/promql/testdata/native_histograms.test @@ -224,3 +224,14 @@ eval instant at 5m histogram_fraction(0, 4, balanced_histogram) # the first populated bucket after the span of empty buckets. eval instant at 5m histogram_quantile(0.5, balanced_histogram) {} 0.5 + +# Add histogram to test sum(last_over_time) regression +load 5m + incr_sum_histogram{number="1"} {{schema:0 sum:0 count:0 buckets:[1]}}+{{schema:0 sum:1 count:1 buckets:[1]}}x10 + incr_sum_histogram{number="2"} {{schema:0 sum:0 count:0 buckets:[1]}}+{{schema:0 sum:2 count:1 buckets:[1]}}x10 + +eval instant at 50m histogram_sum(sum(incr_sum_histogram)) + {} 30 + +eval instant at 50m histogram_sum(sum(last_over_time(incr_sum_histogram[5m]))) + {} 30 From 940f83a54037dbdc2ac4059bf562518b9f0788df Mon Sep 17 00:00:00 2001 From: Danny Kopping Date: Wed, 25 Oct 2023 22:31:26 +0200 Subject: [PATCH 024/237] Implementation NOTE: Rebased from main after refactor in #13014 Signed-off-by: Danny Kopping --- cmd/prometheus/main.go | 27 +- docs/command-line/prometheus.md | 1 + rules/fixtures/rules_dependencies.yaml | 7 + rules/fixtures/rules_multiple.yaml | 14 + rules/fixtures/rules_multiple_groups.yaml | 28 ++ rules/group.go | 135 +++++++- rules/manager.go | 29 +- rules/manager_test.go | 382 ++++++++++++++++++++++ 8 files changed, 595 insertions(+), 28 deletions(-) create mode 100644 rules/fixtures/rules_dependencies.yaml create mode 100644 rules/fixtures/rules_multiple.yaml create mode 100644 rules/fixtures/rules_multiple_groups.yaml diff --git a/cmd/prometheus/main.go b/cmd/prometheus/main.go index f7244646e2..b8b90ffbe7 100644 --- a/cmd/prometheus/main.go +++ b/cmd/prometheus/main.go @@ -137,6 +137,7 @@ type flagConfig struct { forGracePeriod model.Duration outageTolerance model.Duration resendDelay model.Duration + maxConcurrentEvals int64 web web.Options scrape scrape.Options tsdb tsdbOptions @@ -411,6 +412,9 @@ func main() { serverOnlyFlag(a, "rules.alert.resend-delay", "Minimum amount of time to wait before resending an alert to Alertmanager."). Default("1m").SetValue(&cfg.resendDelay) + serverOnlyFlag(a, "rules.max-concurrent-rule-evals", "Global concurrency limit for independent rules which can run concurrently."). + Default("4").Int64Var(&cfg.maxConcurrentEvals) + a.Flag("scrape.adjust-timestamps", "Adjust scrape timestamps by up to `scrape.timestamp-tolerance` to align them to the intended schedule. See https://github.com/prometheus/prometheus/issues/7846 for more context. Experimental. This flag will be removed in a future release."). Hidden().Default("true").BoolVar(&scrape.AlignScrapeTimestamps) @@ -749,17 +753,18 @@ func main() { queryEngine = promql.NewEngine(opts) ruleManager = rules.NewManager(&rules.ManagerOptions{ - Appendable: fanoutStorage, - Queryable: localStorage, - QueryFunc: rules.EngineQueryFunc(queryEngine, fanoutStorage), - NotifyFunc: rules.SendAlerts(notifierManager, cfg.web.ExternalURL.String()), - Context: ctxRule, - ExternalURL: cfg.web.ExternalURL, - Registerer: prometheus.DefaultRegisterer, - Logger: log.With(logger, "component", "rule manager"), - OutageTolerance: time.Duration(cfg.outageTolerance), - ForGracePeriod: time.Duration(cfg.forGracePeriod), - ResendDelay: time.Duration(cfg.resendDelay), + Appendable: fanoutStorage, + Queryable: localStorage, + QueryFunc: rules.EngineQueryFunc(queryEngine, fanoutStorage), + NotifyFunc: rules.SendAlerts(notifierManager, cfg.web.ExternalURL.String()), + Context: ctxRule, + ExternalURL: cfg.web.ExternalURL, + Registerer: prometheus.DefaultRegisterer, + Logger: log.With(logger, "component", "rule manager"), + OutageTolerance: time.Duration(cfg.outageTolerance), + ForGracePeriod: time.Duration(cfg.forGracePeriod), + ResendDelay: time.Duration(cfg.resendDelay), + MaxConcurrentEvals: cfg.maxConcurrentEvals, }) } diff --git a/docs/command-line/prometheus.md b/docs/command-line/prometheus.md index 747457de1d..de3baa1071 100644 --- a/docs/command-line/prometheus.md +++ b/docs/command-line/prometheus.md @@ -48,6 +48,7 @@ The Prometheus monitoring server | --rules.alert.for-outage-tolerance | Max time to tolerate prometheus outage for restoring "for" state of alert. Use with server mode only. | `1h` | | --rules.alert.for-grace-period | Minimum duration between alert and restored "for" state. This is maintained only for alerts with configured "for" time greater than grace period. Use with server mode only. | `10m` | | --rules.alert.resend-delay | Minimum amount of time to wait before resending an alert to Alertmanager. Use with server mode only. | `1m` | +| --rules.max-concurrent-rule-evals | Global concurrency limit for independent rules which can run concurrently. Use with server mode only. | `4` | | --alertmanager.notification-queue-capacity | The capacity of the queue for pending Alertmanager notifications. Use with server mode only. | `10000` | | --query.lookback-delta | The maximum lookback duration for retrieving metrics during expression evaluations and federation. Use with server mode only. | `5m` | | --query.timeout | Maximum time a query may take before being aborted. Use with server mode only. | `2m` | diff --git a/rules/fixtures/rules_dependencies.yaml b/rules/fixtures/rules_dependencies.yaml new file mode 100644 index 0000000000..31d2c61763 --- /dev/null +++ b/rules/fixtures/rules_dependencies.yaml @@ -0,0 +1,7 @@ +groups: + - name: test + rules: + - record: job:http_requests:rate5m + expr: sum by (job)(rate(http_requests_total[5m])) + - record: HighRequestRate + expr: job:http_requests:rate5m > 100 diff --git a/rules/fixtures/rules_multiple.yaml b/rules/fixtures/rules_multiple.yaml new file mode 100644 index 0000000000..db57bede1b --- /dev/null +++ b/rules/fixtures/rules_multiple.yaml @@ -0,0 +1,14 @@ +groups: + - name: test + rules: + # independents + - record: job:http_requests:rate1m + expr: sum by (job)(rate(http_requests_total[1m])) + - record: job:http_requests:rate5m + expr: sum by (job)(rate(http_requests_total[5m])) + + # dependents + - record: job:http_requests:rate15m + expr: sum by (job)(rate(http_requests_total[15m])) + - record: TooManyRequests + expr: job:http_requests:rate15m > 100 diff --git a/rules/fixtures/rules_multiple_groups.yaml b/rules/fixtures/rules_multiple_groups.yaml new file mode 100644 index 0000000000..87f31a6ca5 --- /dev/null +++ b/rules/fixtures/rules_multiple_groups.yaml @@ -0,0 +1,28 @@ +groups: + - name: http + rules: + # independents + - record: job:http_requests:rate1m + expr: sum by (job)(rate(http_requests_total[1m])) + - record: job:http_requests:rate5m + expr: sum by (job)(rate(http_requests_total[5m])) + + # dependents + - record: job:http_requests:rate15m + expr: sum by (job)(rate(http_requests_total[15m])) + - record: TooManyHTTPRequests + expr: job:http_requests:rate15m > 100 + + - name: grpc + rules: + # independents + - record: job:grpc_requests:rate1m + expr: sum by (job)(rate(grpc_requests_total[1m])) + - record: job:grpc_requests:rate5m + expr: sum by (job)(rate(grpc_requests_total[5m])) + + # dependents + - record: job:grpc_requests:rate15m + expr: sum by (job)(rate(grpc_requests_total[15m])) + - record: TooManyGRPCRequests + expr: job:grpc_requests:rate15m > 100 diff --git a/rules/group.go b/rules/group.go index 55673452e5..c742820a81 100644 --- a/rules/group.go +++ b/rules/group.go @@ -21,8 +21,11 @@ import ( "sync" "time" + "go.uber.org/atomic" "golang.org/x/exp/slices" + "github.com/prometheus/prometheus/promql/parser" + "github.com/go-kit/log" "github.com/go-kit/log/level" "github.com/prometheus/client_golang/prometheus" @@ -68,6 +71,7 @@ type Group struct { // Rule group evaluation iteration function, // defaults to DefaultEvalIterationFunc. evalIterationFunc GroupEvalIterationFunc + dependencyMap dependencyMap } // GroupEvalIterationFunc is used to implement and extend rule group @@ -126,6 +130,7 @@ func NewGroup(o GroupOptions) *Group { logger: log.With(o.Opts.Logger, "file", o.File, "group", o.Name), metrics: metrics, evalIterationFunc: evalIterationFunc, + dependencyMap: buildDependencyMap(o.Rules), } } @@ -421,7 +426,7 @@ func (g *Group) CopyState(from *Group) { // Eval runs a single evaluation cycle in which all rules are evaluated sequentially. func (g *Group) Eval(ctx context.Context, ts time.Time) { - var samplesTotal float64 + var samplesTotal atomic.Float64 for i, rule := range g.rules { select { case <-g.done: @@ -429,7 +434,12 @@ func (g *Group) Eval(ctx context.Context, ts time.Time) { default: } - func(i int, rule Rule) { + eval := func(i int, rule Rule, async bool) { + if async { + defer func() { + g.opts.ConcurrentEvalSema.Release(1) + }() + } logger := log.WithPrefix(g.logger, "name", rule.Name(), "index", i) ctx, sp := otel.Tracer("").Start(ctx, "rule") sp.SetAttributes(attribute.String("name", rule.Name())) @@ -465,7 +475,7 @@ func (g *Group) Eval(ctx context.Context, ts time.Time) { } rule.SetHealth(HealthGood) rule.SetLastError(nil) - samplesTotal += float64(len(vector)) + samplesTotal.Add(float64(len(vector))) if ar, ok := rule.(*AlertingRule); ok { ar.sendAlerts(ctx, ts, g.opts.ResendDelay, g.interval, g.opts.NotifyFunc) @@ -554,10 +564,19 @@ func (g *Group) Eval(ctx context.Context, ts time.Time) { } } } - }(i, rule) + } + + // If the rule has no dependencies, it can run concurrently because no other rules in this group depend on its output. + // Try run concurrently if there are slots available. + if g.dependencyMap.isIndependent(rule) && g.opts.ConcurrentEvalSema != nil && g.opts.ConcurrentEvalSema.TryAcquire(1) { + go eval(i, rule, true) + } else { + eval(i, rule, false) + } } + if g.metrics != nil { - g.metrics.GroupSamples.WithLabelValues(GroupKey(g.File(), g.Name())).Set(samplesTotal) + g.metrics.GroupSamples.WithLabelValues(GroupKey(g.File(), g.Name())).Set(samplesTotal.Load()) } g.cleanupStaleSeries(ctx, ts) } @@ -866,3 +885,109 @@ func NewGroupMetrics(reg prometheus.Registerer) *Metrics { return m } + +// dependencyMap is a data-structure which contains the relationships between rules within a group. +// It is used to describe the dependency associations between recording rules in a group whereby one rule uses the +// output metric produced by another recording rule in its expression (i.e. as its "input"). +type dependencyMap map[Rule][]Rule + +// dependents returns all rules which use the output of the given rule as one of their inputs. +func (m dependencyMap) dependents(r Rule) []Rule { + if len(m) == 0 { + return nil + } + + return m[r] +} + +// dependencies returns all the rules on which the given rule is dependent for input. +func (m dependencyMap) dependencies(r Rule) []Rule { + if len(m) == 0 { + return nil + } + + var parents []Rule + for parent, children := range m { + if len(children) == 0 { + continue + } + + for _, child := range children { + if child == r { + parents = append(parents, parent) + } + } + } + + return parents +} + +func (m dependencyMap) isIndependent(r Rule) bool { + if m == nil { + return false + } + + return len(m.dependents(r)) == 0 && len(m.dependencies(r)) == 0 +} + +// buildDependencyMap builds a data-structure which contains the relationships between rules within a group. +func buildDependencyMap(rules []Rule) dependencyMap { + dependencies := make(dependencyMap) + + if len(rules) <= 1 { + // No relationships if group has 1 or fewer rules. + return nil + } + + inputs := make(map[string][]Rule, len(rules)) + outputs := make(map[string][]Rule, len(rules)) + + var indeterminate bool + + for _, rule := range rules { + rule := rule + + name := rule.Name() + outputs[name] = append(outputs[name], rule) + + parser.Inspect(rule.Query(), func(node parser.Node, path []parser.Node) error { + if n, ok := node.(*parser.VectorSelector); ok { + // A wildcard metric expression means we cannot reliably determine if this rule depends on any other, + // which means we cannot safely run any rules concurrently. + if n.Name == "" && len(n.LabelMatchers) > 0 { + indeterminate = true + return nil + } + + // Rules which depend on "meta-metrics" like ALERTS and ALERTS_FOR_STATE will have undefined behaviour + // if they run concurrently. + if n.Name == alertMetricName || n.Name == alertForStateMetricName { + indeterminate = true + return nil + } + + inputs[n.Name] = append(inputs[n.Name], rule) + } + return nil + }) + } + + if indeterminate { + return nil + } + + if len(inputs) == 0 || len(outputs) == 0 { + // No relationships can be inferred. + return nil + } + + for output, outRules := range outputs { + for _, outRule := range outRules { + if rs, found := inputs[output]; found && len(rs) > 0 { + dependencies[outRule] = append(dependencies[outRule], rs...) + } + } + } + + return dependencies +} diff --git a/rules/manager.go b/rules/manager.go index ed4d42ebad..e9fa94e9e2 100644 --- a/rules/manager.go +++ b/rules/manager.go @@ -26,6 +26,7 @@ import ( "github.com/go-kit/log/level" "github.com/prometheus/client_golang/prometheus" "golang.org/x/exp/slices" + "golang.org/x/sync/semaphore" "github.com/prometheus/prometheus/model/labels" "github.com/prometheus/prometheus/model/rulefmt" @@ -103,18 +104,20 @@ type NotifyFunc func(ctx context.Context, expr string, alerts ...*Alert) // ManagerOptions bundles options for the Manager. type ManagerOptions struct { - ExternalURL *url.URL - QueryFunc QueryFunc - NotifyFunc NotifyFunc - Context context.Context - Appendable storage.Appendable - Queryable storage.Queryable - Logger log.Logger - Registerer prometheus.Registerer - OutageTolerance time.Duration - ForGracePeriod time.Duration - ResendDelay time.Duration - GroupLoader GroupLoader + ExternalURL *url.URL + QueryFunc QueryFunc + NotifyFunc NotifyFunc + Context context.Context + Appendable storage.Appendable + Queryable storage.Queryable + Logger log.Logger + Registerer prometheus.Registerer + OutageTolerance time.Duration + ForGracePeriod time.Duration + ResendDelay time.Duration + MaxConcurrentEvals int64 + ConcurrentEvalSema *semaphore.Weighted + GroupLoader GroupLoader Metrics *Metrics } @@ -130,6 +133,8 @@ func NewManager(o *ManagerOptions) *Manager { o.GroupLoader = FileLoader{} } + o.ConcurrentEvalSema = semaphore.NewWeighted(o.MaxConcurrentEvals) + m := &Manager{ groups: map[string]*Group{}, opts: o, diff --git a/rules/manager_test.go b/rules/manager_test.go index 3feae51de6..e3e156038e 100644 --- a/rules/manager_test.go +++ b/rules/manager_test.go @@ -19,15 +19,18 @@ import ( "math" "os" "sort" + "sync" "testing" "time" "github.com/go-kit/log" "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/testutil" "github.com/prometheus/common/model" "github.com/stretchr/testify/require" "go.uber.org/atomic" "go.uber.org/goleak" + "golang.org/x/sync/semaphore" "gopkg.in/yaml.v2" "github.com/prometheus/prometheus/model/labels" @@ -1402,3 +1405,382 @@ func TestNativeHistogramsInRecordingRules(t *testing.T) { require.Equal(t, expHist, fh) require.Equal(t, chunkenc.ValNone, it.Next()) } + +func TestDependencyMap(t *testing.T) { + ctx := context.Background() + opts := &ManagerOptions{ + Context: ctx, + Logger: log.NewNopLogger(), + } + + expr, err := parser.ParseExpr("sum by (user) (rate(requests[1m]))") + require.NoError(t, err) + rule := NewRecordingRule("user:requests:rate1m", expr, labels.Labels{}) + + expr, err = parser.ParseExpr("user:requests:rate1m <= 0") + require.NoError(t, err) + rule2 := NewAlertingRule("ZeroRequests", expr, 0, 0, labels.Labels{}, labels.Labels{}, labels.EmptyLabels(), "", true, log.NewNopLogger()) + group := NewGroup(GroupOptions{ + Name: "rule_group", + Interval: time.Second, + Rules: []Rule{rule, rule2}, + Opts: opts, + }) + + require.Equal(t, []Rule{rule2}, group.dependencyMap.dependents(rule)) + require.Len(t, group.dependencyMap.dependencies(rule), 0) + require.False(t, group.dependencyMap.isIndependent(rule)) + + require.Len(t, group.dependencyMap.dependents(rule2), 0) + require.Equal(t, []Rule{rule}, group.dependencyMap.dependencies(rule2)) + require.False(t, group.dependencyMap.isIndependent(rule2)) +} + +func TestNoDependency(t *testing.T) { + ctx := context.Background() + opts := &ManagerOptions{ + Context: ctx, + Logger: log.NewNopLogger(), + } + + expr, err := parser.ParseExpr("sum by (user) (rate(requests[1m]))") + require.NoError(t, err) + rule := NewRecordingRule("user:requests:rate1m", expr, labels.Labels{}) + + group := NewGroup(GroupOptions{ + Name: "rule_group", + Interval: time.Second, + Rules: []Rule{rule}, + Opts: opts, + }) + + // A group with only one rule cannot have dependencies. + require.False(t, group.dependencyMap.isIndependent(rule)) +} + +func TestNoMetricSelector(t *testing.T) { + ctx := context.Background() + opts := &ManagerOptions{ + Context: ctx, + Logger: log.NewNopLogger(), + } + + expr, err := parser.ParseExpr("sum by (user) (rate(requests[1m]))") + require.NoError(t, err) + rule := NewRecordingRule("user:requests:rate1m", expr, labels.Labels{}) + + expr, err = parser.ParseExpr(`count({user="bob"})`) + require.NoError(t, err) + rule2 := NewRecordingRule("user:requests:rate1m", expr, labels.Labels{}) + + group := NewGroup(GroupOptions{ + Name: "rule_group", + Interval: time.Second, + Rules: []Rule{rule, rule2}, + Opts: opts, + }) + + // A rule with no metric selector cannot be reliably determined to have no dependencies on other rules, and therefore + // all rules are not considered independent. + require.False(t, group.dependencyMap.isIndependent(rule)) + require.False(t, group.dependencyMap.isIndependent(rule2)) +} + +func TestDependentRulesWithNonMetricExpression(t *testing.T) { + ctx := context.Background() + opts := &ManagerOptions{ + Context: ctx, + Logger: log.NewNopLogger(), + } + + expr, err := parser.ParseExpr("sum by (user) (rate(requests[1m]))") + require.NoError(t, err) + rule := NewRecordingRule("user:requests:rate1m", expr, labels.Labels{}) + + expr, err = parser.ParseExpr("user:requests:rate1m <= 0") + require.NoError(t, err) + rule2 := NewAlertingRule("ZeroRequests", expr, 0, 0, labels.Labels{}, labels.Labels{}, labels.EmptyLabels(), "", true, log.NewNopLogger()) + + expr, err = parser.ParseExpr("3") + require.NoError(t, err) + rule3 := NewRecordingRule("three", expr, labels.Labels{}) + + group := NewGroup(GroupOptions{ + Name: "rule_group", + Interval: time.Second, + Rules: []Rule{rule, rule2, rule3}, + Opts: opts, + }) + + require.False(t, group.dependencyMap.isIndependent(rule)) + require.False(t, group.dependencyMap.isIndependent(rule2)) + require.True(t, group.dependencyMap.isIndependent(rule3)) +} + +func TestRulesDependentOnMetaMetrics(t *testing.T) { + ctx := context.Background() + opts := &ManagerOptions{ + Context: ctx, + Logger: log.NewNopLogger(), + } + + // This rule is not dependent on any other rules in its group but it does depend on `ALERTS`, which is produced by + // the rule engine, and is therefore not independent. + expr, err := parser.ParseExpr("count(ALERTS)") + require.NoError(t, err) + rule := NewRecordingRule("alert_count", expr, labels.Labels{}) + + // Create another rule so a dependency map is built (no map is built if a group contains one or fewer rules). + expr, err = parser.ParseExpr("1") + require.NoError(t, err) + rule2 := NewRecordingRule("one", expr, labels.Labels{}) + + group := NewGroup(GroupOptions{ + Name: "rule_group", + Interval: time.Second, + Rules: []Rule{rule, rule2}, + Opts: opts, + }) + + require.False(t, group.dependencyMap.isIndependent(rule)) +} + +func TestDependencyMapUpdatesOnGroupUpdate(t *testing.T) { + files := []string{"fixtures/rules.yaml"} + ruleManager := NewManager(&ManagerOptions{ + Context: context.Background(), + Logger: log.NewNopLogger(), + }) + + ruleManager.start() + defer ruleManager.Stop() + + err := ruleManager.Update(10*time.Second, files, labels.EmptyLabels(), "", nil) + require.NoError(t, err) + require.Greater(t, len(ruleManager.groups), 0, "expected non-empty rule groups") + + orig := make(map[string]dependencyMap, len(ruleManager.groups)) + for _, g := range ruleManager.groups { + // No dependency map is expected because there is only one rule in the group. + require.Empty(t, g.dependencyMap) + orig[g.Name()] = g.dependencyMap + } + + // Update once without changing groups. + err = ruleManager.Update(10*time.Second, files, labels.EmptyLabels(), "", nil) + require.NoError(t, err) + for h, g := range ruleManager.groups { + // Dependency maps are the same because of no updates. + require.Equal(t, orig[h], g.dependencyMap) + } + + // Groups will be recreated when updated. + files[0] = "fixtures/rules_dependencies.yaml" + err = ruleManager.Update(10*time.Second, files, labels.EmptyLabels(), "", nil) + require.NoError(t, err) + + for h, g := range ruleManager.groups { + // Dependency maps must change because the groups would've been updated. + require.NotEqual(t, orig[h], g.dependencyMap) + // We expect there to be some dependencies since the new rule group contains a dependency. + require.Greater(t, len(g.dependencyMap), 0) + } +} + +func TestAsyncRuleEvaluation(t *testing.T) { + storage := teststorage.New(t) + t.Cleanup(func() { storage.Close() }) + + const artificialDelay = time.Second + + var ( + inflightQueries atomic.Int32 + maxInflight atomic.Int32 + ) + + files := []string{"fixtures/rules_multiple.yaml"} + ruleManager := NewManager(&ManagerOptions{ + Context: context.Background(), + Logger: log.NewNopLogger(), + Appendable: storage, + QueryFunc: func(ctx context.Context, q string, ts time.Time) (promql.Vector, error) { + inflightQueries.Add(1) + defer func() { + inflightQueries.Add(-1) + }() + + // Artificially delay all query executions to highly concurrent execution improvement. + time.Sleep(artificialDelay) + + // return a stub sample + return promql.Vector{ + promql.Sample{Metric: labels.FromStrings("__name__", "test"), T: ts.UnixMilli(), F: 12345}, + }, nil + }, + }) + + // Evaluate groups manually to show the impact of async rule evaluations. + groups, errs := ruleManager.LoadGroups(time.Second, labels.EmptyLabels(), "", nil, files...) + require.Empty(t, errs) + require.Len(t, groups, 1) + + expectedRules := 4 + + t.Run("synchronous evaluation with independent rules", func(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + + for _, group := range groups { + require.Len(t, group.rules, expectedRules) + + start := time.Now() + + // Never expect more than 1 inflight query at a time. + go func() { + for { + select { + case <-ctx.Done(): + return + default: + highWatermark := maxInflight.Load() + current := inflightQueries.Load() + if current > highWatermark { + maxInflight.Store(current) + } + + time.Sleep(time.Millisecond) + } + } + }() + + group.Eval(ctx, start) + + require.EqualValues(t, 1, maxInflight.Load()) + // Each rule should take at least 1 second to execute sequentially. + require.GreaterOrEqual(t, time.Since(start).Seconds(), (time.Duration(expectedRules) * artificialDelay).Seconds()) + // Each rule produces one vector. + require.EqualValues(t, expectedRules, testutil.ToFloat64(group.metrics.GroupSamples)) + } + + cancel() + }) + + t.Run("asynchronous evaluation with independent rules", func(t *testing.T) { + // Reset. + inflightQueries.Store(0) + maxInflight.Store(0) + ctx, cancel := context.WithCancel(context.Background()) + + for _, group := range groups { + // Allow up to 2 concurrent rule evaluations. + group.opts.ConcurrentEvalSema = semaphore.NewWeighted(2) + require.Len(t, group.rules, expectedRules) + + start := time.Now() + + go func() { + for { + select { + case <-ctx.Done(): + return + default: + highWatermark := maxInflight.Load() + current := inflightQueries.Load() + if current > highWatermark { + maxInflight.Store(current) + } + + time.Sleep(time.Millisecond) + } + } + }() + + group.Eval(ctx, start) + + require.EqualValues(t, 3, maxInflight.Load()) + // Some rules should execute concurrently so should complete quicker. + require.Less(t, time.Since(start).Seconds(), (time.Duration(expectedRules) * artificialDelay).Seconds()) + // Each rule produces one vector. + require.EqualValues(t, expectedRules, testutil.ToFloat64(group.metrics.GroupSamples)) + } + + cancel() + }) +} + +func TestBoundedRuleEvalConcurrency(t *testing.T) { + storage := teststorage.New(t) + t.Cleanup(func() { storage.Close() }) + + const artificialDelay = time.Millisecond * 100 + + var ( + inflightQueries atomic.Int32 + maxInflight atomic.Int32 + maxConcurrency int64 = 3 + groupCount = 2 + ) + + files := []string{"fixtures/rules_multiple_groups.yaml"} + ruleManager := NewManager(&ManagerOptions{ + Context: context.Background(), + Logger: log.NewNopLogger(), + Appendable: storage, + MaxConcurrentEvals: maxConcurrency, + QueryFunc: func(ctx context.Context, q string, ts time.Time) (promql.Vector, error) { + inflightQueries.Add(1) + defer func() { + inflightQueries.Add(-1) + }() + + // Artificially delay all query executions to highly concurrent execution improvement. + time.Sleep(artificialDelay) + + // return a stub sample + return promql.Vector{ + promql.Sample{Metric: labels.FromStrings("__name__", "test"), T: ts.UnixMilli(), F: 12345}, + }, nil + }, + }) + + // Evaluate groups manually to show the impact of async rule evaluations. + groups, errs := ruleManager.LoadGroups(time.Second, labels.EmptyLabels(), "", nil, files...) + require.Empty(t, errs) + require.Len(t, groups, groupCount) + + ctx, cancel := context.WithCancel(context.Background()) + + go func() { + for { + select { + case <-ctx.Done(): + return + default: + highWatermark := maxInflight.Load() + current := inflightQueries.Load() + if current > highWatermark { + maxInflight.Store(current) + } + + time.Sleep(time.Millisecond) + } + } + }() + + // Evaluate groups concurrently (like they normally do). + var wg sync.WaitGroup + for _, group := range groups { + group := group + + wg.Add(1) + go func() { + group.Eval(ctx, time.Now()) + wg.Done() + }() + } + + wg.Wait() + cancel() + + // Synchronous queries also count towards inflight, so at most we can have maxConcurrency+$groupCount inflight evaluations. + require.EqualValues(t, maxInflight.Load(), int32(maxConcurrency)+int32(groupCount)) +} From ed2933ca60603c0f2eacb03726090493d641c695 Mon Sep 17 00:00:00 2001 From: Danny Kopping Date: Wed, 25 Oct 2023 23:05:01 +0200 Subject: [PATCH 025/237] Add feature flag Signed-off-by: Danny Kopping --- cmd/prometheus/main.go | 4 ++++ docs/feature_flags.md | 10 ++++++++++ 2 files changed, 14 insertions(+) diff --git a/cmd/prometheus/main.go b/cmd/prometheus/main.go index b8b90ffbe7..beb9e3b0ad 100644 --- a/cmd/prometheus/main.go +++ b/cmd/prometheus/main.go @@ -158,6 +158,7 @@ type flagConfig struct { enablePerStepStats bool enableAutoGOMAXPROCS bool enableAutoGOMEMLIMIT bool + enableConcurrentRuleEval bool prometheusURL string corsRegexString string @@ -204,6 +205,9 @@ func (c *flagConfig) setFeatureListOptions(logger log.Logger) error { case "auto-gomemlimit": c.enableAutoGOMEMLIMIT = true level.Info(logger).Log("msg", "Automatically set GOMEMLIMIT to match Linux container or system memory limit") + case "concurrent-rule-eval": + c.enableConcurrentRuleEval = true + level.Info(logger).Log("msg", "Experimental concurrent rule evaluation enabled.") case "no-default-scrape-port": c.scrape.NoDefaultPort = true level.Info(logger).Log("msg", "No default port will be appended to scrape targets' addresses.") diff --git a/docs/feature_flags.md b/docs/feature_flags.md index adefaad4b0..5517018df4 100644 --- a/docs/feature_flags.md +++ b/docs/feature_flags.md @@ -212,3 +212,13 @@ Enables ingestion of created timestamp. Created timestamps are injected as 0 val Currently Prometheus supports created timestamps only on the traditional Prometheus Protobuf protocol (WIP for other protocols). As a result, when enabling this feature, the Prometheus protobuf scrape protocol will be prioritized (See `scrape_config.scrape_protocols` settings for more details). Besides enabling this feature in Prometheus, created timestamps need to be exposed by the application being scraped. + +## Concurrent evaluation of independent rules + +`--enable-feature=concurrent-rule-eval` + +Rule groups execute concurrently, but the rules within a group execute sequentially; this is because rules can use the +output of a preceding rule as its input. However, if there is no detectable relationship between rules then there is no +reason to run them sequentially. This can improve rule reliability at the expense of adding more concurrent query +load. The number of concurrent rule evaluations can be configured with `--rules.max-concurrent-rule-evals` which is set +to `4` by default. From e7758d187e93817f86e3d9adf78dcaf6c79c15c0 Mon Sep 17 00:00:00 2001 From: Danny Kopping Date: Wed, 25 Oct 2023 23:05:25 +0200 Subject: [PATCH 026/237] Refactor concurrency control Signed-off-by: Danny Kopping --- cmd/prometheus/main.go | 25 ++++++++++--------- rules/group.go | 13 +++++----- rules/manager.go | 56 +++++++++++++++++++++++++++++++----------- rules/manager_test.go | 12 ++++----- 4 files changed, 67 insertions(+), 39 deletions(-) diff --git a/cmd/prometheus/main.go b/cmd/prometheus/main.go index beb9e3b0ad..8dd1d88fa0 100644 --- a/cmd/prometheus/main.go +++ b/cmd/prometheus/main.go @@ -757,18 +757,19 @@ func main() { queryEngine = promql.NewEngine(opts) ruleManager = rules.NewManager(&rules.ManagerOptions{ - Appendable: fanoutStorage, - Queryable: localStorage, - QueryFunc: rules.EngineQueryFunc(queryEngine, fanoutStorage), - NotifyFunc: rules.SendAlerts(notifierManager, cfg.web.ExternalURL.String()), - Context: ctxRule, - ExternalURL: cfg.web.ExternalURL, - Registerer: prometheus.DefaultRegisterer, - Logger: log.With(logger, "component", "rule manager"), - OutageTolerance: time.Duration(cfg.outageTolerance), - ForGracePeriod: time.Duration(cfg.forGracePeriod), - ResendDelay: time.Duration(cfg.resendDelay), - MaxConcurrentEvals: cfg.maxConcurrentEvals, + Appendable: fanoutStorage, + Queryable: localStorage, + QueryFunc: rules.EngineQueryFunc(queryEngine, fanoutStorage), + NotifyFunc: rules.SendAlerts(notifierManager, cfg.web.ExternalURL.String()), + Context: ctxRule, + ExternalURL: cfg.web.ExternalURL, + Registerer: prometheus.DefaultRegisterer, + Logger: log.With(logger, "component", "rule manager"), + OutageTolerance: time.Duration(cfg.outageTolerance), + ForGracePeriod: time.Duration(cfg.forGracePeriod), + ResendDelay: time.Duration(cfg.resendDelay), + MaxConcurrentEvals: cfg.maxConcurrentEvals, + ConcurrentEvalsEnabled: cfg.enableConcurrentRuleEval, }) } diff --git a/rules/group.go b/rules/group.go index c742820a81..2be41c8015 100644 --- a/rules/group.go +++ b/rules/group.go @@ -435,11 +435,12 @@ func (g *Group) Eval(ctx context.Context, ts time.Time) { } eval := func(i int, rule Rule, async bool) { - if async { - defer func() { - g.opts.ConcurrentEvalSema.Release(1) - }() - } + defer func() { + if async { + g.opts.ConcurrencyController.Done() + } + }() + logger := log.WithPrefix(g.logger, "name", rule.Name(), "index", i) ctx, sp := otel.Tracer("").Start(ctx, "rule") sp.SetAttributes(attribute.String("name", rule.Name())) @@ -568,7 +569,7 @@ func (g *Group) Eval(ctx context.Context, ts time.Time) { // If the rule has no dependencies, it can run concurrently because no other rules in this group depend on its output. // Try run concurrently if there are slots available. - if g.dependencyMap.isIndependent(rule) && g.opts.ConcurrentEvalSema != nil && g.opts.ConcurrentEvalSema.TryAcquire(1) { + if g.dependencyMap.isIndependent(rule) && g.opts.ConcurrencyController.Allow() { go eval(i, rule, true) } else { eval(i, rule, false) diff --git a/rules/manager.go b/rules/manager.go index e9fa94e9e2..0aeeae1703 100644 --- a/rules/manager.go +++ b/rules/manager.go @@ -104,20 +104,21 @@ type NotifyFunc func(ctx context.Context, expr string, alerts ...*Alert) // ManagerOptions bundles options for the Manager. type ManagerOptions struct { - ExternalURL *url.URL - QueryFunc QueryFunc - NotifyFunc NotifyFunc - Context context.Context - Appendable storage.Appendable - Queryable storage.Queryable - Logger log.Logger - Registerer prometheus.Registerer - OutageTolerance time.Duration - ForGracePeriod time.Duration - ResendDelay time.Duration - MaxConcurrentEvals int64 - ConcurrentEvalSema *semaphore.Weighted - GroupLoader GroupLoader + ExternalURL *url.URL + QueryFunc QueryFunc + NotifyFunc NotifyFunc + Context context.Context + Appendable storage.Appendable + Queryable storage.Queryable + Logger log.Logger + Registerer prometheus.Registerer + OutageTolerance time.Duration + ForGracePeriod time.Duration + ResendDelay time.Duration + MaxConcurrentEvals int64 + ConcurrentEvalsEnabled bool + ConcurrencyController ConcurrencyController + GroupLoader GroupLoader Metrics *Metrics } @@ -133,7 +134,7 @@ func NewManager(o *ManagerOptions) *Manager { o.GroupLoader = FileLoader{} } - o.ConcurrentEvalSema = semaphore.NewWeighted(o.MaxConcurrentEvals) + o.ConcurrencyController = NewConcurrencyController(o.ConcurrentEvalsEnabled, o.MaxConcurrentEvals) m := &Manager{ groups: map[string]*Group{}, @@ -408,3 +409,28 @@ func SendAlerts(s Sender, externalURL string) NotifyFunc { } } } + +type ConcurrencyController struct { + enabled bool + sema *semaphore.Weighted +} + +func NewConcurrencyController(enabled bool, maxConcurrency int64) ConcurrencyController { + return ConcurrencyController{enabled: enabled, sema: semaphore.NewWeighted(maxConcurrency)} +} + +func (c ConcurrencyController) Allow() bool { + if !c.enabled { + return false + } + + return c.sema.TryAcquire(1) +} + +func (c ConcurrencyController) Done() { + if !c.enabled { + return + } + + c.sema.Release(1) +} diff --git a/rules/manager_test.go b/rules/manager_test.go index e3e156038e..c2b23716f1 100644 --- a/rules/manager_test.go +++ b/rules/manager_test.go @@ -30,7 +30,6 @@ import ( "github.com/stretchr/testify/require" "go.uber.org/atomic" "go.uber.org/goleak" - "golang.org/x/sync/semaphore" "gopkg.in/yaml.v2" "github.com/prometheus/prometheus/model/labels" @@ -1672,7 +1671,7 @@ func TestAsyncRuleEvaluation(t *testing.T) { for _, group := range groups { // Allow up to 2 concurrent rule evaluations. - group.opts.ConcurrentEvalSema = semaphore.NewWeighted(2) + group.opts.ConcurrencyController = NewConcurrencyController(true, 2) require.Len(t, group.rules, expectedRules) start := time.Now() @@ -1722,10 +1721,11 @@ func TestBoundedRuleEvalConcurrency(t *testing.T) { files := []string{"fixtures/rules_multiple_groups.yaml"} ruleManager := NewManager(&ManagerOptions{ - Context: context.Background(), - Logger: log.NewNopLogger(), - Appendable: storage, - MaxConcurrentEvals: maxConcurrency, + Context: context.Background(), + Logger: log.NewNopLogger(), + Appendable: storage, + ConcurrentEvalsEnabled: true, + MaxConcurrentEvals: maxConcurrency, QueryFunc: func(ctx context.Context, q string, ts time.Time) (promql.Vector, error) { inflightQueries.Add(1) defer func() { From 0dc7036db3f9747a3b6ee43278f67f6591400ec0 Mon Sep 17 00:00:00 2001 From: Danny Kopping Date: Sat, 28 Oct 2023 11:25:12 +0200 Subject: [PATCH 027/237] Optimising dependencies/dependents funcs to not produce new slices each request Signed-off-by: Danny Kopping --- rules/group.go | 26 +++++++++++--------------- rules/manager_test.go | 27 ++++++++++++++++++++++----- 2 files changed, 33 insertions(+), 20 deletions(-) diff --git a/rules/group.go b/rules/group.go index 2be41c8015..568d606b58 100644 --- a/rules/group.go +++ b/rules/group.go @@ -892,35 +892,31 @@ func NewGroupMetrics(reg prometheus.Registerer) *Metrics { // output metric produced by another recording rule in its expression (i.e. as its "input"). type dependencyMap map[Rule][]Rule -// dependents returns all rules which use the output of the given rule as one of their inputs. -func (m dependencyMap) dependents(r Rule) []Rule { - if len(m) == 0 { - return nil - } - - return m[r] +// dependents returns the count of rules which use the output of the given rule as one of their inputs. +func (m dependencyMap) dependents(r Rule) int { + return len(m[r]) } -// dependencies returns all the rules on which the given rule is dependent for input. -func (m dependencyMap) dependencies(r Rule) []Rule { +// dependencies returns the count of rules on which the given rule is dependent for input. +func (m dependencyMap) dependencies(r Rule) int { if len(m) == 0 { - return nil + return 0 } - var parents []Rule - for parent, children := range m { + var count int + for _, children := range m { if len(children) == 0 { continue } for _, child := range children { if child == r { - parents = append(parents, parent) + count++ } } } - return parents + return count } func (m dependencyMap) isIndependent(r Rule) bool { @@ -928,7 +924,7 @@ func (m dependencyMap) isIndependent(r Rule) bool { return false } - return len(m.dependents(r)) == 0 && len(m.dependencies(r)) == 0 + return m.dependents(r)+m.dependencies(r) == 0 } // buildDependencyMap builds a data-structure which contains the relationships between rules within a group. diff --git a/rules/manager_test.go b/rules/manager_test.go index c2b23716f1..2a9b3a1d73 100644 --- a/rules/manager_test.go +++ b/rules/manager_test.go @@ -1419,20 +1419,37 @@ func TestDependencyMap(t *testing.T) { expr, err = parser.ParseExpr("user:requests:rate1m <= 0") require.NoError(t, err) rule2 := NewAlertingRule("ZeroRequests", expr, 0, 0, labels.Labels{}, labels.Labels{}, labels.EmptyLabels(), "", true, log.NewNopLogger()) + + expr, err = parser.ParseExpr("sum by (user) (rate(requests[5m]))") + require.NoError(t, err) + rule3 := NewRecordingRule("user:requests:rate5m", expr, labels.Labels{}) + + expr, err = parser.ParseExpr("increase(user:requests:rate1m[1h])") + require.NoError(t, err) + rule4 := NewRecordingRule("user:requests:increase1h", expr, labels.Labels{}) + group := NewGroup(GroupOptions{ Name: "rule_group", Interval: time.Second, - Rules: []Rule{rule, rule2}, + Rules: []Rule{rule, rule2, rule3, rule4}, Opts: opts, }) - require.Equal(t, []Rule{rule2}, group.dependencyMap.dependents(rule)) - require.Len(t, group.dependencyMap.dependencies(rule), 0) + require.Zero(t, group.dependencyMap.dependencies(rule)) + require.Equal(t, 2, group.dependencyMap.dependents(rule)) require.False(t, group.dependencyMap.isIndependent(rule)) - require.Len(t, group.dependencyMap.dependents(rule2), 0) - require.Equal(t, []Rule{rule}, group.dependencyMap.dependencies(rule2)) + require.Zero(t, group.dependencyMap.dependents(rule2)) + require.Equal(t, 1, group.dependencyMap.dependencies(rule2)) require.False(t, group.dependencyMap.isIndependent(rule2)) + + require.Zero(t, group.dependencyMap.dependents(rule3)) + require.Zero(t, group.dependencyMap.dependencies(rule3)) + require.True(t, group.dependencyMap.isIndependent(rule3)) + + require.Zero(t, group.dependencyMap.dependents(rule4)) + require.Equal(t, 1, group.dependencyMap.dependencies(rule4)) + require.False(t, group.dependencyMap.isIndependent(rule4)) } func TestNoDependency(t *testing.T) { From 94cdfa30cd724842c2971b6b01ed520041ac119e Mon Sep 17 00:00:00 2001 From: Danny Kopping Date: Sat, 28 Oct 2023 11:44:20 +0200 Subject: [PATCH 028/237] Refactoring Signed-off-by: Danny Kopping --- rules/group.go | 24 +++++++++----- rules/manager.go | 53 ++++++++++++++++++------------- rules/manager_test.go | 73 ++++++++++++++++++++++++++++++++++++++----- 3 files changed, 113 insertions(+), 37 deletions(-) diff --git a/rules/group.go b/rules/group.go index 568d606b58..7f53e1b474 100644 --- a/rules/group.go +++ b/rules/group.go @@ -437,7 +437,7 @@ func (g *Group) Eval(ctx context.Context, ts time.Time) { eval := func(i int, rule Rule, async bool) { defer func() { if async { - g.opts.ConcurrencyController.Done() + g.opts.ConcurrentEvalsController.Done() } }() @@ -569,7 +569,7 @@ func (g *Group) Eval(ctx context.Context, ts time.Time) { // If the rule has no dependencies, it can run concurrently because no other rules in this group depend on its output. // Try run concurrently if there are slots available. - if g.dependencyMap.isIndependent(rule) && g.opts.ConcurrencyController.Allow() { + if g.dependencyMap.isIndependent(rule) && g.opts.ConcurrentEvalsController != nil && g.opts.ConcurrentEvalsController.Allow() { go eval(i, rule, true) } else { eval(i, rule, false) @@ -888,8 +888,8 @@ func NewGroupMetrics(reg prometheus.Registerer) *Metrics { } // dependencyMap is a data-structure which contains the relationships between rules within a group. -// It is used to describe the dependency associations between recording rules in a group whereby one rule uses the -// output metric produced by another recording rule in its expression (i.e. as its "input"). +// It is used to describe the dependency associations between rules in a group whereby one rule uses the +// output metric produced by another rule in its expression (i.e. as its "input"). type dependencyMap map[Rule][]Rule // dependents returns the count of rules which use the output of the given rule as one of their inputs. @@ -905,10 +905,6 @@ func (m dependencyMap) dependencies(r Rule) int { var count int for _, children := range m { - if len(children) == 0 { - continue - } - for _, child := range children { if child == r { count++ @@ -919,6 +915,8 @@ func (m dependencyMap) dependencies(r Rule) int { return count } +// isIndependent determines whether the given rule is not dependent on another rule for its input, nor is any other rule +// dependent on its output. func (m dependencyMap) isIndependent(r Rule) bool { if m == nil { return false @@ -928,6 +926,16 @@ func (m dependencyMap) isIndependent(r Rule) bool { } // buildDependencyMap builds a data-structure which contains the relationships between rules within a group. +// +// Alert rules, by definition, cannot have any dependents - but they can have dependencies. Any recording rule on whose +// output an Alert rule depends will not be able to run concurrently. +// +// There is a class of rule expressions which are considered "indeterminate", because either relationships cannot be +// inferred, or concurrent evaluation of rules depending on these series would produce undefined/unexpected behaviour: +// - wildcard queriers like {cluster="prod1"} which would match every series with that label selector +// - any "meta" series (series produced by Prometheus itself) like ALERTS, ALERTS_FOR_STATE +// +// Rules which are independent can run concurrently with no side-effects. func buildDependencyMap(rules []Rule) dependencyMap { dependencies := make(dependencyMap) diff --git a/rules/manager.go b/rules/manager.go index 0aeeae1703..b982f23754 100644 --- a/rules/manager.go +++ b/rules/manager.go @@ -104,21 +104,21 @@ type NotifyFunc func(ctx context.Context, expr string, alerts ...*Alert) // ManagerOptions bundles options for the Manager. type ManagerOptions struct { - ExternalURL *url.URL - QueryFunc QueryFunc - NotifyFunc NotifyFunc - Context context.Context - Appendable storage.Appendable - Queryable storage.Queryable - Logger log.Logger - Registerer prometheus.Registerer - OutageTolerance time.Duration - ForGracePeriod time.Duration - ResendDelay time.Duration - MaxConcurrentEvals int64 - ConcurrentEvalsEnabled bool - ConcurrencyController ConcurrencyController - GroupLoader GroupLoader + ExternalURL *url.URL + QueryFunc QueryFunc + NotifyFunc NotifyFunc + Context context.Context + Appendable storage.Appendable + Queryable storage.Queryable + Logger log.Logger + Registerer prometheus.Registerer + OutageTolerance time.Duration + ForGracePeriod time.Duration + ResendDelay time.Duration + GroupLoader GroupLoader + MaxConcurrentEvals int64 + ConcurrentEvalsEnabled bool + ConcurrentEvalsController ConcurrentRuleEvalController Metrics *Metrics } @@ -134,7 +134,7 @@ func NewManager(o *ManagerOptions) *Manager { o.GroupLoader = FileLoader{} } - o.ConcurrencyController = NewConcurrencyController(o.ConcurrentEvalsEnabled, o.MaxConcurrentEvals) + o.ConcurrentEvalsController = NewConcurrentRuleEvalController(o.ConcurrentEvalsEnabled, o.MaxConcurrentEvals) m := &Manager{ groups: map[string]*Group{}, @@ -410,16 +410,26 @@ func SendAlerts(s Sender, externalURL string) NotifyFunc { } } -type ConcurrencyController struct { +// ConcurrentRuleEvalController controls whether rules can be evaluated concurrently. Its purpose it to bound the amount +// of concurrency in rule evaluations so they do not overwhelm the Prometheus server with additional query load. +// Concurrency is controlled globally, not on a per-group basis. +type ConcurrentRuleEvalController interface { + Allow() bool + Done() +} + +// concurrentRuleEvalController holds a weighted semaphore which controls the concurrent evaluation of rules. +type concurrentRuleEvalController struct { enabled bool sema *semaphore.Weighted } -func NewConcurrencyController(enabled bool, maxConcurrency int64) ConcurrencyController { - return ConcurrencyController{enabled: enabled, sema: semaphore.NewWeighted(maxConcurrency)} +func NewConcurrentRuleEvalController(enabled bool, maxConcurrency int64) ConcurrentRuleEvalController { + return concurrentRuleEvalController{enabled: enabled, sema: semaphore.NewWeighted(maxConcurrency)} } -func (c ConcurrencyController) Allow() bool { +// Allow determines whether any concurrency slots are available. +func (c concurrentRuleEvalController) Allow() bool { if !c.enabled { return false } @@ -427,7 +437,8 @@ func (c ConcurrencyController) Allow() bool { return c.sema.TryAcquire(1) } -func (c ConcurrencyController) Done() { +// Done releases a concurrent evaluation slot. +func (c concurrentRuleEvalController) Done() { if !c.enabled { return } diff --git a/rules/manager_test.go b/rules/manager_test.go index 2a9b3a1d73..8b3b9c08ff 100644 --- a/rules/manager_test.go +++ b/rules/manager_test.go @@ -1471,7 +1471,53 @@ func TestNoDependency(t *testing.T) { }) // A group with only one rule cannot have dependencies. - require.False(t, group.dependencyMap.isIndependent(rule)) + require.Empty(t, group.dependencyMap) +} + +func TestDependenciesEdgeCases(t *testing.T) { + ctx := context.Background() + opts := &ManagerOptions{ + Context: ctx, + Logger: log.NewNopLogger(), + } + + t.Run("empty group", func(t *testing.T) { + group := NewGroup(GroupOptions{ + Name: "rule_group", + Interval: time.Second, + Rules: []Rule{}, // empty group + Opts: opts, + }) + + expr, err := parser.ParseExpr("sum by (user) (rate(requests[1m]))") + require.NoError(t, err) + rule := NewRecordingRule("user:requests:rate1m", expr, labels.Labels{}) + + // A group with no rules has no dependency map, but doesn't panic if the map is queried. + require.Nil(t, group.dependencyMap) + require.False(t, group.dependencyMap.isIndependent(rule)) + }) + + t.Run("rules which reference no series", func(t *testing.T) { + expr, err := parser.ParseExpr("one") + require.NoError(t, err) + rule1 := NewRecordingRule("1", expr, labels.Labels{}) + + expr, err = parser.ParseExpr("two") + require.NoError(t, err) + rule2 := NewRecordingRule("2", expr, labels.Labels{}) + + group := NewGroup(GroupOptions{ + Name: "rule_group", + Interval: time.Second, + Rules: []Rule{rule1, rule2}, + Opts: opts, + }) + + // A group with rules which reference no series will still produce a dependency map + require.True(t, group.dependencyMap.isIndependent(rule1)) + require.True(t, group.dependencyMap.isIndependent(rule2)) + }) } func TestNoMetricSelector(t *testing.T) { @@ -1596,10 +1642,23 @@ func TestDependencyMapUpdatesOnGroupUpdate(t *testing.T) { require.NoError(t, err) for h, g := range ruleManager.groups { + const ruleName = "job:http_requests:rate5m" + var rr *RecordingRule + + for _, r := range g.rules { + if r.Name() == ruleName { + rr = r.(*RecordingRule) + } + } + + require.NotEmptyf(t, rr, "expected to find %q recording rule in fixture", ruleName) + // Dependency maps must change because the groups would've been updated. require.NotEqual(t, orig[h], g.dependencyMap) // We expect there to be some dependencies since the new rule group contains a dependency. require.Greater(t, len(g.dependencyMap), 0) + require.Equal(t, 1, g.dependencyMap.dependents(rr)) + require.Zero(t, g.dependencyMap.dependencies(rr)) } } @@ -1625,17 +1684,16 @@ func TestAsyncRuleEvaluation(t *testing.T) { inflightQueries.Add(-1) }() - // Artificially delay all query executions to highly concurrent execution improvement. + // Artificially delay all query executions to highlight concurrent execution improvement. time.Sleep(artificialDelay) - // return a stub sample + // Return a stub sample. return promql.Vector{ promql.Sample{Metric: labels.FromStrings("__name__", "test"), T: ts.UnixMilli(), F: 12345}, }, nil }, }) - // Evaluate groups manually to show the impact of async rule evaluations. groups, errs := ruleManager.LoadGroups(time.Second, labels.EmptyLabels(), "", nil, files...) require.Empty(t, errs) require.Len(t, groups, 1) @@ -1688,7 +1746,7 @@ func TestAsyncRuleEvaluation(t *testing.T) { for _, group := range groups { // Allow up to 2 concurrent rule evaluations. - group.opts.ConcurrencyController = NewConcurrencyController(true, 2) + group.opts.ConcurrentEvalsController = NewConcurrentRuleEvalController(true, 2) require.Len(t, group.rules, expectedRules) start := time.Now() @@ -1749,17 +1807,16 @@ func TestBoundedRuleEvalConcurrency(t *testing.T) { inflightQueries.Add(-1) }() - // Artificially delay all query executions to highly concurrent execution improvement. + // Artificially delay all query executions to highlight concurrent execution improvement. time.Sleep(artificialDelay) - // return a stub sample + // Return a stub sample. return promql.Vector{ promql.Sample{Metric: labels.FromStrings("__name__", "test"), T: ts.UnixMilli(), F: 12345}, }, nil }, }) - // Evaluate groups manually to show the impact of async rule evaluations. groups, errs := ruleManager.LoadGroups(time.Second, labels.EmptyLabels(), "", nil, files...) require.Empty(t, errs) require.Len(t, groups, groupCount) From 5bda33375a58b8d7b52195241136f1152f14114c Mon Sep 17 00:00:00 2001 From: Danny Kopping Date: Sat, 28 Oct 2023 13:25:06 +0200 Subject: [PATCH 029/237] Rename flag Signed-off-by: Danny Kopping --- cmd/prometheus/main.go | 2 +- docs/command-line/prometheus.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cmd/prometheus/main.go b/cmd/prometheus/main.go index 8dd1d88fa0..830ae46490 100644 --- a/cmd/prometheus/main.go +++ b/cmd/prometheus/main.go @@ -416,7 +416,7 @@ func main() { serverOnlyFlag(a, "rules.alert.resend-delay", "Minimum amount of time to wait before resending an alert to Alertmanager."). Default("1m").SetValue(&cfg.resendDelay) - serverOnlyFlag(a, "rules.max-concurrent-rule-evals", "Global concurrency limit for independent rules which can run concurrently."). + serverOnlyFlag(a, "rules.max-concurrent-evals", "Global concurrency limit for independent rules which can run concurrently."). Default("4").Int64Var(&cfg.maxConcurrentEvals) a.Flag("scrape.adjust-timestamps", "Adjust scrape timestamps by up to `scrape.timestamp-tolerance` to align them to the intended schedule. See https://github.com/prometheus/prometheus/issues/7846 for more context. Experimental. This flag will be removed in a future release."). diff --git a/docs/command-line/prometheus.md b/docs/command-line/prometheus.md index de3baa1071..fef8ffa54d 100644 --- a/docs/command-line/prometheus.md +++ b/docs/command-line/prometheus.md @@ -48,7 +48,7 @@ The Prometheus monitoring server | --rules.alert.for-outage-tolerance | Max time to tolerate prometheus outage for restoring "for" state of alert. Use with server mode only. | `1h` | | --rules.alert.for-grace-period | Minimum duration between alert and restored "for" state. This is maintained only for alerts with configured "for" time greater than grace period. Use with server mode only. | `10m` | | --rules.alert.resend-delay | Minimum amount of time to wait before resending an alert to Alertmanager. Use with server mode only. | `1m` | -| --rules.max-concurrent-rule-evals | Global concurrency limit for independent rules which can run concurrently. Use with server mode only. | `4` | +| --rules.max-concurrent-evals | Global concurrency limit for independent rules which can run concurrently. Use with server mode only. | `4` | | --alertmanager.notification-queue-capacity | The capacity of the queue for pending Alertmanager notifications. Use with server mode only. | `10000` | | --query.lookback-delta | The maximum lookback duration for retrieving metrics during expression evaluations and federation. Use with server mode only. | `5m` | | --query.timeout | Maximum time a query may take before being aborted. Use with server mode only. | `2m` | From f922534c4df77a35ee6c73bdf153ca89ca4da1a8 Mon Sep 17 00:00:00 2001 From: Danny Kopping Date: Thu, 2 Nov 2023 20:33:06 +0200 Subject: [PATCH 030/237] Refactoring for performance, and to allow controller to be overridden Signed-off-by: Danny Kopping --- rules/group.go | 7 +- rules/manager.go | 64 ++++++++++++++---- rules/manager_test.go | 148 ++++++++++++++++++++++-------------------- 3 files changed, 134 insertions(+), 85 deletions(-) diff --git a/rules/group.go b/rules/group.go index 7f53e1b474..8de0900d1a 100644 --- a/rules/group.go +++ b/rules/group.go @@ -71,7 +71,6 @@ type Group struct { // Rule group evaluation iteration function, // defaults to DefaultEvalIterationFunc. evalIterationFunc GroupEvalIterationFunc - dependencyMap dependencyMap } // GroupEvalIterationFunc is used to implement and extend rule group @@ -130,7 +129,6 @@ func NewGroup(o GroupOptions) *Group { logger: log.With(o.Opts.Logger, "file", o.File, "group", o.Name), metrics: metrics, evalIterationFunc: evalIterationFunc, - dependencyMap: buildDependencyMap(o.Rules), } } @@ -437,7 +435,7 @@ func (g *Group) Eval(ctx context.Context, ts time.Time) { eval := func(i int, rule Rule, async bool) { defer func() { if async { - g.opts.ConcurrentEvalsController.Done() + g.opts.RuleConcurrencyController.Done() } }() @@ -569,7 +567,8 @@ func (g *Group) Eval(ctx context.Context, ts time.Time) { // If the rule has no dependencies, it can run concurrently because no other rules in this group depend on its output. // Try run concurrently if there are slots available. - if g.dependencyMap.isIndependent(rule) && g.opts.ConcurrentEvalsController != nil && g.opts.ConcurrentEvalsController.Allow() { + ctrl := g.opts.RuleConcurrencyController + if ctrl != nil && ctrl.RuleEligible(g, rule) && ctrl.Allow() { go eval(i, rule, true) } else { eval(i, rule, false) diff --git a/rules/manager.go b/rules/manager.go index b982f23754..9ac95cdbd3 100644 --- a/rules/manager.go +++ b/rules/manager.go @@ -118,7 +118,7 @@ type ManagerOptions struct { GroupLoader GroupLoader MaxConcurrentEvals int64 ConcurrentEvalsEnabled bool - ConcurrentEvalsController ConcurrentRuleEvalController + RuleConcurrencyController RuleConcurrencyController Metrics *Metrics } @@ -134,7 +134,9 @@ func NewManager(o *ManagerOptions) *Manager { o.GroupLoader = FileLoader{} } - o.ConcurrentEvalsController = NewConcurrentRuleEvalController(o.ConcurrentEvalsEnabled, o.MaxConcurrentEvals) + if o.RuleConcurrencyController == nil { + o.RuleConcurrencyController = newRuleConcurrencyController(o.ConcurrentEvalsEnabled, o.MaxConcurrentEvals) + } m := &Manager{ groups: map[string]*Group{}, @@ -182,6 +184,10 @@ func (m *Manager) Update(interval time.Duration, files []string, externalLabels m.mtx.Lock() defer m.mtx.Unlock() + if m.opts.RuleConcurrencyController != nil { + m.opts.RuleConcurrencyController.Invalidate() + } + groups, errs := m.LoadGroups(interval, externalLabels, externalURL, groupEvalIterationFunc, files...) if errs != nil { @@ -410,26 +416,55 @@ func SendAlerts(s Sender, externalURL string) NotifyFunc { } } -// ConcurrentRuleEvalController controls whether rules can be evaluated concurrently. Its purpose it to bound the amount -// of concurrency in rule evaluations so they do not overwhelm the Prometheus server with additional query load. +// RuleConcurrencyController controls whether rules can be evaluated concurrently. Its purpose it to bound the amount +// of concurrency in rule evaluations, to not overwhelm the Prometheus server with additional query load. // Concurrency is controlled globally, not on a per-group basis. -type ConcurrentRuleEvalController interface { +type RuleConcurrencyController interface { + // RuleEligible determines if a rule can be run concurrently. + RuleEligible(g *Group, r Rule) bool + + // Allow determines whether any concurrent evaluation slots are available. Allow() bool + + // Done releases a concurrent evaluation slot. Done() + + // Invalidate instructs the controller to invalidate its state. + // This should be called when groups are modified (during a reload, for instance), because the controller may + // store some state about each group in order to more efficiently determine rule eligibility. + Invalidate() +} + +func newRuleConcurrencyController(enabled bool, maxConcurrency int64) RuleConcurrencyController { + return &concurrentRuleEvalController{ + enabled: enabled, + sema: semaphore.NewWeighted(maxConcurrency), + depMaps: map[*Group]dependencyMap{}, + } } // concurrentRuleEvalController holds a weighted semaphore which controls the concurrent evaluation of rules. type concurrentRuleEvalController struct { + mu sync.Mutex enabled bool sema *semaphore.Weighted + depMaps map[*Group]dependencyMap } -func NewConcurrentRuleEvalController(enabled bool, maxConcurrency int64) ConcurrentRuleEvalController { - return concurrentRuleEvalController{enabled: enabled, sema: semaphore.NewWeighted(maxConcurrency)} +func (c *concurrentRuleEvalController) RuleEligible(g *Group, r Rule) bool { + c.mu.Lock() + defer c.mu.Unlock() + + depMap, found := c.depMaps[g] + if !found { + depMap = buildDependencyMap(g.rules) + c.depMaps[g] = depMap + } + + return depMap.isIndependent(r) } -// Allow determines whether any concurrency slots are available. -func (c concurrentRuleEvalController) Allow() bool { +func (c *concurrentRuleEvalController) Allow() bool { if !c.enabled { return false } @@ -437,11 +472,18 @@ func (c concurrentRuleEvalController) Allow() bool { return c.sema.TryAcquire(1) } -// Done releases a concurrent evaluation slot. -func (c concurrentRuleEvalController) Done() { +func (c *concurrentRuleEvalController) Done() { if !c.enabled { return } c.sema.Release(1) } + +func (c *concurrentRuleEvalController) Invalidate() { + c.mu.Lock() + defer c.mu.Unlock() + + // Clear out the memoized dependency maps because some or all groups may have been updated. + c.depMaps = map[*Group]dependencyMap{} +} diff --git a/rules/manager_test.go b/rules/manager_test.go index 8b3b9c08ff..47f0248eb0 100644 --- a/rules/manager_test.go +++ b/rules/manager_test.go @@ -1435,21 +1435,23 @@ func TestDependencyMap(t *testing.T) { Opts: opts, }) - require.Zero(t, group.dependencyMap.dependencies(rule)) - require.Equal(t, 2, group.dependencyMap.dependents(rule)) - require.False(t, group.dependencyMap.isIndependent(rule)) + depMap := buildDependencyMap(group.rules) - require.Zero(t, group.dependencyMap.dependents(rule2)) - require.Equal(t, 1, group.dependencyMap.dependencies(rule2)) - require.False(t, group.dependencyMap.isIndependent(rule2)) + require.Zero(t, depMap.dependencies(rule)) + require.Equal(t, 2, depMap.dependents(rule)) + require.False(t, depMap.isIndependent(rule)) - require.Zero(t, group.dependencyMap.dependents(rule3)) - require.Zero(t, group.dependencyMap.dependencies(rule3)) - require.True(t, group.dependencyMap.isIndependent(rule3)) + require.Zero(t, depMap.dependents(rule2)) + require.Equal(t, 1, depMap.dependencies(rule2)) + require.False(t, depMap.isIndependent(rule2)) - require.Zero(t, group.dependencyMap.dependents(rule4)) - require.Equal(t, 1, group.dependencyMap.dependencies(rule4)) - require.False(t, group.dependencyMap.isIndependent(rule4)) + require.Zero(t, depMap.dependents(rule3)) + require.Zero(t, depMap.dependencies(rule3)) + require.True(t, depMap.isIndependent(rule3)) + + require.Zero(t, depMap.dependents(rule4)) + require.Equal(t, 1, depMap.dependencies(rule4)) + require.False(t, depMap.isIndependent(rule4)) } func TestNoDependency(t *testing.T) { @@ -1470,8 +1472,9 @@ func TestNoDependency(t *testing.T) { Opts: opts, }) + depMap := buildDependencyMap(group.rules) // A group with only one rule cannot have dependencies. - require.Empty(t, group.dependencyMap) + require.Empty(t, depMap) } func TestDependenciesEdgeCases(t *testing.T) { @@ -1493,9 +1496,10 @@ func TestDependenciesEdgeCases(t *testing.T) { require.NoError(t, err) rule := NewRecordingRule("user:requests:rate1m", expr, labels.Labels{}) + depMap := buildDependencyMap(group.rules) // A group with no rules has no dependency map, but doesn't panic if the map is queried. - require.Nil(t, group.dependencyMap) - require.False(t, group.dependencyMap.isIndependent(rule)) + require.Nil(t, depMap) + require.False(t, depMap.isIndependent(rule)) }) t.Run("rules which reference no series", func(t *testing.T) { @@ -1514,9 +1518,10 @@ func TestDependenciesEdgeCases(t *testing.T) { Opts: opts, }) + depMap := buildDependencyMap(group.rules) // A group with rules which reference no series will still produce a dependency map - require.True(t, group.dependencyMap.isIndependent(rule1)) - require.True(t, group.dependencyMap.isIndependent(rule2)) + require.True(t, depMap.isIndependent(rule1)) + require.True(t, depMap.isIndependent(rule2)) }) } @@ -1542,10 +1547,11 @@ func TestNoMetricSelector(t *testing.T) { Opts: opts, }) + depMap := buildDependencyMap(group.rules) // A rule with no metric selector cannot be reliably determined to have no dependencies on other rules, and therefore // all rules are not considered independent. - require.False(t, group.dependencyMap.isIndependent(rule)) - require.False(t, group.dependencyMap.isIndependent(rule2)) + require.False(t, depMap.isIndependent(rule)) + require.False(t, depMap.isIndependent(rule2)) } func TestDependentRulesWithNonMetricExpression(t *testing.T) { @@ -1574,9 +1580,10 @@ func TestDependentRulesWithNonMetricExpression(t *testing.T) { Opts: opts, }) - require.False(t, group.dependencyMap.isIndependent(rule)) - require.False(t, group.dependencyMap.isIndependent(rule2)) - require.True(t, group.dependencyMap.isIndependent(rule3)) + depMap := buildDependencyMap(group.rules) + require.False(t, depMap.isIndependent(rule)) + require.False(t, depMap.isIndependent(rule2)) + require.True(t, depMap.isIndependent(rule3)) } func TestRulesDependentOnMetaMetrics(t *testing.T) { @@ -1604,7 +1611,8 @@ func TestRulesDependentOnMetaMetrics(t *testing.T) { Opts: opts, }) - require.False(t, group.dependencyMap.isIndependent(rule)) + depMap := buildDependencyMap(group.rules) + require.False(t, depMap.isIndependent(rule)) } func TestDependencyMapUpdatesOnGroupUpdate(t *testing.T) { @@ -1623,17 +1631,19 @@ func TestDependencyMapUpdatesOnGroupUpdate(t *testing.T) { orig := make(map[string]dependencyMap, len(ruleManager.groups)) for _, g := range ruleManager.groups { + depMap := buildDependencyMap(g.rules) // No dependency map is expected because there is only one rule in the group. - require.Empty(t, g.dependencyMap) - orig[g.Name()] = g.dependencyMap + require.Empty(t, depMap) + orig[g.Name()] = depMap } // Update once without changing groups. err = ruleManager.Update(10*time.Second, files, labels.EmptyLabels(), "", nil) require.NoError(t, err) for h, g := range ruleManager.groups { + depMap := buildDependencyMap(g.rules) // Dependency maps are the same because of no updates. - require.Equal(t, orig[h], g.dependencyMap) + require.Equal(t, orig[h], depMap) } // Groups will be recreated when updated. @@ -1653,12 +1663,13 @@ func TestDependencyMapUpdatesOnGroupUpdate(t *testing.T) { require.NotEmptyf(t, rr, "expected to find %q recording rule in fixture", ruleName) + depMap := buildDependencyMap(g.rules) // Dependency maps must change because the groups would've been updated. - require.NotEqual(t, orig[h], g.dependencyMap) + require.NotEqual(t, orig[h], depMap) // We expect there to be some dependencies since the new rule group contains a dependency. - require.Greater(t, len(g.dependencyMap), 0) - require.Equal(t, 1, g.dependencyMap.dependents(rr)) - require.Zero(t, g.dependencyMap.dependencies(rr)) + require.Greater(t, len(depMap), 0) + require.Equal(t, 1, depMap.dependents(rr)) + require.Zero(t, depMap.dependencies(rr)) } } @@ -1674,7 +1685,7 @@ func TestAsyncRuleEvaluation(t *testing.T) { ) files := []string{"fixtures/rules_multiple.yaml"} - ruleManager := NewManager(&ManagerOptions{ + opts := &ManagerOptions{ Context: context.Background(), Logger: log.NewNopLogger(), Appendable: storage, @@ -1692,39 +1703,42 @@ func TestAsyncRuleEvaluation(t *testing.T) { promql.Sample{Metric: labels.FromStrings("__name__", "test"), T: ts.UnixMilli(), F: 12345}, }, nil }, - }) + } - groups, errs := ruleManager.LoadGroups(time.Second, labels.EmptyLabels(), "", nil, files...) - require.Empty(t, errs) - require.Len(t, groups, 1) + inflightTracker := func(ctx context.Context) { + for { + select { + case <-ctx.Done(): + return + default: + highWatermark := maxInflight.Load() + current := inflightQueries.Load() + if current > highWatermark { + maxInflight.Store(current) + } + + time.Sleep(time.Millisecond) + } + } + } expectedRules := 4 t.Run("synchronous evaluation with independent rules", func(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) + ruleManager := NewManager(opts) + groups, errs := ruleManager.LoadGroups(time.Second, labels.EmptyLabels(), "", nil, files...) + require.Empty(t, errs) + require.Len(t, groups, 1) + for _, group := range groups { require.Len(t, group.rules, expectedRules) start := time.Now() // Never expect more than 1 inflight query at a time. - go func() { - for { - select { - case <-ctx.Done(): - return - default: - highWatermark := maxInflight.Load() - current := inflightQueries.Load() - if current > highWatermark { - maxInflight.Store(current) - } - - time.Sleep(time.Millisecond) - } - } - }() + go inflightTracker(ctx) group.Eval(ctx, start) @@ -1744,33 +1758,27 @@ func TestAsyncRuleEvaluation(t *testing.T) { maxInflight.Store(0) ctx, cancel := context.WithCancel(context.Background()) + // Configure concurrency settings. + opts.ConcurrentEvalsEnabled = true + opts.MaxConcurrentEvals = 2 + opts.RuleConcurrencyController = nil + ruleManager := NewManager(opts) + + groups, errs := ruleManager.LoadGroups(time.Second, labels.EmptyLabels(), "", nil, files...) + require.Empty(t, errs) + require.Len(t, groups, 1) + for _, group := range groups { - // Allow up to 2 concurrent rule evaluations. - group.opts.ConcurrentEvalsController = NewConcurrentRuleEvalController(true, 2) require.Len(t, group.rules, expectedRules) start := time.Now() - go func() { - for { - select { - case <-ctx.Done(): - return - default: - highWatermark := maxInflight.Load() - current := inflightQueries.Load() - if current > highWatermark { - maxInflight.Store(current) - } - - time.Sleep(time.Millisecond) - } - } - }() + go inflightTracker(ctx) group.Eval(ctx, start) - require.EqualValues(t, 3, maxInflight.Load()) + // Max inflight can be 1 synchronous eval and up to MaxConcurrentEvals concurrent evals. + require.EqualValues(t, opts.MaxConcurrentEvals+1, maxInflight.Load()) // Some rules should execute concurrently so should complete quicker. require.Less(t, time.Since(start).Seconds(), (time.Duration(expectedRules) * artificialDelay).Seconds()) // Each rule produces one vector. From 7aa3b10c3fb6ae025e0173e439f54f6020a7eec8 Mon Sep 17 00:00:00 2001 From: Danny Kopping Date: Fri, 5 Jan 2024 22:48:30 +0200 Subject: [PATCH 031/237] Block until all rules, both sync & async, have completed evaluating Updated & added tests Review feedback nits Return empty map if not indeterminate Use highWatermark to track inflight requests counter Appease the linter Clarify feature flag Signed-off-by: Danny Kopping --- docs/feature_flags.md | 9 +- .../fixtures/rules_multiple_independent.yaml | 15 + rules/group.go | 25 +- rules/manager.go | 17 +- rules/manager_test.go | 287 ++++++++++-------- 5 files changed, 208 insertions(+), 145 deletions(-) create mode 100644 rules/fixtures/rules_multiple_independent.yaml diff --git a/docs/feature_flags.md b/docs/feature_flags.md index 5517018df4..95b6270104 100644 --- a/docs/feature_flags.md +++ b/docs/feature_flags.md @@ -217,8 +217,9 @@ Besides enabling this feature in Prometheus, created timestamps need to be expos `--enable-feature=concurrent-rule-eval` -Rule groups execute concurrently, but the rules within a group execute sequentially; this is because rules can use the +By default, rule groups execute concurrently, but the rules within a group execute sequentially; this is because rules can use the output of a preceding rule as its input. However, if there is no detectable relationship between rules then there is no -reason to run them sequentially. This can improve rule reliability at the expense of adding more concurrent query -load. The number of concurrent rule evaluations can be configured with `--rules.max-concurrent-rule-evals` which is set -to `4` by default. +reason to run them sequentially. +When the `concurrent-rule-eval` feature flag is enabled, rules without any dependency on other rules within a rule group will be evaluated concurrently. +This can improve rule reliability at the expense of adding more concurrent query load. The number of concurrent rule evaluations can be configured +with `--rules.max-concurrent-rule-evals` which is set to `4` by default. diff --git a/rules/fixtures/rules_multiple_independent.yaml b/rules/fixtures/rules_multiple_independent.yaml new file mode 100644 index 0000000000..e071be3eff --- /dev/null +++ b/rules/fixtures/rules_multiple_independent.yaml @@ -0,0 +1,15 @@ +groups: + - name: independents + rules: + - record: job:http_requests:rate1m + expr: sum by (job)(rate(http_requests_total[1m])) + - record: job:http_requests:rate5m + expr: sum by (job)(rate(http_requests_total[5m])) + - record: job:http_requests:rate15m + expr: sum by (job)(rate(http_requests_total[15m])) + - record: job:http_requests:rate30m + expr: sum by (job)(rate(http_requests_total[30m])) + - record: job:http_requests:rate1h + expr: sum by (job)(rate(http_requests_total[1h])) + - record: job:http_requests:rate2h + expr: sum by (job)(rate(http_requests_total[2h])) diff --git a/rules/group.go b/rules/group.go index 8de0900d1a..939d2cd5b6 100644 --- a/rules/group.go +++ b/rules/group.go @@ -423,8 +423,13 @@ func (g *Group) CopyState(from *Group) { } // Eval runs a single evaluation cycle in which all rules are evaluated sequentially. +// Rules can be evaluated concurrently if the `concurrent-rule-eval` feature flag is enabled. func (g *Group) Eval(ctx context.Context, ts time.Time) { - var samplesTotal atomic.Float64 + var ( + samplesTotal atomic.Float64 + wg sync.WaitGroup + ) + for i, rule := range g.rules { select { case <-g.done: @@ -435,6 +440,7 @@ func (g *Group) Eval(ctx context.Context, ts time.Time) { eval := func(i int, rule Rule, async bool) { defer func() { if async { + wg.Done() g.opts.RuleConcurrencyController.Done() } }() @@ -569,12 +575,14 @@ func (g *Group) Eval(ctx context.Context, ts time.Time) { // Try run concurrently if there are slots available. ctrl := g.opts.RuleConcurrencyController if ctrl != nil && ctrl.RuleEligible(g, rule) && ctrl.Allow() { + wg.Add(1) go eval(i, rule, true) } else { eval(i, rule, false) } } + wg.Wait() if g.metrics != nil { g.metrics.GroupSamples.WithLabelValues(GroupKey(g.File(), g.Name())).Set(samplesTotal.Load()) } @@ -940,7 +948,7 @@ func buildDependencyMap(rules []Rule) dependencyMap { if len(rules) <= 1 { // No relationships if group has 1 or fewer rules. - return nil + return dependencies } inputs := make(map[string][]Rule, len(rules)) @@ -949,7 +957,9 @@ func buildDependencyMap(rules []Rule) dependencyMap { var indeterminate bool for _, rule := range rules { - rule := rule + if indeterminate { + break + } name := rule.Name() outputs[name] = append(outputs[name], rule) @@ -980,15 +990,10 @@ func buildDependencyMap(rules []Rule) dependencyMap { return nil } - if len(inputs) == 0 || len(outputs) == 0 { - // No relationships can be inferred. - return nil - } - for output, outRules := range outputs { for _, outRule := range outRules { - if rs, found := inputs[output]; found && len(rs) > 0 { - dependencies[outRule] = append(dependencies[outRule], rs...) + if inRules, found := inputs[output]; found && len(inRules) > 0 { + dependencies[outRule] = append(dependencies[outRule], inRules...) } } } diff --git a/rules/manager.go b/rules/manager.go index 9ac95cdbd3..84b43fba7d 100644 --- a/rules/manager.go +++ b/rules/manager.go @@ -424,6 +424,7 @@ type RuleConcurrencyController interface { RuleEligible(g *Group, r Rule) bool // Allow determines whether any concurrent evaluation slots are available. + // If Allow() returns true, then Done() must be called to release the acquired slot. Allow() bool // Done releases a concurrent evaluation slot. @@ -445,15 +446,15 @@ func newRuleConcurrencyController(enabled bool, maxConcurrency int64) RuleConcur // concurrentRuleEvalController holds a weighted semaphore which controls the concurrent evaluation of rules. type concurrentRuleEvalController struct { - mu sync.Mutex - enabled bool - sema *semaphore.Weighted - depMaps map[*Group]dependencyMap + enabled bool + sema *semaphore.Weighted + depMapsMu sync.Mutex + depMaps map[*Group]dependencyMap } func (c *concurrentRuleEvalController) RuleEligible(g *Group, r Rule) bool { - c.mu.Lock() - defer c.mu.Unlock() + c.depMapsMu.Lock() + defer c.depMapsMu.Unlock() depMap, found := c.depMaps[g] if !found { @@ -481,8 +482,8 @@ func (c *concurrentRuleEvalController) Done() { } func (c *concurrentRuleEvalController) Invalidate() { - c.mu.Lock() - defer c.mu.Unlock() + c.depMapsMu.Lock() + defer c.depMapsMu.Unlock() // Clear out the memoized dependency maps because some or all groups may have been updated. c.depMaps = map[*Group]dependencyMap{} diff --git a/rules/manager_test.go b/rules/manager_test.go index 47f0248eb0..2d1dc6b42e 100644 --- a/rules/manager_test.go +++ b/rules/manager_test.go @@ -1498,8 +1498,8 @@ func TestDependenciesEdgeCases(t *testing.T) { depMap := buildDependencyMap(group.rules) // A group with no rules has no dependency map, but doesn't panic if the map is queried. - require.Nil(t, depMap) - require.False(t, depMap.isIndependent(rule)) + require.Empty(t, depMap) + require.True(t, depMap.isIndependent(rule)) }) t.Run("rules which reference no series", func(t *testing.T) { @@ -1627,7 +1627,7 @@ func TestDependencyMapUpdatesOnGroupUpdate(t *testing.T) { err := ruleManager.Update(10*time.Second, files, labels.EmptyLabels(), "", nil) require.NoError(t, err) - require.Greater(t, len(ruleManager.groups), 0, "expected non-empty rule groups") + require.NotEmpty(t, ruleManager.groups, "expected non-empty rule groups") orig := make(map[string]dependencyMap, len(ruleManager.groups)) for _, g := range ruleManager.groups { @@ -1643,7 +1643,13 @@ func TestDependencyMapUpdatesOnGroupUpdate(t *testing.T) { for h, g := range ruleManager.groups { depMap := buildDependencyMap(g.rules) // Dependency maps are the same because of no updates. - require.Equal(t, orig[h], depMap) + if orig[h] == nil { + require.Empty(t, orig[h]) + require.Empty(t, depMap) + } else { + require.Equal(t, orig[h], depMap) + } + } // Groups will be recreated when updated. @@ -1667,7 +1673,7 @@ func TestDependencyMapUpdatesOnGroupUpdate(t *testing.T) { // Dependency maps must change because the groups would've been updated. require.NotEqual(t, orig[h], depMap) // We expect there to be some dependencies since the new rule group contains a dependency. - require.Greater(t, len(depMap), 0) + require.NotEmpty(t, depMap) require.Equal(t, 1, depMap.dependents(rr)) require.Zero(t, depMap.dependencies(rr)) } @@ -1677,86 +1683,51 @@ func TestAsyncRuleEvaluation(t *testing.T) { storage := teststorage.New(t) t.Cleanup(func() { storage.Close() }) - const artificialDelay = time.Second - var ( inflightQueries atomic.Int32 maxInflight atomic.Int32 ) - files := []string{"fixtures/rules_multiple.yaml"} - opts := &ManagerOptions{ - Context: context.Background(), - Logger: log.NewNopLogger(), - Appendable: storage, - QueryFunc: func(ctx context.Context, q string, ts time.Time) (promql.Vector, error) { - inflightQueries.Add(1) - defer func() { - inflightQueries.Add(-1) - }() - - // Artificially delay all query executions to highlight concurrent execution improvement. - time.Sleep(artificialDelay) - - // Return a stub sample. - return promql.Vector{ - promql.Sample{Metric: labels.FromStrings("__name__", "test"), T: ts.UnixMilli(), F: 12345}, - }, nil - }, - } - - inflightTracker := func(ctx context.Context) { - for { - select { - case <-ctx.Done(): - return - default: - highWatermark := maxInflight.Load() - current := inflightQueries.Load() - if current > highWatermark { - maxInflight.Store(current) - } - - time.Sleep(time.Millisecond) - } - } - } - - expectedRules := 4 - t.Run("synchronous evaluation with independent rules", func(t *testing.T) { - ctx, cancel := context.WithCancel(context.Background()) - - ruleManager := NewManager(opts) - groups, errs := ruleManager.LoadGroups(time.Second, labels.EmptyLabels(), "", nil, files...) - require.Empty(t, errs) - require.Len(t, groups, 1) - - for _, group := range groups { - require.Len(t, group.rules, expectedRules) - - start := time.Now() - - // Never expect more than 1 inflight query at a time. - go inflightTracker(ctx) - - group.Eval(ctx, start) - - require.EqualValues(t, 1, maxInflight.Load()) - // Each rule should take at least 1 second to execute sequentially. - require.GreaterOrEqual(t, time.Since(start).Seconds(), (time.Duration(expectedRules) * artificialDelay).Seconds()) - // Each rule produces one vector. - require.EqualValues(t, expectedRules, testutil.ToFloat64(group.metrics.GroupSamples)) - } - - cancel() - }) - - t.Run("asynchronous evaluation with independent rules", func(t *testing.T) { // Reset. inflightQueries.Store(0) maxInflight.Store(0) + ctx, cancel := context.WithCancel(context.Background()) + t.Cleanup(cancel) + + ruleManager := NewManager(optsFactory(storage, &maxInflight, &inflightQueries, 0)) + groups, errs := ruleManager.LoadGroups(time.Second, labels.EmptyLabels(), "", nil, []string{"fixtures/rules_multiple.yaml"}...) + require.Empty(t, errs) + require.Len(t, groups, 1) + + ruleCount := 4 + + for _, group := range groups { + require.Len(t, group.rules, ruleCount) + + start := time.Now() + group.Eval(ctx, start) + + // Never expect more than 1 inflight query at a time. + require.EqualValues(t, 1, maxInflight.Load()) + // Each rule should take at least 1 second to execute sequentially. + require.GreaterOrEqual(t, time.Since(start).Seconds(), (time.Duration(ruleCount) * artificialDelay).Seconds()) + // Each rule produces one vector. + require.EqualValues(t, ruleCount, testutil.ToFloat64(group.metrics.GroupSamples)) + } + }) + + t.Run("asynchronous evaluation with independent and dependent rules", func(t *testing.T) { + // Reset. + inflightQueries.Store(0) + maxInflight.Store(0) + + ctx, cancel := context.WithCancel(context.Background()) + t.Cleanup(cancel) + + ruleCount := 4 + opts := optsFactory(storage, &maxInflight, &inflightQueries, 0) // Configure concurrency settings. opts.ConcurrentEvalsEnabled = true @@ -1764,28 +1735,97 @@ func TestAsyncRuleEvaluation(t *testing.T) { opts.RuleConcurrencyController = nil ruleManager := NewManager(opts) - groups, errs := ruleManager.LoadGroups(time.Second, labels.EmptyLabels(), "", nil, files...) + groups, errs := ruleManager.LoadGroups(time.Second, labels.EmptyLabels(), "", nil, []string{"fixtures/rules_multiple.yaml"}...) require.Empty(t, errs) require.Len(t, groups, 1) for _, group := range groups { - require.Len(t, group.rules, expectedRules) + require.Len(t, group.rules, ruleCount) start := time.Now() - - go inflightTracker(ctx) - group.Eval(ctx, start) // Max inflight can be 1 synchronous eval and up to MaxConcurrentEvals concurrent evals. require.EqualValues(t, opts.MaxConcurrentEvals+1, maxInflight.Load()) // Some rules should execute concurrently so should complete quicker. - require.Less(t, time.Since(start).Seconds(), (time.Duration(expectedRules) * artificialDelay).Seconds()) + require.Less(t, time.Since(start).Seconds(), (time.Duration(ruleCount) * artificialDelay).Seconds()) // Each rule produces one vector. - require.EqualValues(t, expectedRules, testutil.ToFloat64(group.metrics.GroupSamples)) + require.EqualValues(t, ruleCount, testutil.ToFloat64(group.metrics.GroupSamples)) } + }) - cancel() + t.Run("asynchronous evaluation of all independent rules, insufficient concurrency", func(t *testing.T) { + // Reset. + inflightQueries.Store(0) + maxInflight.Store(0) + + ctx, cancel := context.WithCancel(context.Background()) + t.Cleanup(cancel) + + ruleCount := 6 + opts := optsFactory(storage, &maxInflight, &inflightQueries, 0) + + // Configure concurrency settings. + opts.ConcurrentEvalsEnabled = true + opts.MaxConcurrentEvals = 2 + opts.RuleConcurrencyController = nil + ruleManager := NewManager(opts) + + groups, errs := ruleManager.LoadGroups(time.Second, labels.EmptyLabels(), "", nil, []string{"fixtures/rules_multiple_independent.yaml"}...) + require.Empty(t, errs) + require.Len(t, groups, 1) + + for _, group := range groups { + require.Len(t, group.rules, ruleCount) + + start := time.Now() + group.Eval(ctx, start) + + // Max inflight can be 1 synchronous eval and up to MaxConcurrentEvals concurrent evals. + require.EqualValues(t, opts.MaxConcurrentEvals+1, maxInflight.Load()) + // Some rules should execute concurrently so should complete quicker. + require.Less(t, time.Since(start).Seconds(), (time.Duration(ruleCount) * artificialDelay).Seconds()) + // Each rule produces one vector. + require.EqualValues(t, ruleCount, testutil.ToFloat64(group.metrics.GroupSamples)) + + } + }) + + t.Run("asynchronous evaluation of all independent rules, sufficient concurrency", func(t *testing.T) { + // Reset. + inflightQueries.Store(0) + maxInflight.Store(0) + + ctx, cancel := context.WithCancel(context.Background()) + t.Cleanup(cancel) + + ruleCount := 6 + opts := optsFactory(storage, &maxInflight, &inflightQueries, 0) + + // Configure concurrency settings. + opts.ConcurrentEvalsEnabled = true + opts.MaxConcurrentEvals = int64(ruleCount) * 2 + opts.RuleConcurrencyController = nil + ruleManager := NewManager(opts) + + groups, errs := ruleManager.LoadGroups(time.Second, labels.EmptyLabels(), "", nil, []string{"fixtures/rules_multiple_independent.yaml"}...) + require.Empty(t, errs) + require.Len(t, groups, 1) + + for _, group := range groups { + require.Len(t, group.rules, ruleCount) + + start := time.Now() + + group.Eval(ctx, start) + + // Max inflight can be up to MaxConcurrentEvals concurrent evals, since there is sufficient concurrency to run all rules at once. + require.LessOrEqual(t, int64(maxInflight.Load()), opts.MaxConcurrentEvals) + // Some rules should execute concurrently so should complete quicker. + require.Less(t, time.Since(start).Seconds(), (time.Duration(ruleCount) * artificialDelay).Seconds()) + // Each rule produces one vector. + require.EqualValues(t, ruleCount, testutil.ToFloat64(group.metrics.GroupSamples)) + } }) } @@ -1793,8 +1833,6 @@ func TestBoundedRuleEvalConcurrency(t *testing.T) { storage := teststorage.New(t) t.Cleanup(func() { storage.Close() }) - const artificialDelay = time.Millisecond * 100 - var ( inflightQueries atomic.Int32 maxInflight atomic.Int32 @@ -1803,50 +1841,15 @@ func TestBoundedRuleEvalConcurrency(t *testing.T) { ) files := []string{"fixtures/rules_multiple_groups.yaml"} - ruleManager := NewManager(&ManagerOptions{ - Context: context.Background(), - Logger: log.NewNopLogger(), - Appendable: storage, - ConcurrentEvalsEnabled: true, - MaxConcurrentEvals: maxConcurrency, - QueryFunc: func(ctx context.Context, q string, ts time.Time) (promql.Vector, error) { - inflightQueries.Add(1) - defer func() { - inflightQueries.Add(-1) - }() - // Artificially delay all query executions to highlight concurrent execution improvement. - time.Sleep(artificialDelay) - - // Return a stub sample. - return promql.Vector{ - promql.Sample{Metric: labels.FromStrings("__name__", "test"), T: ts.UnixMilli(), F: 12345}, - }, nil - }, - }) + ruleManager := NewManager(optsFactory(storage, &maxInflight, &inflightQueries, maxConcurrency)) groups, errs := ruleManager.LoadGroups(time.Second, labels.EmptyLabels(), "", nil, files...) require.Empty(t, errs) require.Len(t, groups, groupCount) ctx, cancel := context.WithCancel(context.Background()) - - go func() { - for { - select { - case <-ctx.Done(): - return - default: - highWatermark := maxInflight.Load() - current := inflightQueries.Load() - if current > highWatermark { - maxInflight.Store(current) - } - - time.Sleep(time.Millisecond) - } - } - }() + t.Cleanup(cancel) // Evaluate groups concurrently (like they normally do). var wg sync.WaitGroup @@ -1861,8 +1864,46 @@ func TestBoundedRuleEvalConcurrency(t *testing.T) { } wg.Wait() - cancel() // Synchronous queries also count towards inflight, so at most we can have maxConcurrency+$groupCount inflight evaluations. require.EqualValues(t, maxInflight.Load(), int32(maxConcurrency)+int32(groupCount)) } + +const artificialDelay = 10 * time.Millisecond + +func optsFactory(storage storage.Storage, maxInflight, inflightQueries *atomic.Int32, maxConcurrent int64) *ManagerOptions { + var inflightMu sync.Mutex + + concurrent := maxConcurrent > 0 + + return &ManagerOptions{ + Context: context.Background(), + Logger: log.NewNopLogger(), + ConcurrentEvalsEnabled: concurrent, + MaxConcurrentEvals: maxConcurrent, + Appendable: storage, + QueryFunc: func(ctx context.Context, q string, ts time.Time) (promql.Vector, error) { + inflightMu.Lock() + + current := inflightQueries.Add(1) + defer func() { + inflightQueries.Add(-1) + }() + + highWatermark := maxInflight.Load() + + if current > highWatermark { + maxInflight.Store(current) + } + inflightMu.Unlock() + + // Artificially delay all query executions to highlight concurrent execution improvement. + time.Sleep(artificialDelay) + + // Return a stub sample. + return promql.Vector{ + promql.Sample{Metric: labels.FromStrings("__name__", "test"), T: ts.UnixMilli(), F: 12345}, + }, nil + }, + } +} From ac1c6eb3ef131922a9211aa25a50f7af1df197d2 Mon Sep 17 00:00:00 2001 From: Marco Pracucci Date: Fri, 26 Jan 2024 19:08:07 +0100 Subject: [PATCH 032/237] Fix typo in CLI flag description Signed-off-by: Marco Pracucci --- cmd/prometheus/main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/prometheus/main.go b/cmd/prometheus/main.go index 830ae46490..e36665857b 100644 --- a/cmd/prometheus/main.go +++ b/cmd/prometheus/main.go @@ -416,7 +416,7 @@ func main() { serverOnlyFlag(a, "rules.alert.resend-delay", "Minimum amount of time to wait before resending an alert to Alertmanager."). Default("1m").SetValue(&cfg.resendDelay) - serverOnlyFlag(a, "rules.max-concurrent-evals", "Global concurrency limit for independent rules which can run concurrently."). + serverOnlyFlag(a, "rules.max-concurrent-evals", "Global concurrency limit for independent rules that can run concurrently."). Default("4").Int64Var(&cfg.maxConcurrentEvals) a.Flag("scrape.adjust-timestamps", "Adjust scrape timestamps by up to `scrape.timestamp-tolerance` to align them to the intended schedule. See https://github.com/prometheus/prometheus/issues/7846 for more context. Experimental. This flag will be removed in a future release."). From 6bbb03bd00156e4313067c622c097e83eb70eab9 Mon Sep 17 00:00:00 2001 From: Marco Pracucci Date: Fri, 26 Jan 2024 20:02:18 +0100 Subject: [PATCH 033/237] Fixed auto-generated doc Signed-off-by: Marco Pracucci --- docs/command-line/prometheus.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/command-line/prometheus.md b/docs/command-line/prometheus.md index fef8ffa54d..2faea5b15e 100644 --- a/docs/command-line/prometheus.md +++ b/docs/command-line/prometheus.md @@ -48,7 +48,7 @@ The Prometheus monitoring server | --rules.alert.for-outage-tolerance | Max time to tolerate prometheus outage for restoring "for" state of alert. Use with server mode only. | `1h` | | --rules.alert.for-grace-period | Minimum duration between alert and restored "for" state. This is maintained only for alerts with configured "for" time greater than grace period. Use with server mode only. | `10m` | | --rules.alert.resend-delay | Minimum amount of time to wait before resending an alert to Alertmanager. Use with server mode only. | `1m` | -| --rules.max-concurrent-evals | Global concurrency limit for independent rules which can run concurrently. Use with server mode only. | `4` | +| --rules.max-concurrent-evals | Global concurrency limit for independent rules that can run concurrently. Use with server mode only. | `4` | | --alertmanager.notification-queue-capacity | The capacity of the queue for pending Alertmanager notifications. Use with server mode only. | `10000` | | --query.lookback-delta | The maximum lookback duration for retrieving metrics during expression evaluations and federation. Use with server mode only. | `5m` | | --query.timeout | Maximum time a query may take before being aborted. Use with server mode only. | `2m` | From 1bb341fa517392d504285ed70d428483fdfb76f1 Mon Sep 17 00:00:00 2001 From: Marco Pracucci Date: Fri, 26 Jan 2024 19:09:29 +0100 Subject: [PATCH 034/237] Improve doc Signed-off-by: Marco Pracucci --- docs/feature_flags.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/feature_flags.md b/docs/feature_flags.md index 95b6270104..c3540cc234 100644 --- a/docs/feature_flags.md +++ b/docs/feature_flags.md @@ -221,5 +221,6 @@ By default, rule groups execute concurrently, but the rules within a group execu output of a preceding rule as its input. However, if there is no detectable relationship between rules then there is no reason to run them sequentially. When the `concurrent-rule-eval` feature flag is enabled, rules without any dependency on other rules within a rule group will be evaluated concurrently. -This can improve rule reliability at the expense of adding more concurrent query load. The number of concurrent rule evaluations can be configured -with `--rules.max-concurrent-rule-evals` which is set to `4` by default. +This has the potential to improve rule group evaluation latency and resource utilization at the expense of adding more concurrent query load. + +The number of concurrent rule evaluations can be configured with `--rules.max-concurrent-rule-evals`, which is set to `4` by default. From 21a03dc018cbfc90b690cbf21e945db029b79f00 Mon Sep 17 00:00:00 2001 From: Marco Pracucci Date: Fri, 26 Jan 2024 19:12:40 +0100 Subject: [PATCH 035/237] Simplify the design to update concurrency controller once the rule evaluation has done Signed-off-by: Marco Pracucci --- rules/group.go | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/rules/group.go b/rules/group.go index 939d2cd5b6..b50189fa97 100644 --- a/rules/group.go +++ b/rules/group.go @@ -437,13 +437,10 @@ func (g *Group) Eval(ctx context.Context, ts time.Time) { default: } - eval := func(i int, rule Rule, async bool) { - defer func() { - if async { - wg.Done() - g.opts.RuleConcurrencyController.Done() - } - }() + eval := func(i int, rule Rule, cleanup func()) { + if cleanup != nil { + defer cleanup() + } logger := log.WithPrefix(g.logger, "name", rule.Name(), "index", i) ctx, sp := otel.Tracer("").Start(ctx, "rule") @@ -576,9 +573,13 @@ func (g *Group) Eval(ctx context.Context, ts time.Time) { ctrl := g.opts.RuleConcurrencyController if ctrl != nil && ctrl.RuleEligible(g, rule) && ctrl.Allow() { wg.Add(1) - go eval(i, rule, true) + + go eval(i, rule, func() { + wg.Done() + g.opts.RuleConcurrencyController.Done() + }) } else { - eval(i, rule, false) + eval(i, rule, nil) } } From 52bc568d04da46e448fca62a075f5d92c5f1e47b Mon Sep 17 00:00:00 2001 From: Marco Pracucci Date: Fri, 26 Jan 2024 19:33:24 +0100 Subject: [PATCH 036/237] Add more test cases to TestDependenciesEdgeCases Signed-off-by: Marco Pracucci --- rules/manager_test.go | 66 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) diff --git a/rules/manager_test.go b/rules/manager_test.go index 2d1dc6b42e..4762ef6f5d 100644 --- a/rules/manager_test.go +++ b/rules/manager_test.go @@ -1523,6 +1523,72 @@ func TestDependenciesEdgeCases(t *testing.T) { require.True(t, depMap.isIndependent(rule1)) require.True(t, depMap.isIndependent(rule2)) }) + + t.Run("rule with regexp matcher on metric name", func(t *testing.T) { + expr, err := parser.ParseExpr("sum(requests)") + require.NoError(t, err) + rule1 := NewRecordingRule("first", expr, labels.Labels{}) + + expr, err = parser.ParseExpr(`sum({__name__=~".+"})`) + require.NoError(t, err) + rule2 := NewRecordingRule("second", expr, labels.Labels{}) + + group := NewGroup(GroupOptions{ + Name: "rule_group", + Interval: time.Second, + Rules: []Rule{rule1, rule2}, + Opts: opts, + }) + + depMap := buildDependencyMap(group.rules) + // A rule with regexp matcher on metric name causes the whole group to be indeterminate. + require.False(t, depMap.isIndependent(rule1)) + require.False(t, depMap.isIndependent(rule2)) + }) + + t.Run("rule with not equal matcher on metric name", func(t *testing.T) { + expr, err := parser.ParseExpr("sum(requests)") + require.NoError(t, err) + rule1 := NewRecordingRule("first", expr, labels.Labels{}) + + expr, err = parser.ParseExpr(`sum({__name__!="requests", service="app"})`) + require.NoError(t, err) + rule2 := NewRecordingRule("second", expr, labels.Labels{}) + + group := NewGroup(GroupOptions{ + Name: "rule_group", + Interval: time.Second, + Rules: []Rule{rule1, rule2}, + Opts: opts, + }) + + depMap := buildDependencyMap(group.rules) + // A rule with not equal matcher on metric name causes the whole group to be indeterminate. + require.False(t, depMap.isIndependent(rule1)) + require.False(t, depMap.isIndependent(rule2)) + }) + + t.Run("rule with not regexp matcher on metric name", func(t *testing.T) { + expr, err := parser.ParseExpr("sum(requests)") + require.NoError(t, err) + rule1 := NewRecordingRule("first", expr, labels.Labels{}) + + expr, err = parser.ParseExpr(`sum({__name__!~"requests.+", service="app"})`) + require.NoError(t, err) + rule2 := NewRecordingRule("second", expr, labels.Labels{}) + + group := NewGroup(GroupOptions{ + Name: "rule_group", + Interval: time.Second, + Rules: []Rule{rule1, rule2}, + Opts: opts, + }) + + depMap := buildDependencyMap(group.rules) + // A rule with not regexp matcher on metric name causes the whole group to be indeterminate. + require.False(t, depMap.isIndependent(rule1)) + require.False(t, depMap.isIndependent(rule2)) + }) } func TestNoMetricSelector(t *testing.T) { From 2764c4653112e59c10a8e0af8b18ebbb6f5851a0 Mon Sep 17 00:00:00 2001 From: Marco Pracucci Date: Fri, 26 Jan 2024 19:38:11 +0100 Subject: [PATCH 037/237] Added more test cases to TestDependenciesEdgeCases Signed-off-by: Marco Pracucci --- rules/manager_test.go | 44 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/rules/manager_test.go b/rules/manager_test.go index 4762ef6f5d..ed6fea2532 100644 --- a/rules/manager_test.go +++ b/rules/manager_test.go @@ -1589,6 +1589,50 @@ func TestDependenciesEdgeCases(t *testing.T) { require.False(t, depMap.isIndependent(rule1)) require.False(t, depMap.isIndependent(rule2)) }) + + t.Run("rule querying ALERTS metric", func(t *testing.T) { + expr, err := parser.ParseExpr("sum(requests)") + require.NoError(t, err) + rule1 := NewRecordingRule("first", expr, labels.Labels{}) + + expr, err = parser.ParseExpr(`sum(ALERTS{alertname="test"})`) + require.NoError(t, err) + rule2 := NewRecordingRule("second", expr, labels.Labels{}) + + group := NewGroup(GroupOptions{ + Name: "rule_group", + Interval: time.Second, + Rules: []Rule{rule1, rule2}, + Opts: opts, + }) + + depMap := buildDependencyMap(group.rules) + // A rule querying ALERTS metric causes the whole group to be indeterminate. + require.False(t, depMap.isIndependent(rule1)) + require.False(t, depMap.isIndependent(rule2)) + }) + + t.Run("rule querying ALERTS_FOR_STATE metric", func(t *testing.T) { + expr, err := parser.ParseExpr("sum(requests)") + require.NoError(t, err) + rule1 := NewRecordingRule("first", expr, labels.Labels{}) + + expr, err = parser.ParseExpr(`sum(ALERTS_FOR_STATE{alertname="test"})`) + require.NoError(t, err) + rule2 := NewRecordingRule("second", expr, labels.Labels{}) + + group := NewGroup(GroupOptions{ + Name: "rule_group", + Interval: time.Second, + Rules: []Rule{rule1, rule2}, + Opts: opts, + }) + + depMap := buildDependencyMap(group.rules) + // A rule querying ALERTS_FOR_STATE metric causes the whole group to be indeterminate. + require.False(t, depMap.isIndependent(rule1)) + require.False(t, depMap.isIndependent(rule2)) + }) } func TestNoMetricSelector(t *testing.T) { From 23f89c18b241deeb2d2f6ea24f872e8442619641 Mon Sep 17 00:00:00 2001 From: Marco Pracucci Date: Fri, 26 Jan 2024 19:39:50 +0100 Subject: [PATCH 038/237] Improved RuleConcurrencyController interface doc Signed-off-by: Marco Pracucci --- rules/manager.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/rules/manager.go b/rules/manager.go index 84b43fba7d..7da4eb88b1 100644 --- a/rules/manager.go +++ b/rules/manager.go @@ -416,11 +416,11 @@ func SendAlerts(s Sender, externalURL string) NotifyFunc { } } -// RuleConcurrencyController controls whether rules can be evaluated concurrently. Its purpose it to bound the amount -// of concurrency in rule evaluations, to not overwhelm the Prometheus server with additional query load. -// Concurrency is controlled globally, not on a per-group basis. +// RuleConcurrencyController controls whether rules can be evaluated concurrently. Its purpose is to bound the amount +// of concurrency in rule evaluations to avoid overwhelming the Prometheus server with additional query load and ensure +// the correctness of rules running concurrently. Concurrency is controlled globally, not on a per-group basis. type RuleConcurrencyController interface { - // RuleEligible determines if a rule can be run concurrently. + // RuleEligible determines if the rule can guarantee correct results while running concurrently. RuleEligible(g *Group, r Rule) bool // Allow determines whether any concurrent evaluation slots are available. From 046cd7599f573ff0f342edbafeee8f539a8077a2 Mon Sep 17 00:00:00 2001 From: Marco Pracucci Date: Fri, 26 Jan 2024 19:53:44 +0100 Subject: [PATCH 039/237] Introduced sequentialRuleEvalController Signed-off-by: Marco Pracucci --- rules/group.go | 42 ++++++++++++++++++++++--------------- rules/manager.go | 48 ++++++++++++++++++++++++------------------- rules/manager_test.go | 3 ++- 3 files changed, 54 insertions(+), 39 deletions(-) diff --git a/rules/group.go b/rules/group.go index b50189fa97..56648a60cc 100644 --- a/rules/group.go +++ b/rules/group.go @@ -71,6 +71,9 @@ type Group struct { // Rule group evaluation iteration function, // defaults to DefaultEvalIterationFunc. evalIterationFunc GroupEvalIterationFunc + + // concurrencyController controls the rules evaluation concurrency. + concurrencyController RuleConcurrencyController } // GroupEvalIterationFunc is used to implement and extend rule group @@ -114,21 +117,27 @@ func NewGroup(o GroupOptions) *Group { evalIterationFunc = DefaultEvalIterationFunc } + concurrencyController := o.Opts.RuleConcurrencyController + if concurrencyController == nil { + concurrencyController = sequentialRuleEvalController{} + } + return &Group{ - name: o.Name, - file: o.File, - interval: o.Interval, - limit: o.Limit, - rules: o.Rules, - shouldRestore: o.ShouldRestore, - opts: o.Opts, - seriesInPreviousEval: make([]map[string]labels.Labels, len(o.Rules)), - done: make(chan struct{}), - managerDone: o.done, - terminated: make(chan struct{}), - logger: log.With(o.Opts.Logger, "file", o.File, "group", o.Name), - metrics: metrics, - evalIterationFunc: evalIterationFunc, + name: o.Name, + file: o.File, + interval: o.Interval, + limit: o.Limit, + rules: o.Rules, + shouldRestore: o.ShouldRestore, + opts: o.Opts, + seriesInPreviousEval: make([]map[string]labels.Labels, len(o.Rules)), + done: make(chan struct{}), + managerDone: o.done, + terminated: make(chan struct{}), + logger: log.With(o.Opts.Logger, "file", o.File, "group", o.Name), + metrics: metrics, + evalIterationFunc: evalIterationFunc, + concurrencyController: concurrencyController, } } @@ -570,13 +579,12 @@ func (g *Group) Eval(ctx context.Context, ts time.Time) { // If the rule has no dependencies, it can run concurrently because no other rules in this group depend on its output. // Try run concurrently if there are slots available. - ctrl := g.opts.RuleConcurrencyController - if ctrl != nil && ctrl.RuleEligible(g, rule) && ctrl.Allow() { + if ctrl := g.concurrencyController; ctrl.RuleEligible(g, rule) && ctrl.Allow() { wg.Add(1) go eval(i, rule, func() { wg.Done() - g.opts.RuleConcurrencyController.Done() + ctrl.Done() }) } else { eval(i, rule, nil) diff --git a/rules/manager.go b/rules/manager.go index 7da4eb88b1..477508dc04 100644 --- a/rules/manager.go +++ b/rules/manager.go @@ -135,7 +135,11 @@ func NewManager(o *ManagerOptions) *Manager { } if o.RuleConcurrencyController == nil { - o.RuleConcurrencyController = newRuleConcurrencyController(o.ConcurrentEvalsEnabled, o.MaxConcurrentEvals) + if o.ConcurrentEvalsEnabled { + o.RuleConcurrencyController = newRuleConcurrencyController(o.MaxConcurrentEvals) + } else { + o.RuleConcurrencyController = sequentialRuleEvalController{} + } } m := &Manager{ @@ -184,9 +188,7 @@ func (m *Manager) Update(interval time.Duration, files []string, externalLabels m.mtx.Lock() defer m.mtx.Unlock() - if m.opts.RuleConcurrencyController != nil { - m.opts.RuleConcurrencyController.Invalidate() - } + m.opts.RuleConcurrencyController.Invalidate() groups, errs := m.LoadGroups(interval, externalLabels, externalURL, groupEvalIterationFunc, files...) @@ -436,22 +438,20 @@ type RuleConcurrencyController interface { Invalidate() } -func newRuleConcurrencyController(enabled bool, maxConcurrency int64) RuleConcurrencyController { - return &concurrentRuleEvalController{ - enabled: enabled, - sema: semaphore.NewWeighted(maxConcurrency), - depMaps: map[*Group]dependencyMap{}, - } -} - // concurrentRuleEvalController holds a weighted semaphore which controls the concurrent evaluation of rules. type concurrentRuleEvalController struct { - enabled bool sema *semaphore.Weighted depMapsMu sync.Mutex depMaps map[*Group]dependencyMap } +func newRuleConcurrencyController(maxConcurrency int64) RuleConcurrencyController { + return &concurrentRuleEvalController{ + sema: semaphore.NewWeighted(maxConcurrency), + depMaps: map[*Group]dependencyMap{}, + } +} + func (c *concurrentRuleEvalController) RuleEligible(g *Group, r Rule) bool { c.depMapsMu.Lock() defer c.depMapsMu.Unlock() @@ -466,18 +466,10 @@ func (c *concurrentRuleEvalController) RuleEligible(g *Group, r Rule) bool { } func (c *concurrentRuleEvalController) Allow() bool { - if !c.enabled { - return false - } - return c.sema.TryAcquire(1) } func (c *concurrentRuleEvalController) Done() { - if !c.enabled { - return - } - c.sema.Release(1) } @@ -488,3 +480,17 @@ func (c *concurrentRuleEvalController) Invalidate() { // Clear out the memoized dependency maps because some or all groups may have been updated. c.depMaps = map[*Group]dependencyMap{} } + +// sequentialRuleEvalController is a RuleConcurrencyController that runs every rule sequentially. +type sequentialRuleEvalController struct{} + +func (c sequentialRuleEvalController) RuleEligible(_ *Group, _ Rule) bool { + return false +} + +func (c sequentialRuleEvalController) Allow() bool { + return false +} + +func (c sequentialRuleEvalController) Done() {} +func (c sequentialRuleEvalController) Invalidate() {} diff --git a/rules/manager_test.go b/rules/manager_test.go index ed6fea2532..7d5a2bd9fe 100644 --- a/rules/manager_test.go +++ b/rules/manager_test.go @@ -676,7 +676,8 @@ func TestDeletedRuleMarkedStale(t *testing.T) { rules: []Rule{}, seriesInPreviousEval: []map[string]labels.Labels{}, opts: &ManagerOptions{ - Appendable: st, + Appendable: st, + RuleConcurrencyController: sequentialRuleEvalController{}, }, } newGroup.CopyState(oldGroup) From cbbbd6e70ab7c60da020ce118830c014c909be1d Mon Sep 17 00:00:00 2001 From: Marco Pracucci Date: Mon, 29 Jan 2024 10:21:57 +0100 Subject: [PATCH 040/237] Remove superfluous nil check in Group.metrics Signed-off-by: Marco Pracucci --- rules/group.go | 5 ++--- rules/manager_test.go | 1 + 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/rules/group.go b/rules/group.go index 56648a60cc..5ee06dc0ba 100644 --- a/rules/group.go +++ b/rules/group.go @@ -592,9 +592,8 @@ func (g *Group) Eval(ctx context.Context, ts time.Time) { } wg.Wait() - if g.metrics != nil { - g.metrics.GroupSamples.WithLabelValues(GroupKey(g.File(), g.Name())).Set(samplesTotal.Load()) - } + + g.metrics.GroupSamples.WithLabelValues(GroupKey(g.File(), g.Name())).Set(samplesTotal.Load()) g.cleanupStaleSeries(ctx, ts) } diff --git a/rules/manager_test.go b/rules/manager_test.go index 7d5a2bd9fe..07ec06104d 100644 --- a/rules/manager_test.go +++ b/rules/manager_test.go @@ -679,6 +679,7 @@ func TestDeletedRuleMarkedStale(t *testing.T) { Appendable: st, RuleConcurrencyController: sequentialRuleEvalController{}, }, + metrics: NewGroupMetrics(nil), } newGroup.CopyState(oldGroup) From e3040bfabc41ec2e1947f5a560915d7d1d427fa6 Mon Sep 17 00:00:00 2001 From: Leegin <114397475+Leegin-darknight@users.noreply.github.com> Date: Mon, 29 Jan 2024 15:49:02 +0530 Subject: [PATCH 041/237] api: Serialize discovered and target labels into JSON directly (#13469) Converted maps into labels.Labels to avoid a lot of copying of data which leads to very high memory consumption while opening the /service-discovery endpoint in the Prometheus UI Signed-off-by: Leegin <114397475+Leegin-darknight@users.noreply.github.com> --- web/api/v1/api.go | 8 ++++---- web/api/v1/api_test.go | 36 ++++++++++++------------------------ 2 files changed, 16 insertions(+), 28 deletions(-) diff --git a/web/api/v1/api.go b/web/api/v1/api.go index d9d4cfd1df..edbbe8d80b 100644 --- a/web/api/v1/api.go +++ b/web/api/v1/api.go @@ -879,9 +879,9 @@ func (api *API) dropSeries(_ *http.Request) apiFuncResult { // Target has the information for one target. type Target struct { // Labels before any processing. - DiscoveredLabels map[string]string `json:"discoveredLabels"` + DiscoveredLabels labels.Labels `json:"discoveredLabels"` // Any labels that are added to this target and its metrics. - Labels map[string]string `json:"labels"` + Labels labels.Labels `json:"labels"` ScrapePool string `json:"scrapePool"` ScrapeURL string `json:"scrapeUrl"` @@ -1024,8 +1024,8 @@ func (api *API) targets(r *http.Request) apiFuncResult { globalURL, err := getGlobalURL(target.URL(), api.globalURLOptions) res.ActiveTargets = append(res.ActiveTargets, &Target{ - DiscoveredLabels: target.DiscoveredLabels().Map(), - Labels: target.Labels().Map(), + DiscoveredLabels: target.DiscoveredLabels(), + Labels: target.Labels(), ScrapePool: key, ScrapeURL: target.URL().String(), GlobalURL: globalURL.String(), diff --git a/web/api/v1/api_test.go b/web/api/v1/api_test.go index c9ab84087e..9100a9ef90 100644 --- a/web/api/v1/api_test.go +++ b/web/api/v1/api_test.go @@ -1411,10 +1411,8 @@ func testEndpoints(t *testing.T, api *API, tr *testTargetRetriever, es storage.E response: &TargetDiscovery{ ActiveTargets: []*Target{ { - DiscoveredLabels: map[string]string{}, - Labels: map[string]string{ - "job": "blackbox", - }, + DiscoveredLabels: labels.FromStrings(), + Labels: labels.FromStrings("job", "blackbox"), ScrapePool: "blackbox", ScrapeURL: "http://localhost:9115/probe?target=example.com", GlobalURL: "http://localhost:9115/probe?target=example.com", @@ -1426,10 +1424,8 @@ func testEndpoints(t *testing.T, api *API, tr *testTargetRetriever, es storage.E ScrapeTimeout: "10s", }, { - DiscoveredLabels: map[string]string{}, - Labels: map[string]string{ - "job": "test", - }, + DiscoveredLabels: labels.FromStrings(), + Labels: labels.FromStrings("job", "test"), ScrapePool: "test", ScrapeURL: "http://example.com:8080/metrics", GlobalURL: "http://example.com:8080/metrics", @@ -1464,10 +1460,8 @@ func testEndpoints(t *testing.T, api *API, tr *testTargetRetriever, es storage.E response: &TargetDiscovery{ ActiveTargets: []*Target{ { - DiscoveredLabels: map[string]string{}, - Labels: map[string]string{ - "job": "blackbox", - }, + DiscoveredLabels: labels.FromStrings(), + Labels: labels.FromStrings("job", "blackbox"), ScrapePool: "blackbox", ScrapeURL: "http://localhost:9115/probe?target=example.com", GlobalURL: "http://localhost:9115/probe?target=example.com", @@ -1479,10 +1473,8 @@ func testEndpoints(t *testing.T, api *API, tr *testTargetRetriever, es storage.E ScrapeTimeout: "10s", }, { - DiscoveredLabels: map[string]string{}, - Labels: map[string]string{ - "job": "test", - }, + DiscoveredLabels: labels.FromStrings(), + Labels: labels.FromStrings("job", "test"), ScrapePool: "test", ScrapeURL: "http://example.com:8080/metrics", GlobalURL: "http://example.com:8080/metrics", @@ -1517,10 +1509,8 @@ func testEndpoints(t *testing.T, api *API, tr *testTargetRetriever, es storage.E response: &TargetDiscovery{ ActiveTargets: []*Target{ { - DiscoveredLabels: map[string]string{}, - Labels: map[string]string{ - "job": "blackbox", - }, + DiscoveredLabels: labels.FromStrings(), + Labels: labels.FromStrings("job", "blackbox"), ScrapePool: "blackbox", ScrapeURL: "http://localhost:9115/probe?target=example.com", GlobalURL: "http://localhost:9115/probe?target=example.com", @@ -1532,10 +1522,8 @@ func testEndpoints(t *testing.T, api *API, tr *testTargetRetriever, es storage.E ScrapeTimeout: "10s", }, { - DiscoveredLabels: map[string]string{}, - Labels: map[string]string{ - "job": "test", - }, + DiscoveredLabels: labels.FromStrings(), + Labels: labels.FromStrings("job", "test"), ScrapePool: "test", ScrapeURL: "http://example.com:8080/metrics", GlobalURL: "http://example.com:8080/metrics", From 19efd0a6759ac2a0d84e6e593a6aa49f39d034d7 Mon Sep 17 00:00:00 2001 From: Leegin <114397475+Leegin-darknight@users.noreply.github.com> Date: Mon, 29 Jan 2024 15:50:20 +0530 Subject: [PATCH 042/237] api: Serialize discovered labels into JSON directly in dropped targets (#13484) Converted maps into labels.Labels to avoid a lot of copying of data which leads to very high memory consumption while opening the /service-discovery endpoint in the Prometheus UI Signed-off-by: Leegin <114397475+Leegin-darknight@users.noreply.github.com> --- web/api/v1/api.go | 4 ++-- web/api/v1/api_test.go | 48 +++++++++++++++++++++--------------------- 2 files changed, 26 insertions(+), 26 deletions(-) diff --git a/web/api/v1/api.go b/web/api/v1/api.go index edbbe8d80b..0c02293bfa 100644 --- a/web/api/v1/api.go +++ b/web/api/v1/api.go @@ -903,7 +903,7 @@ type ScrapePoolsDiscovery struct { // DroppedTarget has the information for one target that was dropped during relabelling. type DroppedTarget struct { // Labels before any processing. - DiscoveredLabels map[string]string `json:"discoveredLabels"` + DiscoveredLabels labels.Labels `json:"discoveredLabels"` } // TargetDiscovery has all the active targets. @@ -1063,7 +1063,7 @@ func (api *API) targets(r *http.Request) apiFuncResult { } for _, target := range targetsDropped[key] { res.DroppedTargets = append(res.DroppedTargets, &DroppedTarget{ - DiscoveredLabels: target.DiscoveredLabels().Map(), + DiscoveredLabels: target.DiscoveredLabels(), }) } } diff --git a/web/api/v1/api_test.go b/web/api/v1/api_test.go index 9100a9ef90..3dc83548de 100644 --- a/web/api/v1/api_test.go +++ b/web/api/v1/api_test.go @@ -1439,14 +1439,14 @@ func testEndpoints(t *testing.T, api *API, tr *testTargetRetriever, es storage.E }, DroppedTargets: []*DroppedTarget{ { - DiscoveredLabels: map[string]string{ - "__address__": "http://dropped.example.com:9115", - "__metrics_path__": "/probe", - "__scheme__": "http", - "job": "blackbox", - "__scrape_interval__": "30s", - "__scrape_timeout__": "15s", - }, + DiscoveredLabels: labels.FromStrings( + "__address__", "http://dropped.example.com:9115", + "__metrics_path__", "/probe", + "__scheme__", "http", + "job", "blackbox", + "__scrape_interval__", "30s", + "__scrape_timeout__", "15s", + ), }, }, DroppedTargetCounts: map[string]int{"blackbox": 1}, @@ -1488,14 +1488,14 @@ func testEndpoints(t *testing.T, api *API, tr *testTargetRetriever, es storage.E }, DroppedTargets: []*DroppedTarget{ { - DiscoveredLabels: map[string]string{ - "__address__": "http://dropped.example.com:9115", - "__metrics_path__": "/probe", - "__scheme__": "http", - "job": "blackbox", - "__scrape_interval__": "30s", - "__scrape_timeout__": "15s", - }, + DiscoveredLabels: labels.FromStrings( + "__address__", "http://dropped.example.com:9115", + "__metrics_path__", "/probe", + "__scheme__", "http", + "job", "blackbox", + "__scrape_interval__", "30s", + "__scrape_timeout__", "15s", + ), }, }, DroppedTargetCounts: map[string]int{"blackbox": 1}, @@ -1547,14 +1547,14 @@ func testEndpoints(t *testing.T, api *API, tr *testTargetRetriever, es storage.E ActiveTargets: []*Target{}, DroppedTargets: []*DroppedTarget{ { - DiscoveredLabels: map[string]string{ - "__address__": "http://dropped.example.com:9115", - "__metrics_path__": "/probe", - "__scheme__": "http", - "job": "blackbox", - "__scrape_interval__": "30s", - "__scrape_timeout__": "15s", - }, + DiscoveredLabels: labels.FromStrings( + "__address__", "http://dropped.example.com:9115", + "__metrics_path__", "/probe", + "__scheme__", "http", + "job", "blackbox", + "__scrape_interval__", "30s", + "__scrape_timeout__", "15s", + ), }, }, DroppedTargetCounts: map[string]int{"blackbox": 1}, From 501bc6419ee54ee5617d75c3bc16ecf36d94c552 Mon Sep 17 00:00:00 2001 From: Marco Pracucci Date: Mon, 29 Jan 2024 12:57:27 +0100 Subject: [PATCH 043/237] Add ShardedPostings() support to TSDB (#10421) This PR is a reference implementation of the proposal described in #10420. In addition to what described in #10420, in this PR I've introduced labels.StableHash(). The idea is to offer an hashing function which doesn't change over time, and that's used by query sharding in order to get a stable behaviour over time. The implementation of labels.StableHash() is the hashing function used by Prometheus before stringlabels, and what's used by Grafana Mimir for query sharding (because built before stringlabels was a thing). Follow up work As mentioned in #10420, if this PR is accepted I'm also open to upload another foundamental piece used by Grafana Mimir query sharding to accelerate the query execution: an optional, configurable and fast in-memory cache for the series hashes. Signed-off-by: Marco Pracucci --- model/labels/sharding.go | 47 ++++++++++++ model/labels/sharding_stringlabels.go | 54 ++++++++++++++ model/labels/sharding_test.go | 32 ++++++++ storage/interface.go | 14 ++++ tsdb/block.go | 9 +++ tsdb/db.go | 5 ++ tsdb/head.go | 23 ++++-- tsdb/head_read.go | 32 ++++++++ tsdb/head_read_test.go | 2 +- tsdb/head_test.go | 101 ++++++++++++++++++++++---- tsdb/index/index.go | 37 +++++++++- tsdb/index/index_test.go | 101 ++++++++++++++++++++++++++ tsdb/ooo_head_read.go | 4 + tsdb/querier.go | 9 +++ tsdb/querier_test.go | 25 +++++++ 15 files changed, 475 insertions(+), 20 deletions(-) create mode 100644 model/labels/sharding.go create mode 100644 model/labels/sharding_stringlabels.go create mode 100644 model/labels/sharding_test.go diff --git a/model/labels/sharding.go b/model/labels/sharding.go new file mode 100644 index 0000000000..6b4119860a --- /dev/null +++ b/model/labels/sharding.go @@ -0,0 +1,47 @@ +// Copyright 2020 The Prometheus Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//go:build !stringlabels + +package labels + +import ( + "github.com/cespare/xxhash/v2" +) + +// StableHash is a labels hashing implementation which is guaranteed to not change over time. +// This function should be used whenever labels hashing backward compatibility must be guaranteed. +func StableHash(ls Labels) uint64 { + // Use xxhash.Sum64(b) for fast path as it's faster. + b := make([]byte, 0, 1024) + for i, v := range ls { + if len(b)+len(v.Name)+len(v.Value)+2 >= cap(b) { + // If labels entry is 1KB+ do not allocate whole entry. + h := xxhash.New() + _, _ = h.Write(b) + for _, v := range ls[i:] { + _, _ = h.WriteString(v.Name) + _, _ = h.Write(seps) + _, _ = h.WriteString(v.Value) + _, _ = h.Write(seps) + } + return h.Sum64() + } + + b = append(b, v.Name...) + b = append(b, seps[0]) + b = append(b, v.Value...) + b = append(b, seps[0]) + } + return xxhash.Sum64(b) +} diff --git a/model/labels/sharding_stringlabels.go b/model/labels/sharding_stringlabels.go new file mode 100644 index 0000000000..3ad2027d8c --- /dev/null +++ b/model/labels/sharding_stringlabels.go @@ -0,0 +1,54 @@ +// Copyright 2020 The Prometheus Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//go:build stringlabels + +package labels + +import ( + "github.com/cespare/xxhash/v2" +) + +// StableHash is a labels hashing implementation which is guaranteed to not change over time. +// This function should be used whenever labels hashing backward compatibility must be guaranteed. +func StableHash(ls Labels) uint64 { + // Use xxhash.Sum64(b) for fast path as it's faster. + b := make([]byte, 0, 1024) + var h *xxhash.Digest + for i := 0; i < len(ls.data); { + var v Label + v.Name, i = decodeString(ls.data, i) + v.Value, i = decodeString(ls.data, i) + if h == nil && len(b)+len(v.Name)+len(v.Value)+2 >= cap(b) { + // If labels entry is 1KB+, switch to Write API. Copy in the values up to this point. + h = xxhash.New() + _, _ = h.Write(b) + } + if h != nil { + _, _ = h.WriteString(v.Name) + _, _ = h.Write(seps) + _, _ = h.WriteString(v.Value) + _, _ = h.Write(seps) + continue + } + + b = append(b, v.Name...) + b = append(b, seps[0]) + b = append(b, v.Value...) + b = append(b, seps[0]) + } + if h != nil { + return h.Sum64() + } + return xxhash.Sum64(b) +} diff --git a/model/labels/sharding_test.go b/model/labels/sharding_test.go new file mode 100644 index 0000000000..78e3047509 --- /dev/null +++ b/model/labels/sharding_test.go @@ -0,0 +1,32 @@ +// Copyright 2020 The Prometheus Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package labels + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +// TestStableHash tests that StableHash is stable. +// The hashes this test asserts should not be changed. +func TestStableHash(t *testing.T) { + for expectedHash, lbls := range map[uint64]Labels{ + 0xef46db3751d8e999: EmptyLabels(), + 0x347c8ee7a9e29708: FromStrings("hello", "world"), + 0xcbab40540f26097d: FromStrings(MetricName, "metric", "label", "value"), + } { + require.Equal(t, expectedHash, StableHash(lbls)) + } +} diff --git a/storage/interface.go b/storage/interface.go index 675e44c0ee..347e779b56 100644 --- a/storage/interface.go +++ b/storage/interface.go @@ -197,6 +197,20 @@ type SelectHints struct { By bool // Indicate whether it is without or by. Range int64 // Range vector selector range in milliseconds. + // ShardCount is the total number of shards that series should be split into + // at query time. Then, only series in the ShardIndex shard will be returned + // by the query. + // + // ShardCount equal to 0 means that sharding is disabled. + ShardCount uint64 + + // ShardIndex is the series shard index to query. The index must be between 0 and ShardCount-1. + // When ShardCount is set to a value > 0, then a query will only process series within the + // ShardIndex's shard. + // + // Series are sharded by "labels stable hash" mod "ShardCount". + ShardIndex uint64 + // DisableTrimming allows to disable trimming of matching series chunks based on query Start and End time. // When disabled, the result may contain samples outside the queried time range but Select() performances // may be improved. diff --git a/tsdb/block.go b/tsdb/block.go index e2562de03c..7833e187ff 100644 --- a/tsdb/block.go +++ b/tsdb/block.go @@ -81,6 +81,11 @@ type IndexReader interface { // by the label set of the underlying series. SortedPostings(index.Postings) index.Postings + // ShardedPostings returns a postings list filtered by the provided shardIndex + // out of shardCount. For a given posting, its shard MUST be computed hashing + // the series labels mod shardCount, using a hash function which is consistent over time. + ShardedPostings(p index.Postings, shardIndex, shardCount uint64) index.Postings + // Series populates the given builder and chunk metas for the series identified // by the reference. // Returns storage.ErrNotFound if the ref does not resolve to a known series. @@ -517,6 +522,10 @@ func (r blockIndexReader) SortedPostings(p index.Postings) index.Postings { return r.ir.SortedPostings(p) } +func (r blockIndexReader) ShardedPostings(p index.Postings, shardIndex, shardCount uint64) index.Postings { + return r.ir.ShardedPostings(p, shardIndex, shardCount) +} + func (r blockIndexReader) Series(ref storage.SeriesRef, builder *labels.ScratchBuilder, chks *[]chunks.Meta) error { if err := r.ir.Series(ref, builder, chks); err != nil { return fmt.Errorf("block: %s: %w", r.b.Meta().ULID, err) diff --git a/tsdb/db.go b/tsdb/db.go index 2436fab2ac..e9265c55e5 100644 --- a/tsdb/db.go +++ b/tsdb/db.go @@ -84,6 +84,7 @@ func DefaultOptions() *Options { HeadChunksWriteQueueSize: chunks.DefaultWriteQueueSize, OutOfOrderCapMax: DefaultOutOfOrderCapMax, EnableOverlappingCompaction: true, + EnableSharding: false, } } @@ -186,6 +187,9 @@ type Options struct { // they'd rather keep overlapping blocks and let another component do the overlapping compaction later. // For Prometheus, this will always be true. EnableOverlappingCompaction bool + + // EnableSharding enables query sharding support in TSDB. + EnableSharding bool } type BlocksToDeleteFunc func(blocks []*Block) map[ulid.ULID]struct{} @@ -875,6 +879,7 @@ func open(dir string, l log.Logger, r prometheus.Registerer, opts *Options, rngs headOpts.EnableNativeHistograms.Store(opts.EnableNativeHistograms) headOpts.OutOfOrderTimeWindow.Store(opts.OutOfOrderTimeWindow) headOpts.OutOfOrderCapMax.Store(opts.OutOfOrderCapMax) + headOpts.EnableSharding = opts.EnableSharding if opts.WALReplayConcurrency > 0 { headOpts.WALReplayConcurrency = opts.WALReplayConcurrency } diff --git a/tsdb/head.go b/tsdb/head.go index cdcd3ea568..86ab7fe06c 100644 --- a/tsdb/head.go +++ b/tsdb/head.go @@ -176,6 +176,9 @@ type HeadOptions struct { // The default value is GOMAXPROCS. // If it is set to a negative value or zero, the default value is used. WALReplayConcurrency int + + // EnableSharding enables ShardedPostings() support in the Head. + EnableSharding bool } const ( @@ -1663,7 +1666,12 @@ func (h *Head) getOrCreate(hash uint64, lset labels.Labels) (*memSeries, bool, e func (h *Head) getOrCreateWithID(id chunks.HeadSeriesRef, hash uint64, lset labels.Labels) (*memSeries, bool, error) { s, created, err := h.series.getOrSet(hash, lset, func() *memSeries { - return newMemSeries(lset, id, h.opts.IsolationDisabled) + shardHash := uint64(0) + if h.opts.EnableSharding { + shardHash = labels.StableHash(lset) + } + + return newMemSeries(lset, id, shardHash, h.opts.IsolationDisabled) }) if err != nil { return nil, false, err @@ -2022,6 +2030,10 @@ type memSeries struct { lset labels.Labels meta *metadata.Metadata + // Series labels hash to use for sharding purposes. The value is always 0 when sharding has not + // been explicitly enabled in TSDB. + shardHash uint64 + // Immutable chunks on disk that have not yet gone into a block, in order of ascending time stamps. // When compaction runs, chunks get moved into a block and all pointers are shifted like so: // @@ -2071,11 +2083,12 @@ type memSeriesOOOFields struct { firstOOOChunkID chunks.HeadChunkID // HeadOOOChunkID for oooMmappedChunks[0]. } -func newMemSeries(lset labels.Labels, id chunks.HeadSeriesRef, isolationDisabled bool) *memSeries { +func newMemSeries(lset labels.Labels, id chunks.HeadSeriesRef, shardHash uint64, isolationDisabled bool) *memSeries { s := &memSeries{ - lset: lset, - ref: id, - nextAt: math.MinInt64, + lset: lset, + ref: id, + nextAt: math.MinInt64, + shardHash: shardHash, } if !isolationDisabled { s.txs = newTxRing(0) diff --git a/tsdb/head_read.go b/tsdb/head_read.go index 29f845c7bd..457d3e1c47 100644 --- a/tsdb/head_read.go +++ b/tsdb/head_read.go @@ -149,7 +149,35 @@ func (h *headIndexReader) SortedPostings(p index.Postings) index.Postings { return index.NewListPostings(ep) } +// ShardedPostings implements IndexReader. This function returns an failing postings list if sharding +// has not been enabled in the Head. +func (h *headIndexReader) ShardedPostings(p index.Postings, shardIndex, shardCount uint64) index.Postings { + if !h.head.opts.EnableSharding { + return index.ErrPostings(errors.New("sharding is disabled")) + } + + out := make([]storage.SeriesRef, 0, 128) + + for p.Next() { + s := h.head.series.getByID(chunks.HeadSeriesRef(p.At())) + if s == nil { + level.Debug(h.head.logger).Log("msg", "Looked up series not found") + continue + } + + // Check if the series belong to the shard. + if s.shardHash%shardCount != shardIndex { + continue + } + + out = append(out, storage.SeriesRef(s.ref)) + } + + return index.NewListPostings(out) +} + // Series returns the series for the given reference. +// Chunks are skipped if chks is nil. func (h *headIndexReader) Series(ref storage.SeriesRef, builder *labels.ScratchBuilder, chks *[]chunks.Meta) error { s := h.head.series.getByID(chunks.HeadSeriesRef(ref)) @@ -159,6 +187,10 @@ func (h *headIndexReader) Series(ref storage.SeriesRef, builder *labels.ScratchB } builder.Assign(s.lset) + if chks == nil { + return nil + } + s.Lock() defer s.Unlock() diff --git a/tsdb/head_read_test.go b/tsdb/head_read_test.go index b063512019..de97d70a56 100644 --- a/tsdb/head_read_test.go +++ b/tsdb/head_read_test.go @@ -526,7 +526,7 @@ func TestMemSeries_chunk(t *testing.T) { require.NoError(t, chunkDiskMapper.Close()) }() - series := newMemSeries(labels.EmptyLabels(), 1, true) + series := newMemSeries(labels.EmptyLabels(), 1, 0, true) if tc.setup != nil { tc.setup(t, series, chunkDiskMapper) diff --git a/tsdb/head_test.go b/tsdb/head_test.go index 83abeacbeb..90e187b58c 100644 --- a/tsdb/head_test.go +++ b/tsdb/head_test.go @@ -52,20 +52,30 @@ import ( "github.com/prometheus/prometheus/tsdb/wlog" ) -func newTestHead(t testing.TB, chunkRange int64, compressWAL wlog.CompressionType, oooEnabled bool) (*Head, *wlog.WL) { - dir := t.TempDir() - wal, err := wlog.NewSize(nil, nil, filepath.Join(dir, "wal"), 32768, compressWAL) - require.NoError(t, err) - +// newTestHeadDefaultOptions returns the HeadOptions that should be used by default in unit tests. +func newTestHeadDefaultOptions(chunkRange int64, oooEnabled bool) *HeadOptions { opts := DefaultHeadOptions() opts.ChunkRange = chunkRange - opts.ChunkDirRoot = dir opts.EnableExemplarStorage = true opts.MaxExemplars.Store(config.DefaultExemplarsConfig.MaxExemplars) opts.EnableNativeHistograms.Store(true) if oooEnabled { opts.OutOfOrderTimeWindow.Store(10 * time.Minute.Milliseconds()) } + return opts +} + +func newTestHead(t testing.TB, chunkRange int64, compressWAL wlog.CompressionType, oooEnabled bool) (*Head, *wlog.WL) { + return newTestHeadWithOptions(t, compressWAL, newTestHeadDefaultOptions(chunkRange, oooEnabled)) +} + +func newTestHeadWithOptions(t testing.TB, compressWAL wlog.CompressionType, opts *HeadOptions) (*Head, *wlog.WL) { + dir := t.TempDir() + wal, err := wlog.NewSize(nil, nil, filepath.Join(dir, "wal"), 32768, compressWAL) + require.NoError(t, err) + + // Override the chunks dir with the testing one. + opts.ChunkDirRoot = dir h, err := NewHead(nil, nil, wal, nil, opts, nil) require.NoError(t, err) @@ -342,7 +352,7 @@ func BenchmarkLoadWLs(b *testing.B) { } for k := 0; k < c.batches*c.seriesPerBatch; k++ { // Create one mmapped chunk per series, with one sample at the given time. - s := newMemSeries(labels.Labels{}, chunks.HeadSeriesRef(k)*101, defaultIsolationDisabled) + s := newMemSeries(labels.Labels{}, chunks.HeadSeriesRef(k)*101, 0, defaultIsolationDisabled) s.append(c.mmappedChunkT, 42, 0, cOpts) // There's only one head chunk because only a single sample is appended. mmapChunks() // ignores the latest chunk, so we need to cut a new head chunk to guarantee the chunk with @@ -912,7 +922,7 @@ func TestMemSeries_truncateChunks(t *testing.T) { }, } - s := newMemSeries(labels.FromStrings("a", "b"), 1, defaultIsolationDisabled) + s := newMemSeries(labels.FromStrings("a", "b"), 1, 0, defaultIsolationDisabled) for i := 0; i < 4000; i += 5 { ok, _ := s.append(int64(i), float64(i), 0, cOpts) @@ -1053,7 +1063,7 @@ func TestMemSeries_truncateChunks_scenarios(t *testing.T) { require.NoError(t, chunkDiskMapper.Close()) }() - series := newMemSeries(labels.EmptyLabels(), 1, true) + series := newMemSeries(labels.EmptyLabels(), 1, 0, true) cOpts := chunkOpts{ chunkDiskMapper: chunkDiskMapper, @@ -1631,7 +1641,7 @@ func TestMemSeries_append(t *testing.T) { samplesPerChunk: DefaultSamplesPerChunk, } - s := newMemSeries(labels.Labels{}, 1, defaultIsolationDisabled) + s := newMemSeries(labels.Labels{}, 1, 0, defaultIsolationDisabled) // Add first two samples at the very end of a chunk range and the next two // on and after it. @@ -1692,7 +1702,7 @@ func TestMemSeries_appendHistogram(t *testing.T) { samplesPerChunk: DefaultSamplesPerChunk, } - s := newMemSeries(labels.Labels{}, 1, defaultIsolationDisabled) + s := newMemSeries(labels.Labels{}, 1, 0, defaultIsolationDisabled) histograms := tsdbutil.GenerateTestHistograms(4) histogramWithOneMoreBucket := histograms[3].Copy() @@ -1754,7 +1764,7 @@ func TestMemSeries_append_atVariableRate(t *testing.T) { samplesPerChunk: samplesPerChunk, } - s := newMemSeries(labels.Labels{}, 1, defaultIsolationDisabled) + s := newMemSeries(labels.Labels{}, 1, 0, defaultIsolationDisabled) // At this slow rate, we will fill the chunk in two block durations. slowRate := (DefaultBlockDuration * 2) / samplesPerChunk @@ -2900,6 +2910,71 @@ func TestHeadLabelNamesWithMatchers(t *testing.T) { } } +func TestHeadShardedPostings(t *testing.T) { + headOpts := newTestHeadDefaultOptions(1000, false) + headOpts.EnableSharding = true + head, _ := newTestHeadWithOptions(t, wlog.CompressionNone, headOpts) + defer func() { + require.NoError(t, head.Close()) + }() + + ctx := context.Background() + + // Append some series. + app := head.Appender(ctx) + for i := 0; i < 100; i++ { + _, err := app.Append(0, labels.FromStrings("unique", fmt.Sprintf("value%d", i), "const", "1"), 100, 0) + require.NoError(t, err) + } + require.NoError(t, app.Commit()) + + ir := head.indexRange(0, 200) + + // List all postings for a given label value. This is what we expect to get + // in output from all shards. + p, err := ir.Postings(ctx, "const", "1") + require.NoError(t, err) + + var expected []storage.SeriesRef + for p.Next() { + expected = append(expected, p.At()) + } + require.NoError(t, p.Err()) + require.NotEmpty(t, expected) + + // Query the same postings for each shard. + const shardCount = uint64(4) + actualShards := make(map[uint64][]storage.SeriesRef) + actualPostings := make([]storage.SeriesRef, 0, len(expected)) + + for shardIndex := uint64(0); shardIndex < shardCount; shardIndex++ { + p, err = ir.Postings(ctx, "const", "1") + require.NoError(t, err) + + p = ir.ShardedPostings(p, shardIndex, shardCount) + for p.Next() { + ref := p.At() + + actualShards[shardIndex] = append(actualShards[shardIndex], ref) + actualPostings = append(actualPostings, ref) + } + require.NoError(t, p.Err()) + } + + // We expect the postings merged out of shards is the exact same of the non sharded ones. + require.ElementsMatch(t, expected, actualPostings) + + // We expect the series in each shard are the expected ones. + for shardIndex, ids := range actualShards { + for _, id := range ids { + var lbls labels.ScratchBuilder + + require.NoError(t, ir.Series(id, &lbls, nil)) + require.Equal(t, shardIndex, labels.StableHash(lbls.Labels())%shardCount) + } + } +} + func TestErrReuseAppender(t *testing.T) { head, _ := newTestHead(t, 1000, wlog.CompressionNone, false) defer func() { @@ -3038,7 +3113,7 @@ func TestIteratorSeekIntoBuffer(t *testing.T) { samplesPerChunk: DefaultSamplesPerChunk, } - s := newMemSeries(labels.Labels{}, 1, defaultIsolationDisabled) + s := newMemSeries(labels.Labels{}, 1, 0, defaultIsolationDisabled) for i := 0; i < 7; i++ { ok, _ := s.append(int64(i), float64(i), 0, cOpts) diff --git a/tsdb/index/index.go b/tsdb/index/index.go index c2ca581f7c..36b8878bc4 100644 --- a/tsdb/index/index.go +++ b/tsdb/index/index.go @@ -1744,6 +1744,33 @@ func (r *Reader) SortedPostings(p Postings) Postings { return p } +// ShardedPostings returns a postings list filtered by the provided shardIndex out of shardCount. +func (r *Reader) ShardedPostings(p Postings, shardIndex, shardCount uint64) Postings { + var ( + out = make([]storage.SeriesRef, 0, 128) + bufLbls = labels.ScratchBuilder{} + ) + + for p.Next() { + id := p.At() + + // Get the series labels (no chunks). + err := r.Series(id, &bufLbls, nil) + if err != nil { + return ErrPostings(fmt.Errorf("series %d not found", id)) + } + + // Check if the series belong to the shard. + if labels.StableHash(bufLbls.Labels())%shardCount != shardIndex { + continue + } + + out = append(out, id) + } + + return NewListPostings(out) +} + // Size returns the size of an index file. func (r *Reader) Size() int64 { return int64(r.b.Len()) @@ -1864,9 +1891,12 @@ func (dec *Decoder) LabelValueFor(ctx context.Context, b []byte, label string) ( // Series decodes a series entry from the given byte slice into builder and chks. // Previous contents of builder can be overwritten - make sure you copy before retaining. +// Skips reading chunks metadata if chks is nil. func (dec *Decoder) Series(b []byte, builder *labels.ScratchBuilder, chks *[]chunks.Meta) error { builder.Reset() - *chks = (*chks)[:0] + if chks != nil { + *chks = (*chks)[:0] + } d := encoding.Decbuf{B: b} @@ -1892,6 +1922,11 @@ func (dec *Decoder) Series(b []byte, builder *labels.ScratchBuilder, chks *[]chu builder.Add(ln, lv) } + // Skip reading chunks metadata if chks is nil. + if chks == nil { + return d.Err() + } + // Read the chunks meta data. k = d.Uvarint() diff --git a/tsdb/index/index_test.go b/tsdb/index/index_test.go index 369d337384..ef88870355 100644 --- a/tsdb/index/index_test.go +++ b/tsdb/index/index_test.go @@ -242,6 +242,58 @@ func TestIndexRW_Postings(t *testing.T) { }, labelIndices) require.NoError(t, ir.Close()) + + t.Run("ShardedPostings()", func(t *testing.T) { + ir, err := NewFileReader(fn) + require.NoError(t, err) + t.Cleanup(func() { + require.NoError(t, ir.Close()) + }) + + // List all postings for a given label value. This is what we expect to get + // in output from all shards. + p, err = ir.Postings(ctx, "a", "1") + require.NoError(t, err) + + var expected []storage.SeriesRef + for p.Next() { + expected = append(expected, p.At()) + } + require.NoError(t, p.Err()) + require.NotEmpty(t, expected) + + // Query the same postings for each shard. + const shardCount = uint64(4) + actualShards := make(map[uint64][]storage.SeriesRef) + actualPostings := make([]storage.SeriesRef, 0, len(expected)) + + for shardIndex := uint64(0); shardIndex < shardCount; shardIndex++ { + p, err = ir.Postings(ctx, "a", "1") + require.NoError(t, err) + + p = ir.ShardedPostings(p, shardIndex, shardCount) + for p.Next() { + ref := p.At() + + actualShards[shardIndex] = append(actualShards[shardIndex], ref) + actualPostings = append(actualPostings, ref) + } + require.NoError(t, p.Err()) + } + + // We expect the postings merged out of shards is the exact same of the non sharded ones. + require.ElementsMatch(t, expected, actualPostings) + + // We expect the series in each shard are the expected ones. + for shardIndex, ids := range actualShards { + for _, id := range ids { + var lbls labels.ScratchBuilder + + require.NoError(t, ir.Series(id, &lbls, nil)) + require.Equal(t, shardIndex, labels.StableHash(lbls.Labels())%shardCount) + } + } + }) } func TestPostingsMany(t *testing.T) { @@ -565,6 +617,55 @@ func TestSymbols(t *testing.T) { require.NoError(t, iter.Err()) } +func BenchmarkReader_ShardedPostings(b *testing.B) { + const ( + numSeries = 10000 + numShards = 16 + ) + + dir, err := os.MkdirTemp("", "benchmark_reader_sharded_postings") + require.NoError(b, err) + defer func() { + require.NoError(b, os.RemoveAll(dir)) + }() + + ctx := context.Background() + + // Generate an index. + fn := filepath.Join(dir, indexFilename) + + iw, err := NewWriter(ctx, fn) + require.NoError(b, err) + + for i := 1; i <= numSeries; i++ { + require.NoError(b, iw.AddSymbol(fmt.Sprintf("%10d", i))) + } + require.NoError(b, iw.AddSymbol("const")) + require.NoError(b, iw.AddSymbol("unique")) + + for i := 1; i <= numSeries; i++ { + require.NoError(b, iw.AddSeries(storage.SeriesRef(i), + labels.FromStrings("const", fmt.Sprintf("%10d", 1), "unique", fmt.Sprintf("%10d", i)))) + } + + require.NoError(b, iw.Close()) + + b.ResetTimer() + + // Create a reader to read back all postings from the index. + ir, err := NewFileReader(fn) + require.NoError(b, err) + + b.ResetTimer() + + for n := 0; n < b.N; n++ { + allPostings, err := ir.Postings(ctx, "const", fmt.Sprintf("%10d", 1)) + require.NoError(b, err) + + ir.ShardedPostings(allPostings, uint64(n%numShards), numShards) + } +} + func TestDecoder_Postings_WrongInput(t *testing.T) { _, _, err := (&Decoder{}).Postings([]byte("the cake is a lie")) require.Error(t, err) diff --git a/tsdb/ooo_head_read.go b/tsdb/ooo_head_read.go index 440130f7db..3a801bf98b 100644 --- a/tsdb/ooo_head_read.go +++ b/tsdb/ooo_head_read.go @@ -440,6 +440,10 @@ func (ir *OOOCompactionHeadIndexReader) SortedPostings(p index.Postings) index.P return p } +func (ir *OOOCompactionHeadIndexReader) ShardedPostings(p index.Postings, shardIndex, shardCount uint64) index.Postings { + return ir.ch.oooIR.ShardedPostings(p, shardIndex, shardCount) +} + func (ir *OOOCompactionHeadIndexReader) Series(ref storage.SeriesRef, builder *labels.ScratchBuilder, chks *[]chunks.Meta) error { return ir.ch.oooIR.series(ref, builder, chks, 0, ir.ch.lastMmapRef) } diff --git a/tsdb/querier.go b/tsdb/querier.go index a692c98f1a..ab2c53d70d 100644 --- a/tsdb/querier.go +++ b/tsdb/querier.go @@ -131,11 +131,15 @@ func (q *blockQuerier) Select(ctx context.Context, sortSeries bool, hints *stora mint := q.mint maxt := q.maxt disableTrimming := false + sharded := hints != nil && hints.ShardCount > 0 p, err := PostingsForMatchers(ctx, q.index, ms...) if err != nil { return storage.ErrSeriesSet(err) } + if sharded { + p = q.index.ShardedPostings(p, hints.ShardIndex, hints.ShardCount) + } if sortSeries { p = q.index.SortedPostings(p) } @@ -171,6 +175,8 @@ func (q *blockChunkQuerier) Select(ctx context.Context, sortSeries bool, hints * mint := q.mint maxt := q.maxt disableTrimming := false + sharded := hints != nil && hints.ShardCount > 0 + if hints != nil { mint = hints.Start maxt = hints.End @@ -180,6 +186,9 @@ func (q *blockChunkQuerier) Select(ctx context.Context, sortSeries bool, hints * if err != nil { return storage.ErrChunkSeriesSet(err) } + if sharded { + p = q.index.ShardedPostings(p, hints.ShardIndex, hints.ShardCount) + } if sortSeries { p = q.index.SortedPostings(p) } diff --git a/tsdb/querier_test.go b/tsdb/querier_test.go index fcedc54621..c2bd717082 100644 --- a/tsdb/querier_test.go +++ b/tsdb/querier_test.go @@ -2326,6 +2326,27 @@ func (m mockIndex) SortedPostings(p index.Postings) index.Postings { return index.NewListPostings(ep) } +func (m mockIndex) ShardedPostings(p index.Postings, shardIndex, shardCount uint64) index.Postings { + out := make([]storage.SeriesRef, 0, 128) + + for p.Next() { + ref := p.At() + s, ok := m.series[ref] + if !ok { + continue + } + + // Check if the series belong to the shard. + if s.l.Hash()%shardCount != shardIndex { + continue + } + + out = append(out, ref) + } + + return index.NewListPostings(out) +} + func (m mockIndex) Series(ref storage.SeriesRef, builder *labels.ScratchBuilder, chks *[]chunks.Meta) error { s, ok := m.series[ref] if !ok { @@ -3272,6 +3293,10 @@ func (m mockMatcherIndex) SortedPostings(p index.Postings) index.Postings { return index.EmptyPostings() } +func (m mockMatcherIndex) ShardedPostings(ps index.Postings, shardIndex, shardCount uint64) index.Postings { + return ps +} + func (m mockMatcherIndex) Series(ref storage.SeriesRef, builder *labels.ScratchBuilder, chks *[]chunks.Meta) error { return nil } From d9483bb77ca811393d697b7e31d9137bb8f967f6 Mon Sep 17 00:00:00 2001 From: Bryan Boreham Date: Mon, 29 Jan 2024 12:52:03 +0000 Subject: [PATCH 044/237] storage/remote: add BenchmarkStoreSeries Signed-off-by: Bryan Boreham --- storage/remote/queue_manager_test.go | 90 ++++++++++++++++++++++------ 1 file changed, 72 insertions(+), 18 deletions(-) diff --git a/storage/remote/queue_manager_test.go b/storage/remote/queue_manager_test.go index 778861e2b7..cd3c39d9db 100644 --- a/storage/remote/queue_manager_test.go +++ b/storage/remote/queue_manager_test.go @@ -38,6 +38,7 @@ import ( "github.com/prometheus/prometheus/config" "github.com/prometheus/prometheus/model/histogram" "github.com/prometheus/prometheus/model/labels" + "github.com/prometheus/prometheus/model/relabel" "github.com/prometheus/prometheus/model/timestamp" "github.com/prometheus/prometheus/prompb" "github.com/prometheus/prometheus/scrape" @@ -868,29 +869,30 @@ func (c *NopWriteClient) Store(context.Context, []byte, int) error { return nil func (c *NopWriteClient) Name() string { return "nopwriteclient" } func (c *NopWriteClient) Endpoint() string { return "http://test-remote.com/1234" } +// Extra labels to make a more realistic workload - taken from Kubernetes' embedded cAdvisor metrics. +var extraLabels []labels.Label = []labels.Label{ + {Name: "kubernetes_io_arch", Value: "amd64"}, + {Name: "kubernetes_io_instance_type", Value: "c3.somesize"}, + {Name: "kubernetes_io_os", Value: "linux"}, + {Name: "container_name", Value: "some-name"}, + {Name: "failure_domain_kubernetes_io_region", Value: "somewhere-1"}, + {Name: "failure_domain_kubernetes_io_zone", Value: "somewhere-1b"}, + {Name: "id", Value: "/kubepods/burstable/pod6e91c467-e4c5-11e7-ace3-0a97ed59c75e/a3c8498918bd6866349fed5a6f8c643b77c91836427fb6327913276ebc6bde28"}, + {Name: "image", Value: "registry/organisation/name@sha256:dca3d877a80008b45d71d7edc4fd2e44c0c8c8e7102ba5cbabec63a374d1d506"}, + {Name: "instance", Value: "ip-111-11-1-11.ec2.internal"}, + {Name: "job", Value: "kubernetes-cadvisor"}, + {Name: "kubernetes_io_hostname", Value: "ip-111-11-1-11"}, + {Name: "monitor", Value: "prod"}, + {Name: "name", Value: "k8s_some-name_some-other-name-5j8s8_kube-system_6e91c467-e4c5-11e7-ace3-0a97ed59c75e_0"}, + {Name: "namespace", Value: "kube-system"}, + {Name: "pod_name", Value: "some-other-name-5j8s8"}, +} + func BenchmarkSampleSend(b *testing.B) { // Send one sample per series, which is the typical remote_write case const numSamples = 1 const numSeries = 10000 - // Extra labels to make a more realistic workload - taken from Kubernetes' embedded cAdvisor metrics. - extraLabels := []labels.Label{ - {Name: "kubernetes_io_arch", Value: "amd64"}, - {Name: "kubernetes_io_instance_type", Value: "c3.somesize"}, - {Name: "kubernetes_io_os", Value: "linux"}, - {Name: "container_name", Value: "some-name"}, - {Name: "failure_domain_kubernetes_io_region", Value: "somewhere-1"}, - {Name: "failure_domain_kubernetes_io_zone", Value: "somewhere-1b"}, - {Name: "id", Value: "/kubepods/burstable/pod6e91c467-e4c5-11e7-ace3-0a97ed59c75e/a3c8498918bd6866349fed5a6f8c643b77c91836427fb6327913276ebc6bde28"}, - {Name: "image", Value: "registry/organisation/name@sha256:dca3d877a80008b45d71d7edc4fd2e44c0c8c8e7102ba5cbabec63a374d1d506"}, - {Name: "instance", Value: "ip-111-11-1-11.ec2.internal"}, - {Name: "job", Value: "kubernetes-cadvisor"}, - {Name: "kubernetes_io_hostname", Value: "ip-111-11-1-11"}, - {Name: "monitor", Value: "prod"}, - {Name: "name", Value: "k8s_some-name_some-other-name-5j8s8_kube-system_6e91c467-e4c5-11e7-ace3-0a97ed59c75e_0"}, - {Name: "namespace", Value: "kube-system"}, - {Name: "pod_name", Value: "some-other-name-5j8s8"}, - } samples, series := createTimeseries(numSamples, numSeries, extraLabels...) c := NewNopWriteClient() @@ -921,6 +923,58 @@ func BenchmarkSampleSend(b *testing.B) { b.StopTimer() } +// Check how long it takes to add N series, including external labels processing. +func BenchmarkStoreSeries(b *testing.B) { + externalLabels := []labels.Label{ + {Name: "cluster", Value: "mycluster"}, + {Name: "replica", Value: "1"}, + } + relabelConfigs := []*relabel.Config{{ + SourceLabels: model.LabelNames{"namespace"}, + Separator: ";", + Regex: relabel.MustNewRegexp("kube.*"), + TargetLabel: "job", + Replacement: "$1", + Action: relabel.Replace, + }} + testCases := []struct { + name string + externalLabels []labels.Label + ts []prompb.TimeSeries + relabelConfigs []*relabel.Config + }{ + {name: "plain"}, + {name: "externalLabels", externalLabels: externalLabels}, + {name: "relabel", relabelConfigs: relabelConfigs}, + { + name: "externalLabels+relabel", + externalLabels: externalLabels, + relabelConfigs: relabelConfigs, + }, + } + + // numSeries chosen to be big enough that StoreSeries dominates creating a new queue manager. + const numSeries = 1000 + _, series := createTimeseries(0, numSeries, extraLabels...) + + for _, tc := range testCases { + b.Run(tc.name, func(b *testing.B) { + for i := 0; i < b.N; i++ { + c := NewTestWriteClient() + dir := b.TempDir() + cfg := config.DefaultQueueConfig + mcfg := config.DefaultMetadataConfig + metrics := newQueueManagerMetrics(nil, "", "") + m := NewQueueManager(metrics, nil, nil, nil, dir, newEWMARate(ewmaWeight, shardUpdateDuration), cfg, mcfg, labels.EmptyLabels(), nil, c, defaultFlushDeadline, newPool(), newHighestTimestampMetric(), nil, false, false) + m.externalLabels = tc.externalLabels + m.relabelConfigs = tc.relabelConfigs + + m.StoreSeries(series, 0) + } + }) + } +} + func BenchmarkStartup(b *testing.B) { dir := os.Getenv("WALDIR") if dir == "" { From dcd024a095f52e5a58dfc2d6701cc813d06c6594 Mon Sep 17 00:00:00 2001 From: Bryan Boreham Date: Mon, 29 Jan 2024 18:49:55 +0000 Subject: [PATCH 045/237] storage/remote: speed up StoreSeries by re-using labels.Builder Relabeling can take a pre-populated `Builder` instead of making a new one every time. This is much more efficient. Signed-off-by: Bryan Boreham --- storage/remote/queue_manager.go | 40 ++++++++++------------------ storage/remote/queue_manager_test.go | 7 +++-- 2 files changed, 19 insertions(+), 28 deletions(-) diff --git a/storage/remote/queue_manager.go b/storage/remote/queue_manager.go index e37ec8c705..4b0c72cf60 100644 --- a/storage/remote/queue_manager.go +++ b/storage/remote/queue_manager.go @@ -413,9 +413,10 @@ type QueueManager struct { clientMtx sync.RWMutex storeClient WriteClient - seriesMtx sync.Mutex // Covers seriesLabels and droppedSeries. + seriesMtx sync.Mutex // Covers seriesLabels, droppedSeries and builder. seriesLabels map[chunks.HeadSeriesRef]labels.Labels droppedSeries map[chunks.HeadSeriesRef]struct{} + builder *labels.Builder seriesSegmentMtx sync.Mutex // Covers seriesSegmentIndexes - if you also lock seriesMtx, take seriesMtx first. seriesSegmentIndexes map[chunks.HeadSeriesRef]int @@ -482,6 +483,7 @@ func NewQueueManager( seriesLabels: make(map[chunks.HeadSeriesRef]labels.Labels), seriesSegmentIndexes: make(map[chunks.HeadSeriesRef]int), droppedSeries: make(map[chunks.HeadSeriesRef]struct{}), + builder: labels.NewBuilder(labels.EmptyLabels()), numShards: cfg.MinShards, reshardChan: make(chan int), @@ -897,12 +899,14 @@ func (t *QueueManager) StoreSeries(series []record.RefSeries, index int) { // Just make sure all the Refs of Series will insert into seriesSegmentIndexes map for tracking. t.seriesSegmentIndexes[s.Ref] = index - ls := processExternalLabels(s.Labels, t.externalLabels) - lbls, keep := relabel.Process(ls, t.relabelConfigs...) - if !keep || lbls.IsEmpty() { + t.builder.Reset(s.Labels) + processExternalLabels(t.builder, t.externalLabels) + keep := relabel.ProcessBuilder(t.builder, t.relabelConfigs...) + if !keep { t.droppedSeries[s.Ref] = struct{}{} continue } + lbls := t.builder.Labels() t.internLabels(lbls) // We should not ever be replacing a series labels in the map, but just @@ -967,30 +971,14 @@ func (t *QueueManager) releaseLabels(ls labels.Labels) { ls.ReleaseStrings(t.interner.release) } -// processExternalLabels merges externalLabels into ls. If ls contains -// a label in externalLabels, the value in ls wins. -func processExternalLabels(ls labels.Labels, externalLabels []labels.Label) labels.Labels { - if len(externalLabels) == 0 { - return ls - } - - b := labels.NewScratchBuilder(ls.Len() + len(externalLabels)) - j := 0 - ls.Range(func(l labels.Label) { - for j < len(externalLabels) && l.Name > externalLabels[j].Name { - b.Add(externalLabels[j].Name, externalLabels[j].Value) - j++ +// processExternalLabels merges externalLabels into b. If b contains +// a label in externalLabels, the value in b wins. +func processExternalLabels(b *labels.Builder, externalLabels []labels.Label) { + for _, el := range externalLabels { + if b.Get(el.Name) == "" { + b.Set(el.Name, el.Value) } - if j < len(externalLabels) && l.Name == externalLabels[j].Name { - j++ - } - b.Add(l.Name, l.Value) - }) - for ; j < len(externalLabels); j++ { - b.Add(externalLabels[j].Name, externalLabels[j].Value) } - - return b.Labels() } func (t *QueueManager) updateShardsLoop() { diff --git a/storage/remote/queue_manager_test.go b/storage/remote/queue_manager_test.go index cd3c39d9db..8a6f68208b 100644 --- a/storage/remote/queue_manager_test.go +++ b/storage/remote/queue_manager_test.go @@ -1013,7 +1013,8 @@ func BenchmarkStartup(b *testing.B) { } func TestProcessExternalLabels(t *testing.T) { - for _, tc := range []struct { + b := labels.NewBuilder(labels.EmptyLabels()) + for i, tc := range []struct { labels labels.Labels externalLabels []labels.Label expected labels.Labels @@ -1074,7 +1075,9 @@ func TestProcessExternalLabels(t *testing.T) { expected: labels.FromStrings("a", "b", "c", "d", "e", "f"), }, } { - require.Equal(t, tc.expected, processExternalLabels(tc.labels, tc.externalLabels)) + b.Reset(tc.labels) + processExternalLabels(b, tc.externalLabels) + require.Equal(t, tc.expected, b.Labels(), "test %d", i) } } From 14b4fbc2ff5aaad23937ca2a620b7aa8de24bdad Mon Sep 17 00:00:00 2001 From: Bryan Boreham Date: Mon, 29 Jan 2024 18:54:54 +0000 Subject: [PATCH 046/237] labels: make InternStrings a no-op for stringlabels version The current implementation of `InternStrings` will only save memory when the whole set of labels is identical to one already seen, and this cannot happen in the one place it is called from in Prometheus, remote-write, which already detects identical series. Signed-off-by: Bryan Boreham --- model/labels/labels_stringlabels.go | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/model/labels/labels_stringlabels.go b/model/labels/labels_stringlabels.go index f53c3b2d05..3e0488bd07 100644 --- a/model/labels/labels_stringlabels.go +++ b/model/labels/labels_stringlabels.go @@ -450,14 +450,12 @@ func (ls Labels) DropMetricName() Labels { return ls } -// InternStrings calls intern on every string value inside ls, replacing them with what it returns. +// InternStrings is a no-op because it would only save when the whole set of labels is identical. func (ls *Labels) InternStrings(intern func(string) string) { - ls.data = intern(ls.data) } -// ReleaseStrings calls release on every string value inside ls. +// ReleaseStrings is a no-op for the same reason as InternStrings. func (ls Labels) ReleaseStrings(release func(string)) { - release(ls.data) } // Labels returns the labels from the builder. From 5b5230deb7d58214af496de7df3c74e023606aab Mon Sep 17 00:00:00 2001 From: Craig Peterson <192540+captncraig@users.noreply.github.com> Date: Mon, 29 Jan 2024 22:49:40 -0500 Subject: [PATCH 047/237] remote client: apply custom headers before sigv4 transport Signed-off-by: Craig Peterson <192540+captncraig@users.noreply.github.com> --- storage/remote/client.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/storage/remote/client.go b/storage/remote/client.go index fbb6804983..e765b47c3e 100644 --- a/storage/remote/client.go +++ b/storage/remote/client.go @@ -141,24 +141,24 @@ func NewWriteClient(name string, conf *ClientConfig) (WriteClient, error) { } t := httpClient.Transport + if len(conf.Headers) > 0 { + t = newInjectHeadersRoundTripper(conf.Headers, t) + } + if conf.SigV4Config != nil { - t, err = sigv4.NewSigV4RoundTripper(conf.SigV4Config, httpClient.Transport) + t, err = sigv4.NewSigV4RoundTripper(conf.SigV4Config, t) if err != nil { return nil, err } } if conf.AzureADConfig != nil { - t, err = azuread.NewAzureADRoundTripper(conf.AzureADConfig, httpClient.Transport) + t, err = azuread.NewAzureADRoundTripper(conf.AzureADConfig, t) if err != nil { return nil, err } } - if len(conf.Headers) > 0 { - t = newInjectHeadersRoundTripper(conf.Headers, t) - } - httpClient.Transport = otelhttp.NewTransport(t) return &Client{ From b9fdf3dad15748a2f17663dc479a9d25eb7e876b Mon Sep 17 00:00:00 2001 From: Bryan Boreham Date: Tue, 30 Jan 2024 16:48:01 +0000 Subject: [PATCH 048/237] storage/remote: document why two benchmarks are skipped One was silently doing nothing; one was doing something but the work didn't go up linearly with iteration count. Signed-off-by: Bryan Boreham --- storage/remote/queue_manager_test.go | 2 +- storage/remote/write_handler_test.go | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/storage/remote/queue_manager_test.go b/storage/remote/queue_manager_test.go index 778861e2b7..5a96006298 100644 --- a/storage/remote/queue_manager_test.go +++ b/storage/remote/queue_manager_test.go @@ -924,7 +924,7 @@ func BenchmarkSampleSend(b *testing.B) { func BenchmarkStartup(b *testing.B) { dir := os.Getenv("WALDIR") if dir == "" { - return + b.Skip("WALDIR env var not set") } // Find the second largest segment; we will replay up to this. diff --git a/storage/remote/write_handler_test.go b/storage/remote/write_handler_test.go index 9f7dcd175d..df92dc6bcc 100644 --- a/storage/remote/write_handler_test.go +++ b/storage/remote/write_handler_test.go @@ -204,6 +204,7 @@ func TestCommitErr(t *testing.T) { } func BenchmarkRemoteWriteOOOSamples(b *testing.B) { + b.Skip("Not a valid benchmark (does not count to b.N)") dir := b.TempDir() opts := tsdb.DefaultOptions() From 839b9e5b5300f2a44c50714ede9b1423e875d6bb Mon Sep 17 00:00:00 2001 From: Will Bollock Date: Wed, 31 Jan 2024 09:02:49 -0500 Subject: [PATCH 049/237] fix: PrometheusNotIngestingSamples label matching This alert will never return anything as the left side of the query has the labels `[component, environment, instance, job, type]` while the right side has `[component, environment, instance, job]`. The `type` label was added to `prometheus_tsdb_head_samples_appended_total` in this PR but the mixin wasn't updated for the new label: https://github.com/prometheus/prometheus/pull/11395 This was found with [pint](https://github.com/cloudflare/pint) PromQL linting Signed-off-by: Will Bollock --- documentation/prometheus-mixin/alerts.libsonnet | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/prometheus-mixin/alerts.libsonnet b/documentation/prometheus-mixin/alerts.libsonnet index 3efb0f27d1..508d89c244 100644 --- a/documentation/prometheus-mixin/alerts.libsonnet +++ b/documentation/prometheus-mixin/alerts.libsonnet @@ -122,7 +122,7 @@ alert: 'PrometheusNotIngestingSamples', expr: ||| ( - rate(prometheus_tsdb_head_samples_appended_total{%(prometheusSelector)s}[5m]) <= 0 + sum without(type) (rate(prometheus_tsdb_head_samples_appended_total{%(prometheusSelector)s}[5m])) <= 0 and ( sum without(scrape_job) (prometheus_target_metadata_cache_entries{%(prometheusSelector)s}) > 0 From 5961f781861fbd91775fea212dc57a5d4a190512 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Szulik?= Date: Thu, 2 Sep 2021 17:43:54 +0200 Subject: [PATCH 050/237] Refactor tsdb tests to use testify. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Paweł Szulik --- tsdb/db_test.go | 29 ++++++++----------- tsdb/fileutil/flock_test.go | 55 +++++++++++++------------------------ tsdb/head_test.go | 8 ++---- tsdb/index/postings_test.go | 28 +++++-------------- tsdb/querier_test.go | 25 ++--------------- tsdb/wal_test.go | 2 +- tsdb/wlog/reader_test.go | 12 ++++---- tsdb/wlog/wlog_test.go | 4 +-- 8 files changed, 50 insertions(+), 113 deletions(-) diff --git a/tsdb/db_test.go b/tsdb/db_test.go index 9e543ed500..fb36bbb868 100644 --- a/tsdb/db_test.go +++ b/tsdb/db_test.go @@ -27,7 +27,6 @@ import ( "path/filepath" "sort" "strconv" - "strings" "sync" "testing" "time" @@ -2327,9 +2326,7 @@ func TestBlockRanges(t *testing.T) { app := db.Appender(ctx) lbl := labels.FromStrings("a", "b") _, err = app.Append(0, lbl, firstBlockMaxT-1, rand.Float64()) - if err == nil { - t.Fatalf("appending a sample with a timestamp covered by a previous block shouldn't be possible") - } + require.Error(t, err, "appending a sample with a timestamp covered by a previous block shouldn't be possible") _, err = app.Append(0, lbl, firstBlockMaxT+1, rand.Float64()) require.NoError(t, err) _, err = app.Append(0, lbl, firstBlockMaxT+2, rand.Float64()) @@ -2347,9 +2344,8 @@ func TestBlockRanges(t *testing.T) { } require.Len(t, db.Blocks(), 2, "no new block created after the set timeout") - if db.Blocks()[0].Meta().MaxTime > db.Blocks()[1].Meta().MinTime { - t.Fatalf("new block overlaps old:%v,new:%v", db.Blocks()[0].Meta(), db.Blocks()[1].Meta()) - } + require.LessOrEqual(t, db.Blocks()[1].Meta().MinTime, db.Blocks()[0].Meta().MaxTime, + "new block overlaps old:%v,new:%v", db.Blocks()[0].Meta(), db.Blocks()[1].Meta()) // Test that wal records are skipped when an existing block covers the same time ranges // and compaction doesn't create an overlapping block. @@ -2389,9 +2385,8 @@ func TestBlockRanges(t *testing.T) { require.Len(t, db.Blocks(), 4, "no new block created after the set timeout") - if db.Blocks()[2].Meta().MaxTime > db.Blocks()[3].Meta().MinTime { - t.Fatalf("new block overlaps old:%v,new:%v", db.Blocks()[2].Meta(), db.Blocks()[3].Meta()) - } + require.LessOrEqual(t, db.Blocks()[3].Meta().MinTime, db.Blocks()[2].Meta().MaxTime, + "new block overlaps old:%v,new:%v", db.Blocks()[2].Meta(), db.Blocks()[3].Meta()) } // TestDBReadOnly ensures that opening a DB in readonly mode doesn't modify any files on the disk. @@ -3180,9 +3175,8 @@ func TestOpen_VariousBlockStates(t *testing.T) { var loaded int for _, l := range loadedBlocks { - if _, ok := expectedLoadedDirs[filepath.Join(tmpDir, l.meta.ULID.String())]; !ok { - t.Fatal("unexpected block", l.meta.ULID, "was loaded") - } + _, ok := expectedLoadedDirs[filepath.Join(tmpDir, l.meta.ULID.String())] + require.True(t, ok, "unexpected block", l.meta.ULID, "was loaded") loaded++ } require.Len(t, expectedLoadedDirs, loaded) @@ -3193,9 +3187,8 @@ func TestOpen_VariousBlockStates(t *testing.T) { var ignored int for _, f := range files { - if _, ok := expectedRemovedDirs[filepath.Join(tmpDir, f.Name())]; ok { - t.Fatal("expected", filepath.Join(tmpDir, f.Name()), "to be removed, but still exists") - } + _, ok := expectedRemovedDirs[filepath.Join(tmpDir, f.Name())] + require.False(t, ok, "expected", filepath.Join(tmpDir, f.Name()), "to be removed, but still exists") if _, ok := expectedIgnoredDirs[filepath.Join(tmpDir, f.Name())]; ok { ignored++ } @@ -3486,8 +3479,8 @@ func testQuerierShouldNotPanicIfHeadChunkIsTruncatedWhileReadingQueriedChunks(t // the "cannot populate chunk XXX: not found" error occurred. This error can occur // when the iterator tries to fetch an head chunk which has been offloaded because // of the head compaction in the meanwhile. - if firstErr != nil && !strings.Contains(firstErr.Error(), "cannot populate chunk") { - t.Fatalf("unexpected error: %s", firstErr.Error()) + if firstErr != nil { + require.ErrorContains(t, firstErr, "cannot populate chunk", "unexpected error: %s", firstErr.Error()) } } diff --git a/tsdb/fileutil/flock_test.go b/tsdb/fileutil/flock_test.go index f9cbbcf2b3..240e0954b1 100644 --- a/tsdb/fileutil/flock_test.go +++ b/tsdb/fileutil/flock_test.go @@ -18,6 +18,8 @@ import ( "path/filepath" "testing" + "github.com/stretchr/testify/require" + "github.com/prometheus/prometheus/util/testutil" ) @@ -27,54 +29,35 @@ func TestLocking(t *testing.T) { fileName := filepath.Join(dir.Path(), "LOCK") - if _, err := os.Stat(fileName); err == nil { - t.Fatalf("File %q unexpectedly exists.", fileName) - } + _, err := os.Stat(fileName) + require.Error(t, err, "File %q unexpectedly exists.", fileName) lock, existed, err := Flock(fileName) - if err != nil { - t.Fatalf("Error locking file %q: %s", fileName, err) - } - if existed { - t.Errorf("File %q reported as existing during locking.", fileName) - } + require.NoError(t, err, "Error locking file %q: %s", fileName, err) + require.False(t, existed, "File %q reported as existing during locking.", fileName) // File must now exist. - if _, err = os.Stat(fileName); err != nil { - t.Errorf("Could not stat file %q expected to exist: %s", fileName, err) - } + _, err = os.Stat(fileName) + require.NoError(t, err, "Could not stat file %q expected to exist: %s", fileName, err) // Try to lock again. lockedAgain, existed, err := Flock(fileName) - if err == nil { - t.Fatalf("File %q locked twice.", fileName) - } - if lockedAgain != nil { - t.Error("Unsuccessful locking did not return nil.") - } - if !existed { - t.Errorf("Existing file %q not recognized.", fileName) - } + require.Error(t, err, "File %q locked twice.", fileName) + require.Nil(t, lockedAgain, "Unsuccessful locking did not return nil.") + require.True(t, existed, "Existing file %q not recognized.", fileName) - if err := lock.Release(); err != nil { - t.Errorf("Error releasing lock for file %q: %s", fileName, err) - } + err = lock.Release() + require.NoError(t, err, "Error releasing lock for file %q: %s", fileName, err) // File must still exist. - if _, err = os.Stat(fileName); err != nil { - t.Errorf("Could not stat file %q expected to exist: %s", fileName, err) - } + _, err = os.Stat(fileName) + require.NoError(t, err, "Could not stat file %q expected to exist: %s", fileName, err) // Lock existing file. lock, existed, err = Flock(fileName) - if err != nil { - t.Fatalf("Error locking file %q: %s", fileName, err) - } - if !existed { - t.Errorf("Existing file %q not recognized.", fileName) - } + require.NoError(t, err, "Error locking file %q: %s", fileName, err) + require.True(t, existed, "Existing file %q not recognized.", fileName) - if err := lock.Release(); err != nil { - t.Errorf("Error releasing lock for file %q: %s", fileName, err) - } + err = lock.Release() + require.NoError(t, err, "Error releasing lock for file %q: %s", fileName, err) } diff --git a/tsdb/head_test.go b/tsdb/head_test.go index 90e187b58c..cec7c06bb6 100644 --- a/tsdb/head_test.go +++ b/tsdb/head_test.go @@ -206,7 +206,7 @@ func readTestWAL(t testing.TB, dir string) (recs []interface{}) { require.NoError(t, err) recs = append(recs, exemplars) default: - t.Fatalf("unknown record type") + require.Fail(t, "unknown record type") } } require.NoError(t, r.Err()) @@ -1371,7 +1371,7 @@ func TestDeletedSamplesAndSeriesStillInWALAfterCheckpoint(t *testing.T) { case []record.RefMetadata: metadata++ default: - t.Fatalf("unknown record type") + require.Fail(t, "unknown record type") } } require.Equal(t, 1, series) @@ -1620,9 +1620,7 @@ func TestComputeChunkEndTime(t *testing.T) { for testName, tc := range cases { t.Run(testName, func(t *testing.T) { got := computeChunkEndTime(tc.start, tc.cur, tc.max, tc.ratioToFull) - if got != tc.res { - t.Errorf("expected %d for (start: %d, cur: %d, max: %d, ratioToFull: %f), got %d", tc.res, tc.start, tc.cur, tc.max, tc.ratioToFull, got) - } + require.Equal(t, tc.res, got, "(start: %d, cur: %d, max: %d)", tc.start, tc.cur, tc.max) }) } } diff --git a/tsdb/index/postings_test.go b/tsdb/index/postings_test.go index e8df6dbd29..9e6bd23f8c 100644 --- a/tsdb/index/postings_test.go +++ b/tsdb/index/postings_test.go @@ -61,9 +61,7 @@ func TestMemPostings_ensureOrder(t *testing.T) { ok := sort.SliceIsSorted(l, func(i, j int) bool { return l[i] < l[j] }) - if !ok { - t.Fatalf("postings list %v is not sorted", l) - } + require.True(t, ok, "postings list %v is not sorted", l) } } } @@ -214,9 +212,7 @@ func TestIntersect(t *testing.T) { for _, c := range cases { t.Run("", func(t *testing.T) { - if c.res == nil { - t.Fatal("intersect result expectancy cannot be nil") - } + require.NotNil(t, c.res, "intersect result expectancy cannot be nil") expected, err := ExpandPostings(c.res) require.NoError(t, err) @@ -228,9 +224,7 @@ func TestIntersect(t *testing.T) { return } - if i == EmptyPostings() { - t.Fatal("intersect unexpected result: EmptyPostings sentinel") - } + require.NotEqual(t, EmptyPostings(), i, "intersect unexpected result: EmptyPostings sentinel") res, err := ExpandPostings(i) require.NoError(t, err) @@ -501,9 +495,7 @@ func TestMergedPostings(t *testing.T) { for _, c := range cases { t.Run("", func(t *testing.T) { - if c.res == nil { - t.Fatal("merge result expectancy cannot be nil") - } + require.NotNil(t, c.res, "merge result expectancy cannot be nil") ctx := context.Background() @@ -517,9 +509,7 @@ func TestMergedPostings(t *testing.T) { return } - if m == EmptyPostings() { - t.Fatal("merge unexpected result: EmptyPostings sentinel") - } + require.NotEqual(t, EmptyPostings(), m, "merge unexpected result: EmptyPostings sentinel") res, err := ExpandPostings(m) require.NoError(t, err) @@ -897,9 +887,7 @@ func TestWithoutPostings(t *testing.T) { for _, c := range cases { t.Run("", func(t *testing.T) { - if c.res == nil { - t.Fatal("without result expectancy cannot be nil") - } + require.NotNil(t, c.res, "without result expectancy cannot be nil") expected, err := ExpandPostings(c.res) require.NoError(t, err) @@ -911,9 +899,7 @@ func TestWithoutPostings(t *testing.T) { return } - if w == EmptyPostings() { - t.Fatal("without unexpected result: EmptyPostings sentinel") - } + require.NotEqual(t, EmptyPostings(), w, "without unexpected result: EmptyPostings sentinel") res, err := ExpandPostings(w) require.NoError(t, err) diff --git a/tsdb/querier_test.go b/tsdb/querier_test.go index c2bd717082..6e1a19ee4b 100644 --- a/tsdb/querier_test.go +++ b/tsdb/querier_test.go @@ -2702,22 +2702,7 @@ func TestFindSetMatches(t *testing.T) { } for _, c := range cases { - matches := findSetMatches(c.pattern) - if len(c.exp) == 0 { - if len(matches) != 0 { - t.Errorf("Evaluating %s, unexpected result %v", c.pattern, matches) - } - } else { - if len(matches) != len(c.exp) { - t.Errorf("Evaluating %s, length of result not equal to exp", c.pattern) - } else { - for i := 0; i < len(c.exp); i++ { - if c.exp[i] != matches[i] { - t.Errorf("Evaluating %s, unexpected result %s", c.pattern, matches[i]) - } - } - } - } + require.Equal(t, c.exp, findSetMatches(c.pattern), "Evaluating %s, unexpected result.", c.pattern) } } @@ -3016,9 +3001,7 @@ func TestPostingsForMatchers(t *testing.T) { } } require.NoError(t, p.Err()) - if len(exp) != 0 { - t.Errorf("Evaluating %v, missing results %+v", c.matchers, exp) - } + require.Empty(t, exp, "Evaluating %v", c.matchers) }) } } @@ -3101,9 +3084,7 @@ func TestClose(t *testing.T) { createBlock(t, dir, genSeries(1, 1, 10, 20)) db, err := Open(dir, nil, nil, DefaultOptions(), nil) - if err != nil { - t.Fatalf("Opening test storage failed: %s", err) - } + require.NoError(t, err, "Opening test storage failed: %s", err) defer func() { require.NoError(t, db.Close()) }() diff --git a/tsdb/wal_test.go b/tsdb/wal_test.go index 9004f5093f..8700a70754 100644 --- a/tsdb/wal_test.go +++ b/tsdb/wal_test.go @@ -528,7 +528,7 @@ func TestMigrateWAL_Fuzz(t *testing.T) { require.NoError(t, err) res = append(res, s) default: - t.Fatalf("unknown record type %d", dec.Type(rec)) + require.Fail(t, "unknown record type %d", dec.Type(rec)) } } require.NoError(t, r.Err()) diff --git a/tsdb/wlog/reader_test.go b/tsdb/wlog/reader_test.go index 0f510e0c1e..1546a97527 100644 --- a/tsdb/wlog/reader_test.go +++ b/tsdb/wlog/reader_test.go @@ -182,16 +182,14 @@ func TestReader(t *testing.T) { t.Logf("record %d", j) rec := r.Record() - if j >= len(c.exp) { - t.Fatal("received more records than expected") - } + require.Less(t, j, len(c.exp), "received more records than expected") require.Equal(t, c.exp[j], rec, "Bytes within record did not match expected Bytes") } - if !c.fail && r.Err() != nil { - t.Fatalf("unexpected error: %s", r.Err()) + if !c.fail { + require.NoError(t, r.Err(), "unexpected error: %s", r.Err()) } - if c.fail && r.Err() == nil { - t.Fatalf("expected error but got none") + if c.fail { + require.Error(t, r.Err(), "expected error but got none") } }) } diff --git a/tsdb/wlog/wlog_test.go b/tsdb/wlog/wlog_test.go index 8f4533e0ea..7d96920117 100644 --- a/tsdb/wlog/wlog_test.go +++ b/tsdb/wlog/wlog_test.go @@ -192,9 +192,7 @@ func TestWALRepair_ReadingError(t *testing.T) { require.Len(t, result, test.intactRecs, "Wrong number of intact records") for i, r := range result { - if !bytes.Equal(records[i], r) { - t.Fatalf("record %d diverges: want %x, got %x", i, records[i][:10], r[:10]) - } + require.True(t, bytes.Equal(records[i], r), "record %d diverges: want %x, got %x", i, records[i][:10], r[:10]) } // Make sure there is a new 0 size Segment after the corrupted Segment. From 7f24efccdb7c3d7ca57ac3c6dedb5697d723b4df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Szulik?= Date: Fri, 3 Sep 2021 15:14:25 +0200 Subject: [PATCH 051/237] Refactor discovery tests to use testify. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Paweł Szulik --- discovery/consul/consul_test.go | 68 ++-- discovery/file/file_test.go | 4 +- discovery/kubernetes/kubernetes_test.go | 10 +- discovery/manager_test.go | 55 +--- discovery/marathon/marathon_test.go | 395 +++++++----------------- discovery/openstack/mock_test.go | 12 +- discovery/refresh/refresh_test.go | 2 +- discovery/zookeeper/zookeeper_test.go | 5 +- 8 files changed, 172 insertions(+), 379 deletions(-) diff --git a/discovery/consul/consul_test.go b/discovery/consul/consul_test.go index 040040ae97..ee55abdc91 100644 --- a/discovery/consul/consul_test.go +++ b/discovery/consul/consul_test.go @@ -56,15 +56,12 @@ func TestConfiguredService(t *testing.T) { metrics := NewTestMetrics(t, conf, prometheus.NewRegistry()) consulDiscovery, err := NewDiscovery(conf, nil, metrics) - if err != nil { - t.Errorf("Unexpected error when initializing discovery %v", err) - } - if !consulDiscovery.shouldWatch("configuredServiceName", []string{""}) { - t.Errorf("Expected service %s to be watched", "configuredServiceName") - } - if consulDiscovery.shouldWatch("nonConfiguredServiceName", []string{""}) { - t.Errorf("Expected service %s to not be watched", "nonConfiguredServiceName") - } + require.NoError(t, err, "when initializing discovery") + require.True(t, consulDiscovery.shouldWatch("configuredServiceName", []string{""}), + "Expected service %s to be watched", "configuredServiceName") + require.False(t, consulDiscovery.shouldWatch("nonConfiguredServiceName", []string{""}), + "Expected service %s to not be watched", "nonConfiguredServiceName") + } func TestConfiguredServiceWithTag(t *testing.T) { @@ -76,21 +73,18 @@ func TestConfiguredServiceWithTag(t *testing.T) { metrics := NewTestMetrics(t, conf, prometheus.NewRegistry()) consulDiscovery, err := NewDiscovery(conf, nil, metrics) - if err != nil { - t.Errorf("Unexpected error when initializing discovery %v", err) - } - if consulDiscovery.shouldWatch("configuredServiceName", []string{""}) { - t.Errorf("Expected service %s to not be watched without tag", "configuredServiceName") - } - if !consulDiscovery.shouldWatch("configuredServiceName", []string{"http"}) { - t.Errorf("Expected service %s to be watched with tag %s", "configuredServiceName", "http") - } - if consulDiscovery.shouldWatch("nonConfiguredServiceName", []string{""}) { - t.Errorf("Expected service %s to not be watched without tag", "nonConfiguredServiceName") - } - if consulDiscovery.shouldWatch("nonConfiguredServiceName", []string{"http"}) { - t.Errorf("Expected service %s to not be watched with tag %s", "nonConfiguredServiceName", "http") - } + require.NoError(t, err, "when initializing discovery") + require.False(t, consulDiscovery.shouldWatch("configuredServiceName", []string{""}), + "Expected service %s to not be watched without tag", "configuredServiceName") + + require.True(t, consulDiscovery.shouldWatch("configuredServiceName", []string{"http"}), + "Expected service %s to be watched with tag %s", "configuredServiceName", "http") + + require.False(t, consulDiscovery.shouldWatch("nonConfiguredServiceName", []string{""}), + "Expected service %s to not be watched without tag", "nonConfiguredServiceName") + + require.False(t, consulDiscovery.shouldWatch("nonConfiguredServiceName", []string{"http"}), + "Expected service %s to not be watched with tag %s", "nonConfiguredServiceName", "http") } func TestConfiguredServiceWithTags(t *testing.T) { @@ -173,13 +167,10 @@ func TestConfiguredServiceWithTags(t *testing.T) { metrics := NewTestMetrics(t, tc.conf, prometheus.NewRegistry()) consulDiscovery, err := NewDiscovery(tc.conf, nil, metrics) - if err != nil { - t.Errorf("Unexpected error when initializing discovery %v", err) - } + require.NoError(t, err, "when initializing discovery") ret := consulDiscovery.shouldWatch(tc.serviceName, tc.serviceTags) - if ret != tc.shouldWatch { - t.Errorf("Expected should watch? %t, got %t. Watched service and tags: %s %+v, input was %s %+v", tc.shouldWatch, ret, tc.conf.Services, tc.conf.ServiceTags, tc.serviceName, tc.serviceTags) - } + require.Equal(t, tc.shouldWatch, ret, "Watched service and tags: %s %+v, input was %s %+v", + tc.conf.Services, tc.conf.ServiceTags, tc.serviceName, tc.serviceTags) } } @@ -189,12 +180,8 @@ func TestNonConfiguredService(t *testing.T) { metrics := NewTestMetrics(t, conf, prometheus.NewRegistry()) consulDiscovery, err := NewDiscovery(conf, nil, metrics) - if err != nil { - t.Errorf("Unexpected error when initializing discovery %v", err) - } - if !consulDiscovery.shouldWatch("nonConfiguredServiceName", []string{""}) { - t.Errorf("Expected service %s to be watched", "nonConfiguredServiceName") - } + require.NoError(t, err, "when initializing discovery") + require.True(t, consulDiscovery.shouldWatch("nonConfiguredServiceName", []string{""}), "Expected service %s to be watched", "nonConfiguredServiceName") } const ( @@ -267,7 +254,7 @@ func newServer(t *testing.T) (*httptest.Server, *SDConfig) { time.Sleep(5 * time.Second) response = ServicesTestAnswer default: - t.Errorf("Unhandled consul call: %s", r.URL) + require.FailNow(t, "Unhandled consul call: %s", r.URL) } w.Header().Add("X-Consul-Index", "1") w.Write([]byte(response)) @@ -502,13 +489,10 @@ oauth2: var config SDConfig err := config.UnmarshalYAML(unmarshal([]byte(test.config))) if err != nil { - require.Equalf(t, err.Error(), test.errMessage, "Expected error '%s', got '%v'", test.errMessage, err) - return - } - if test.errMessage != "" { - t.Errorf("Expected error %s, got none", test.errMessage) + require.EqualError(t, err, test.errMessage) return } + require.Empty(t, test.errMessage, "Expected error.") require.Equal(t, test.expected, config) }) diff --git a/discovery/file/file_test.go b/discovery/file/file_test.go index 8a50570899..b473c2ce6e 100644 --- a/discovery/file/file_test.go +++ b/discovery/file/file_test.go @@ -358,9 +358,7 @@ func TestInvalidFile(t *testing.T) { // Verify that we've received nothing. time.Sleep(defaultWait) - if runner.lastReceive().After(now) { - t.Fatalf("unexpected targets received: %v", runner.targets()) - } + require.False(t, runner.lastReceive().After(now), "Unexpected targets received.") }) } } diff --git a/discovery/kubernetes/kubernetes_test.go b/discovery/kubernetes/kubernetes_test.go index 4071ebc34e..f1f8070e88 100644 --- a/discovery/kubernetes/kubernetes_test.go +++ b/discovery/kubernetes/kubernetes_test.go @@ -131,14 +131,8 @@ func (d k8sDiscoveryTest) Run(t *testing.T) { go readResultWithTimeout(t, ch, d.expectedMaxItems, time.Second, resChan) dd, ok := d.discovery.(hasSynced) - if !ok { - t.Errorf("discoverer does not implement hasSynced interface") - return - } - if !cache.WaitForCacheSync(ctx.Done(), dd.hasSynced) { - t.Errorf("discoverer failed to sync: %v", dd) - return - } + require.True(t, ok, "discoverer does not implement hasSynced interface") + require.True(t, cache.WaitForCacheSync(ctx.Done(), dd.hasSynced), "discoverer failed to sync: %v", dd) if d.afterStart != nil { d.afterStart() diff --git a/discovery/manager_test.go b/discovery/manager_test.go index 2f6e03aa5a..52159d94f6 100644 --- a/discovery/manager_test.go +++ b/discovery/manager_test.go @@ -694,7 +694,7 @@ func TestTargetUpdatesOrder(t *testing.T) { for x := 0; x < totalUpdatesCount; x++ { select { case <-ctx.Done(): - t.Fatalf("%d: no update arrived within the timeout limit", x) + require.FailNow(t, "%d: no update arrived within the timeout limit", x) case tgs := <-provUpdates: discoveryManager.updateGroup(poolKey{setName: strconv.Itoa(i), provider: tc.title}, tgs) for _, got := range discoveryManager.allGroups() { @@ -756,10 +756,8 @@ func verifySyncedPresence(t *testing.T, tGroups map[string][]*targetgroup.Group, func verifyPresence(t *testing.T, tSets map[poolKey]map[string]*targetgroup.Group, poolKey poolKey, label string, present bool) { t.Helper() - if _, ok := tSets[poolKey]; !ok { - t.Fatalf("'%s' should be present in Pool keys: %v", poolKey, tSets) - return - } + _, ok := tSets[poolKey] + require.True(t, ok, "'%s' should be present in Pool keys: %v", poolKey, tSets) match := false var mergedTargets string @@ -776,7 +774,7 @@ func verifyPresence(t *testing.T, tSets map[poolKey]map[string]*targetgroup.Grou if !present { msg = "not" } - t.Fatalf("%q should %s be present in Targets labels: %q", label, msg, mergedTargets) + require.FailNow(t, "%q should %s be present in Targets labels: %q", label, msg, mergedTargets) } } @@ -1088,22 +1086,14 @@ func TestTargetSetRecreatesEmptyStaticConfigs(t *testing.T) { syncedTargets = <-discoveryManager.SyncCh() p = pk("static", "prometheus", 1) targetGroups, ok := discoveryManager.targets[p] - if !ok { - t.Fatalf("'%v' should be present in target groups", p) - } + require.True(t, ok, "'%v' should be present in target groups", p) group, ok := targetGroups[""] - if !ok { - t.Fatalf("missing '' key in target groups %v", targetGroups) - } + require.True(t, ok, "missing '' key in target groups %v", targetGroups) - if len(group.Targets) != 0 { - t.Fatalf("Invalid number of targets: expected 0, got %d", len(group.Targets)) - } + require.Empty(t, group.Targets, "Invalid number of targets.") require.Len(t, syncedTargets, 1) require.Len(t, syncedTargets["prometheus"], 1) - if lbls := syncedTargets["prometheus"][0].Labels; lbls != nil { - t.Fatalf("Unexpected Group: expected nil Labels, got %v", lbls) - } + require.Nil(t, syncedTargets["prometheus"][0].Labels) } func TestIdenticalConfigurationsAreCoalesced(t *testing.T) { @@ -1131,9 +1121,7 @@ func TestIdenticalConfigurationsAreCoalesced(t *testing.T) { syncedTargets := <-discoveryManager.SyncCh() verifyPresence(t, discoveryManager.targets, pk("static", "prometheus", 0), "{__address__=\"foo:9090\"}", true) verifyPresence(t, discoveryManager.targets, pk("static", "prometheus2", 0), "{__address__=\"foo:9090\"}", true) - if len(discoveryManager.providers) != 1 { - t.Fatalf("Invalid number of providers: expected 1, got %d", len(discoveryManager.providers)) - } + require.Len(t, discoveryManager.providers, 1, "Invalid number of providers.") require.Len(t, syncedTargets, 2) verifySyncedPresence(t, syncedTargets, "prometheus", "{__address__=\"foo:9090\"}", true) require.Len(t, syncedTargets["prometheus"], 1) @@ -1231,9 +1219,7 @@ func TestGaugeFailedConfigs(t *testing.T) { <-discoveryManager.SyncCh() failedCount := client_testutil.ToFloat64(discoveryManager.metrics.FailedConfigs) - if failedCount != 3 { - t.Fatalf("Expected to have 3 failed configs, got: %v", failedCount) - } + require.Equal(t, 3.0, failedCount, "Expected to have 3 failed configs.") c["prometheus"] = Configs{ staticConfig("foo:9090"), @@ -1242,9 +1228,7 @@ func TestGaugeFailedConfigs(t *testing.T) { <-discoveryManager.SyncCh() failedCount = client_testutil.ToFloat64(discoveryManager.metrics.FailedConfigs) - if failedCount != 0 { - t.Fatalf("Expected to get no failed config, got: %v", failedCount) - } + require.Equal(t, 0.0, failedCount, "Expected to get no failed config.") } func TestCoordinationWithReceiver(t *testing.T) { @@ -1388,19 +1372,14 @@ func TestCoordinationWithReceiver(t *testing.T) { time.Sleep(expected.delay) select { case <-ctx.Done(): - t.Fatalf("step %d: no update received in the expected timeframe", i) + require.FailNow(t, "step %d: no update received in the expected timeframe", i) case tgs, ok := <-mgr.SyncCh(): - if !ok { - t.Fatalf("step %d: discovery manager channel is closed", i) - } - if len(tgs) != len(expected.tgs) { - t.Fatalf("step %d: target groups mismatch, got: %d, expected: %d\ngot: %#v\nexpected: %#v", - i, len(tgs), len(expected.tgs), tgs, expected.tgs) - } + require.True(t, ok, "step %d: discovery manager channel is closed", i) + require.Equal(t, len(expected.tgs), len(tgs), "step %d: targets mismatch", i) + for k := range expected.tgs { - if _, ok := tgs[k]; !ok { - t.Fatalf("step %d: target group not found: %s\ngot: %#v", i, k, tgs) - } + _, ok := tgs[k] + require.True(t, ok, "step %d: target group not found: %s", i, k) assertEqualGroups(t, tgs[k], expected.tgs[k]) } } diff --git a/discovery/marathon/marathon_test.go b/discovery/marathon/marathon_test.go index c78cc1e3c6..d177ae49ff 100644 --- a/discovery/marathon/marathon_test.go +++ b/discovery/marathon/marathon_test.go @@ -69,23 +69,15 @@ func TestMarathonSDHandleError(t *testing.T) { } ) tgs, err := testUpdateServices(client) - if !errors.Is(err, errTesting) { - t.Fatalf("Expected error: %s", err) - } - if len(tgs) != 0 { - t.Fatalf("Got group: %s", tgs) - } + require.ErrorIs(t, err, errTesting) + require.Empty(t, tgs, "Expected no target groups.") } func TestMarathonSDEmptyList(t *testing.T) { client := func(_ context.Context, _ *http.Client, _ string) (*appList, error) { return &appList{}, nil } tgs, err := testUpdateServices(client) - if err != nil { - t.Fatalf("Got error: %s", err) - } - if len(tgs) > 0 { - t.Fatalf("Got group: %v", tgs) - } + require.NoError(t, err) + require.Empty(t, tgs, "Expected no target groups.") } func marathonTestAppList(labels map[string]string, runningTasks int) *appList { @@ -119,28 +111,16 @@ func TestMarathonSDSendGroup(t *testing.T) { return marathonTestAppList(marathonValidLabel, 1), nil } tgs, err := testUpdateServices(client) - if err != nil { - t.Fatalf("Got error: %s", err) - } - if len(tgs) != 1 { - t.Fatal("Expected 1 target group, got", len(tgs)) - } + require.NoError(t, err) + require.Equal(t, 1, len(tgs), "Expected 1 target group.") tg := tgs[0] + require.Equal(t, "test-service", tg.Source, "Wrong target group name.") + require.Equal(t, 1, len(tg.Targets), "Expected 1 target.") - if tg.Source != "test-service" { - t.Fatalf("Wrong target group name: %s", tg.Source) - } - if len(tg.Targets) != 1 { - t.Fatalf("Wrong number of targets: %v", tg.Targets) - } tgt := tg.Targets[0] - if tgt[model.AddressLabel] != "mesos-slave1:31000" { - t.Fatalf("Wrong target address: %s", tgt[model.AddressLabel]) - } - if tgt[model.LabelName(portMappingLabelPrefix+"prometheus")] != "yes" { - t.Fatalf("Wrong first portMappings label from the first port: %s", tgt[model.AddressLabel]) - } + require.Equal(t, "mesos-slave1:31000", string(tgt[model.AddressLabel]), "Wrong target address.") + require.Equal(t, "yes", string(tgt[model.LabelName(portMappingLabelPrefix+"prometheus")]), "Wrong portMappings label from the first port.") } func TestMarathonSDRemoveApp(t *testing.T) { @@ -153,40 +133,27 @@ func TestMarathonSDRemoveApp(t *testing.T) { defer refreshMetrics.Unregister() md, err := NewDiscovery(cfg, nil, metrics) - if err != nil { - t.Fatalf("%s", err) - } + require.NoError(t, err) md.appsClient = func(_ context.Context, _ *http.Client, _ string) (*appList, error) { return marathonTestAppList(marathonValidLabel, 1), nil } tgs, err := md.refresh(context.Background()) - if err != nil { - t.Fatalf("Got error on first update: %s", err) - } - if len(tgs) != 1 { - t.Fatal("Expected 1 targetgroup, got", len(tgs)) - } + require.NoError(t, err, "Got error on first update.") + require.Equal(t, 1, len(tgs), "Expected 1 targetgroup.") tg1 := tgs[0] md.appsClient = func(_ context.Context, _ *http.Client, _ string) (*appList, error) { return marathonTestAppList(marathonValidLabel, 0), nil } tgs, err = md.refresh(context.Background()) - if err != nil { - t.Fatalf("Got error on second update: %s", err) - } - if len(tgs) != 1 { - t.Fatal("Expected 1 targetgroup, got", len(tgs)) - } + require.NoError(t, err, "Got error on second update.") + require.Equal(t, 1, len(tgs), "Expected 1 targetgroup.") + tg2 := tgs[0] - if tg2.Source != tg1.Source { - if len(tg2.Targets) > 0 { - t.Errorf("Got a non-empty target set: %s", tg2.Targets) - } - t.Fatalf("Source is different: %s != %s", tg1.Source, tg2.Source) - } + require.Equal(t, tg1.Source, tg2.Source, "Source is different.") + require.NotEmpty(t, tg2.Targets, "Got a non-empty target set.") } func marathonTestAppListWithMultiplePorts(labels map[string]string, runningTasks int) *appList { @@ -221,34 +188,22 @@ func TestMarathonSDSendGroupWithMultiplePort(t *testing.T) { return marathonTestAppListWithMultiplePorts(marathonValidLabel, 1), nil } tgs, err := testUpdateServices(client) - if err != nil { - t.Fatalf("Got error: %s", err) - } - if len(tgs) != 1 { - t.Fatal("Expected 1 target group, got", len(tgs)) - } - tg := tgs[0] + require.NoError(t, err) + require.Equal(t, 1, len(tgs), "Expected 1 target group.") + + tg := tgs[0] + require.Equal(t, "test-service", tg.Source, "Wrong target group name.") + require.Equal(t, 2, len(tg.Targets), "Wrong number of targets.") - if tg.Source != "test-service" { - t.Fatalf("Wrong target group name: %s", tg.Source) - } - if len(tg.Targets) != 2 { - t.Fatalf("Wrong number of targets: %v", tg.Targets) - } tgt := tg.Targets[0] - if tgt[model.AddressLabel] != "mesos-slave1:31000" { - t.Fatalf("Wrong target address: %s", tgt[model.AddressLabel]) - } - if tgt[model.LabelName(portMappingLabelPrefix+"prometheus")] != "yes" { - t.Fatalf("Wrong first portMappings label from the first port: %s", tgt[model.AddressLabel]) - } + require.Equal(t, "mesos-slave1:31000", string(tgt[model.AddressLabel]), "Wrong target address.") + require.Equal(t, "yes", string(tgt[model.LabelName(portMappingLabelPrefix+"prometheus")]), + "Wrong portMappings label from the first port: %s", tgt[model.AddressLabel]) + tgt = tg.Targets[1] - if tgt[model.AddressLabel] != "mesos-slave1:32000" { - t.Fatalf("Wrong target address: %s", tgt[model.AddressLabel]) - } - if tgt[model.LabelName(portMappingLabelPrefix+"prometheus")] != "" { - t.Fatalf("Wrong portMappings label from the second port: %s", tgt[model.AddressLabel]) - } + require.Equal(t, "mesos-slave1:32000", string(tgt[model.AddressLabel]), "Wrong target address.") + require.Equal(t, "", string(tgt[model.LabelName(portMappingLabelPrefix+"prometheus")]), + "Wrong portMappings label from the second port: %s", tgt[model.AddressLabel]) } func marathonTestZeroTaskPortAppList(labels map[string]string, runningTasks int) *appList { @@ -278,20 +233,12 @@ func TestMarathonZeroTaskPorts(t *testing.T) { return marathonTestZeroTaskPortAppList(marathonValidLabel, 1), nil } tgs, err := testUpdateServices(client) - if err != nil { - t.Fatalf("Got error: %s", err) - } - if len(tgs) != 1 { - t.Fatal("Expected 1 target group, got", len(tgs)) - } - tg := tgs[0] + require.NoError(t, err) + require.Equal(t, 1, len(tgs), "Expected 1 target group.") - if tg.Source != "test-service-zero-ports" { - t.Fatalf("Wrong target group name: %s", tg.Source) - } - if len(tg.Targets) != 0 { - t.Fatalf("Wrong number of targets: %v", tg.Targets) - } + tg := tgs[0] + require.Equal(t, "test-service-zero-ports", tg.Source, "Wrong target group name.") + require.Empty(t, tg.Targets, "Wrong number of targets.") } func Test500ErrorHttpResponseWithValidJSONBody(t *testing.T) { @@ -306,9 +253,7 @@ func Test500ErrorHttpResponseWithValidJSONBody(t *testing.T) { defer ts.Close() // Execute test case and validate behavior. _, err := testUpdateServices(nil) - if err == nil { - t.Fatalf("Expected error for 5xx HTTP response from marathon server, got nil") - } + require.Error(t, err, "Expected error for 5xx HTTP response from marathon server.") } func marathonTestAppListWithPortDefinitions(labels map[string]string, runningTasks int) *appList { @@ -346,40 +291,24 @@ func TestMarathonSDSendGroupWithPortDefinitions(t *testing.T) { return marathonTestAppListWithPortDefinitions(marathonValidLabel, 1), nil } tgs, err := testUpdateServices(client) - if err != nil { - t.Fatalf("Got error: %s", err) - } - if len(tgs) != 1 { - t.Fatal("Expected 1 target group, got", len(tgs)) - } - tg := tgs[0] + require.NoError(t, err) + require.Equal(t, 1, len(tgs), "Expected 1 target group.") + + tg := tgs[0] + require.Equal(t, "test-service", tg.Source, "Wrong target group name.") + require.Equal(t, 2, len(tg.Targets), "Wrong number of targets.") - if tg.Source != "test-service" { - t.Fatalf("Wrong target group name: %s", tg.Source) - } - if len(tg.Targets) != 2 { - t.Fatalf("Wrong number of targets: %v", tg.Targets) - } tgt := tg.Targets[0] - if tgt[model.AddressLabel] != "mesos-slave1:1234" { - t.Fatalf("Wrong target address: %s", tgt[model.AddressLabel]) - } - if tgt[model.LabelName(portMappingLabelPrefix+"prometheus")] != "" { - t.Fatalf("Wrong first portMappings label from the first port: %s", tgt[model.AddressLabel]) - } - if tgt[model.LabelName(portDefinitionLabelPrefix+"prometheus")] != "" { - t.Fatalf("Wrong first portDefinitions label from the first port: %s", tgt[model.AddressLabel]) - } + require.Equal(t, "mesos-slave1:1234", string(tgt[model.AddressLabel]), "Wrong target address.") + require.Equal(t, "", string(tgt[model.LabelName(portMappingLabelPrefix+"prometheus")]), + "Wrong portMappings label from the first port.") + require.Equal(t, "", string(tgt[model.LabelName(portDefinitionLabelPrefix+"prometheus")]), + "Wrong portDefinitions label from the first port.") + tgt = tg.Targets[1] - if tgt[model.AddressLabel] != "mesos-slave1:5678" { - t.Fatalf("Wrong target address: %s", tgt[model.AddressLabel]) - } - if tgt[model.LabelName(portMappingLabelPrefix+"prometheus")] != "" { - t.Fatalf("Wrong portMappings label from the second port: %s", tgt[model.AddressLabel]) - } - if tgt[model.LabelName(portDefinitionLabelPrefix+"prometheus")] != "yes" { - t.Fatalf("Wrong portDefinitions label from the second port: %s", tgt[model.AddressLabel]) - } + require.Equal(t, "mesos-slave1:5678", string(tgt[model.AddressLabel]), "Wrong target address.") + require.Equal(t, "", string(tgt[model.LabelName(portMappingLabelPrefix+"prometheus")]), "Wrong portMappings label from the second port.") + require.Equal(t, "yes", string(tgt[model.LabelName(portDefinitionLabelPrefix+"prometheus")]), "Wrong portDefinitions label from the second port.") } func marathonTestAppListWithPortDefinitionsRequirePorts(labels map[string]string, runningTasks int) *appList { @@ -416,40 +345,22 @@ func TestMarathonSDSendGroupWithPortDefinitionsRequirePorts(t *testing.T) { return marathonTestAppListWithPortDefinitionsRequirePorts(marathonValidLabel, 1), nil } tgs, err := testUpdateServices(client) - if err != nil { - t.Fatalf("Got error: %s", err) - } - if len(tgs) != 1 { - t.Fatal("Expected 1 target group, got", len(tgs)) - } - tg := tgs[0] + require.NoError(t, err) + require.Equal(t, 1, len(tgs), "Expected 1 target group.") + + tg := tgs[0] + require.Equal(t, "test-service", tg.Source, "Wrong target group name.") + require.Equal(t, 2, len(tg.Targets), "Wrong number of targets.") - if tg.Source != "test-service" { - t.Fatalf("Wrong target group name: %s", tg.Source) - } - if len(tg.Targets) != 2 { - t.Fatalf("Wrong number of targets: %v", tg.Targets) - } tgt := tg.Targets[0] - if tgt[model.AddressLabel] != "mesos-slave1:31000" { - t.Fatalf("Wrong target address: %s", tgt[model.AddressLabel]) - } - if tgt[model.LabelName(portMappingLabelPrefix+"prometheus")] != "" { - t.Fatalf("Wrong first portMappings label from the first port: %s", tgt[model.AddressLabel]) - } - if tgt[model.LabelName(portDefinitionLabelPrefix+"prometheus")] != "" { - t.Fatalf("Wrong first portDefinitions label from the first port: %s", tgt[model.AddressLabel]) - } + require.Equal(t, "mesos-slave1:31000", string(tgt[model.AddressLabel]), "Wrong target address.") + require.Equal(t, "", string(tgt[model.LabelName(portMappingLabelPrefix+"prometheus")]), "Wrong portMappings label from the first port.") + require.Equal(t, "", string(tgt[model.LabelName(portDefinitionLabelPrefix+"prometheus")]), "Wrong portDefinitions label from the first port.") + tgt = tg.Targets[1] - if tgt[model.AddressLabel] != "mesos-slave1:32000" { - t.Fatalf("Wrong target address: %s", tgt[model.AddressLabel]) - } - if tgt[model.LabelName(portMappingLabelPrefix+"prometheus")] != "" { - t.Fatalf("Wrong portMappings label from the second port: %s", tgt[model.AddressLabel]) - } - if tgt[model.LabelName(portDefinitionLabelPrefix+"prometheus")] != "yes" { - t.Fatalf("Wrong portDefinitions label from the second port: %s", tgt[model.AddressLabel]) - } + require.Equal(t, "mesos-slave1:32000", string(tgt[model.AddressLabel]), "Wrong target address.") + require.Equal(t, "", string(tgt[model.LabelName(portMappingLabelPrefix+"prometheus")]), "Wrong portMappings label from the second port.") + require.Equal(t, "yes", string(tgt[model.LabelName(portDefinitionLabelPrefix+"prometheus")]), "Wrong portDefinitions label from the second port.") } func marathonTestAppListWithPorts(labels map[string]string, runningTasks int) *appList { @@ -481,40 +392,22 @@ func TestMarathonSDSendGroupWithPorts(t *testing.T) { return marathonTestAppListWithPorts(marathonValidLabel, 1), nil } tgs, err := testUpdateServices(client) - if err != nil { - t.Fatalf("Got error: %s", err) - } - if len(tgs) != 1 { - t.Fatal("Expected 1 target group, got", len(tgs)) - } - tg := tgs[0] + require.NoError(t, err) + require.Equal(t, 1, len(tgs), "Expected 1 target group.") + + tg := tgs[0] + require.Equal(t, "test-service", tg.Source, "Wrong target group name.") + require.Equal(t, 2, len(tg.Targets), "Wrong number of targets.") - if tg.Source != "test-service" { - t.Fatalf("Wrong target group name: %s", tg.Source) - } - if len(tg.Targets) != 2 { - t.Fatalf("Wrong number of targets: %v", tg.Targets) - } tgt := tg.Targets[0] - if tgt[model.AddressLabel] != "mesos-slave1:31000" { - t.Fatalf("Wrong target address: %s", tgt[model.AddressLabel]) - } - if tgt[model.LabelName(portMappingLabelPrefix+"prometheus")] != "" { - t.Fatalf("Wrong first portMappings label from the first port: %s", tgt[model.AddressLabel]) - } - if tgt[model.LabelName(portDefinitionLabelPrefix+"prometheus")] != "" { - t.Fatalf("Wrong first portDefinitions label from the first port: %s", tgt[model.AddressLabel]) - } + require.Equal(t, "mesos-slave1:31000", string(tgt[model.AddressLabel]), "Wrong target address.") + require.Equal(t, "", string(tgt[model.LabelName(portMappingLabelPrefix+"prometheus")]), "Wrong portMappings label from the first port.") + require.Equal(t, "", string(tgt[model.LabelName(portDefinitionLabelPrefix+"prometheus")]), "Wrong portDefinitions label from the first port.") + tgt = tg.Targets[1] - if tgt[model.AddressLabel] != "mesos-slave1:32000" { - t.Fatalf("Wrong target address: %s", tgt[model.AddressLabel]) - } - if tgt[model.LabelName(portMappingLabelPrefix+"prometheus")] != "" { - t.Fatalf("Wrong portMappings label from the second port: %s", tgt[model.AddressLabel]) - } - if tgt[model.LabelName(portDefinitionLabelPrefix+"prometheus")] != "" { - t.Fatalf("Wrong portDefinitions label from the second port: %s", tgt[model.AddressLabel]) - } + require.Equal(t, "mesos-slave1:32000", string(tgt[model.AddressLabel]), "Wrong target address.") + require.Equal(t, "", string(tgt[model.LabelName(portMappingLabelPrefix+"prometheus")]), "Wrong portMappings label from the second port.") + require.Equal(t, "", string(tgt[model.LabelName(portDefinitionLabelPrefix+"prometheus")]), "Wrong portDefinitions label from the second port.") } func marathonTestAppListWithContainerPortMappings(labels map[string]string, runningTasks int) *appList { @@ -555,40 +448,22 @@ func TestMarathonSDSendGroupWithContainerPortMappings(t *testing.T) { return marathonTestAppListWithContainerPortMappings(marathonValidLabel, 1), nil } tgs, err := testUpdateServices(client) - if err != nil { - t.Fatalf("Got error: %s", err) - } - if len(tgs) != 1 { - t.Fatal("Expected 1 target group, got", len(tgs)) - } - tg := tgs[0] + require.NoError(t, err) + require.Equal(t, 1, len(tgs), "Expected 1 target group.") + + tg := tgs[0] + require.Equal(t, "test-service", tg.Source, "Wrong target group name.") + require.Equal(t, 2, len(tg.Targets), "Wrong number of targets.") - if tg.Source != "test-service" { - t.Fatalf("Wrong target group name: %s", tg.Source) - } - if len(tg.Targets) != 2 { - t.Fatalf("Wrong number of targets: %v", tg.Targets) - } tgt := tg.Targets[0] - if tgt[model.AddressLabel] != "mesos-slave1:12345" { - t.Fatalf("Wrong target address: %s", tgt[model.AddressLabel]) - } - if tgt[model.LabelName(portMappingLabelPrefix+"prometheus")] != "yes" { - t.Fatalf("Wrong first portMappings label from the first port: %s", tgt[model.AddressLabel]) - } - if tgt[model.LabelName(portDefinitionLabelPrefix+"prometheus")] != "" { - t.Fatalf("Wrong first portDefinitions label from the first port: %s", tgt[model.AddressLabel]) - } + require.Equal(t, "mesos-slave1:12345", string(tgt[model.AddressLabel]), "Wrong target address.") + require.Equal(t, "yes", string(tgt[model.LabelName(portMappingLabelPrefix+"prometheus")]), "Wrong portMappings label from the first port.") + require.Equal(t, "", string(tgt[model.LabelName(portDefinitionLabelPrefix+"prometheus")]), "Wrong portDefinitions label from the first port.") + tgt = tg.Targets[1] - if tgt[model.AddressLabel] != "mesos-slave1:32000" { - t.Fatalf("Wrong target address: %s", tgt[model.AddressLabel]) - } - if tgt[model.LabelName(portMappingLabelPrefix+"prometheus")] != "" { - t.Fatalf("Wrong portMappings label from the second port: %s", tgt[model.AddressLabel]) - } - if tgt[model.LabelName(portDefinitionLabelPrefix+"prometheus")] != "" { - t.Fatalf("Wrong portDefinitions label from the second port: %s", tgt[model.AddressLabel]) - } + require.Equal(t, "mesos-slave1:32000", string(tgt[model.AddressLabel]), "Wrong target address.") + require.Equal(t, "", string(tgt[model.LabelName(portMappingLabelPrefix+"prometheus")]), "Wrong portMappings label from the second port.") + require.Equal(t, "", string(tgt[model.LabelName(portDefinitionLabelPrefix+"prometheus")]), "Wrong portDefinitions label from the second port.") } func marathonTestAppListWithDockerContainerPortMappings(labels map[string]string, runningTasks int) *appList { @@ -629,40 +504,22 @@ func TestMarathonSDSendGroupWithDockerContainerPortMappings(t *testing.T) { return marathonTestAppListWithDockerContainerPortMappings(marathonValidLabel, 1), nil } tgs, err := testUpdateServices(client) - if err != nil { - t.Fatalf("Got error: %s", err) - } - if len(tgs) != 1 { - t.Fatal("Expected 1 target group, got", len(tgs)) - } - tg := tgs[0] + require.NoError(t, err) + require.Equal(t, 1, len(tgs), "Expected 1 target group.") + + tg := tgs[0] + require.Equal(t, "test-service", tg.Source, "Wrong target group name.") + require.Equal(t, 2, len(tg.Targets), "Wrong number of targets.") - if tg.Source != "test-service" { - t.Fatalf("Wrong target group name: %s", tg.Source) - } - if len(tg.Targets) != 2 { - t.Fatalf("Wrong number of targets: %v", tg.Targets) - } tgt := tg.Targets[0] - if tgt[model.AddressLabel] != "mesos-slave1:31000" { - t.Fatalf("Wrong target address: %s", tgt[model.AddressLabel]) - } - if tgt[model.LabelName(portMappingLabelPrefix+"prometheus")] != "yes" { - t.Fatalf("Wrong first portMappings label from the first port: %s", tgt[model.AddressLabel]) - } - if tgt[model.LabelName(portDefinitionLabelPrefix+"prometheus")] != "" { - t.Fatalf("Wrong first portDefinitions label from the first port: %s", tgt[model.AddressLabel]) - } + require.Equal(t, "mesos-slave1:31000", string(tgt[model.AddressLabel]), "Wrong target address.") + require.Equal(t, "yes", string(tgt[model.LabelName(portMappingLabelPrefix+"prometheus")]), "Wrong portMappings label from the first port.") + require.Equal(t, "", string(tgt[model.LabelName(portDefinitionLabelPrefix+"prometheus")]), "Wrong portDefinitions label from the first port.") + tgt = tg.Targets[1] - if tgt[model.AddressLabel] != "mesos-slave1:12345" { - t.Fatalf("Wrong target address: %s", tgt[model.AddressLabel]) - } - if tgt[model.LabelName(portMappingLabelPrefix+"prometheus")] != "" { - t.Fatalf("Wrong portMappings label from the second port: %s", tgt[model.AddressLabel]) - } - if tgt[model.LabelName(portDefinitionLabelPrefix+"prometheus")] != "" { - t.Fatalf("Wrong portDefinitions label from the second port: %s", tgt[model.AddressLabel]) - } + require.Equal(t, "mesos-slave1:12345", string(tgt[model.AddressLabel]), "Wrong target address.") + require.Equal(t, "", string(tgt[model.LabelName(portMappingLabelPrefix+"prometheus")]), "Wrong portMappings label from the second port.") + require.Equal(t, "", string(tgt[model.LabelName(portDefinitionLabelPrefix+"prometheus")]), "Wrong portDefinitions label from the second port.") } func marathonTestAppListWithContainerNetworkAndPortMappings(labels map[string]string, runningTasks int) *appList { @@ -707,38 +564,20 @@ func TestMarathonSDSendGroupWithContainerNetworkAndPortMapping(t *testing.T) { return marathonTestAppListWithContainerNetworkAndPortMappings(marathonValidLabel, 1), nil } tgs, err := testUpdateServices(client) - if err != nil { - t.Fatalf("Got error: %s", err) - } - if len(tgs) != 1 { - t.Fatal("Expected 1 target group, got", len(tgs)) - } - tg := tgs[0] + require.NoError(t, err) + require.Equal(t, 1, len(tgs), "Expected 1 target group.") + + tg := tgs[0] + require.Equal(t, "test-service", tg.Source, "Wrong target group name.") + require.Equal(t, 2, len(tg.Targets), "Wrong number of targets.") - if tg.Source != "test-service" { - t.Fatalf("Wrong target group name: %s", tg.Source) - } - if len(tg.Targets) != 2 { - t.Fatalf("Wrong number of targets: %v", tg.Targets) - } tgt := tg.Targets[0] - if tgt[model.AddressLabel] != "1.2.3.4:8080" { - t.Fatalf("Wrong target address: %s", tgt[model.AddressLabel]) - } - if tgt[model.LabelName(portMappingLabelPrefix+"prometheus")] != "yes" { - t.Fatalf("Wrong first portMappings label from the first port: %s", tgt[model.AddressLabel]) - } - if tgt[model.LabelName(portDefinitionLabelPrefix+"prometheus")] != "" { - t.Fatalf("Wrong first portDefinitions label from the first port: %s", tgt[model.AddressLabel]) - } + require.Equal(t, "1.2.3.4:8080", string(tgt[model.AddressLabel]), "Wrong target address.") + require.Equal(t, "yes", string(tgt[model.LabelName(portMappingLabelPrefix+"prometheus")]), "Wrong portMappings label from the first port.") + require.Equal(t, "", string(tgt[model.LabelName(portDefinitionLabelPrefix+"prometheus")]), "Wrong portDefinitions label from the first port.") + tgt = tg.Targets[1] - if tgt[model.AddressLabel] != "1.2.3.4:1234" { - t.Fatalf("Wrong target address: %s", tgt[model.AddressLabel]) - } - if tgt[model.LabelName(portMappingLabelPrefix+"prometheus")] != "" { - t.Fatalf("Wrong portMappings label from the second port: %s", tgt[model.AddressLabel]) - } - if tgt[model.LabelName(portDefinitionLabelPrefix+"prometheus")] != "" { - t.Fatalf("Wrong portDefinitions label from the second port: %s", tgt[model.AddressLabel]) - } + require.Equal(t, "1.2.3.4:1234", string(tgt[model.AddressLabel]), "Wrong target address.") + require.Equal(t, "", string(tgt[model.LabelName(portMappingLabelPrefix+"prometheus")]), "Wrong portMappings label from the second port.") + require.Equal(t, "", string(tgt[model.LabelName(portDefinitionLabelPrefix+"prometheus")]), "Wrong portDefinitions label from the second port.") } diff --git a/discovery/openstack/mock_test.go b/discovery/openstack/mock_test.go index e279b0ca8c..4aa871e11f 100644 --- a/discovery/openstack/mock_test.go +++ b/discovery/openstack/mock_test.go @@ -18,6 +18,8 @@ import ( "net/http" "net/http/httptest" "testing" + + "github.com/stretchr/testify/require" ) // SDMock is the interface for the OpenStack mock. @@ -49,15 +51,13 @@ func (m *SDMock) Setup() { const tokenID = "cbc36478b0bd8e67e89469c7749d4127" func testMethod(t *testing.T, r *http.Request, expected string) { - if expected != r.Method { - t.Errorf("Request method = %v, expected %v", r.Method, expected) - } + require.Equal(t, expected, r.Method, "Unexpected request method.") } func testHeader(t *testing.T, r *http.Request, header, expected string) { - if actual := r.Header.Get(header); expected != actual { - t.Errorf("Header %s = %s, expected %s", header, actual, expected) - } + t.Helper() + actual := r.Header.Get(header) + require.Equal(t, expected, actual, "Unexpected value for request header %s.", header) } // HandleVersionsSuccessfully mocks version call. diff --git a/discovery/refresh/refresh_test.go b/discovery/refresh/refresh_test.go index 407f0a7fa0..b70a326355 100644 --- a/discovery/refresh/refresh_test.go +++ b/discovery/refresh/refresh_test.go @@ -97,7 +97,7 @@ func TestRefresh(t *testing.T) { defer tick.Stop() select { case <-ch: - t.Fatal("Unexpected target group") + require.FailNow(t, "Unexpected target group") case <-tick.C: } } diff --git a/discovery/zookeeper/zookeeper_test.go b/discovery/zookeeper/zookeeper_test.go index d0e67b50ac..c2b41ce7a3 100644 --- a/discovery/zookeeper/zookeeper_test.go +++ b/discovery/zookeeper/zookeeper_test.go @@ -18,6 +18,7 @@ import ( "time" "github.com/prometheus/common/model" + "github.com/stretchr/testify/require" "go.uber.org/goleak" ) @@ -31,7 +32,5 @@ func TestNewDiscoveryError(t *testing.T) { time.Second, []string{"/"}, nil, func(data []byte, path string) (model.LabelSet, error) { return nil, nil }) - if err == nil { - t.Fatalf("expected error, got nil") - } + require.Error(t, err) } From 581d8d86b4c3a33ec1e4a7099456ab370d1b6d34 Mon Sep 17 00:00:00 2001 From: Ayoub Mrini Date: Thu, 1 Feb 2024 13:34:37 +0100 Subject: [PATCH 052/237] Pod status changes not discovered by Kube Endpoints SD (#13337) * fix(discovery/kubernetes/endpoints): react to changes on Pods because some modifications can occur on them without triggering an update on the related Endpoints (The Pod phase changing from Pending to Running e.g.). --------- Signed-off-by: machine424 Co-authored-by: Guillermo Sanchez Gavier --- discovery/kubernetes/endpoints.go | 39 +++++++- discovery/kubernetes/endpoints_test.go | 120 +++++++++++++++++++++++++ discovery/kubernetes/endpointslice.go | 2 +- discovery/kubernetes/kubernetes.go | 19 ++++ discovery/kubernetes/pod.go | 7 +- 5 files changed, 183 insertions(+), 4 deletions(-) diff --git a/discovery/kubernetes/endpoints.go b/discovery/kubernetes/endpoints.go index d8c9689cad..c7a60ae6d3 100644 --- a/discovery/kubernetes/endpoints.go +++ b/discovery/kubernetes/endpoints.go @@ -62,6 +62,8 @@ func NewEndpoints(l log.Logger, eps cache.SharedIndexInformer, svc, pod, node ca svcUpdateCount := eventCount.WithLabelValues(RoleService.String(), MetricLabelRoleUpdate) svcDeleteCount := eventCount.WithLabelValues(RoleService.String(), MetricLabelRoleDelete) + podUpdateCount := eventCount.WithLabelValues(RolePod.String(), MetricLabelRoleUpdate) + e := &Endpoints{ logger: l, endpointsInf: eps, @@ -131,6 +133,29 @@ func NewEndpoints(l log.Logger, eps cache.SharedIndexInformer, svc, pod, node ca if err != nil { level.Error(l).Log("msg", "Error adding services event handler.", "err", err) } + _, err = e.podInf.AddEventHandler(cache.ResourceEventHandlerFuncs{ + UpdateFunc: func(old, cur interface{}) { + podUpdateCount.Inc() + oldPod, ok := old.(*apiv1.Pod) + if !ok { + return + } + + curPod, ok := cur.(*apiv1.Pod) + if !ok { + return + } + + // the Pod's phase may change without triggering an update on the Endpoints/Service. + // https://github.com/prometheus/prometheus/issues/11305. + if curPod.Status.Phase != oldPod.Status.Phase { + e.enqueuePod(namespacedName(curPod.Namespace, curPod.Name)) + } + }, + }) + if err != nil { + level.Error(l).Log("msg", "Error adding pods event handler.", "err", err) + } if e.withNodeMetadata { _, err = e.nodeInf.AddEventHandler(cache.ResourceEventHandlerFuncs{ AddFunc: func(o interface{}) { @@ -166,6 +191,18 @@ func (e *Endpoints) enqueueNode(nodeName string) { } } +func (e *Endpoints) enqueuePod(podNamespacedName string) { + endpoints, err := e.endpointsInf.GetIndexer().ByIndex(podIndex, podNamespacedName) + if err != nil { + level.Error(e.logger).Log("msg", "Error getting endpoints for pod", "pod", podNamespacedName, "err", err) + return + } + + for _, endpoint := range endpoints { + e.enqueue(endpoint) + } +} + func (e *Endpoints) enqueue(obj interface{}) { key, err := cache.DeletionHandlingMetaNamespaceKeyFunc(obj) if err != nil { @@ -312,7 +349,7 @@ func (e *Endpoints) buildEndpoints(eps *apiv1.Endpoints) *targetgroup.Group { tg.Targets = append(tg.Targets, target) return } - s := pod.Namespace + "/" + pod.Name + s := namespacedName(pod.Namespace, pod.Name) sp, ok := seenPods[s] if !ok { diff --git a/discovery/kubernetes/endpoints_test.go b/discovery/kubernetes/endpoints_test.go index cf7fd9aee0..e877657dba 100644 --- a/discovery/kubernetes/endpoints_test.go +++ b/discovery/kubernetes/endpoints_test.go @@ -969,3 +969,123 @@ func TestEndpointsDiscoveryEmptyPodStatus(t *testing.T) { expectedRes: map[string]*targetgroup.Group{}, }.Run(t) } + +// TestEndpointsUpdatePod makes sure that Endpoints discovery detects underlying Pods changes. +// See https://github.com/prometheus/prometheus/issues/11305 for more details. +func TestEndpointsDiscoveryUpdatePod(t *testing.T) { + pod := &v1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "testpod", + Namespace: "default", + UID: types.UID("deadbeef"), + }, + Spec: v1.PodSpec{ + NodeName: "testnode", + Containers: []v1.Container{ + { + Name: "c1", + Image: "c1:latest", + Ports: []v1.ContainerPort{ + { + Name: "mainport", + ContainerPort: 9000, + Protocol: v1.ProtocolTCP, + }, + }, + }, + }, + }, + Status: v1.PodStatus{ + // Pod is in Pending phase when discovered for first time. + Phase: "Pending", + Conditions: []v1.PodCondition{ + { + Type: v1.PodReady, + Status: v1.ConditionFalse, + }, + }, + HostIP: "2.3.4.5", + PodIP: "4.3.2.1", + }, + } + objs := []runtime.Object{ + &v1.Endpoints{ + ObjectMeta: metav1.ObjectMeta{ + Name: "testendpoints", + Namespace: "default", + }, + Subsets: []v1.EndpointSubset{ + { + Addresses: []v1.EndpointAddress{ + { + IP: "4.3.2.1", + // The Pending Pod may be included because the Endpoints was created manually. + // Or because the corresponding service has ".spec.publishNotReadyAddresses: true". + TargetRef: &v1.ObjectReference{ + Kind: "Pod", + Name: "testpod", + Namespace: "default", + }, + }, + }, + Ports: []v1.EndpointPort{ + { + Name: "mainport", + Port: 9000, + Protocol: v1.ProtocolTCP, + }, + }, + }, + }, + }, + pod, + } + n, c := makeDiscovery(RoleEndpoint, NamespaceDiscovery{}, objs...) + + k8sDiscoveryTest{ + discovery: n, + afterStart: func() { + // the Pod becomes Ready. + pod.Status.Phase = "Running" + pod.Status.Conditions = []v1.PodCondition{ + { + Type: v1.PodReady, + Status: v1.ConditionTrue, + }, + } + c.CoreV1().Pods(pod.Namespace).Update(context.Background(), pod, metav1.UpdateOptions{}) + }, + expectedMaxItems: 2, + expectedRes: map[string]*targetgroup.Group{ + "endpoints/default/testendpoints": { + Targets: []model.LabelSet{ + { + "__address__": "4.3.2.1:9000", + "__meta_kubernetes_endpoint_port_name": "mainport", + "__meta_kubernetes_endpoint_port_protocol": "TCP", + "__meta_kubernetes_endpoint_ready": "true", + "__meta_kubernetes_endpoint_address_target_kind": "Pod", + "__meta_kubernetes_endpoint_address_target_name": "testpod", + "__meta_kubernetes_pod_name": "testpod", + "__meta_kubernetes_pod_ip": "4.3.2.1", + "__meta_kubernetes_pod_ready": "true", + "__meta_kubernetes_pod_phase": "Running", + "__meta_kubernetes_pod_node_name": "testnode", + "__meta_kubernetes_pod_host_ip": "2.3.4.5", + "__meta_kubernetes_pod_container_name": "c1", + "__meta_kubernetes_pod_container_image": "c1:latest", + "__meta_kubernetes_pod_container_port_name": "mainport", + "__meta_kubernetes_pod_container_port_number": "9000", + "__meta_kubernetes_pod_container_port_protocol": "TCP", + "__meta_kubernetes_pod_uid": "deadbeef", + }, + }, + Labels: model.LabelSet{ + "__meta_kubernetes_namespace": "default", + "__meta_kubernetes_endpoints_name": "testendpoints", + }, + Source: "endpoints/default/testendpoints", + }, + }, + }.Run(t) +} diff --git a/discovery/kubernetes/endpointslice.go b/discovery/kubernetes/endpointslice.go index f977ce2626..116f02076f 100644 --- a/discovery/kubernetes/endpointslice.go +++ b/discovery/kubernetes/endpointslice.go @@ -358,7 +358,7 @@ func (e *EndpointSlice) buildEndpointSlice(eps endpointSliceAdaptor) *targetgrou tg.Targets = append(tg.Targets, target) return } - s := pod.Namespace + "/" + pod.Name + s := namespacedName(pod.Namespace, pod.Name) sp, ok := seenPods[s] if !ok { diff --git a/discovery/kubernetes/kubernetes.go b/discovery/kubernetes/kubernetes.go index 362e053267..489365fa46 100644 --- a/discovery/kubernetes/kubernetes.go +++ b/discovery/kubernetes/kubernetes.go @@ -767,6 +767,21 @@ func (d *Discovery) newPodsByNodeInformer(plw *cache.ListWatch) cache.SharedInde func (d *Discovery) newEndpointsByNodeInformer(plw *cache.ListWatch) cache.SharedIndexInformer { indexers := make(map[string]cache.IndexFunc) + indexers[podIndex] = func(obj interface{}) ([]string, error) { + e, ok := obj.(*apiv1.Endpoints) + if !ok { + return nil, fmt.Errorf("object is not endpoints") + } + var pods []string + for _, target := range e.Subsets { + for _, addr := range target.Addresses { + if addr.TargetRef != nil && addr.TargetRef.Kind == "Pod" { + pods = append(pods, namespacedName(addr.TargetRef.Namespace, addr.TargetRef.Name)) + } + } + } + return pods, nil + } if !d.attachMetadata.Node { return cache.NewSharedIndexInformer(plw, &apiv1.Endpoints{}, resyncDisabled, indexers) } @@ -872,3 +887,7 @@ func addObjectMetaLabels(labelSet model.LabelSet, objectMeta metav1.ObjectMeta, labelSet[model.LabelName(metaLabelPrefix+string(role)+"_annotationpresent_"+ln)] = presentValue } } + +func namespacedName(namespace, name string) string { + return namespace + "/" + name +} diff --git a/discovery/kubernetes/pod.go b/discovery/kubernetes/pod.go index 615717c138..02990e415f 100644 --- a/discovery/kubernetes/pod.go +++ b/discovery/kubernetes/pod.go @@ -33,7 +33,10 @@ import ( "github.com/prometheus/prometheus/discovery/targetgroup" ) -const nodeIndex = "node" +const ( + nodeIndex = "node" + podIndex = "pod" +) // Pod discovers new pod targets. type Pod struct { @@ -326,7 +329,7 @@ func podSource(pod *apiv1.Pod) string { } func podSourceFromNamespaceAndName(namespace, name string) string { - return "pod/" + namespace + "/" + name + return "pod/" + namespacedName(namespace, name) } func podReady(pod *apiv1.Pod) model.LabelValue { From 5f2c3a5d3e8a197aa26d62011845cfd6b9673a16 Mon Sep 17 00:00:00 2001 From: Mikhail Fesenko Date: Thu, 1 Feb 2024 14:30:50 +0100 Subject: [PATCH 053/237] Small improvements, add const, remove copypasta (#8106) Signed-off-by: Mikhail Fesenko Signed-off-by: Jesus Vazquez --- tsdb/index/index.go | 30 +++++++++++++++++------------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/tsdb/index/index.go b/tsdb/index/index.go index 36b8878bc4..cc57c3868e 100644 --- a/tsdb/index/index.go +++ b/tsdb/index/index.go @@ -50,6 +50,8 @@ const ( FormatV2 = 2 indexFilename = "index" + + seriesByteAlign = 16 ) type indexWriterSeries struct { @@ -436,11 +438,11 @@ func (w *Writer) AddSeries(ref storage.SeriesRef, lset labels.Labels, chunks ... } // We add padding to 16 bytes to increase the addressable space we get through 4 byte // series references. - if err := w.addPadding(16); err != nil { + if err := w.addPadding(seriesByteAlign); err != nil { return fmt.Errorf("failed to write padding bytes: %w", err) } - if w.f.pos%16 != 0 { + if w.f.pos%seriesByteAlign != 0 { return fmt.Errorf("series write not 16-byte aligned at %d", w.f.pos) } @@ -711,6 +713,7 @@ func (w *Writer) writeLabelIndexesOffsetTable() error { return err } } + // Write out the length. w.buf1.Reset() l := w.f.pos - startPos - 4 @@ -722,9 +725,7 @@ func (w *Writer) writeLabelIndexesOffsetTable() error { return err } - w.buf1.Reset() - w.buf1.PutHashSum(w.crc32) - return w.write(w.buf1.Get()) + return w.writeLenghtAndHash(startPos) } // writePostingsOffsetTable writes the postings offset table. @@ -792,6 +793,10 @@ func (w *Writer) writePostingsOffsetTable() error { } w.fPO = nil + return w.writeLenghtAndHash(startPos) +} + +func (w *Writer) writeLenghtAndHash(startPos uint64) error { // Write out the length. w.buf1.Reset() l := w.f.pos - startPos - 4 @@ -803,7 +808,7 @@ func (w *Writer) writePostingsOffsetTable() error { return err } - // Finally write the hash. + // Write out the hash. w.buf1.Reset() w.buf1.PutHashSum(w.crc32) return w.write(w.buf1.Get()) @@ -849,10 +854,10 @@ func (w *Writer) writePostingsToTmpFiles() error { for d.Len() > 0 { d.ConsumePadding() startPos := w.toc.LabelIndices - uint64(d.Len()) - if startPos%16 != 0 { + if startPos%seriesByteAlign != 0 { return fmt.Errorf("series not 16-byte aligned at %d", startPos) } - offsets = append(offsets, uint32(startPos/16)) + offsets = append(offsets, uint32(startPos/seriesByteAlign)) // Skip to next series. x := d.Uvarint() d.Skip(x + crc32.Size) @@ -911,7 +916,7 @@ func (w *Writer) writePostingsToTmpFiles() error { if _, ok := postings[lno]; !ok { postings[lno] = map[uint32][]uint32{} } - postings[lno][lvo] = append(postings[lno][lvo], uint32(startPos/16)) + postings[lno][lvo] = append(postings[lno][lvo], uint32(startPos/seriesByteAlign)) } } // Skip to next series. @@ -948,7 +953,6 @@ func (w *Writer) writePostingsToTmpFiles() error { return w.ctx.Err() default: } - } return nil } @@ -1556,7 +1560,7 @@ func (r *Reader) LabelNamesFor(ctx context.Context, ids ...storage.SeriesRef) ([ // In version 2 series IDs are no longer exact references but series are 16-byte padded // and the ID is the multiple of 16 of the actual position. if r.version == FormatV2 { - offset = id * 16 + offset = id * seriesByteAlign } d := encoding.NewDecbufUvarintAt(r.b, int(offset), castagnoliTable) @@ -1595,7 +1599,7 @@ func (r *Reader) LabelValueFor(ctx context.Context, id storage.SeriesRef, label // In version 2 series IDs are no longer exact references but series are 16-byte padded // and the ID is the multiple of 16 of the actual position. if r.version == FormatV2 { - offset = id * 16 + offset = id * seriesByteAlign } d := encoding.NewDecbufUvarintAt(r.b, int(offset), castagnoliTable) buf := d.Get() @@ -1621,7 +1625,7 @@ func (r *Reader) Series(id storage.SeriesRef, builder *labels.ScratchBuilder, ch // In version 2 series IDs are no longer exact references but series are 16-byte padded // and the ID is the multiple of 16 of the actual position. if r.version == FormatV2 { - offset = id * 16 + offset = id * seriesByteAlign } d := encoding.NewDecbufUvarintAt(r.b, int(offset), castagnoliTable) if d.Err() != nil { From d5eb636a89d5ecbffe1c8b866b186e5dc560cf49 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Szulik?= Date: Thu, 2 Sep 2021 17:54:19 +0200 Subject: [PATCH 054/237] Refactor cmd tests to use testify. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Paweł Szulik --- cmd/prometheus/main_test.go | 41 +++++++++++--------------------- cmd/prometheus/main_unix_test.go | 21 +++++++--------- cmd/promtool/unittest_test.go | 7 +++--- 3 files changed, 27 insertions(+), 42 deletions(-) diff --git a/cmd/prometheus/main_test.go b/cmd/prometheus/main_test.go index f4fe3855c6..03f3a9bc39 100644 --- a/cmd/prometheus/main_test.go +++ b/cmd/prometheus/main_test.go @@ -126,12 +126,9 @@ func TestFailedStartupExitCode(t *testing.T) { require.Error(t, err) var exitError *exec.ExitError - if errors.As(err, &exitError) { - status := exitError.Sys().(syscall.WaitStatus) - require.Equal(t, expectedExitStatus, status.ExitStatus()) - } else { - t.Errorf("unable to retrieve the exit status for prometheus: %v", err) - } + require.ErrorAs(t, err, &exitError) + status := exitError.Sys().(syscall.WaitStatus) + require.Equal(t, expectedExitStatus, status.ExitStatus()) } type senderFunc func(alerts ...*notifier.Alert) @@ -194,9 +191,7 @@ func TestSendAlerts(t *testing.T) { tc := tc t.Run(fmt.Sprintf("%d", i), func(t *testing.T) { senderFunc := senderFunc(func(alerts ...*notifier.Alert) { - if len(tc.in) == 0 { - t.Fatalf("sender called with 0 alert") - } + require.NotEmpty(t, tc.in, "sender called with 0 alert") require.Equal(t, tc.exp, alerts) }) rules.SendAlerts(senderFunc, "http://localhost:9090")(context.TODO(), "up", tc.in...) @@ -228,7 +223,7 @@ func TestWALSegmentSizeBounds(t *testing.T) { go func() { done <- prom.Wait() }() select { case err := <-done: - t.Errorf("prometheus should be still running: %v", err) + require.Fail(t, "prometheus should be still running: %v", err) case <-time.After(startupTime): prom.Process.Kill() <-done @@ -239,12 +234,9 @@ func TestWALSegmentSizeBounds(t *testing.T) { err = prom.Wait() require.Error(t, err) var exitError *exec.ExitError - if errors.As(err, &exitError) { - status := exitError.Sys().(syscall.WaitStatus) - require.Equal(t, expectedExitStatus, status.ExitStatus()) - } else { - t.Errorf("unable to retrieve the exit status for prometheus: %v", err) - } + require.ErrorAs(t, err, &exitError) + status := exitError.Sys().(syscall.WaitStatus) + require.Equal(t, expectedExitStatus, status.ExitStatus()) } } @@ -274,7 +266,7 @@ func TestMaxBlockChunkSegmentSizeBounds(t *testing.T) { go func() { done <- prom.Wait() }() select { case err := <-done: - t.Errorf("prometheus should be still running: %v", err) + require.Fail(t, "prometheus should be still running: %v", err) case <-time.After(startupTime): prom.Process.Kill() <-done @@ -285,12 +277,9 @@ func TestMaxBlockChunkSegmentSizeBounds(t *testing.T) { err = prom.Wait() require.Error(t, err) var exitError *exec.ExitError - if errors.As(err, &exitError) { - status := exitError.Sys().(syscall.WaitStatus) - require.Equal(t, expectedExitStatus, status.ExitStatus()) - } else { - t.Errorf("unable to retrieve the exit status for prometheus: %v", err) - } + require.ErrorAs(t, err, &exitError) + status := exitError.Sys().(syscall.WaitStatus) + require.Equal(t, expectedExitStatus, status.ExitStatus()) } } @@ -347,10 +336,8 @@ func getCurrentGaugeValuesFor(t *testing.T, reg prometheus.Gatherer, metricNames } require.Len(t, g.GetMetric(), 1) - if _, ok := res[m]; ok { - t.Error("expected only one metric family for", m) - t.FailNow() - } + _, ok := res[m] + require.False(t, ok, "expected only one metric family for", m) res[m] = *g.GetMetric()[0].GetGauge().Value } } diff --git a/cmd/prometheus/main_unix_test.go b/cmd/prometheus/main_unix_test.go index 417d062d66..59c5fe2bce 100644 --- a/cmd/prometheus/main_unix_test.go +++ b/cmd/prometheus/main_unix_test.go @@ -23,6 +23,8 @@ import ( "testing" "time" + "github.com/stretchr/testify/require" + "github.com/prometheus/prometheus/util/testutil" ) @@ -37,9 +39,7 @@ func TestStartupInterrupt(t *testing.T) { prom := exec.Command(promPath, "-test.main", "--config.file="+promConfig, "--storage.tsdb.path="+t.TempDir(), "--web.listen-address=0.0.0.0"+port) err := prom.Start() - if err != nil { - t.Fatalf("execution error: %v", err) - } + require.NoError(t, err) done := make(chan error, 1) go func() { @@ -68,14 +68,11 @@ Loop: time.Sleep(500 * time.Millisecond) } - if !startedOk { - t.Fatal("prometheus didn't start in the specified timeout") - } - switch err := prom.Process.Kill(); { - case err == nil: - t.Errorf("prometheus didn't shutdown gracefully after sending the Interrupt signal") - case stoppedErr != nil && stoppedErr.Error() != "signal: interrupt": - // TODO: find a better way to detect when the process didn't exit as expected! - t.Errorf("prometheus exited with an unexpected error: %v", stoppedErr) + require.True(t, startedOk, "prometheus didn't start in the specified timeout") + err = prom.Process.Kill() + require.Error(t, err, "prometheus didn't shutdown gracefully after sending the Interrupt signal") + // TODO - find a better way to detect when the process didn't exit as expected! + if stoppedErr != nil { + require.EqualError(t, stoppedErr, "signal: interrupt", "prometheus exited with an unexpected error: %v", stoppedErr) } } diff --git a/cmd/promtool/unittest_test.go b/cmd/promtool/unittest_test.go index b8170d784e..971ddb40c5 100644 --- a/cmd/promtool/unittest_test.go +++ b/cmd/promtool/unittest_test.go @@ -16,6 +16,8 @@ package main import ( "testing" + "github.com/stretchr/testify/require" + "github.com/prometheus/prometheus/promql" ) @@ -178,9 +180,8 @@ func TestRulesUnitTestRun(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if got := RulesUnitTest(tt.queryOpts, tt.args.run, false, tt.args.files...); got != tt.want { - t.Errorf("RulesUnitTest() = %v, want %v", got, tt.want) - } + got := RulesUnitTest(tt.queryOpts, tt.args.run, false, tt.args.files...) + require.Equal(t, tt.want, got) }) } } From 6e9cca8158e924e810a24e58dea57f2c0c132707 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Szulik?= Date: Fri, 3 Sep 2021 11:51:27 +0200 Subject: [PATCH 055/237] Refactor web tests to use testify. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Paweł Szulik --- web/api/v1/api_test.go | 129 +++++++++++------------------------------ web/web_test.go | 4 +- 2 files changed, 37 insertions(+), 96 deletions(-) diff --git a/web/api/v1/api_test.go b/web/api/v1/api_test.go index 3dc83548de..97a4c6220d 100644 --- a/web/api/v1/api_test.go +++ b/web/api/v1/api_test.go @@ -216,18 +216,11 @@ type rulesRetrieverMock struct { func (m *rulesRetrieverMock) CreateAlertingRules() { expr1, err := parser.ParseExpr(`absent(test_metric3) != 1`) - if err != nil { - m.testing.Fatalf("unable to parse alert expression: %s", err) - } + require.NoError(m.testing, err) expr2, err := parser.ParseExpr(`up == 1`) - if err != nil { - m.testing.Fatalf("Unable to parse alert expression: %s", err) - } - + require.NoError(m.testing, err) expr3, err := parser.ParseExpr(`vector(1)`) - if err != nil { - m.testing.Fatalf("Unable to parse alert expression: %s", err) - } + require.NoError(m.testing, err) rule1 := rules.NewAlertingRule( "test_metric3", @@ -302,9 +295,7 @@ func (m *rulesRetrieverMock) CreateRuleGroups() { } recordingExpr, err := parser.ParseExpr(`vector(1)`) - if err != nil { - m.testing.Fatalf("unable to parse alert expression: %s", err) - } + require.NoError(m.testing, err, "unable to parse alert expression: %s", err) recordingRule := rules.NewRecordingRule("recording-rule-1", recordingExpr, labels.Labels{}) r = append(r, recordingRule) @@ -714,9 +705,7 @@ func TestQueryExemplars(t *testing.T) { for _, te := range tc.exemplars { for _, e := range te.Exemplars { _, err := es.AppendExemplar(0, te.SeriesLabels, e) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) } } @@ -2832,9 +2821,7 @@ func testEndpoints(t *testing.T, api *API, tr *testTargetRetriever, es storage.E } req, err := request(method, test.query) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) tr.ResetMetadataStore() for _, tm := range test.metadata { @@ -2844,9 +2831,7 @@ func testEndpoints(t *testing.T, api *API, tr *testTargetRetriever, es storage.E for _, te := range test.exemplars { for _, e := range te.Exemplars { _, err := es.AppendExemplar(0, te.SeriesLabels, e) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) } } @@ -2882,17 +2867,11 @@ func describeAPIFunc(f apiFunc) string { func assertAPIError(t *testing.T, got *apiError, exp errorType) { t.Helper() - if got != nil { - if exp == errorNone { - t.Fatalf("Unexpected error: %s", got) - } - if exp != got.typ { - t.Fatalf("Expected error of type %q but got type %q (%q)", exp, got.typ, got) - } - return - } - if exp != errorNone { - t.Fatalf("Expected error of type %q but got none", exp) + if exp == errorNone { + require.Nil(t, got) + } else { + require.NotNil(t, got) + require.Equal(t, exp, got.typ, "(%q)", got) } } @@ -2906,13 +2885,7 @@ func assertAPIResponseLength(t *testing.T, got interface{}, expLen int) { t.Helper() gotLen := reflect.ValueOf(got).Len() - if gotLen != expLen { - t.Fatalf( - "Response length does not match, expected:\n%d\ngot:\n%d", - expLen, - gotLen, - ) - } + require.Equal(t, expLen, gotLen, "Response length does not match") } func assertAPIResponseMetadataLen(t *testing.T, got interface{}, expLen int) { @@ -2924,13 +2897,7 @@ func assertAPIResponseMetadataLen(t *testing.T, got interface{}, expLen int) { gotLen += len(m) } - if gotLen != expLen { - t.Fatalf( - "Amount of metadata in the response does not match, expected:\n%d\ngot:\n%d", - expLen, - gotLen, - ) - } + require.Equal(t, expLen, gotLen, "Amount of metadata in the response does not match") } type fakeDB struct { @@ -3271,26 +3238,18 @@ func TestRespondError(t *testing.T) { defer s.Close() resp, err := http.Get(s.URL) - if err != nil { - t.Fatalf("Error on test request: %s", err) - } + require.NoError(t, err, "Error on test request: %s", err) body, err := io.ReadAll(resp.Body) defer resp.Body.Close() - if err != nil { - t.Fatalf("Error reading response body: %s", err) - } - - if want, have := http.StatusServiceUnavailable, resp.StatusCode; want != have { - t.Fatalf("Return code %d expected in error response but got %d", want, have) - } - if h := resp.Header.Get("Content-Type"); h != "application/json" { - t.Fatalf("Expected Content-Type %q but got %q", "application/json", h) - } + require.NoError(t, err, "Error reading response body: %s", err) + want, have := http.StatusServiceUnavailable, resp.StatusCode + require.Equal(t, want, have, "Return code %d expected in error response but got %d", want, have) + h := resp.Header.Get("Content-Type") + require.Equal(t, "application/json", h, "Expected Content-Type %q but got %q", "application/json", h) var res Response - if err = json.Unmarshal(body, &res); err != nil { - t.Fatalf("Error unmarshaling JSON body: %s", err) - } + err = json.Unmarshal(body, &res) + require.NoError(t, err, "Error unmarshaling JSON body: %s", err) exp := &Response{ Status: statusError, @@ -3419,17 +3378,13 @@ func TestParseTime(t *testing.T) { for _, test := range tests { ts, err := parseTime(test.input) - if err != nil && !test.fail { - t.Errorf("Unexpected error for %q: %s", test.input, err) + if !test.fail { + require.NoError(t, err, "Unexpected error for %q: %s", test.input, err) + require.NotNil(t, ts) + require.True(t, ts.Equal(test.result), "Expected time %v for input %q but got %v", test.result, test.input, ts) continue } - if err == nil && test.fail { - t.Errorf("Expected error for %q but got none", test.input) - continue - } - if !test.fail && !ts.Equal(test.result) { - t.Errorf("Expected time %v for input %q but got %v", test.result, test.input, ts) - } + require.Error(t, err, "Expected error for %q but got none", test.input) } } @@ -3473,17 +3428,12 @@ func TestParseDuration(t *testing.T) { for _, test := range tests { d, err := parseDuration(test.input) - if err != nil && !test.fail { - t.Errorf("Unexpected error for %q: %s", test.input, err) + if !test.fail { + require.NoError(t, err, "Unexpected error for %q: %s", test.input, err) + require.Equal(t, test.result, d, "Expected duration %v for input %q but got %v", test.result, test.input, d) continue } - if err == nil && test.fail { - t.Errorf("Expected error for %q but got none", test.input) - continue - } - if !test.fail && d != test.result { - t.Errorf("Expected duration %v for input %q but got %v", test.result, test.input, d) - } + require.Error(t, err, "Expected error for %q but got none", test.input) } } @@ -3496,18 +3446,11 @@ func TestOptionsMethod(t *testing.T) { defer s.Close() req, err := http.NewRequest("OPTIONS", s.URL+"/any_path", nil) - if err != nil { - t.Fatalf("Error creating OPTIONS request: %s", err) - } + require.NoError(t, err, "Error creating OPTIONS request") client := &http.Client{} resp, err := client.Do(req) - if err != nil { - t.Fatalf("Error executing OPTIONS request: %s", err) - } - - if resp.StatusCode != http.StatusNoContent { - t.Fatalf("Expected status %d, got %d", http.StatusNoContent, resp.StatusCode) - } + require.NoError(t, err, "Error executing OPTIONS request") + require.Equal(t, http.StatusNoContent, resp.StatusCode) } func TestTSDBStatus(t *testing.T) { @@ -3546,9 +3489,7 @@ func TestTSDBStatus(t *testing.T) { api := &API{db: tc.db, gatherer: prometheus.DefaultGatherer} endpoint := tc.endpoint(api) req, err := http.NewRequest(tc.method, fmt.Sprintf("?%s", tc.values.Encode()), nil) - if err != nil { - t.Fatalf("Error when creating test request: %s", err) - } + require.NoError(t, err, "Error when creating test request") res := endpoint(req) assertAPIError(t, res.err, tc.errType) }) diff --git a/web/web_test.go b/web/web_test.go index 8832c28390..62bdb2ae31 100644 --- a/web/web_test.go +++ b/web/web_test.go @@ -440,7 +440,7 @@ func TestShutdownWithStaleConnection(t *testing.T) { select { case <-closed: case <-time.After(timeout + 5*time.Second): - t.Fatalf("Server still running after read timeout.") + require.FailNow(t, "Server still running after read timeout.") } } @@ -502,7 +502,7 @@ func TestHandleMultipleQuitRequests(t *testing.T) { select { case <-closed: case <-time.After(5 * time.Second): - t.Fatalf("Server still running after 5 seconds.") + require.FailNow(t, "Server still running after 5 seconds.") } } From b0c538787d9f89a7e3a9802223a51bcb84441485 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Szulik?= Date: Sat, 4 Sep 2021 14:35:03 +0200 Subject: [PATCH 056/237] Refactor scrape tests to use testify. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Paweł Szulik --- scrape/manager_test.go | 60 ++++++++------------ scrape/scrape_test.go | 122 ++++++++++++++++------------------------- scrape/target_test.go | 102 +++++++++------------------------- 3 files changed, 98 insertions(+), 186 deletions(-) diff --git a/scrape/manager_test.go b/scrape/manager_test.go index 524424269e..a73b730786 100644 --- a/scrape/manager_test.go +++ b/scrape/manager_test.go @@ -458,9 +458,9 @@ func loadConfiguration(t testing.TB, c string) *config.Config { t.Helper() cfg := &config.Config{} - if err := yaml.UnmarshalStrict([]byte(c), cfg); err != nil { - t.Fatalf("Unable to load YAML config: %s", err) - } + err := yaml.UnmarshalStrict([]byte(c), cfg) + require.NoError(t, err, "Unable to load YAML config.") + return cfg } @@ -533,42 +533,38 @@ scrape_configs: } // Apply the initial configuration. - if err := scrapeManager.ApplyConfig(cfg1); err != nil { - t.Fatalf("unable to apply configuration: %s", err) - } + err = scrapeManager.ApplyConfig(cfg1) + require.NoError(t, err, "Unable to apply configuration.") select { case <-ch: - t.Fatal("reload happened") + require.FailNow(t, "Reload happened.") default: } // Apply a configuration for which the reload fails. - if err := scrapeManager.ApplyConfig(cfg2); err == nil { - t.Fatalf("expecting error but got none") - } + err = scrapeManager.ApplyConfig(cfg2) + require.Error(t, err, "Expecting error but got none.") select { case <-ch: - t.Fatal("reload happened") + require.FailNow(t, "Reload happened.") default: } // Apply a configuration for which the reload succeeds. - if err := scrapeManager.ApplyConfig(cfg3); err != nil { - t.Fatalf("unable to apply configuration: %s", err) - } + err = scrapeManager.ApplyConfig(cfg3) + require.NoError(t, err, "Unable to apply configuration.") select { case <-ch: default: - t.Fatal("reload didn't happen") + require.FailNow(t, "Reload didn't happen.") } // Re-applying the same configuration shouldn't trigger a reload. - if err := scrapeManager.ApplyConfig(cfg3); err != nil { - t.Fatalf("unable to apply configuration: %s", err) - } + err = scrapeManager.ApplyConfig(cfg3) + require.NoError(t, err, "Unable to apply configuration.") select { case <-ch: - t.Fatal("reload happened") + require.FailNow(t, "Reload happened.") default: } } @@ -595,7 +591,7 @@ func TestManagerTargetsUpdates(t *testing.T) { select { case ts <- tgSent: case <-time.After(10 * time.Millisecond): - t.Error("Scrape manager's channel remained blocked after the set threshold.") + require.Fail(t, "Scrape manager's channel remained blocked after the set threshold.") } } @@ -609,7 +605,7 @@ func TestManagerTargetsUpdates(t *testing.T) { select { case <-m.triggerReload: default: - t.Error("No scrape loops reload was triggered after targets update.") + require.Fail(t, "No scrape loops reload was triggered after targets update.") } } @@ -622,9 +618,8 @@ global: ` cfg := &config.Config{} - if err := yaml.UnmarshalStrict([]byte(cfgText), cfg); err != nil { - t.Fatalf("Unable to load YAML config cfgYaml: %s", err) - } + err := yaml.UnmarshalStrict([]byte(cfgText), cfg) + require.NoError(t, err, "Unable to load YAML config cfgYaml.") return cfg } @@ -636,25 +631,18 @@ global: // Load the first config. cfg1 := getConfig("ha1") - if err := scrapeManager.setOffsetSeed(cfg1.GlobalConfig.ExternalLabels); err != nil { - t.Error(err) - } + err = scrapeManager.setOffsetSeed(cfg1.GlobalConfig.ExternalLabels) + require.NoError(t, err) offsetSeed1 := scrapeManager.offsetSeed - if offsetSeed1 == 0 { - t.Error("Offset seed has to be a hash of uint64") - } + require.NotZero(t, offsetSeed1, "Offset seed has to be a hash of uint64.") // Load the first config. cfg2 := getConfig("ha2") - if err := scrapeManager.setOffsetSeed(cfg2.GlobalConfig.ExternalLabels); err != nil { - t.Error(err) - } + require.NoError(t, scrapeManager.setOffsetSeed(cfg2.GlobalConfig.ExternalLabels)) offsetSeed2 := scrapeManager.offsetSeed - if offsetSeed1 == offsetSeed2 { - t.Error("Offset seed should not be the same on different set of external labels") - } + require.NotEqual(t, offsetSeed1, offsetSeed2, "Offset seed should not be the same on different set of external labels.") } func TestManagerScrapePools(t *testing.T) { diff --git a/scrape/scrape_test.go b/scrape/scrape_test.go index 95e4e182a0..f827ffc8da 100644 --- a/scrape/scrape_test.go +++ b/scrape/scrape_test.go @@ -72,15 +72,11 @@ func TestNewScrapePool(t *testing.T) { sp, _ = newScrapePool(cfg, app, 0, nil, nil, &Options{}, newTestScrapeMetrics(t)) ) - if a, ok := sp.appendable.(*nopAppendable); !ok || a != app { - t.Fatalf("Wrong sample appender") - } - if sp.config != cfg { - t.Fatalf("Wrong scrape config") - } - if sp.newLoop == nil { - t.Fatalf("newLoop function not initialized") - } + a, ok := sp.appendable.(*nopAppendable) + require.True(t, ok, "Failure to append.") + require.Equal(t, app, a, "Wrong sample appender.") + require.Equal(t, cfg, sp.config, "Wrong scrape config.") + require.NotNil(t, sp.newLoop, "newLoop function not initialized.") } func TestDroppedTargetsList(t *testing.T) { @@ -233,12 +229,10 @@ func TestScrapePoolStop(t *testing.T) { select { case <-time.After(5 * time.Second): - t.Fatalf("scrapeLoop.stop() did not return as expected") + require.Fail(t, "scrapeLoop.stop() did not return as expected") case <-done: // This should have taken at least as long as the last target slept. - if time.Since(stopTime) < time.Duration(numTargets*20)*time.Millisecond { - t.Fatalf("scrapeLoop.stop() exited before all targets stopped") - } + require.GreaterOrEqual(t, time.Since(stopTime), time.Duration(numTargets*20)*time.Millisecond, "scrapeLoop.stop() exited before all targets stopped") } mtx.Lock() @@ -324,12 +318,10 @@ func TestScrapePoolReload(t *testing.T) { select { case <-time.After(5 * time.Second): - t.Fatalf("scrapeLoop.reload() did not return as expected") + require.FailNow(t, "scrapeLoop.reload() did not return as expected") case <-done: // This should have taken at least as long as the last target slept. - if time.Since(reloadTime) < time.Duration(numTargets*20)*time.Millisecond { - t.Fatalf("scrapeLoop.stop() exited before all targets stopped") - } + require.GreaterOrEqual(t, time.Since(reloadTime), time.Duration(numTargets*20)*time.Millisecond, "scrapeLoop.stop() exited before all targets stopped") } mtx.Lock() @@ -703,13 +695,13 @@ func TestScrapeLoopStopBeforeRun(t *testing.T) { select { case <-stopDone: - t.Fatalf("Stopping terminated before run exited successfully") + require.FailNow(t, "Stopping terminated before run exited successfully.") case <-time.After(500 * time.Millisecond): } // Running the scrape loop must exit before calling the scraper even once. scraper.scrapeFunc = func(context.Context, io.Writer) error { - t.Fatalf("scraper was called for terminated scrape loop") + require.FailNow(t, "Scraper was called for terminated scrape loop.") return nil } @@ -722,13 +714,13 @@ func TestScrapeLoopStopBeforeRun(t *testing.T) { select { case <-runDone: case <-time.After(1 * time.Second): - t.Fatalf("Running terminated scrape loop did not exit") + require.FailNow(t, "Running terminated scrape loop did not exit.") } select { case <-stopDone: case <-time.After(1 * time.Second): - t.Fatalf("Stopping did not terminate after running exited") + require.FailNow(t, "Stopping did not terminate after running exited.") } } @@ -765,14 +757,13 @@ func TestScrapeLoopStop(t *testing.T) { select { case <-signal: case <-time.After(5 * time.Second): - t.Fatalf("Scrape wasn't stopped.") + require.FailNow(t, "Scrape wasn't stopped.") } // We expected 1 actual sample for each scrape plus 5 for report samples. // At least 2 scrapes were made, plus the final stale markers. - if len(appender.resultFloats) < 6*3 || len(appender.resultFloats)%6 != 0 { - t.Fatalf("Expected at least 3 scrapes with 6 samples each, got %d samples", len(appender.resultFloats)) - } + require.GreaterOrEqual(t, len(appender.resultFloats), 6*3, "Expected at least 3 scrapes with 6 samples each.") + require.Zero(t, len(appender.resultFloats)%6, "There is a scrape with missing samples.") // All samples in a scrape must have the same timestamp. var ts int64 for i, s := range appender.resultFloats { @@ -785,9 +776,7 @@ func TestScrapeLoopStop(t *testing.T) { } // All samples from the last scrape must be stale markers. for _, s := range appender.resultFloats[len(appender.resultFloats)-5:] { - if !value.IsStaleNaN(s.f) { - t.Fatalf("Appended last sample not as expected. Wanted: stale NaN Got: %x", math.Float64bits(s.f)) - } + require.True(t, value.IsStaleNaN(s.f), "Appended last sample not as expected. Wanted: stale NaN Got: %x", math.Float64bits(s.f)) } } @@ -843,9 +832,9 @@ func TestScrapeLoopRun(t *testing.T) { select { case <-signal: case <-time.After(5 * time.Second): - t.Fatalf("Cancellation during initial offset failed") + require.FailNow(t, "Cancellation during initial offset failed.") case err := <-errc: - t.Fatalf("Unexpected error: %s", err) + require.FailNow(t, "Unexpected error: %s", err) } // The provided timeout must cause cancellation of the context passed down to the @@ -873,11 +862,9 @@ func TestScrapeLoopRun(t *testing.T) { select { case err := <-errc: - if !errors.Is(err, context.DeadlineExceeded) { - t.Fatalf("Expected timeout error but got: %s", err) - } + require.ErrorIs(t, err, context.DeadlineExceeded) case <-time.After(3 * time.Second): - t.Fatalf("Expected timeout error but got none") + require.FailNow(t, "Expected timeout error but got none.") } // We already caught the timeout error and are certainly in the loop. @@ -890,9 +877,9 @@ func TestScrapeLoopRun(t *testing.T) { case <-signal: // Loop terminated as expected. case err := <-errc: - t.Fatalf("Unexpected error: %s", err) + require.FailNow(t, "Unexpected error: %s", err) case <-time.After(3 * time.Second): - t.Fatalf("Loop did not terminate on context cancellation") + require.FailNow(t, "Loop did not terminate on context cancellation") } } @@ -912,7 +899,7 @@ func TestScrapeLoopForcedErr(t *testing.T) { sl.setForcedError(forcedErr) scraper.scrapeFunc = func(context.Context, io.Writer) error { - t.Fatalf("should not be scraped") + require.FailNow(t, "Should not be scraped.") return nil } @@ -923,18 +910,16 @@ func TestScrapeLoopForcedErr(t *testing.T) { select { case err := <-errc: - if !errors.Is(err, forcedErr) { - t.Fatalf("Expected forced error but got: %s", err) - } + require.ErrorIs(t, err, forcedErr) case <-time.After(3 * time.Second): - t.Fatalf("Expected forced error but got none") + require.FailNow(t, "Expected forced error but got none.") } cancel() select { case <-signal: case <-time.After(5 * time.Second): - t.Fatalf("Scrape not stopped") + require.FailNow(t, "Scrape not stopped.") } } @@ -1141,7 +1126,7 @@ func TestScrapeLoopRunCreatesStaleMarkersOnFailedScrape(t *testing.T) { select { case <-signal: case <-time.After(5 * time.Second): - t.Fatalf("Scrape wasn't stopped.") + require.FailNow(t, "Scrape wasn't stopped.") } // 1 successfully scraped sample, 1 stale marker after first fail, 5 report samples for @@ -1188,7 +1173,7 @@ func TestScrapeLoopRunCreatesStaleMarkersOnParseFailure(t *testing.T) { select { case <-signal: case <-time.After(5 * time.Second): - t.Fatalf("Scrape wasn't stopped.") + require.FailNow(t, "Scrape wasn't stopped.") } // 1 successfully scraped sample, 1 stale marker after first fail, 5 report samples for @@ -1220,19 +1205,15 @@ func TestScrapeLoopCache(t *testing.T) { scraper.scrapeFunc = func(ctx context.Context, w io.Writer) error { switch numScrapes { case 1, 2: - if _, ok := sl.cache.series["metric_a"]; !ok { - t.Errorf("metric_a missing from cache after scrape %d", numScrapes) - } - if _, ok := sl.cache.series["metric_b"]; !ok { - t.Errorf("metric_b missing from cache after scrape %d", numScrapes) - } + _, ok := sl.cache.series["metric_a"] + require.True(t, ok, "metric_a missing from cache after scrape %d", numScrapes) + _, ok = sl.cache.series["metric_b"] + require.True(t, ok, "metric_b missing from cache after scrape %d", numScrapes) case 3: - if _, ok := sl.cache.series["metric_a"]; !ok { - t.Errorf("metric_a missing from cache after scrape %d", numScrapes) - } - if _, ok := sl.cache.series["metric_b"]; ok { - t.Errorf("metric_b present in cache after scrape %d", numScrapes) - } + _, ok := sl.cache.series["metric_a"] + require.True(t, ok, "metric_a missing from cache after scrape %d", numScrapes) + _, ok = sl.cache.series["metric_b"] + require.False(t, ok, "metric_b present in cache after scrape %d", numScrapes) } numScrapes++ @@ -1257,7 +1238,7 @@ func TestScrapeLoopCache(t *testing.T) { select { case <-signal: case <-time.After(5 * time.Second): - t.Fatalf("Scrape wasn't stopped.") + require.FailNow(t, "Scrape wasn't stopped.") } // 1 successfully scraped sample, 1 stale marker after first fail, 5 report samples for @@ -1305,12 +1286,10 @@ func TestScrapeLoopCacheMemoryExhaustionProtection(t *testing.T) { select { case <-signal: case <-time.After(5 * time.Second): - t.Fatalf("Scrape wasn't stopped.") + require.FailNow(t, "Scrape wasn't stopped.") } - if len(sl.cache.series) > 2000 { - t.Fatalf("More than 2000 series cached. Got: %d", len(sl.cache.series)) - } + require.LessOrEqual(t, len(sl.cache.series), 2000, "More than 2000 series cached.") } func TestScrapeLoopAppend(t *testing.T) { @@ -1541,9 +1520,7 @@ func TestScrapeLoopAppendSampleLimit(t *testing.T) { now := time.Now() slApp := sl.appender(context.Background()) total, added, seriesAdded, err := sl.append(app, []byte("metric_a 1\nmetric_b 1\nmetric_c 1\n"), "", now) - if !errors.Is(err, errSampleLimit) { - t.Fatalf("Did not see expected sample limit error: %s", err) - } + require.ErrorIs(t, err, errSampleLimit) require.NoError(t, slApp.Rollback()) require.Equal(t, 3, total) require.Equal(t, 3, added) @@ -1572,9 +1549,7 @@ func TestScrapeLoopAppendSampleLimit(t *testing.T) { now = time.Now() slApp = sl.appender(context.Background()) total, added, seriesAdded, err = sl.append(slApp, []byte("metric_a 1\nmetric_b 1\nmetric_c{deleteme=\"yes\"} 1\nmetric_d 1\nmetric_e 1\nmetric_f 1\nmetric_g 1\nmetric_h{deleteme=\"yes\"} 1\nmetric_i{deleteme=\"yes\"} 1\n"), "", now) - if !errors.Is(err, errSampleLimit) { - t.Fatalf("Did not see expected sample limit error: %s", err) - } + require.ErrorIs(t, err, errSampleLimit) require.NoError(t, slApp.Rollback()) require.Equal(t, 9, total) require.Equal(t, 6, added) @@ -2357,15 +2332,12 @@ func TestTargetScraperScrapeOK(t *testing.T) { http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if protobufParsing { accept := r.Header.Get("Accept") - if !strings.HasPrefix(accept, "application/vnd.google.protobuf;") { - t.Errorf("Expected Accept header to prefer application/vnd.google.protobuf, got %q", accept) - } + require.True(t, strings.HasPrefix(accept, "application/vnd.google.protobuf;"), + "Expected Accept header to prefer application/vnd.google.protobuf.") } timeout := r.Header.Get("X-Prometheus-Scrape-Timeout-Seconds") - if timeout != expectedTimeout { - t.Errorf("Expected scrape timeout header %q, got %q", expectedTimeout, timeout) - } + require.Equal(t, expectedTimeout, timeout, "Expected scrape timeout header.") w.Header().Set("Content-Type", `text/plain; version=0.0.4`) w.Write([]byte("metric_a 1\nmetric_b 2\n")) @@ -2453,7 +2425,7 @@ func TestTargetScrapeScrapeCancel(t *testing.T) { select { case <-time.After(5 * time.Second): - t.Fatalf("Scrape function did not return unexpectedly") + require.FailNow(t, "Scrape function did not return unexpectedly.") case err := <-errc: require.NoError(t, err) } @@ -3053,7 +3025,7 @@ func TestScrapeReportSingleAppender(t *testing.T) { select { case <-signal: case <-time.After(5 * time.Second): - t.Fatalf("Scrape wasn't stopped.") + require.FailNow(t, "Scrape wasn't stopped.") } } diff --git a/scrape/target_test.go b/scrape/target_test.go index dac502a80e..6e87ce71d9 100644 --- a/scrape/target_test.go +++ b/scrape/target_test.go @@ -77,9 +77,7 @@ func TestTargetOffset(t *testing.T) { buckets := make([]int, interval/bucketSize) for _, offset := range offsets { - if offset < 0 || offset >= interval { - t.Fatalf("Offset %v out of bounds", offset) - } + require.InDelta(t, time.Duration(0), offset, float64(interval), "Offset %v out of bounds.", offset) bucket := offset / bucketSize buckets[bucket]++ @@ -98,9 +96,7 @@ func TestTargetOffset(t *testing.T) { diff = -diff } - if float64(diff)/float64(avg) > tolerance { - t.Fatalf("Bucket out of tolerance bounds") - } + require.LessOrEqual(t, float64(diff)/float64(avg), tolerance, "Bucket out of tolerance bounds.") } } @@ -150,9 +146,7 @@ func TestNewHTTPBearerToken(t *testing.T) { func(w http.ResponseWriter, r *http.Request) { expected := "Bearer 1234" received := r.Header.Get("Authorization") - if expected != received { - t.Fatalf("Authorization header was not set correctly: expected '%v', got '%v'", expected, received) - } + require.Equal(t, expected, received, "Authorization header was not set correctly.") }, ), ) @@ -162,13 +156,9 @@ func TestNewHTTPBearerToken(t *testing.T) { BearerToken: "1234", } c, err := config_util.NewClientFromConfig(cfg, "test") - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) _, err = c.Get(server.URL) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) } func TestNewHTTPBearerTokenFile(t *testing.T) { @@ -177,9 +167,7 @@ func TestNewHTTPBearerTokenFile(t *testing.T) { func(w http.ResponseWriter, r *http.Request) { expected := "Bearer 12345" received := r.Header.Get("Authorization") - if expected != received { - t.Fatalf("Authorization header was not set correctly: expected '%v', got '%v'", expected, received) - } + require.Equal(t, expected, received, "Authorization header was not set correctly.") }, ), ) @@ -189,13 +177,9 @@ func TestNewHTTPBearerTokenFile(t *testing.T) { BearerTokenFile: "testdata/bearertoken.txt", } c, err := config_util.NewClientFromConfig(cfg, "test") - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) _, err = c.Get(server.URL) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) } func TestNewHTTPBasicAuth(t *testing.T) { @@ -203,9 +187,9 @@ func TestNewHTTPBasicAuth(t *testing.T) { http.HandlerFunc( func(w http.ResponseWriter, r *http.Request) { username, password, ok := r.BasicAuth() - if !(ok && username == "user" && password == "password123") { - t.Fatalf("Basic authorization header was not set correctly: expected '%v:%v', got '%v:%v'", "user", "password123", username, password) - } + require.True(t, ok, "Basic authorization header was not set correctly.") + require.Equal(t, "user", username) + require.Equal(t, "password123", password) }, ), ) @@ -218,13 +202,9 @@ func TestNewHTTPBasicAuth(t *testing.T) { }, } c, err := config_util.NewClientFromConfig(cfg, "test") - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) _, err = c.Get(server.URL) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) } func TestNewHTTPCACert(t *testing.T) { @@ -246,13 +226,9 @@ func TestNewHTTPCACert(t *testing.T) { }, } c, err := config_util.NewClientFromConfig(cfg, "test") - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) _, err = c.Get(server.URL) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) } func TestNewHTTPClientCert(t *testing.T) { @@ -279,13 +255,9 @@ func TestNewHTTPClientCert(t *testing.T) { }, } c, err := config_util.NewClientFromConfig(cfg, "test") - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) _, err = c.Get(server.URL) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) } func TestNewHTTPWithServerName(t *testing.T) { @@ -308,13 +280,9 @@ func TestNewHTTPWithServerName(t *testing.T) { }, } c, err := config_util.NewClientFromConfig(cfg, "test") - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) _, err = c.Get(server.URL) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) } func TestNewHTTPWithBadServerName(t *testing.T) { @@ -337,31 +305,23 @@ func TestNewHTTPWithBadServerName(t *testing.T) { }, } c, err := config_util.NewClientFromConfig(cfg, "test") - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) _, err = c.Get(server.URL) - if err == nil { - t.Fatal("Expected error, got nil.") - } + require.Error(t, err) } func newTLSConfig(certName string, t *testing.T) *tls.Config { tlsConfig := &tls.Config{} caCertPool := x509.NewCertPool() caCert, err := os.ReadFile(caCertPath) - if err != nil { - t.Fatalf("Couldn't set up TLS server: %v", err) - } + require.NoError(t, err, "Couldn't read CA cert.") caCertPool.AppendCertsFromPEM(caCert) tlsConfig.RootCAs = caCertPool tlsConfig.ServerName = "127.0.0.1" certPath := fmt.Sprintf("testdata/%s.cer", certName) keyPath := fmt.Sprintf("testdata/%s.key", certName) cert, err := tls.LoadX509KeyPair(certPath, keyPath) - if err != nil { - t.Errorf("Unable to use specified server cert (%s) & key (%v): %s", certPath, keyPath, err) - } + require.NoError(t, err, "Unable to use specified server cert (%s) & key (%v).", certPath, keyPath) tlsConfig.Certificates = []tls.Certificate{cert} return tlsConfig } @@ -375,9 +335,7 @@ func TestNewClientWithBadTLSConfig(t *testing.T) { }, } _, err := config_util.NewClientFromConfig(cfg, "test") - if err == nil { - t.Fatalf("Expected error, got nil.") - } + require.Error(t, err) } func TestTargetsFromGroup(t *testing.T) { @@ -389,15 +347,9 @@ func TestTargetsFromGroup(t *testing.T) { } lb := labels.NewBuilder(labels.EmptyLabels()) targets, failures := TargetsFromGroup(&targetgroup.Group{Targets: []model.LabelSet{{}, {model.AddressLabel: "localhost:9090"}}}, &cfg, false, nil, lb) - if len(targets) != 1 { - t.Fatalf("Expected 1 target, got %v", len(targets)) - } - if len(failures) != 1 { - t.Fatalf("Expected 1 failure, got %v", len(failures)) - } - if failures[0].Error() != expectedError { - t.Fatalf("Expected error %s, got %s", expectedError, failures[0]) - } + require.Len(t, targets, 1) + require.Len(t, failures, 1) + require.EqualError(t, failures[0], expectedError) } func BenchmarkTargetsFromGroup(b *testing.B) { From 1a47c7d59bef3c8aac2be0b5d65517ea1ae1ddeb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Szulik?= Date: Wed, 15 Sep 2021 14:22:37 +0200 Subject: [PATCH 057/237] Refactor lexer tests to use testify. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Paweł Szulik --- promql/parser/lex_test.go | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/promql/parser/lex_test.go b/promql/parser/lex_test.go index 23c9dfbee0..4a29351b06 100644 --- a/promql/parser/lex_test.go +++ b/promql/parser/lex_test.go @@ -815,16 +815,10 @@ func TestLexer(t *testing.T) { hasError = true } } - if !hasError { - t.Logf("%d: input %q", i, test.input) - require.Fail(t, "expected lexing error but did not fail") - } + require.True(t, hasError, "%d: input %q, expected lexing error but did not fail", i, test.input) continue } - if lastItem.Typ == ERROR { - t.Logf("%d: input %q", i, test.input) - require.Fail(t, "unexpected lexing error at position %d: %s", lastItem.Pos, lastItem) - } + require.NotEqual(t, ERROR, lastItem.Typ, "%d: input %q, unexpected lexing error at position %d: %s", i, test.input, lastItem.Pos, lastItem) eofItem := Item{EOF, posrange.Pos(len(test.input)), ""} require.Equal(t, lastItem, eofItem, "%d: input %q", i, test.input) From 16e68c01e4bf0df3da3a02a214f0076bed911d4d Mon Sep 17 00:00:00 2001 From: Bryan Boreham Date: Thu, 1 Feb 2024 14:18:01 +0000 Subject: [PATCH 058/237] tests: remove err from message when testify prints it already For instance `require.NoError` will print the unexpected error; we don't need to include it in the message. Signed-off-by: Bryan Boreham --- cmd/prometheus/main_unix_test.go | 2 +- tsdb/db_test.go | 2 +- tsdb/fileutil/flock_test.go | 12 ++++++------ tsdb/querier_test.go | 2 +- tsdb/wlog/reader_test.go | 7 +++---- web/api/v1/api_test.go | 12 ++++++------ 6 files changed, 18 insertions(+), 19 deletions(-) diff --git a/cmd/prometheus/main_unix_test.go b/cmd/prometheus/main_unix_test.go index 59c5fe2bce..2011fb123f 100644 --- a/cmd/prometheus/main_unix_test.go +++ b/cmd/prometheus/main_unix_test.go @@ -73,6 +73,6 @@ Loop: require.Error(t, err, "prometheus didn't shutdown gracefully after sending the Interrupt signal") // TODO - find a better way to detect when the process didn't exit as expected! if stoppedErr != nil { - require.EqualError(t, stoppedErr, "signal: interrupt", "prometheus exited with an unexpected error: %v", stoppedErr) + require.EqualError(t, stoppedErr, "signal: interrupt", "prometheus exit") } } diff --git a/tsdb/db_test.go b/tsdb/db_test.go index fb36bbb868..70b12d487a 100644 --- a/tsdb/db_test.go +++ b/tsdb/db_test.go @@ -3480,7 +3480,7 @@ func testQuerierShouldNotPanicIfHeadChunkIsTruncatedWhileReadingQueriedChunks(t // when the iterator tries to fetch an head chunk which has been offloaded because // of the head compaction in the meanwhile. if firstErr != nil { - require.ErrorContains(t, firstErr, "cannot populate chunk", "unexpected error: %s", firstErr.Error()) + require.ErrorContains(t, firstErr, "cannot populate chunk") } } diff --git a/tsdb/fileutil/flock_test.go b/tsdb/fileutil/flock_test.go index 240e0954b1..7aff789a26 100644 --- a/tsdb/fileutil/flock_test.go +++ b/tsdb/fileutil/flock_test.go @@ -33,12 +33,12 @@ func TestLocking(t *testing.T) { require.Error(t, err, "File %q unexpectedly exists.", fileName) lock, existed, err := Flock(fileName) - require.NoError(t, err, "Error locking file %q: %s", fileName, err) + require.NoError(t, err, "Error locking file %q", fileName) require.False(t, existed, "File %q reported as existing during locking.", fileName) // File must now exist. _, err = os.Stat(fileName) - require.NoError(t, err, "Could not stat file %q expected to exist: %s", fileName, err) + require.NoError(t, err, "Could not stat file %q expected to exist", fileName) // Try to lock again. lockedAgain, existed, err := Flock(fileName) @@ -47,17 +47,17 @@ func TestLocking(t *testing.T) { require.True(t, existed, "Existing file %q not recognized.", fileName) err = lock.Release() - require.NoError(t, err, "Error releasing lock for file %q: %s", fileName, err) + require.NoError(t, err, "Error releasing lock for file %q", fileName) // File must still exist. _, err = os.Stat(fileName) - require.NoError(t, err, "Could not stat file %q expected to exist: %s", fileName, err) + require.NoError(t, err, "Could not stat file %q expected to exist", fileName) // Lock existing file. lock, existed, err = Flock(fileName) - require.NoError(t, err, "Error locking file %q: %s", fileName, err) + require.NoError(t, err, "Error locking file %q", fileName) require.True(t, existed, "Existing file %q not recognized.", fileName) err = lock.Release() - require.NoError(t, err, "Error releasing lock for file %q: %s", fileName, err) + require.NoError(t, err, "Error releasing lock for file %q", fileName) } diff --git a/tsdb/querier_test.go b/tsdb/querier_test.go index 6e1a19ee4b..93704809b9 100644 --- a/tsdb/querier_test.go +++ b/tsdb/querier_test.go @@ -3084,7 +3084,7 @@ func TestClose(t *testing.T) { createBlock(t, dir, genSeries(1, 1, 10, 20)) db, err := Open(dir, nil, nil, DefaultOptions(), nil) - require.NoError(t, err, "Opening test storage failed: %s", err) + require.NoError(t, err, "Opening test storage failed: %s") defer func() { require.NoError(t, db.Close()) }() diff --git a/tsdb/wlog/reader_test.go b/tsdb/wlog/reader_test.go index 1546a97527..484eff3664 100644 --- a/tsdb/wlog/reader_test.go +++ b/tsdb/wlog/reader_test.go @@ -186,10 +186,9 @@ func TestReader(t *testing.T) { require.Equal(t, c.exp[j], rec, "Bytes within record did not match expected Bytes") } if !c.fail { - require.NoError(t, r.Err(), "unexpected error: %s", r.Err()) - } - if c.fail { - require.Error(t, r.Err(), "expected error but got none") + require.NoError(t, r.Err()) + } else { + require.Error(t, r.Err()) } }) } diff --git a/web/api/v1/api_test.go b/web/api/v1/api_test.go index 97a4c6220d..eceadb20ae 100644 --- a/web/api/v1/api_test.go +++ b/web/api/v1/api_test.go @@ -295,7 +295,7 @@ func (m *rulesRetrieverMock) CreateRuleGroups() { } recordingExpr, err := parser.ParseExpr(`vector(1)`) - require.NoError(m.testing, err, "unable to parse alert expression: %s", err) + require.NoError(m.testing, err, "unable to parse alert expression") recordingRule := rules.NewRecordingRule("recording-rule-1", recordingExpr, labels.Labels{}) r = append(r, recordingRule) @@ -3238,10 +3238,10 @@ func TestRespondError(t *testing.T) { defer s.Close() resp, err := http.Get(s.URL) - require.NoError(t, err, "Error on test request: %s", err) + require.NoError(t, err, "Error on test request") body, err := io.ReadAll(resp.Body) defer resp.Body.Close() - require.NoError(t, err, "Error reading response body: %s", err) + require.NoError(t, err, "Error reading response body") want, have := http.StatusServiceUnavailable, resp.StatusCode require.Equal(t, want, have, "Return code %d expected in error response but got %d", want, have) h := resp.Header.Get("Content-Type") @@ -3249,7 +3249,7 @@ func TestRespondError(t *testing.T) { var res Response err = json.Unmarshal(body, &res) - require.NoError(t, err, "Error unmarshaling JSON body: %s", err) + require.NoError(t, err, "Error unmarshaling JSON body") exp := &Response{ Status: statusError, @@ -3379,7 +3379,7 @@ func TestParseTime(t *testing.T) { for _, test := range tests { ts, err := parseTime(test.input) if !test.fail { - require.NoError(t, err, "Unexpected error for %q: %s", test.input, err) + require.NoError(t, err, "Unexpected error for %q", test.input) require.NotNil(t, ts) require.True(t, ts.Equal(test.result), "Expected time %v for input %q but got %v", test.result, test.input, ts) continue @@ -3429,7 +3429,7 @@ func TestParseDuration(t *testing.T) { for _, test := range tests { d, err := parseDuration(test.input) if !test.fail { - require.NoError(t, err, "Unexpected error for %q: %s", test.input, err) + require.NoError(t, err, "Unexpected error for %q", test.input) require.Equal(t, test.result, d, "Expected duration %v for input %q but got %v", test.result, test.input, d) continue } From 46008fdecde9e45f3002425574395ee008dd4ff0 Mon Sep 17 00:00:00 2001 From: Bryan Boreham Date: Thu, 1 Feb 2024 14:57:11 +0000 Subject: [PATCH 059/237] lint Signed-off-by: Bryan Boreham --- discovery/consul/consul_test.go | 1 - discovery/marathon/marathon_test.go | 38 ++++++++++++++--------------- 2 files changed, 19 insertions(+), 20 deletions(-) diff --git a/discovery/consul/consul_test.go b/discovery/consul/consul_test.go index ee55abdc91..a7763e16d5 100644 --- a/discovery/consul/consul_test.go +++ b/discovery/consul/consul_test.go @@ -61,7 +61,6 @@ func TestConfiguredService(t *testing.T) { "Expected service %s to be watched", "configuredServiceName") require.False(t, consulDiscovery.shouldWatch("nonConfiguredServiceName", []string{""}), "Expected service %s to not be watched", "nonConfiguredServiceName") - } func TestConfiguredServiceWithTag(t *testing.T) { diff --git a/discovery/marathon/marathon_test.go b/discovery/marathon/marathon_test.go index d177ae49ff..c9555e9d31 100644 --- a/discovery/marathon/marathon_test.go +++ b/discovery/marathon/marathon_test.go @@ -112,11 +112,11 @@ func TestMarathonSDSendGroup(t *testing.T) { } tgs, err := testUpdateServices(client) require.NoError(t, err) - require.Equal(t, 1, len(tgs), "Expected 1 target group.") + require.Len(t, tgs, 1, "Expected 1 target group.") tg := tgs[0] require.Equal(t, "test-service", tg.Source, "Wrong target group name.") - require.Equal(t, 1, len(tg.Targets), "Expected 1 target.") + require.Len(t, tg.Targets, 1, "Expected 1 target.") tgt := tg.Targets[0] require.Equal(t, "mesos-slave1:31000", string(tgt[model.AddressLabel]), "Wrong target address.") @@ -140,7 +140,7 @@ func TestMarathonSDRemoveApp(t *testing.T) { } tgs, err := md.refresh(context.Background()) require.NoError(t, err, "Got error on first update.") - require.Equal(t, 1, len(tgs), "Expected 1 targetgroup.") + require.Len(t, tgs, 1, "Expected 1 targetgroup.") tg1 := tgs[0] md.appsClient = func(_ context.Context, _ *http.Client, _ string) (*appList, error) { @@ -148,7 +148,7 @@ func TestMarathonSDRemoveApp(t *testing.T) { } tgs, err = md.refresh(context.Background()) require.NoError(t, err, "Got error on second update.") - require.Equal(t, 1, len(tgs), "Expected 1 targetgroup.") + require.Len(t, tgs, 1, "Expected 1 targetgroup.") tg2 := tgs[0] @@ -189,11 +189,11 @@ func TestMarathonSDSendGroupWithMultiplePort(t *testing.T) { } tgs, err := testUpdateServices(client) require.NoError(t, err) - require.Equal(t, 1, len(tgs), "Expected 1 target group.") + require.Len(t, tgs, 1, "Expected 1 target group.") tg := tgs[0] require.Equal(t, "test-service", tg.Source, "Wrong target group name.") - require.Equal(t, 2, len(tg.Targets), "Wrong number of targets.") + require.Len(t, tg.Targets, 2, "Wrong number of targets.") tgt := tg.Targets[0] require.Equal(t, "mesos-slave1:31000", string(tgt[model.AddressLabel]), "Wrong target address.") @@ -234,7 +234,7 @@ func TestMarathonZeroTaskPorts(t *testing.T) { } tgs, err := testUpdateServices(client) require.NoError(t, err) - require.Equal(t, 1, len(tgs), "Expected 1 target group.") + require.Len(t, tgs, 1, "Expected 1 target group.") tg := tgs[0] require.Equal(t, "test-service-zero-ports", tg.Source, "Wrong target group name.") @@ -292,11 +292,11 @@ func TestMarathonSDSendGroupWithPortDefinitions(t *testing.T) { } tgs, err := testUpdateServices(client) require.NoError(t, err) - require.Equal(t, 1, len(tgs), "Expected 1 target group.") + require.Len(t, tgs, 1, "Expected 1 target group.") tg := tgs[0] require.Equal(t, "test-service", tg.Source, "Wrong target group name.") - require.Equal(t, 2, len(tg.Targets), "Wrong number of targets.") + require.Len(t, tg.Targets, 2, "Wrong number of targets.") tgt := tg.Targets[0] require.Equal(t, "mesos-slave1:1234", string(tgt[model.AddressLabel]), "Wrong target address.") @@ -346,11 +346,11 @@ func TestMarathonSDSendGroupWithPortDefinitionsRequirePorts(t *testing.T) { } tgs, err := testUpdateServices(client) require.NoError(t, err) - require.Equal(t, 1, len(tgs), "Expected 1 target group.") + require.Len(t, tgs, 1, "Expected 1 target group.") tg := tgs[0] require.Equal(t, "test-service", tg.Source, "Wrong target group name.") - require.Equal(t, 2, len(tg.Targets), "Wrong number of targets.") + require.Len(t, tg.Targets, 2, "Wrong number of targets.") tgt := tg.Targets[0] require.Equal(t, "mesos-slave1:31000", string(tgt[model.AddressLabel]), "Wrong target address.") @@ -393,11 +393,11 @@ func TestMarathonSDSendGroupWithPorts(t *testing.T) { } tgs, err := testUpdateServices(client) require.NoError(t, err) - require.Equal(t, 1, len(tgs), "Expected 1 target group.") + require.Len(t, tgs, 1, "Expected 1 target group.") tg := tgs[0] require.Equal(t, "test-service", tg.Source, "Wrong target group name.") - require.Equal(t, 2, len(tg.Targets), "Wrong number of targets.") + require.Len(t, tg.Targets, 2, "Wrong number of targets.") tgt := tg.Targets[0] require.Equal(t, "mesos-slave1:31000", string(tgt[model.AddressLabel]), "Wrong target address.") @@ -449,11 +449,11 @@ func TestMarathonSDSendGroupWithContainerPortMappings(t *testing.T) { } tgs, err := testUpdateServices(client) require.NoError(t, err) - require.Equal(t, 1, len(tgs), "Expected 1 target group.") + require.Len(t, tgs, 1, "Expected 1 target group.") tg := tgs[0] require.Equal(t, "test-service", tg.Source, "Wrong target group name.") - require.Equal(t, 2, len(tg.Targets), "Wrong number of targets.") + require.Len(t, tg.Targets, 2, "Wrong number of targets.") tgt := tg.Targets[0] require.Equal(t, "mesos-slave1:12345", string(tgt[model.AddressLabel]), "Wrong target address.") @@ -505,11 +505,11 @@ func TestMarathonSDSendGroupWithDockerContainerPortMappings(t *testing.T) { } tgs, err := testUpdateServices(client) require.NoError(t, err) - require.Equal(t, 1, len(tgs), "Expected 1 target group.") + require.Len(t, tgs, 1, "Expected 1 target group.") tg := tgs[0] require.Equal(t, "test-service", tg.Source, "Wrong target group name.") - require.Equal(t, 2, len(tg.Targets), "Wrong number of targets.") + require.Len(t, tg.Targets, 2, "Wrong number of targets.") tgt := tg.Targets[0] require.Equal(t, "mesos-slave1:31000", string(tgt[model.AddressLabel]), "Wrong target address.") @@ -565,11 +565,11 @@ func TestMarathonSDSendGroupWithContainerNetworkAndPortMapping(t *testing.T) { } tgs, err := testUpdateServices(client) require.NoError(t, err) - require.Equal(t, 1, len(tgs), "Expected 1 target group.") + require.Len(t, tgs, 1, "Expected 1 target group.") tg := tgs[0] require.Equal(t, "test-service", tg.Source, "Wrong target group name.") - require.Equal(t, 2, len(tg.Targets), "Wrong number of targets.") + require.Len(t, tg.Targets, 2, "Wrong number of targets.") tgt := tg.Targets[0] require.Equal(t, "1.2.3.4:8080", string(tgt[model.AddressLabel]), "Wrong target address.") From b17f88b7fb823c08b0814551acbc1cf1b35a35ed Mon Sep 17 00:00:00 2001 From: Bryan Boreham Date: Thu, 1 Feb 2024 15:01:48 +0000 Subject: [PATCH 060/237] consul sd tests: don't call FailNow from a background goroutine This is not allowed by the Go test framework. Signed-off-by: Bryan Boreham --- discovery/consul/consul_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/discovery/consul/consul_test.go b/discovery/consul/consul_test.go index a7763e16d5..e3bc7938f5 100644 --- a/discovery/consul/consul_test.go +++ b/discovery/consul/consul_test.go @@ -253,7 +253,7 @@ func newServer(t *testing.T) (*httptest.Server, *SDConfig) { time.Sleep(5 * time.Second) response = ServicesTestAnswer default: - require.FailNow(t, "Unhandled consul call: %s", r.URL) + t.Errorf("Unhandled consul call: %s", r.URL) } w.Header().Add("X-Consul-Index", "1") w.Write([]byte(response)) From c006c57efceec54c7bb44f571cbb208a357bdd9b Mon Sep 17 00:00:00 2001 From: Alan Protasio Date: Thu, 1 Feb 2024 13:22:38 -0300 Subject: [PATCH 061/237] Proposal to improve FPointSlice and HPointSlice allocation. (#13448) * Reusing points slice from previous series when the slice is under utilized * Adding comments on the bench test Signed-off-by: Alan Protasio --- promql/bench_test.go | 19 +++++++++++++++++++ promql/engine.go | 38 ++++++++++++++++++++++++++++++++++---- 2 files changed, 53 insertions(+), 4 deletions(-) diff --git a/promql/bench_test.go b/promql/bench_test.go index b7a4978de2..516b0d7482 100644 --- a/promql/bench_test.go +++ b/promql/bench_test.go @@ -33,6 +33,10 @@ func setupRangeQueryTestData(stor *teststorage.TestStorage, _ *Engine, interval, ctx := context.Background() metrics := []labels.Labels{} + // Generating test series: a_X, b_X, and h_X, where X can take values of one, ten, or hundred, + // representing the number of series each metric name contains. + // Metric a_X and b_X are simple metrics where h_X is a histogram. + // These metrics will have data for all test time range metrics = append(metrics, labels.FromStrings("__name__", "a_one")) metrics = append(metrics, labels.FromStrings("__name__", "b_one")) for j := 0; j < 10; j++ { @@ -59,6 +63,9 @@ func setupRangeQueryTestData(stor *teststorage.TestStorage, _ *Engine, interval, } refs := make([]storage.SeriesRef, len(metrics)) + // Number points for each different label value of "l" for the sparse series + pointsPerSparseSeries := numIntervals / 50 + for s := 0; s < numIntervals; s++ { a := stor.Appender(context.Background()) ts := int64(s * interval) @@ -66,10 +73,18 @@ func setupRangeQueryTestData(stor *teststorage.TestStorage, _ *Engine, interval, ref, _ := a.Append(refs[i], metric, ts, float64(s)+float64(i)/float64(len(metrics))) refs[i] = ref } + // Generating a sparse time series: each label value of "l" will contain data only for + // pointsPerSparseSeries points + metric := labels.FromStrings("__name__", "sparse", "l", strconv.Itoa(s/pointsPerSparseSeries)) + _, err := a.Append(0, metric, ts, float64(s)/float64(len(metrics))) + if err != nil { + return err + } if err := a.Commit(); err != nil { return err } } + stor.DB.ForceHeadMMap() // Ensure we have at most one head chunk for every series. stor.DB.Compact(ctx) return nil @@ -94,6 +109,10 @@ func rangeQueryCases() []benchCase { expr: "rate(a_X[1m])", steps: 10000, }, + { + expr: "rate(sparse[1m])", + steps: 10000, + }, // Holt-Winters and long ranges. { expr: "holt_winters(a_X[1d], 0.3, 0.3)", diff --git a/promql/engine.go b/promql/engine.go index 786b76e1f4..02004e5f96 100644 --- a/promql/engine.go +++ b/promql/engine.go @@ -1452,6 +1452,7 @@ func (ev *evaluator) eval(expr parser.Expr) (parser.Value, annotations.Annotatio // Reuse objects across steps to save memory allocations. var floats []FPoint var histograms []HPoint + var prevSS *Series inMatrix := make(Matrix, 1) inArgs[matrixArgIndex] = inMatrix enh := &EvalNodeHelper{Out: make(Vector, 0, 1)} @@ -1512,12 +1513,13 @@ func (ev *evaluator) eval(expr parser.Expr) (parser.Value, annotations.Annotatio if len(outVec) > 0 { if outVec[0].H == nil { if ss.Floats == nil { - ss.Floats = getFPointSlice(numSteps) + ss.Floats = reuseOrGetFPointSlices(prevSS, numSteps) } + ss.Floats = append(ss.Floats, FPoint{F: outVec[0].F, T: ts}) } else { if ss.Histograms == nil { - ss.Histograms = getHPointSlice(numSteps) + ss.Histograms = reuseOrGetHPointSlices(prevSS, numSteps) } ss.Histograms = append(ss.Histograms, HPoint{H: outVec[0].H, T: ts}) } @@ -1526,9 +1528,11 @@ func (ev *evaluator) eval(expr parser.Expr) (parser.Value, annotations.Annotatio it.ReduceDelta(stepRange) } histSamples := totalHPointSize(ss.Histograms) + if len(ss.Floats)+histSamples > 0 { if ev.currentSamples+len(ss.Floats)+histSamples <= ev.maxSamples { mat = append(mat, ss) + prevSS = &mat[len(mat)-1] ev.currentSamples += len(ss.Floats) + histSamples } else { ev.error(ErrTooManySamples(env)) @@ -1678,6 +1682,7 @@ func (ev *evaluator) eval(expr parser.Expr) (parser.Value, annotations.Annotatio ev.error(errWithWarnings{fmt.Errorf("expanding series: %w", err), ws}) } mat := make(Matrix, 0, len(e.Series)) + var prevSS *Series it := storage.NewMemoizedEmptyIterator(durationMilliseconds(ev.lookbackDelta)) var chkIter chunkenc.Iterator for i, s := range e.Series { @@ -1697,14 +1702,14 @@ func (ev *evaluator) eval(expr parser.Expr) (parser.Value, annotations.Annotatio if ev.currentSamples < ev.maxSamples { if h == nil { if ss.Floats == nil { - ss.Floats = getFPointSlice(numSteps) + ss.Floats = reuseOrGetFPointSlices(prevSS, numSteps) } ss.Floats = append(ss.Floats, FPoint{F: f, T: ts}) ev.currentSamples++ ev.samplesStats.IncrementSamplesAtStep(step, 1) } else { if ss.Histograms == nil { - ss.Histograms = getHPointSlice(numSteps) + ss.Histograms = reuseOrGetHPointSlices(prevSS, numSteps) } point := HPoint{H: h, T: ts} ss.Histograms = append(ss.Histograms, point) @@ -1720,6 +1725,7 @@ func (ev *evaluator) eval(expr parser.Expr) (parser.Value, annotations.Annotatio if len(ss.Floats)+len(ss.Histograms) > 0 { mat = append(mat, ss) + prevSS = &mat[len(mat)-1] } } ev.samplesStats.UpdatePeak(ev.currentSamples) @@ -1840,6 +1846,30 @@ func (ev *evaluator) eval(expr parser.Expr) (parser.Value, annotations.Annotatio panic(fmt.Errorf("unhandled expression of type: %T", expr)) } +// reuseOrGetFPointSlices reuses the space from previous slice to create new slice if the former has lots of room. +// The previous slices capacity is adjusted so when it is re-used from the pool it doesn't overflow into the new one. +func reuseOrGetHPointSlices(prevSS *Series, numSteps int) (r []HPoint) { + if prevSS != nil && cap(prevSS.Histograms)-2*len(prevSS.Histograms) > 0 { + r = prevSS.Histograms[len(prevSS.Histograms):] + prevSS.Histograms = prevSS.Histograms[0:len(prevSS.Histograms):len(prevSS.Histograms)] + return + } + + return getHPointSlice(numSteps) +} + +// reuseOrGetFPointSlices reuses the space from previous slice to create new slice if the former has lots of room. +// The previous slices capacity is adjusted so when it is re-used from the pool it doesn't overflow into the new one. +func reuseOrGetFPointSlices(prevSS *Series, numSteps int) (r []FPoint) { + if prevSS != nil && cap(prevSS.Floats)-2*len(prevSS.Floats) > 0 { + r = prevSS.Floats[len(prevSS.Floats):] + prevSS.Floats = prevSS.Floats[0:len(prevSS.Floats):len(prevSS.Floats)] + return + } + + return getFPointSlice(numSteps) +} + func (ev *evaluator) rangeEvalTimestampFunctionOverVectorSelector(vs *parser.VectorSelector, call FunctionCall, e *parser.Call) (parser.Value, annotations.Annotations) { ws, err := checkAndExpandSeriesSet(ev.ctx, vs) if err != nil { From 6feffeb92e36b064a53d2283e50d6db355c95cb0 Mon Sep 17 00:00:00 2001 From: Faustas Butkus <33425982+faustuzas@users.noreply.github.com> Date: Thu, 1 Feb 2024 19:28:42 +0200 Subject: [PATCH 062/237] promql: add histogram_avg function (#13467) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add histogram_avg function --------- Signed-off-by: Faustas Butkus Signed-off-by: Björn Rabenstein Co-authored-by: Björn Rabenstein --- docs/querying/functions.md | 28 +++++++++++---- promql/functions.go | 18 ++++++++++ promql/parser/functions.go | 5 +++ promql/testdata/native_histograms.test | 34 +++++++++++++++++++ .../src/complete/promql.terms.ts | 6 ++++ .../src/parser/parser.test.ts | 12 +++++++ .../codemirror-promql/src/types/function.ts | 7 ++++ web/ui/module/lezer-promql/src/highlight.js | 2 +- web/ui/module/lezer-promql/src/promql.grammar | 2 ++ 9 files changed, 106 insertions(+), 8 deletions(-) diff --git a/docs/querying/functions.md b/docs/querying/functions.md index 375104a0da..c9e65fe6cc 100644 --- a/docs/querying/functions.md +++ b/docs/querying/functions.md @@ -175,6 +175,27 @@ Special cases are: `floor(v instant-vector)` rounds the sample values of all elements in `v` down to the nearest integer. +## `histogram_avg()` + +_This function only acts on native histograms, which are an experimental +feature. The behavior of this function may change in future versions of +Prometheus, including its removal from PromQL._ + +`histogram_avg(v instant-vector)` returns the arithmetic average of observed values stored in +a native histogram. Samples that are not native histograms are ignored and do +not show up in the returned vector. + +Use `histogram_avg` as demonstrated below to compute the average request duration +over a 5-minute window from a native histogram: + + histogram_avg(rate(http_request_duration_seconds[5m])) + +Which is equivalent to the following query: + + histogram_sum(rate(http_request_duration_seconds[5m])) + / + histogram_count(rate(http_request_duration_seconds[5m])) + ## `histogram_count()` and `histogram_sum()` _Both functions only act on native histograms, which are an experimental @@ -193,13 +214,6 @@ Use `histogram_count` in the following way to calculate a rate of observations histogram_count(rate(http_request_duration_seconds[10m])) -The additional use of `histogram_sum` enables the calculation of the average of -observed values (in this case corresponding to “average request duration”): - - histogram_sum(rate(http_request_duration_seconds[10m])) - / - histogram_count(rate(http_request_duration_seconds[10m])) - ## `histogram_fraction()` _This function only acts on native histograms, which are an experimental diff --git a/promql/functions.go b/promql/functions.go index a0a118c114..fe1a5644ec 100644 --- a/promql/functions.go +++ b/promql/functions.go @@ -1081,6 +1081,23 @@ func funcHistogramSum(vals []parser.Value, args parser.Expressions, enh *EvalNod return enh.Out, nil } +// === histogram_avg(Vector parser.ValueTypeVector) (Vector, Annotations) === +func funcHistogramAvg(vals []parser.Value, args parser.Expressions, enh *EvalNodeHelper) (Vector, annotations.Annotations) { + inVec := vals[0].(Vector) + + for _, sample := range inVec { + // Skip non-histogram samples. + if sample.H == nil { + continue + } + enh.Out = append(enh.Out, Sample{ + Metric: sample.Metric.DropMetricName(), + F: sample.H.Sum / sample.H.Count, + }) + } + return enh.Out, nil +} + // === histogram_stddev(Vector parser.ValueTypeVector) (Vector, Annotations) === func funcHistogramStdDev(vals []parser.Value, args parser.Expressions, enh *EvalNodeHelper) (Vector, annotations.Annotations) { inVec := vals[0].(Vector) @@ -1532,6 +1549,7 @@ var FunctionCalls = map[string]FunctionCall{ "deriv": funcDeriv, "exp": funcExp, "floor": funcFloor, + "histogram_avg": funcHistogramAvg, "histogram_count": funcHistogramCount, "histogram_fraction": funcHistogramFraction, "histogram_quantile": funcHistogramQuantile, diff --git a/promql/parser/functions.go b/promql/parser/functions.go index 46d50d5476..99b41321fe 100644 --- a/promql/parser/functions.go +++ b/promql/parser/functions.go @@ -167,6 +167,11 @@ var Functions = map[string]*Function{ ArgTypes: []ValueType{ValueTypeVector}, ReturnType: ValueTypeVector, }, + "histogram_avg": { + Name: "histogram_avg", + ArgTypes: []ValueType{ValueTypeVector}, + ReturnType: ValueTypeVector, + }, "histogram_count": { Name: "histogram_count", ArgTypes: []ValueType{ValueTypeVector}, diff --git a/promql/testdata/native_histograms.test b/promql/testdata/native_histograms.test index 5f0945d32f..1da68a385f 100644 --- a/promql/testdata/native_histograms.test +++ b/promql/testdata/native_histograms.test @@ -11,6 +11,9 @@ eval instant at 5m histogram_count(empty_histogram) eval instant at 5m histogram_sum(empty_histogram) {} 0 +eval instant at 5m histogram_avg(empty_histogram) + {} NaN + eval instant at 5m histogram_fraction(-Inf, +Inf, empty_histogram) {} NaN @@ -31,6 +34,10 @@ eval instant at 5m histogram_count(single_histogram) eval instant at 5m histogram_sum(single_histogram) {} 5 +# histogram_avg calculates the average from sum and count properties. +eval instant at 5m histogram_avg(single_histogram) + {} 1.25 + # We expect half of the values to fall in the range 1 < x <= 2. eval instant at 5m histogram_fraction(1, 2, single_histogram) {} 0.5 @@ -55,6 +62,9 @@ eval instant at 5m histogram_count(multi_histogram) eval instant at 5m histogram_sum(multi_histogram) {} 5 +eval instant at 5m histogram_avg(multi_histogram) + {} 1.25 + eval instant at 5m histogram_fraction(1, 2, multi_histogram) {} 0.5 @@ -69,6 +79,9 @@ eval instant at 50m histogram_count(multi_histogram) eval instant at 50m histogram_sum(multi_histogram) {} 5 +eval instant at 50m histogram_avg(multi_histogram) + {} 1.25 + eval instant at 50m histogram_fraction(1, 2, multi_histogram) {} 0.5 @@ -89,6 +102,9 @@ eval instant at 5m histogram_count(incr_histogram) eval instant at 5m histogram_sum(incr_histogram) {} 6 +eval instant at 5m histogram_avg(incr_histogram) + {} 1.2 + # We expect 3/5ths of the values to fall in the range 1 < x <= 2. eval instant at 5m histogram_fraction(1, 2, incr_histogram) {} 0.6 @@ -106,6 +122,9 @@ eval instant at 50m histogram_count(incr_histogram) eval instant at 50m histogram_sum(incr_histogram) {} 24 +eval instant at 50m histogram_avg(incr_histogram) + {} 1.7142857142857142 + # We expect 12/14ths of the values to fall in the range 1 < x <= 2. eval instant at 50m histogram_fraction(1, 2, incr_histogram) {} 0.8571428571428571 @@ -140,6 +159,9 @@ eval instant at 5m histogram_count(low_res_histogram) eval instant at 5m histogram_sum(low_res_histogram) {} 8 +eval instant at 5m histogram_avg(low_res_histogram) + {} 1.6 + # We expect all values to fall into the lower-resolution bucket with the range 1 < x <= 4. eval instant at 5m histogram_fraction(1, 4, low_res_histogram) {} 1 @@ -157,6 +179,9 @@ eval instant at 5m histogram_count(single_zero_histogram) eval instant at 5m histogram_sum(single_zero_histogram) {} 0.25 +eval instant at 5m histogram_avg(single_zero_histogram) + {} 0.25 + # When only the zero bucket is populated, or there are negative buckets, the distribution is assumed to be equally # distributed around zero; i.e. that there are an equal number of positive and negative observations. Therefore the # entire distribution must lie within the full range of the zero bucket, in this case: -0.5 < x <= +0.5. @@ -179,6 +204,9 @@ eval instant at 5m histogram_count(negative_histogram) eval instant at 5m histogram_sum(negative_histogram) {} -5 +eval instant at 5m histogram_avg(negative_histogram) + {} -1.25 + # We expect half of the values to fall in the range -2 < x <= -1. eval instant at 5m histogram_fraction(-2, -1, negative_histogram) {} 0.5 @@ -199,6 +227,9 @@ eval instant at 10m histogram_count(two_samples_histogram) eval instant at 10m histogram_sum(two_samples_histogram) {} -4 +eval instant at 10m histogram_avg(two_samples_histogram) + {} -1 + eval instant at 10m histogram_fraction(-2, -1, two_samples_histogram) {} 0.5 @@ -217,6 +248,9 @@ eval instant at 5m histogram_count(balanced_histogram) eval instant at 5m histogram_sum(balanced_histogram) {} 0 +eval instant at 5m histogram_avg(balanced_histogram) + {} 0 + eval instant at 5m histogram_fraction(0, 4, balanced_histogram) {} 0.5 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 963fc95f2f..9d5d55f60b 100644 --- a/web/ui/module/codemirror-promql/src/complete/promql.terms.ts +++ b/web/ui/module/codemirror-promql/src/complete/promql.terms.ts @@ -215,6 +215,12 @@ export const functionIdentifierTerms = [ info: 'Round down values of input series to nearest integer', type: 'function', }, + { + label: 'histogram_avg', + detail: 'function', + info: 'Return the average of observations from a native histogram (experimental feature)', + type: 'function', + }, { label: 'histogram_count', detail: 'function', diff --git a/web/ui/module/codemirror-promql/src/parser/parser.test.ts b/web/ui/module/codemirror-promql/src/parser/parser.test.ts index 78195a5c64..54b95553cb 100644 --- a/web/ui/module/codemirror-promql/src/parser/parser.test.ts +++ b/web/ui/module/codemirror-promql/src/parser/parser.test.ts @@ -757,6 +757,18 @@ describe('promql operations', () => { expectedValueType: ValueType.vector, expectedDiag: [], }, + { + expr: + 'histogram_avg( # Root of the query, final result, returns the average of observations.\n' + + ' sum by(method, path) ( # Argument to histogram_avg(), an aggregated histogram.\n' + + ' rate( # Argument to sum(), the per-second increase of a histogram over 5m.\n' + + ' demo_api_request_duration_seconds{job="demo"}[5m] # Argument to rate(), a vector of sparse histogram series over the last 5m.\n' + + ' )\n' + + ' )\n' + + ')', + expectedValueType: ValueType.vector, + expectedDiag: [], + }, { expr: 'histogram_stddev( # Root of the query, final result, returns the standard deviation of observations.\n' + diff --git a/web/ui/module/codemirror-promql/src/types/function.ts b/web/ui/module/codemirror-promql/src/types/function.ts index 3694781586..2505edc227 100644 --- a/web/ui/module/codemirror-promql/src/types/function.ts +++ b/web/ui/module/codemirror-promql/src/types/function.ts @@ -39,6 +39,7 @@ import { Deriv, Exp, Floor, + HistogramAvg, HistogramCount, HistogramFraction, HistogramQuantile, @@ -269,6 +270,12 @@ const promqlFunctions: { [key: number]: PromQLFunction } = { variadic: 0, returnType: ValueType.vector, }, + [HistogramAvg]: { + name: 'histogram_avg', + argTypes: [ValueType.vector], + variadic: 0, + returnType: ValueType.vector, + }, [HistogramCount]: { name: 'histogram_count', argTypes: [ValueType.vector], diff --git a/web/ui/module/lezer-promql/src/highlight.js b/web/ui/module/lezer-promql/src/highlight.js index 1bf342d28f..53321c75e3 100644 --- a/web/ui/module/lezer-promql/src/highlight.js +++ b/web/ui/module/lezer-promql/src/highlight.js @@ -20,7 +20,7 @@ export const promQLHighLight = styleTags({ NumberLiteral: tags.number, Duration: tags.number, 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 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': + '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, 'By Without Bool On Ignoring GroupLeft GroupRight Offset Start End': tags.modifier, diff --git a/web/ui/module/lezer-promql/src/promql.grammar b/web/ui/module/lezer-promql/src/promql.grammar index ab627c8296..4966483179 100644 --- a/web/ui/module/lezer-promql/src/promql.grammar +++ b/web/ui/module/lezer-promql/src/promql.grammar @@ -138,6 +138,7 @@ FunctionIdentifier { HistogramStdDev | HistogramStdVar | HistogramSum | + HistogramAvg | HoltWinters | Hour | Idelta | @@ -364,6 +365,7 @@ NumberLiteral { Deriv { condFn<"deriv"> } Exp { condFn<"exp"> } Floor { condFn<"floor"> } + HistogramAvg { condFn<"histogram_avg"> } HistogramCount { condFn<"histogram_count"> } HistogramFraction { condFn<"histogram_fraction"> } HistogramQuantile { condFn<"histogram_quantile"> } From 419dd265ccd198fbf312659b3671dac2ecaad510 Mon Sep 17 00:00:00 2001 From: Mikhail Fesenko Date: Fri, 2 Feb 2024 10:00:38 +0100 Subject: [PATCH 063/237] Fix strange code, add messages to code brought in #8106 (#13509) Signed-off-by: Mikhail Fesenko --- tsdb/index/index.go | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/tsdb/index/index.go b/tsdb/index/index.go index cc57c3868e..2856fc78f0 100644 --- a/tsdb/index/index.go +++ b/tsdb/index/index.go @@ -715,17 +715,11 @@ func (w *Writer) writeLabelIndexesOffsetTable() error { } // Write out the length. - w.buf1.Reset() - l := w.f.pos - startPos - 4 - if l > math.MaxUint32 { - return fmt.Errorf("label indexes offset table size exceeds 4 bytes: %d", l) + err := w.writeLengthAndHash(startPos) + if err != nil { + return fmt.Errorf("label indexes offset table length/crc32 write error: %w", err) } - w.buf1.PutBE32int(int(l)) - if err := w.writeAt(w.buf1.Get(), startPos); err != nil { - return err - } - - return w.writeLenghtAndHash(startPos) + return nil } // writePostingsOffsetTable writes the postings offset table. @@ -793,25 +787,31 @@ func (w *Writer) writePostingsOffsetTable() error { } w.fPO = nil - return w.writeLenghtAndHash(startPos) + err = w.writeLengthAndHash(startPos) + if err != nil { + return fmt.Errorf("postings offset table length/crc32 write error: %w", err) + } + return nil } -func (w *Writer) writeLenghtAndHash(startPos uint64) error { - // Write out the length. +func (w *Writer) writeLengthAndHash(startPos uint64) error { w.buf1.Reset() l := w.f.pos - startPos - 4 if l > math.MaxUint32 { - return fmt.Errorf("postings offset table size exceeds 4 bytes: %d", l) + return fmt.Errorf("length size exceeds 4 bytes: %d", l) } w.buf1.PutBE32int(int(l)) if err := w.writeAt(w.buf1.Get(), startPos); err != nil { - return err + return fmt.Errorf("write length from buffer error: %w", err) } // Write out the hash. w.buf1.Reset() w.buf1.PutHashSum(w.crc32) - return w.write(w.buf1.Get()) + if err := w.write(w.buf1.Get()); err != nil { + return fmt.Errorf("write buffer's crc32 error: %w", err) + } + return nil } const indexTOCLen = 6*8 + crc32.Size From 5ee3fbe825e5000acd86a844defda4f842459e99 Mon Sep 17 00:00:00 2001 From: Marco Pracucci Date: Fri, 2 Feb 2024 10:06:37 +0100 Subject: [PATCH 064/237] Decouple ruler dependency controller from concurrency controller Signed-off-by: Marco Pracucci --- rules/alerting.go | 21 +++++++++++++ rules/alerting_test.go | 42 ++++++++++++++++++++++++++ rules/group.go | 6 +++- rules/manager.go | 43 ++++++++++++++++++--------- rules/manager_test.go | 62 +++++++++++++++++++++++++++++++++++++++ rules/origin.go | 18 +++++++++--- rules/origin_test.go | 65 +++++++++++++++++++++++++++++++++++++++++ rules/recording.go | 21 +++++++++++++ rules/recording_test.go | 22 ++++++++++++++ rules/rule.go | 16 ++++++++++ 10 files changed, 298 insertions(+), 18 deletions(-) diff --git a/rules/alerting.go b/rules/alerting.go index 72a3c7913e..a99b2b4aa4 100644 --- a/rules/alerting.go +++ b/rules/alerting.go @@ -142,6 +142,9 @@ type AlertingRule struct { active map[uint64]*Alert logger log.Logger + + noDependentRules *atomic.Bool + noDependencyRules *atomic.Bool } // NewAlertingRule constructs a new AlertingRule. @@ -168,6 +171,8 @@ func NewAlertingRule( evaluationTimestamp: atomic.NewTime(time.Time{}), evaluationDuration: atomic.NewDuration(0), lastError: atomic.NewError(nil), + noDependentRules: atomic.NewBool(false), + noDependencyRules: atomic.NewBool(false), } } @@ -317,6 +322,22 @@ func (r *AlertingRule) Restored() bool { return r.restored.Load() } +func (r *AlertingRule) SetNoDependentRules(noDependentRules bool) { + r.noDependentRules.Store(noDependentRules) +} + +func (r *AlertingRule) NoDependentRules() bool { + return r.noDependentRules.Load() +} + +func (r *AlertingRule) SetNoDependencyRules(noDependencyRules bool) { + r.noDependencyRules.Store(noDependencyRules) +} + +func (r *AlertingRule) NoDependencyRules() bool { + return r.noDependencyRules.Load() +} + // resolvedRetention is the duration for which a resolved alert instance // is kept in memory state and consequently repeatedly sent to the AlertManager. const resolvedRetention = 15 * time.Minute diff --git a/rules/alerting_test.go b/rules/alerting_test.go index dd324d1ee0..ba39fbf7a4 100644 --- a/rules/alerting_test.go +++ b/rules/alerting_test.go @@ -920,3 +920,45 @@ func TestAlertingEvalWithOrigin(t *testing.T) { require.NoError(t, err) require.Equal(t, detail, NewRuleDetail(rule)) } + +func TestAlertingRule_SetNoDependentRules(t *testing.T) { + rule := NewAlertingRule( + "test", + &parser.NumberLiteral{Val: 1}, + time.Minute, + 0, + labels.FromStrings("test", "test"), + labels.EmptyLabels(), + labels.EmptyLabels(), + "", + true, log.NewNopLogger(), + ) + require.False(t, rule.NoDependentRules()) + + rule.SetNoDependentRules(false) + require.False(t, rule.NoDependentRules()) + + rule.SetNoDependentRules(true) + require.True(t, rule.NoDependentRules()) +} + +func TestAlertingRule_SetNoDependencyRules(t *testing.T) { + rule := NewAlertingRule( + "test", + &parser.NumberLiteral{Val: 1}, + time.Minute, + 0, + labels.FromStrings("test", "test"), + labels.EmptyLabels(), + labels.EmptyLabels(), + "", + true, log.NewNopLogger(), + ) + require.False(t, rule.NoDependencyRules()) + + rule.SetNoDependencyRules(false) + require.False(t, rule.NoDependencyRules()) + + rule.SetNoDependencyRules(true) + require.True(t, rule.NoDependencyRules()) +} diff --git a/rules/group.go b/rules/group.go index 5ee06dc0ba..6f7844bac9 100644 --- a/rules/group.go +++ b/rules/group.go @@ -579,7 +579,7 @@ func (g *Group) Eval(ctx context.Context, ts time.Time) { // If the rule has no dependencies, it can run concurrently because no other rules in this group depend on its output. // Try run concurrently if there are slots available. - if ctrl := g.concurrencyController; ctrl.RuleEligible(g, rule) && ctrl.Allow() { + if ctrl := g.concurrencyController; isRuleEligibleForConcurrentExecution(rule) && ctrl.Allow() { wg.Add(1) go eval(i, rule, func() { @@ -1008,3 +1008,7 @@ func buildDependencyMap(rules []Rule) dependencyMap { return dependencies } + +func isRuleEligibleForConcurrentExecution(rule Rule) bool { + return rule.NoDependentRules() && rule.NoDependencyRules() +} diff --git a/rules/manager.go b/rules/manager.go index 477508dc04..66dcdcf2e4 100644 --- a/rules/manager.go +++ b/rules/manager.go @@ -119,6 +119,7 @@ type ManagerOptions struct { MaxConcurrentEvals int64 ConcurrentEvalsEnabled bool RuleConcurrencyController RuleConcurrencyController + RuleDependencyController RuleDependencyController Metrics *Metrics } @@ -142,6 +143,10 @@ func NewManager(o *ManagerOptions) *Manager { } } + if o.RuleDependencyController == nil { + o.RuleDependencyController = ruleDependencyController{} + } + m := &Manager{ groups: map[string]*Group{}, opts: o, @@ -188,8 +193,6 @@ func (m *Manager) Update(interval time.Duration, files []string, externalLabels m.mtx.Lock() defer m.mtx.Unlock() - m.opts.RuleConcurrencyController.Invalidate() - groups, errs := m.LoadGroups(interval, externalLabels, externalURL, groupEvalIterationFunc, files...) if errs != nil { @@ -322,6 +325,9 @@ func (m *Manager) LoadGroups( )) } + // Check dependencies between rules and store it on the Rule itself. + m.opts.RuleDependencyController.AnalyseRules(rules) + groups[GroupKey(fn, rg.Name)] = NewGroup(GroupOptions{ Name: rg.Name, File: fn, @@ -418,24 +424,35 @@ func SendAlerts(s Sender, externalURL string) NotifyFunc { } } -// RuleConcurrencyController controls whether rules can be evaluated concurrently. Its purpose is to bound the amount -// of concurrency in rule evaluations to avoid overwhelming the Prometheus server with additional query load and ensure -// the correctness of rules running concurrently. Concurrency is controlled globally, not on a per-group basis. -type RuleConcurrencyController interface { - // RuleEligible determines if the rule can guarantee correct results while running concurrently. - RuleEligible(g *Group, r Rule) bool +// RuleDependencyController controls whether a set of rules have dependencies between each other. +type RuleDependencyController interface { + // AnalyseRules analyses dependencies between the input rules. For each rule that it's guaranteed + // not having any dependants and/or dependency, this function should call Rule.SetNoDependentRules(true) + // and/or Rule.SetNoDependencyRules(true). + AnalyseRules(rules []Rule) +} +type ruleDependencyController struct{} + +// AnalyseRules implements RuleDependencyController. +func (c ruleDependencyController) AnalyseRules(rules []Rule) { + depMap := buildDependencyMap(rules) + for _, r := range rules { + r.SetNoDependentRules(depMap.dependents(r) == 0) + r.SetNoDependencyRules(depMap.dependencies(r) == 0) + } +} + +// RuleConcurrencyController controls concurrency for rules that are safe to be evaluated concurrently. +// Its purpose is to bound the amount of concurrency in rule evaluations to avoid overwhelming the Prometheus +// server with additional query load. Concurrency is controlled globally, not on a per-group basis. +type RuleConcurrencyController interface { // Allow determines whether any concurrent evaluation slots are available. // If Allow() returns true, then Done() must be called to release the acquired slot. Allow() bool // Done releases a concurrent evaluation slot. Done() - - // Invalidate instructs the controller to invalidate its state. - // This should be called when groups are modified (during a reload, for instance), because the controller may - // store some state about each group in order to more efficiently determine rule eligibility. - Invalidate() } // concurrentRuleEvalController holds a weighted semaphore which controls the concurrent evaluation of rules. diff --git a/rules/manager_test.go b/rules/manager_test.go index 07ec06104d..5e3a609220 100644 --- a/rules/manager_test.go +++ b/rules/manager_test.go @@ -1314,6 +1314,8 @@ func TestRuleGroupEvalIterationFunc(t *testing.T) { evaluationTimestamp: atomic.NewTime(time.Time{}), evaluationDuration: atomic.NewDuration(0), lastError: atomic.NewError(nil), + noDependentRules: atomic.NewBool(false), + noDependencyRules: atomic.NewBool(false), } group := NewGroup(GroupOptions{ @@ -1407,6 +1409,66 @@ func TestNativeHistogramsInRecordingRules(t *testing.T) { require.Equal(t, chunkenc.ValNone, it.Next()) } +func TestManager_LoadGroups_ShouldCheckWhetherEachRuleHasDependentsAndDependencies(t *testing.T) { + storage := teststorage.New(t) + t.Cleanup(func() { + require.NoError(t, storage.Close()) + }) + + ruleManager := NewManager(&ManagerOptions{ + Context: context.Background(), + Logger: log.NewNopLogger(), + Appendable: storage, + QueryFunc: func(ctx context.Context, q string, ts time.Time) (promql.Vector, error) { return nil, nil }, + }) + + t.Run("load a mix of dependent and independent rules", func(t *testing.T) { + groups, errs := ruleManager.LoadGroups(time.Second, labels.EmptyLabels(), "", nil, []string{"fixtures/rules_multiple.yaml"}...) + require.Empty(t, errs) + require.Len(t, groups, 1) + + expected := map[string]struct { + noDependentRules bool + noDependencyRules bool + }{ + "job:http_requests:rate1m": { + noDependentRules: true, + noDependencyRules: true, + }, + "job:http_requests:rate5m": { + noDependentRules: true, + noDependencyRules: true, + }, + "job:http_requests:rate15m": { + noDependentRules: true, + noDependencyRules: false, + }, + "TooManyRequests": { + noDependentRules: false, + noDependencyRules: true, + }, + } + + for _, r := range ruleManager.Rules() { + exp, ok := expected[r.Name()] + require.Truef(t, ok, "rule: %s", r.String()) + require.Equalf(t, exp.noDependentRules, r.NoDependentRules(), "rule: %s", r.String()) + require.Equalf(t, exp.noDependencyRules, r.NoDependencyRules(), "rule: %s", r.String()) + } + }) + + t.Run("load only independent rules", func(t *testing.T) { + groups, errs := ruleManager.LoadGroups(time.Second, labels.EmptyLabels(), "", nil, []string{"fixtures/rules_multiple_independent.yaml"}...) + require.Empty(t, errs) + require.Len(t, groups, 1) + + for _, r := range ruleManager.Rules() { + require.Truef(t, r.NoDependentRules(), "rule: %s", r.String()) + require.Truef(t, r.NoDependencyRules(), "rule: %s", r.String()) + } + }) +} + func TestDependencyMap(t *testing.T) { ctx := context.Background() opts := &ManagerOptions{ diff --git a/rules/origin.go b/rules/origin.go index 996538767d..695fc5f838 100644 --- a/rules/origin.go +++ b/rules/origin.go @@ -28,6 +28,14 @@ type RuleDetail struct { Query string Labels labels.Labels Kind string + + // NoDependentRules is set to true if it's guaranteed that in the rule group there's no other rule + // which depends on this one. + NoDependentRules bool + + // NoDependencyRules is set to true if it's guaranteed that this rule doesn't depend on any other + // rule within the rule group. + NoDependencyRules bool } const ( @@ -48,10 +56,12 @@ func NewRuleDetail(r Rule) RuleDetail { } return RuleDetail{ - Name: r.Name(), - Query: r.Query().String(), - Labels: r.Labels(), - Kind: kind, + Name: r.Name(), + Query: r.Query().String(), + Labels: r.Labels(), + Kind: kind, + NoDependentRules: r.NoDependentRules(), + NoDependencyRules: r.NoDependencyRules(), } } diff --git a/rules/origin_test.go b/rules/origin_test.go index ea4f4f905d..ca466301dd 100644 --- a/rules/origin_test.go +++ b/rules/origin_test.go @@ -19,6 +19,7 @@ import ( "testing" "time" + "github.com/go-kit/log" "github.com/stretchr/testify/require" "github.com/prometheus/prometheus/model/labels" @@ -43,9 +44,73 @@ func (u unknownRule) SetEvaluationDuration(time.Duration) {} func (u unknownRule) GetEvaluationDuration() time.Duration { return 0 } func (u unknownRule) SetEvaluationTimestamp(time.Time) {} func (u unknownRule) GetEvaluationTimestamp() time.Time { return time.Time{} } +func (u unknownRule) SetNoDependentRules(bool) {} +func (u unknownRule) NoDependentRules() bool { return false } +func (u unknownRule) SetNoDependencyRules(bool) {} +func (u unknownRule) NoDependencyRules() bool { return false } func TestNewRuleDetailPanics(t *testing.T) { require.PanicsWithValue(t, `unknown rule type "rules.unknownRule"`, func() { NewRuleDetail(unknownRule{}) }) } + +func TestFromOriginContext(t *testing.T) { + t.Run("should return zero value if RuleDetail is missing in the context", func(t *testing.T) { + detail := FromOriginContext(context.Background()) + require.Zero(t, detail) + + // The zero value for NoDependentRules must be the most conservative option. + require.False(t, detail.NoDependentRules) + + // The zero value for NoDependencyRules must be the most conservative option. + require.False(t, detail.NoDependencyRules) + }) +} + +func TestNewRuleDetail(t *testing.T) { + t.Run("should populate NoDependentRules and NoDependencyRules for a RecordingRule", func(t *testing.T) { + rule := NewRecordingRule("test", &parser.NumberLiteral{Val: 1}, labels.EmptyLabels()) + detail := NewRuleDetail(rule) + require.False(t, detail.NoDependentRules) + require.False(t, detail.NoDependencyRules) + + rule.SetNoDependentRules(true) + detail = NewRuleDetail(rule) + require.True(t, detail.NoDependentRules) + require.False(t, detail.NoDependencyRules) + + rule.SetNoDependencyRules(true) + detail = NewRuleDetail(rule) + require.True(t, detail.NoDependentRules) + require.True(t, detail.NoDependencyRules) + }) + + t.Run("should populate NoDependentRules and NoDependencyRules for a AlertingRule", func(t *testing.T) { + rule := NewAlertingRule( + "test", + &parser.NumberLiteral{Val: 1}, + time.Minute, + 0, + labels.FromStrings("test", "test"), + labels.EmptyLabels(), + labels.EmptyLabels(), + "", + true, log.NewNopLogger(), + ) + + detail := NewRuleDetail(rule) + require.False(t, detail.NoDependentRules) + require.False(t, detail.NoDependencyRules) + + rule.SetNoDependentRules(true) + detail = NewRuleDetail(rule) + require.True(t, detail.NoDependentRules) + require.False(t, detail.NoDependencyRules) + + rule.SetNoDependencyRules(true) + detail = NewRuleDetail(rule) + require.True(t, detail.NoDependentRules) + require.True(t, detail.NoDependencyRules) + }) +} diff --git a/rules/recording.go b/rules/recording.go index b6a886cddc..e2b0a31a03 100644 --- a/rules/recording.go +++ b/rules/recording.go @@ -41,6 +41,9 @@ type RecordingRule struct { lastError *atomic.Error // Duration of how long it took to evaluate the recording rule. evaluationDuration *atomic.Duration + + noDependentRules *atomic.Bool + noDependencyRules *atomic.Bool } // NewRecordingRule returns a new recording rule. @@ -53,6 +56,8 @@ func NewRecordingRule(name string, vector parser.Expr, lset labels.Labels) *Reco evaluationTimestamp: atomic.NewTime(time.Time{}), evaluationDuration: atomic.NewDuration(0), lastError: atomic.NewError(nil), + noDependentRules: atomic.NewBool(false), + noDependencyRules: atomic.NewBool(false), } } @@ -166,3 +171,19 @@ func (rule *RecordingRule) SetEvaluationTimestamp(ts time.Time) { func (rule *RecordingRule) GetEvaluationTimestamp() time.Time { return rule.evaluationTimestamp.Load() } + +func (rule *RecordingRule) SetNoDependentRules(noDependentRules bool) { + rule.noDependentRules.Store(noDependentRules) +} + +func (rule *RecordingRule) NoDependentRules() bool { + return rule.noDependentRules.Load() +} + +func (rule *RecordingRule) SetNoDependencyRules(noDependencyRules bool) { + rule.noDependencyRules.Store(noDependencyRules) +} + +func (rule *RecordingRule) NoDependencyRules() bool { + return rule.noDependencyRules.Load() +} diff --git a/rules/recording_test.go b/rules/recording_test.go index 960ff4bdb8..7a09cd6d8a 100644 --- a/rules/recording_test.go +++ b/rules/recording_test.go @@ -249,3 +249,25 @@ func TestRecordingEvalWithOrigin(t *testing.T) { require.NoError(t, err) require.Equal(t, detail, NewRuleDetail(rule)) } + +func TestRecordingRule_SetNoDependentRules(t *testing.T) { + rule := NewRecordingRule("1", &parser.NumberLiteral{Val: 1}, labels.EmptyLabels()) + require.False(t, rule.NoDependentRules()) + + rule.SetNoDependentRules(false) + require.False(t, rule.NoDependentRules()) + + rule.SetNoDependentRules(true) + require.True(t, rule.NoDependentRules()) +} + +func TestRecordingRule_SetNoDependencyRules(t *testing.T) { + rule := NewRecordingRule("1", &parser.NumberLiteral{Val: 1}, labels.EmptyLabels()) + require.False(t, rule.NoDependencyRules()) + + rule.SetNoDependencyRules(false) + require.False(t, rule.NoDependencyRules()) + + rule.SetNoDependencyRules(true) + require.True(t, rule.NoDependencyRules()) +} diff --git a/rules/rule.go b/rules/rule.go index a4a8c04459..59af3e0bba 100644 --- a/rules/rule.go +++ b/rules/rule.go @@ -61,4 +61,20 @@ type Rule interface { // GetEvaluationTimestamp returns last evaluation timestamp. // NOTE: Used dynamically by rules.html template. GetEvaluationTimestamp() time.Time + + // SetNoDependentRules sets whether there's no other rule in the rule group that depends on this rule. + SetNoDependentRules(bool) + + // NoDependentRules returns true if it's guaranteed that in the rule group there's no other rule + // which depends on this one. In case this function returns false there's no such guarantee, which + // means there may or may not be other rules depending on this one. + NoDependentRules() bool + + // SetNoDependencyRules sets whether this rule doesn't depend on the output of any rule in the rule group. + SetNoDependencyRules(bool) + + // NoDependencyRules returns true if it's guaranteed that this rule doesn't depend on the output of + // any other rule in the group. In case this function returns false there's no such guarantee, which + // means the rule may or may not depend on other rules. + NoDependencyRules() bool } From fdd5b85e06c3bd451af32906a185748bf451e35c Mon Sep 17 00:00:00 2001 From: Bryan Boreham Date: Sun, 4 Feb 2024 11:08:20 +0100 Subject: [PATCH 065/237] promql: faster range-query of label_replace and label_join These functions act on the labels only, so don't need to go step by step over the samples in a range query. Signed-off-by: Bryan Boreham --- promql/engine.go | 9 ++++ promql/functions.go | 129 +++++++++++++++++--------------------------- 2 files changed, 57 insertions(+), 81 deletions(-) diff --git a/promql/engine.go b/promql/engine.go index 02004e5f96..36132295b3 100644 --- a/promql/engine.go +++ b/promql/engine.go @@ -1409,6 +1409,15 @@ func (ev *evaluator) eval(expr parser.Expr) (parser.Value, annotations.Annotatio break } } + + // Special handling for functions that work on series not samples. + switch e.Func.Name { + case "label_replace": + return ev.evalLabelReplace(e.Args) + case "label_join": + return ev.evalLabelJoin(e.Args) + } + if !matrixArg { // Does not have a matrix argument. return ev.rangeEval(nil, func(v []parser.Value, _ [][]EvalSeriesHelper, enh *EvalNodeHelper) (Vector, annotations.Annotations) { diff --git a/promql/functions.go b/promql/functions.go index fe1a5644ec..eb3eb7e562 100644 --- a/promql/functions.go +++ b/promql/functions.go @@ -1321,59 +1321,47 @@ func funcChanges(vals []parser.Value, args parser.Expressions, enh *EvalNodeHelp return append(enh.Out, Sample{F: float64(changes)}), nil } -// === label_replace(Vector parser.ValueTypeVector, dst_label, replacement, src_labelname, regex parser.ValueTypeString) (Vector, Annotations) === -func funcLabelReplace(vals []parser.Value, args parser.Expressions, enh *EvalNodeHelper) (Vector, annotations.Annotations) { +// label_replace function operates only on series; does not look at timestamps or values. +func (ev *evaluator) evalLabelReplace(args parser.Expressions) (parser.Value, annotations.Annotations) { var ( - vector = vals[0].(Vector) dst = stringFromArg(args[1]) repl = stringFromArg(args[2]) src = stringFromArg(args[3]) regexStr = stringFromArg(args[4]) ) - if enh.regex == nil { - var err error - enh.regex, err = regexp.Compile("^(?:" + regexStr + ")$") - if err != nil { - panic(fmt.Errorf("invalid regular expression in label_replace(): %s", regexStr)) - } - if !model.LabelNameRE.MatchString(dst) { - panic(fmt.Errorf("invalid destination label name in label_replace(): %s", dst)) - } - enh.Dmn = make(map[uint64]labels.Labels, len(enh.Out)) + regex, err := regexp.Compile("^(?:" + regexStr + ")$") + if err != nil { + panic(fmt.Errorf("invalid regular expression in label_replace(): %s", regexStr)) + } + if !model.LabelNameRE.MatchString(dst) { + panic(fmt.Errorf("invalid destination label name in label_replace(): %s", dst)) } - for _, el := range vector { - h := el.Metric.Hash() - var outMetric labels.Labels - if l, ok := enh.Dmn[h]; ok { - outMetric = l - } else { - srcVal := el.Metric.Get(src) - indexes := enh.regex.FindStringSubmatchIndex(srcVal) - if indexes == nil { - // If there is no match, no replacement should take place. - outMetric = el.Metric - enh.Dmn[h] = outMetric - } else { - res := enh.regex.ExpandString([]byte{}, repl, srcVal, indexes) + val, ws := ev.eval(args[0]) + matrix := val.(Matrix) + lb := labels.NewBuilder(labels.EmptyLabels()) - lb := labels.NewBuilder(el.Metric).Del(dst) - if len(res) > 0 { - lb.Set(dst, string(res)) - } - outMetric = lb.Labels() - enh.Dmn[h] = outMetric - } + for i, el := range matrix { + srcVal := el.Metric.Get(src) + indexes := regex.FindStringSubmatchIndex(srcVal) + if indexes != nil { // Only replace when regexp matches. + res := regex.ExpandString([]byte{}, repl, srcVal, indexes) + lb.Reset(el.Metric) + lb.Set(dst, string(res)) + matrix[i].Metric = lb.Labels() } - - enh.Out = append(enh.Out, Sample{ - Metric: outMetric, - F: el.F, - H: el.H, - }) } - return enh.Out, nil + if matrix.ContainsSameLabelset() { + ev.errorf("vector cannot contain metrics with the same labelset") + } + + return matrix, ws +} + +// === label_replace(Vector parser.ValueTypeVector, dst_label, replacement, src_labelname, regex parser.ValueTypeString) (Vector, Annotations) === +func funcLabelReplace(vals []parser.Value, args parser.Expressions, enh *EvalNodeHelper) (Vector, annotations.Annotations) { + panic("funcLabelReplace wrong implementation called") } // === Vector(s Scalar) (Vector, Annotations) === @@ -1385,19 +1373,13 @@ func funcVector(vals []parser.Value, args parser.Expressions, enh *EvalNodeHelpe }), nil } -// === label_join(vector model.ValVector, dest_labelname, separator, src_labelname...) (Vector, Annotations) === -func funcLabelJoin(vals []parser.Value, args parser.Expressions, enh *EvalNodeHelper) (Vector, annotations.Annotations) { +// label_join function operates only on series; does not look at timestamps or values. +func (ev *evaluator) evalLabelJoin(args parser.Expressions) (parser.Value, annotations.Annotations) { var ( - vector = vals[0].(Vector) dst = stringFromArg(args[1]) sep = stringFromArg(args[2]) srcLabels = make([]string, len(args)-3) ) - - if enh.Dmn == nil { - enh.Dmn = make(map[uint64]labels.Labels, len(enh.Out)) - } - for i := 3; i < len(args); i++ { src := stringFromArg(args[i]) if !model.LabelName(src).IsValid() { @@ -1406,42 +1388,27 @@ func funcLabelJoin(vals []parser.Value, args parser.Expressions, enh *EvalNodeHe srcLabels[i-3] = src } - if !model.LabelName(dst).IsValid() { - panic(fmt.Errorf("invalid destination label name in label_join(): %s", dst)) - } - + val, ws := ev.eval(args[0]) + matrix := val.(Matrix) srcVals := make([]string, len(srcLabels)) - for _, el := range vector { - h := el.Metric.Hash() - var outMetric labels.Labels - if l, ok := enh.Dmn[h]; ok { - outMetric = l - } else { + lb := labels.NewBuilder(labels.EmptyLabels()) - for i, src := range srcLabels { - srcVals[i] = el.Metric.Get(src) - } - - lb := labels.NewBuilder(el.Metric) - - strval := strings.Join(srcVals, sep) - if strval == "" { - lb.Del(dst) - } else { - lb.Set(dst, strval) - } - - outMetric = lb.Labels() - enh.Dmn[h] = outMetric + for i, el := range matrix { + for i, src := range srcLabels { + srcVals[i] = el.Metric.Get(src) } - - enh.Out = append(enh.Out, Sample{ - Metric: outMetric, - F: el.F, - H: el.H, - }) + strval := strings.Join(srcVals, sep) + lb.Reset(el.Metric) + lb.Set(dst, strval) + matrix[i].Metric = lb.Labels() } - return enh.Out, nil + + return matrix, ws +} + +// === label_join(vector model.ValVector, dest_labelname, separator, src_labelname...) (Vector, Annotations) === +func funcLabelJoin(vals []parser.Value, args parser.Expressions, enh *EvalNodeHelper) (Vector, annotations.Annotations) { + panic("funcLabelReplace wrong implementation called") } // Common code for date related functions. From d3c1f0d8e061ea098ff209103e90069dc7612a5d Mon Sep 17 00:00:00 2001 From: Bryan Boreham Date: Sun, 4 Feb 2024 11:32:26 +0100 Subject: [PATCH 066/237] promql: can now remove regex field from EvalNodeHelper Signed-off-by: Bryan Boreham --- promql/engine.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/promql/engine.go b/promql/engine.go index 36132295b3..cc2a54318a 100644 --- a/promql/engine.go +++ b/promql/engine.go @@ -30,7 +30,6 @@ import ( "github.com/go-kit/log" "github.com/go-kit/log/level" - "github.com/grafana/regexp" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/common/model" "go.opentelemetry.io/otel" @@ -1080,8 +1079,6 @@ type EvalNodeHelper struct { Dmn map[uint64]labels.Labels // funcHistogramQuantile for classic histograms. signatureToMetricWithBuckets map[string]*metricWithBuckets - // label_replace. - regex *regexp.Regexp lb *labels.Builder lblBuf []byte From 857138d3ce874f568ab5ada4d0410be13719f078 Mon Sep 17 00:00:00 2001 From: Bryan Boreham Date: Sun, 4 Feb 2024 15:52:50 +0100 Subject: [PATCH 067/237] review feedback Signed-off-by: Bryan Boreham --- discovery/file/file_test.go | 2 +- discovery/marathon/marathon_test.go | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/discovery/file/file_test.go b/discovery/file/file_test.go index b473c2ce6e..521a3c0f16 100644 --- a/discovery/file/file_test.go +++ b/discovery/file/file_test.go @@ -358,7 +358,7 @@ func TestInvalidFile(t *testing.T) { // Verify that we've received nothing. time.Sleep(defaultWait) - require.False(t, runner.lastReceive().After(now), "Unexpected targets received.") + require.False(t, runner.lastReceive().After(now), "unexpected targets received: %v", runner.targets()) }) } } diff --git a/discovery/marathon/marathon_test.go b/discovery/marathon/marathon_test.go index c9555e9d31..659899f163 100644 --- a/discovery/marathon/marathon_test.go +++ b/discovery/marathon/marathon_test.go @@ -152,8 +152,8 @@ func TestMarathonSDRemoveApp(t *testing.T) { tg2 := tgs[0] - require.Equal(t, tg1.Source, tg2.Source, "Source is different.") require.NotEmpty(t, tg2.Targets, "Got a non-empty target set.") + require.Equal(t, tg1.Source, tg2.Source, "Source is different.") } func marathonTestAppListWithMultiplePorts(labels map[string]string, runningTasks int) *appList { @@ -307,7 +307,7 @@ func TestMarathonSDSendGroupWithPortDefinitions(t *testing.T) { tgt = tg.Targets[1] require.Equal(t, "mesos-slave1:5678", string(tgt[model.AddressLabel]), "Wrong target address.") - require.Equal(t, "", string(tgt[model.LabelName(portMappingLabelPrefix+"prometheus")]), "Wrong portMappings label from the second port.") + require.Empty(t, tgt[model.LabelName(portMappingLabelPrefix+"prometheus")], "Wrong portMappings label from the second port.") require.Equal(t, "yes", string(tgt[model.LabelName(portDefinitionLabelPrefix+"prometheus")]), "Wrong portDefinitions label from the second port.") } From e2b9cfeeeb1efbe299c386226622a5064f23f377 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Peter=20=C5=A0tibran=C3=BD?= Date: Sun, 4 Feb 2024 16:31:49 +0100 Subject: [PATCH 068/237] Enforce chunks ordering when writing index. (#8085) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Document conditions on chunks. Add check on chunk time ordering. Signed-off-by: Peter Štibraný --- tsdb/docs/format/index.md | 4 +++ tsdb/index/index.go | 30 ++++++++++++++++++--- tsdb/index/index_test.go | 55 ++++++++++++++++++++++++++++++++++++--- 3 files changed, 82 insertions(+), 7 deletions(-) diff --git a/tsdb/docs/format/index.md b/tsdb/docs/format/index.md index 6279d34f68..53b77d9abe 100644 --- a/tsdb/docs/format/index.md +++ b/tsdb/docs/format/index.md @@ -82,6 +82,10 @@ Each series section is aligned to 16 bytes. The ID for a series is the `offset/1 Every series entry first holds its number of labels, followed by tuples of symbol table references that contain the label name and value. The label pairs are lexicographically sorted. After the labels, the number of indexed chunks is encoded, followed by a sequence of metadata entries containing the chunks minimum (`mint`) and maximum (`maxt`) timestamp and a reference to its position in the chunk file. The `mint` is the time of the first sample and `maxt` is the time of the last sample in the chunk. Holding the time range data in the index allows dropping chunks irrelevant to queried time ranges without accessing them directly. +Chunk references within single series must be increasing, and chunk references for `series_(N+1)` must be higher than chunk references for `series_N`. +This property guarantees that chunks that belong to the same series are grouped together in the segment files. +Furthermore chunk `mint` must be less or equal than `maxt`, and subsequent chunks within single series must have increasing `mint` and `maxt` and not overlap. + `mint` of the first chunk is stored, it's `maxt` is stored as a delta and the `mint` and `maxt` are encoded as deltas to the previous time for subsequent chunks. Similarly, the reference of the first chunk is stored and the next ref is stored as a delta to the previous one. ``` diff --git a/tsdb/index/index.go b/tsdb/index/index.go index 2856fc78f0..84c7716849 100644 --- a/tsdb/index/index.go +++ b/tsdb/index/index.go @@ -146,8 +146,11 @@ type Writer struct { labelNames map[string]uint64 // Label names, and their usage. // Hold last series to validate that clients insert new series in order. - lastSeries labels.Labels - lastRef storage.SeriesRef + lastSeries labels.Labels + lastSeriesRef storage.SeriesRef + + // Hold last added chunk reference to make sure that chunks are ordered properly. + lastChunkRef chunks.ChunkRef crc32 hash.Hash @@ -433,9 +436,27 @@ func (w *Writer) AddSeries(ref storage.SeriesRef, lset labels.Labels, chunks ... return fmt.Errorf("out-of-order series added with label set %q", lset) } - if ref < w.lastRef && !w.lastSeries.IsEmpty() { + if ref < w.lastSeriesRef && !w.lastSeries.IsEmpty() { return fmt.Errorf("series with reference greater than %d already added", ref) } + + lastChunkRef := w.lastChunkRef + lastMaxT := int64(0) + for ix, c := range chunks { + if c.Ref < lastChunkRef { + return fmt.Errorf("unsorted chunk reference: %d, previous: %d", c.Ref, lastChunkRef) + } + lastChunkRef = c.Ref + + if ix > 0 && c.MinTime <= lastMaxT { + return fmt.Errorf("chunk minT %d is not higher than previous chunk maxT %d", c.MinTime, lastMaxT) + } + if c.MaxTime < c.MinTime { + return fmt.Errorf("chunk maxT %d is less than minT %d", c.MaxTime, c.MinTime) + } + lastMaxT = c.MaxTime + } + // We add padding to 16 bytes to increase the addressable space we get through 4 byte // series references. if err := w.addPadding(seriesByteAlign); err != nil { @@ -510,7 +531,8 @@ func (w *Writer) AddSeries(ref storage.SeriesRef, lset labels.Labels, chunks ... } w.lastSeries.CopyFrom(lset) - w.lastRef = ref + w.lastSeriesRef = ref + w.lastChunkRef = lastChunkRef return nil } diff --git a/tsdb/index/index_test.go b/tsdb/index/index_test.go index ef88870355..5ff48ef191 100644 --- a/tsdb/index/index_test.go +++ b/tsdb/index/index_test.go @@ -18,7 +18,6 @@ import ( "errors" "fmt" "hash/crc32" - "math/rand" "os" "path/filepath" "sort" @@ -407,15 +406,17 @@ func TestPersistence_index_e2e(t *testing.T) { var input indexWriterSeriesSlice + ref := uint64(0) // Generate ChunkMetas for every label set. for i, lset := range lbls { var metas []chunks.Meta for j := 0; j <= (i % 20); j++ { + ref++ metas = append(metas, chunks.Meta{ MinTime: int64(j * 10000), - MaxTime: int64((j + 1) * 10000), - Ref: chunks.ChunkRef(rand.Uint64()), + MaxTime: int64((j+1)*10000) - 1, + Ref: chunks.ChunkRef(ref), Chunk: chunkenc.NewXORChunk(), }) } @@ -670,3 +671,51 @@ func TestDecoder_Postings_WrongInput(t *testing.T) { _, _, err := (&Decoder{}).Postings([]byte("the cake is a lie")) require.Error(t, err) } + +func TestChunksRefOrdering(t *testing.T) { + dir := t.TempDir() + + idx, err := NewWriter(context.Background(), filepath.Join(dir, "index")) + require.NoError(t, err) + + require.NoError(t, idx.AddSymbol("1")) + require.NoError(t, idx.AddSymbol("2")) + require.NoError(t, idx.AddSymbol("__name__")) + + c50 := chunks.Meta{Ref: 50} + c100 := chunks.Meta{Ref: 100} + c200 := chunks.Meta{Ref: 200} + + require.NoError(t, idx.AddSeries(1, labels.FromStrings("__name__", "1"), c100)) + require.EqualError(t, idx.AddSeries(2, labels.FromStrings("__name__", "2"), c50), "unsorted chunk reference: 50, previous: 100") + require.NoError(t, idx.AddSeries(2, labels.FromStrings("__name__", "2"), c200)) + require.NoError(t, idx.Close()) +} + +func TestChunksTimeOrdering(t *testing.T) { + dir := t.TempDir() + + idx, err := NewWriter(context.Background(), filepath.Join(dir, "index")) + require.NoError(t, err) + + require.NoError(t, idx.AddSymbol("1")) + require.NoError(t, idx.AddSymbol("2")) + require.NoError(t, idx.AddSymbol("__name__")) + + require.NoError(t, idx.AddSeries(1, labels.FromStrings("__name__", "1"), + chunks.Meta{Ref: 1, MinTime: 0, MaxTime: 10}, // Also checks that first chunk can have MinTime: 0. + chunks.Meta{Ref: 2, MinTime: 11, MaxTime: 20}, + chunks.Meta{Ref: 3, MinTime: 21, MaxTime: 30}, + )) + + require.EqualError(t, idx.AddSeries(1, labels.FromStrings("__name__", "2"), + chunks.Meta{Ref: 10, MinTime: 0, MaxTime: 10}, + chunks.Meta{Ref: 20, MinTime: 10, MaxTime: 20}, + ), "chunk minT 10 is not higher than previous chunk maxT 10") + + require.EqualError(t, idx.AddSeries(1, labels.FromStrings("__name__", "2"), + chunks.Meta{Ref: 10, MinTime: 100, MaxTime: 30}, + ), "chunk maxT 30 is less than minT 100") + + require.NoError(t, idx.Close()) +} From e43a1d546a808332e9833cbe949274b512b6e894 Mon Sep 17 00:00:00 2001 From: Arve Knudsen Date: Tue, 6 Feb 2024 12:34:03 +0400 Subject: [PATCH 069/237] scripts/genproto.sh: Support OSX sed Signed-off-by: Arve Knudsen --- scripts/genproto.sh | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/scripts/genproto.sh b/scripts/genproto.sh index 3c4a26658d..dee51d4aad 100755 --- a/scripts/genproto.sh +++ b/scripts/genproto.sh @@ -43,11 +43,11 @@ for dir in ${DIRS}; do protoc --gogofast_out=Mgoogle/protobuf/timestamp.proto=github.com/gogo/protobuf/types,paths=source_relative:. -I=. \ -I="${GOGOPROTO_PATH}" \ ./io/prometheus/client/*.proto - sed -i.bak -E 's/import _ \"github.com\/gogo\/protobuf\/gogoproto\"//g' -- *.pb.go - sed -i.bak -E 's/import _ \"google\/protobuf\"//g' -- *.pb.go - sed -i.bak -E 's/\t_ \"google\/protobuf\"//g' -- *.pb.go - sed -i.bak -E 's/golang\/protobuf\/descriptor/gogo\/protobuf\/protoc-gen-gogo\/descriptor/g' -- *.go - sed -i.bak -E 's/golang\/protobuf/gogo\/protobuf/g' -- *.go + sed -i.bak -E 's/import _ \"github.com\/gogo\/protobuf\/gogoproto\"//g' *.pb.go + sed -i.bak -E 's/import _ \"google\/protobuf\"//g' *.pb.go + sed -i.bak -E 's/\t_ \"google\/protobuf\"//g' *.pb.go + sed -i.bak -E 's/golang\/protobuf\/descriptor/gogo\/protobuf\/protoc-gen-gogo\/descriptor/g' *.go + sed -i.bak -E 's/golang\/protobuf/gogo\/protobuf/g' *.go rm -f -- *.bak goimports -w ./*.go ./io/prometheus/client/*.go popd From 7d7787f2508e22d00415cc4fe6c31fb1acc4e83b Mon Sep 17 00:00:00 2001 From: Arve Knudsen Date: Tue, 6 Feb 2024 13:51:16 +0400 Subject: [PATCH 070/237] Makefile: Support golangci-lint on ARM64 Signed-off-by: Arve Knudsen --- Makefile.common | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile.common b/Makefile.common index bc2a07d728..5fd1782371 100644 --- a/Makefile.common +++ b/Makefile.common @@ -62,10 +62,10 @@ SKIP_GOLANGCI_LINT := GOLANGCI_LINT := GOLANGCI_LINT_OPTS ?= GOLANGCI_LINT_VERSION ?= v1.55.2 -# golangci-lint only supports linux, darwin and windows platforms on i386/amd64. +# golangci-lint only supports linux, darwin and windows platforms on i386/amd64/arm64. # windows isn't included here because of the path separator being different. ifeq ($(GOHOSTOS),$(filter $(GOHOSTOS),linux darwin)) - ifeq ($(GOHOSTARCH),$(filter $(GOHOSTARCH),amd64 i386)) + ifeq ($(GOHOSTARCH),$(filter $(GOHOSTARCH),amd64 i386 arm64)) # If we're in CI and there is an Actions file, that means the linter # is being run in Actions, so we don't need to run it here. ifneq (,$(SKIP_GOLANGCI_LINT)) From 0853e52f4e735c4ad933cce429f4c83da649aca6 Mon Sep 17 00:00:00 2001 From: Arve Knudsen Date: Tue, 6 Feb 2024 13:53:37 +0100 Subject: [PATCH 071/237] util/runtime: Ignore nolintlint failure on OSX (#13546) util/runtime: Make nolintlint ignore unconvert linter directive --------- Signed-off-by: Arve Knudsen --- util/runtime/statfs_default.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/util/runtime/statfs_default.go b/util/runtime/statfs_default.go index 8d283c1e44..78cfb1fe41 100644 --- a/util/runtime/statfs_default.go +++ b/util/runtime/statfs_default.go @@ -71,7 +71,8 @@ func Statfs(path string) string { var fs syscall.Statfs_t err := syscall.Statfs(path, &fs) - //nolint:unconvert // This ensure Type format on all Platforms + // nolintlint might cry out depending on the architecture (e.g. ARM64), so ignore it. + //nolint:unconvert,nolintlint // This ensures Type format on all Platforms. localType := int64(fs.Type) if err != nil { return strconv.FormatInt(localType, 16) From 384ab025e0239479a086ec8affd12f30be80dfea Mon Sep 17 00:00:00 2001 From: beorn7 Date: Wed, 7 Feb 2024 18:06:05 +0100 Subject: [PATCH 072/237] promql: Expose issue #11708 The shorter range in the test makes early points drop out of the range in range queries, exposing the issue. Signed-off-by: beorn7 --- promql/testdata/functions.test | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/promql/testdata/functions.test b/promql/testdata/functions.test index c40a6272bc..4e104e406e 100644 --- a/promql/testdata/functions.test +++ b/promql/testdata/functions.test @@ -221,19 +221,19 @@ eval instant at 50m deriv(testcounter_reset_middle[100m]) # intercept at t=0: 6.818181818181818 # intercept at t=3000: 38.63636363636364 # intercept at t=3000+3600: 76.81818181818181 -eval instant at 50m predict_linear(testcounter_reset_middle[100m], 3600) +eval instant at 50m predict_linear(testcounter_reset_middle[50m], 3600) {} 76.81818181818181 # intercept at t = 3000+3600 = 6600 -eval instant at 50m predict_linear(testcounter_reset_middle[100m] @ 3000, 3600) +eval instant at 50m predict_linear(testcounter_reset_middle[50m] @ 3000, 3600) {} 76.81818181818181 # intercept at t = 600+3600 = 4200 -eval instant at 10m predict_linear(testcounter_reset_middle[100m] @ 3000, 3600) +eval instant at 10m predict_linear(testcounter_reset_middle[50m] @ 3000, 3600) {} 51.36363636363637 # intercept at t = 4200+3600 = 7800 -eval instant at 70m predict_linear(testcounter_reset_middle[100m] @ 3000, 3600) +eval instant at 70m predict_linear(testcounter_reset_middle[50m] @ 3000, 3600) {} 89.54545454545455 # With http_requests, there is a sample value exactly at the end of From 86d7618d843f4a3a90e7e05e2228b94fe6f7744c Mon Sep 17 00:00:00 2001 From: beorn7 Date: Wed, 7 Feb 2024 18:07:51 +0100 Subject: [PATCH 073/237] promql: Fix wrongly scoped range vectors Fixes #11708. If a range vector is fixen in time with the @ modifier, it gets still moved around for different steps in a range query. Since no additional points are retrieved from the TSDB, this leads to steadily emptying the range, leading to the weird behavior described in isse #11708. This only happens for functions listed in `AtModifierUnsafeFunctions`, and the only of those that takes a range vector is `predict_linear`, which is the reason why we see it only for this particular function. Signed-off-by: beorn7 --- promql/engine.go | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/promql/engine.go b/promql/engine.go index 02004e5f96..575ca58eae 100644 --- a/promql/engine.go +++ b/promql/engine.go @@ -1494,10 +1494,14 @@ func (ev *evaluator) eval(expr parser.Expr) (parser.Value, annotations.Annotatio otherInArgs[j][0].F = otherArgs[j][0].Floats[step].F } } - maxt := ts - offset - mint := maxt - selRange - // Evaluate the matrix selector for this series for this step. - floats, histograms = ev.matrixIterSlice(it, mint, maxt, floats, histograms) + // Evaluate the matrix selector for this series + // for this step, but only if this is the 1st + // iteration or no @ modifier has been used. + if ts == ev.startTimestamp || selVS.Timestamp == nil { + maxt := ts - offset + mint := maxt - selRange + floats, histograms = ev.matrixIterSlice(it, mint, maxt, floats, histograms) + } if len(floats)+len(histograms) == 0 { continue } From 553d92affd423db09112be2c04e64a7de8671358 Mon Sep 17 00:00:00 2001 From: beorn7 Date: Wed, 7 Feb 2024 18:12:26 +0100 Subject: [PATCH 074/237] model/labels: Fix new lint warning in test Signed-off-by: beorn7 --- model/labels/labels_test.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/model/labels/labels_test.go b/model/labels/labels_test.go index c497851d1b..0221c66eb4 100644 --- a/model/labels/labels_test.go +++ b/model/labels/labels_test.go @@ -708,7 +708,8 @@ func TestScratchBuilder(t *testing.T) { func TestLabels_Hash(t *testing.T) { lbls := FromStrings("foo", "bar", "baz", "qux") - require.Equal(t, lbls.Hash(), lbls.Hash()) + hash1, hash2 := lbls.Hash(), lbls.Hash() + require.Equal(t, hash1, hash2) require.NotEqual(t, lbls.Hash(), FromStrings("foo", "bar").Hash(), "different labels match.") } From d77c30102ec3b59c1e4011a9ff7fb06cf118a4dc Mon Sep 17 00:00:00 2001 From: Bryan Boreham Date: Sat, 29 Apr 2023 19:58:55 +0100 Subject: [PATCH 075/237] utils: add DeepEqual replacement using go-cmp go-cmp allows control over how unexported fields and implementation details are handled. Signed-off-by: Bryan Boreham --- go.mod | 2 +- util/testutil/cmp.go | 43 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+), 1 deletion(-) create mode 100644 util/testutil/cmp.go diff --git a/go.mod b/go.mod index 7e586580e6..0ba4deb40d 100644 --- a/go.mod +++ b/go.mod @@ -28,6 +28,7 @@ require ( github.com/go-zookeeper/zk v1.0.3 github.com/gogo/protobuf v1.3.2 github.com/golang/snappy v0.0.4 + github.com/google/go-cmp v0.6.0 github.com/google/pprof v0.0.0-20240117000934-35fc243c5815 github.com/google/uuid v1.5.0 github.com/gophercloud/gophercloud v1.8.0 @@ -135,7 +136,6 @@ require ( github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/protobuf v1.5.3 // indirect github.com/google/gnostic-models v0.6.8 // indirect - github.com/google/go-cmp v0.6.0 // indirect github.com/google/go-querystring v1.1.0 // indirect github.com/google/gofuzz v1.2.0 // indirect github.com/google/s2a-go v0.1.7 // indirect diff --git a/util/testutil/cmp.go b/util/testutil/cmp.go new file mode 100644 index 0000000000..370d191f3f --- /dev/null +++ b/util/testutil/cmp.go @@ -0,0 +1,43 @@ +// Copyright 2023 The Prometheus Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package testutil + +import ( + "fmt" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/stretchr/testify/require" + + "github.com/prometheus/prometheus/model/labels" +) + +// Replacement for require.Equal using go-cmp adapted for Prometheus data structures, instead of DeepEqual. +func RequireEqual(t testing.TB, expected, actual interface{}, msgAndArgs ...interface{}) { + t.Helper() + RequireEqualWithOptions(t, expected, actual, nil, msgAndArgs...) +} + +// As RequireEqual but allows extra cmp.Options. +func RequireEqualWithOptions(t testing.TB, expected, actual interface{}, extra []cmp.Option, msgAndArgs ...interface{}) { + t.Helper() + options := append([]cmp.Option{cmp.Comparer(labels.Equal)}, extra...) + if cmp.Equal(expected, actual, options...) { + return + } + diff := cmp.Diff(expected, actual, options...) + require.Fail(t, fmt.Sprintf("Not equal: \n"+ + "expected: %s\n"+ + "actual : %s%s", expected, actual, diff), msgAndArgs...) +} From 39af788dbd418ac90cba0547592757ae0e3ca816 Mon Sep 17 00:00:00 2001 From: Bryan Boreham Date: Sun, 16 Apr 2023 14:13:31 +0200 Subject: [PATCH 076/237] Tests: use replacement DeepEquals using go-cmp Use DeepEqual replacement using go-cmp, which is more flexible. Signed-off-by: Bryan Boreham --- cmd/promtool/backfill_test.go | 3 ++- cmd/promtool/sd_test.go | 3 ++- config/config_test.go | 7 ++++--- promql/engine_test.go | 15 ++++++++------- rules/alerting_test.go | 13 +++++++------ rules/manager_test.go | 5 +++-- rules/recording_test.go | 3 ++- scrape/scrape_test.go | 25 ++++++++++++++++--------- tsdb/db_test.go | 4 ++-- tsdb/head_test.go | 4 +++- tsdb/record/record_test.go | 5 +++-- tsdb/wal_test.go | 11 ++++++----- tsdb/wlog/checkpoint_test.go | 3 ++- web/api/v1/api_test.go | 5 +++-- web/federate_test.go | 3 ++- 15 files changed, 65 insertions(+), 44 deletions(-) diff --git a/cmd/promtool/backfill_test.go b/cmd/promtool/backfill_test.go index 7d29690e47..32abfa46a8 100644 --- a/cmd/promtool/backfill_test.go +++ b/cmd/promtool/backfill_test.go @@ -26,6 +26,7 @@ import ( "github.com/prometheus/prometheus/storage" "github.com/prometheus/prometheus/tsdb" "github.com/prometheus/prometheus/tsdb/chunkenc" + "github.com/prometheus/prometheus/util/testutil" ) type backfillSample struct { @@ -76,7 +77,7 @@ func testBlocks(t *testing.T, db *tsdb.DB, expectedMinTime, expectedMaxTime, exp allSamples := queryAllSeries(t, q, expectedMinTime, expectedMaxTime) sortSamples(allSamples) sortSamples(expectedSamples) - require.Equal(t, expectedSamples, allSamples, "did not create correct samples") + testutil.RequireEqual(t, expectedSamples, allSamples, "did not create correct samples") if len(allSamples) > 0 { require.Equal(t, expectedMinTime, allSamples[0].Timestamp, "timestamp of first sample is not the expected minimum time") diff --git a/cmd/promtool/sd_test.go b/cmd/promtool/sd_test.go index 2f4d3aba7d..cb65ee72aa 100644 --- a/cmd/promtool/sd_test.go +++ b/cmd/promtool/sd_test.go @@ -23,6 +23,7 @@ import ( "github.com/prometheus/prometheus/discovery/targetgroup" "github.com/prometheus/prometheus/model/labels" "github.com/prometheus/prometheus/model/relabel" + "github.com/prometheus/prometheus/util/testutil" "github.com/stretchr/testify/require" ) @@ -69,5 +70,5 @@ func TestSDCheckResult(t *testing.T) { }, } - require.Equal(t, expectedSDCheckResult, getSDCheckResult(targetGroups, scrapeConfig, true)) + testutil.RequireEqual(t, expectedSDCheckResult, getSDCheckResult(targetGroups, scrapeConfig, true)) } diff --git a/config/config_test.go b/config/config_test.go index e614a44637..36b125f794 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -58,6 +58,7 @@ import ( "github.com/prometheus/prometheus/discovery/zookeeper" "github.com/prometheus/prometheus/model/labels" "github.com/prometheus/prometheus/model/relabel" + "github.com/prometheus/prometheus/util/testutil" ) func mustParseURL(u string) *config.URL { @@ -2037,16 +2038,16 @@ func TestExpandExternalLabels(t *testing.T) { c, err := LoadFile("testdata/external_labels.good.yml", false, false, log.NewNopLogger()) require.NoError(t, err) - require.Equal(t, labels.FromStrings("bar", "foo", "baz", "foo${TEST}bar", "foo", "${TEST}", "qux", "foo$${TEST}", "xyz", "foo$$bar"), c.GlobalConfig.ExternalLabels) + testutil.RequireEqual(t, labels.FromStrings("bar", "foo", "baz", "foo${TEST}bar", "foo", "${TEST}", "qux", "foo$${TEST}", "xyz", "foo$$bar"), c.GlobalConfig.ExternalLabels) c, err = LoadFile("testdata/external_labels.good.yml", false, true, log.NewNopLogger()) require.NoError(t, err) - require.Equal(t, labels.FromStrings("bar", "foo", "baz", "foobar", "foo", "", "qux", "foo${TEST}", "xyz", "foo$bar"), c.GlobalConfig.ExternalLabels) + testutil.RequireEqual(t, labels.FromStrings("bar", "foo", "baz", "foobar", "foo", "", "qux", "foo${TEST}", "xyz", "foo$bar"), c.GlobalConfig.ExternalLabels) os.Setenv("TEST", "TestValue") c, err = LoadFile("testdata/external_labels.good.yml", false, true, log.NewNopLogger()) require.NoError(t, err) - require.Equal(t, labels.FromStrings("bar", "foo", "baz", "fooTestValuebar", "foo", "TestValue", "qux", "foo${TEST}", "xyz", "foo$bar"), c.GlobalConfig.ExternalLabels) + testutil.RequireEqual(t, labels.FromStrings("bar", "foo", "baz", "fooTestValuebar", "foo", "TestValue", "qux", "foo${TEST}", "xyz", "foo$bar"), c.GlobalConfig.ExternalLabels) } func TestAgentMode(t *testing.T) { diff --git a/promql/engine_test.go b/promql/engine_test.go index 105cdc10d5..5dc385942c 100644 --- a/promql/engine_test.go +++ b/promql/engine_test.go @@ -38,6 +38,7 @@ import ( "github.com/prometheus/prometheus/util/annotations" "github.com/prometheus/prometheus/util/stats" "github.com/prometheus/prometheus/util/teststorage" + "github.com/prometheus/prometheus/util/testutil" ) func TestMain(m *testing.M) { @@ -1631,7 +1632,7 @@ load 1ms sort.Sort(expMat) sort.Sort(res.Value.(Matrix)) } - require.Equal(t, c.result, res.Value, "query %q failed", c.query) + testutil.RequireEqual(t, c.result, res.Value, "query %q failed", c.query) }) } } @@ -1956,7 +1957,7 @@ func TestSubquerySelector(t *testing.T) { require.Equal(t, c.Result.Err, res.Err) mat := res.Value.(Matrix) sort.Sort(mat) - require.Equal(t, c.Result.Value, mat) + testutil.RequireEqual(t, c.Result.Value, mat) }) } }) @@ -2001,7 +2002,7 @@ load 1m res := qry.Exec(context.Background()) require.NoError(t, res.Err) - require.Equal(t, expectedResult, res.Value) + testutil.RequireEqual(t, expectedResult, res.Value) } type FakeQueryLogger struct { @@ -3147,7 +3148,7 @@ func TestRangeQuery(t *testing.T) { res := qry.Exec(context.Background()) require.NoError(t, res.Err) - require.Equal(t, c.Result, res.Value) + testutil.RequireEqual(t, c.Result, res.Value) }) } } @@ -4347,7 +4348,7 @@ func TestNativeHistogram_Sum_Count_Add_AvgOperator(t *testing.T) { vector, err := res.Vector() require.NoError(t, err) - require.Equal(t, exp, vector) + testutil.RequireEqual(t, exp, vector) } // sum(). @@ -4605,7 +4606,7 @@ func TestNativeHistogram_SubOperator(t *testing.T) { } } - require.Equal(t, exp, vector) + testutil.RequireEqual(t, exp, vector) } // - operator. @@ -4753,7 +4754,7 @@ func TestNativeHistogram_MulDivOperator(t *testing.T) { vector, err := res.Vector() require.NoError(t, err) - require.Equal(t, exp, vector) + testutil.RequireEqual(t, exp, vector) } // histogram * scalar. diff --git a/rules/alerting_test.go b/rules/alerting_test.go index ba39fbf7a4..45527dd300 100644 --- a/rules/alerting_test.go +++ b/rules/alerting_test.go @@ -31,6 +31,7 @@ import ( "github.com/prometheus/prometheus/promql/parser" "github.com/prometheus/prometheus/storage" "github.com/prometheus/prometheus/util/teststorage" + "github.com/prometheus/prometheus/util/testutil" ) var testEngine = promql.NewEngine(promql.EngineOpts{ @@ -180,7 +181,7 @@ func TestAlertingRuleLabelsUpdate(t *testing.T) { } } - require.Equal(t, result, filteredRes) + testutil.RequireEqual(t, result, filteredRes) } evalTime := baseTime.Add(time.Duration(len(results)) * time.Minute) res, err := rule.Eval(context.TODO(), evalTime, EngineQueryFunc(testEngine, storage), nil, 0) @@ -278,7 +279,7 @@ func TestAlertingRuleExternalLabelsInTemplate(t *testing.T) { } } - require.Equal(t, result, filteredRes) + testutil.RequireEqual(t, result, filteredRes) } func TestAlertingRuleExternalURLInTemplate(t *testing.T) { @@ -371,7 +372,7 @@ func TestAlertingRuleExternalURLInTemplate(t *testing.T) { } } - require.Equal(t, result, filteredRes) + testutil.RequireEqual(t, result, filteredRes) } func TestAlertingRuleEmptyLabelFromTemplate(t *testing.T) { @@ -425,7 +426,7 @@ func TestAlertingRuleEmptyLabelFromTemplate(t *testing.T) { require.Equal(t, "ALERTS_FOR_STATE", smplName) } } - require.Equal(t, result, filteredRes) + testutil.RequireEqual(t, result, filteredRes) } func TestAlertingRuleQueryInTemplate(t *testing.T) { @@ -823,7 +824,7 @@ func TestKeepFiringFor(t *testing.T) { } } - require.Equal(t, result, filteredRes) + testutil.RequireEqual(t, result, filteredRes) } evalTime := baseTime.Add(time.Duration(len(results)) * time.Minute) res, err := rule.Eval(context.TODO(), evalTime, EngineQueryFunc(testEngine, storage), nil, 0) @@ -870,7 +871,7 @@ func TestPendingAndKeepFiringFor(t *testing.T) { for _, smpl := range res { smplName := smpl.Metric.Get("__name__") if smplName == "ALERTS" { - require.Equal(t, result, smpl) + testutil.RequireEqual(t, result, smpl) } else { // If not 'ALERTS', it has to be 'ALERTS_FOR_STATE'. require.Equal(t, "ALERTS_FOR_STATE", smplName) diff --git a/rules/manager_test.go b/rules/manager_test.go index 5e3a609220..4215ca4e43 100644 --- a/rules/manager_test.go +++ b/rules/manager_test.go @@ -42,6 +42,7 @@ import ( "github.com/prometheus/prometheus/tsdb/chunkenc" "github.com/prometheus/prometheus/tsdb/tsdbutil" "github.com/prometheus/prometheus/util/teststorage" + prom_testutil "github.com/prometheus/prometheus/util/testutil" ) func TestMain(m *testing.M) { @@ -180,7 +181,7 @@ func TestAlertingRule(t *testing.T) { sort.Slice(filteredRes, func(i, j int) bool { return labels.Compare(filteredRes[i].Metric, filteredRes[j].Metric) < 0 }) - require.Equal(t, test.result, filteredRes) + prom_testutil.RequireEqual(t, test.result, filteredRes) for _, aa := range rule.ActiveAlerts() { require.Zero(t, aa.Labels.Get(model.MetricNameLabel), "%s label set on active alert: %s", model.MetricNameLabel, aa.Labels) @@ -330,7 +331,7 @@ func TestForStateAddSamples(t *testing.T) { sort.Slice(filteredRes, func(i, j int) bool { return labels.Compare(filteredRes[i].Metric, filteredRes[j].Metric) < 0 }) - require.Equal(t, test.result, filteredRes) + prom_testutil.RequireEqual(t, test.result, filteredRes) for _, aa := range rule.ActiveAlerts() { require.Zero(t, aa.Labels.Get(model.MetricNameLabel), "%s label set on active alert: %s", model.MetricNameLabel, aa.Labels) diff --git a/rules/recording_test.go b/rules/recording_test.go index 7a09cd6d8a..24b7d65390 100644 --- a/rules/recording_test.go +++ b/rules/recording_test.go @@ -25,6 +25,7 @@ import ( "github.com/prometheus/prometheus/promql" "github.com/prometheus/prometheus/promql/parser" "github.com/prometheus/prometheus/util/teststorage" + "github.com/prometheus/prometheus/util/testutil" ) var ( @@ -126,7 +127,7 @@ func TestRuleEval(t *testing.T) { rule := NewRecordingRule("test_rule", scenario.expr, scenario.ruleLabels) result, err := rule.Eval(context.TODO(), ruleEvaluationTime, EngineQueryFunc(testEngine, storage), nil, 0) require.NoError(t, err) - require.Equal(t, scenario.expected, result) + testutil.RequireEqual(t, scenario.expected, result) }) } } diff --git a/scrape/scrape_test.go b/scrape/scrape_test.go index f827ffc8da..c1c6960bcb 100644 --- a/scrape/scrape_test.go +++ b/scrape/scrape_test.go @@ -32,6 +32,7 @@ import ( "github.com/go-kit/log" "github.com/gogo/protobuf/proto" + "github.com/google/go-cmp/cmp" "github.com/prometheus/client_golang/prometheus" dto "github.com/prometheus/client_model/go" config_util "github.com/prometheus/common/config" @@ -1383,10 +1384,16 @@ func TestScrapeLoopAppend(t *testing.T) { } t.Logf("Test:%s", test.title) - require.Equal(t, expected, app.resultFloats) + requireEqual(t, expected, app.resultFloats) } } +func requireEqual(t *testing.T, expected, actual interface{}, msgAndArgs ...interface{}) { + testutil.RequireEqualWithOptions(t, expected, actual, + []cmp.Option{cmp.AllowUnexported(floatSample{}), cmp.AllowUnexported(histogramSample{})}, + msgAndArgs...) +} + func TestScrapeLoopAppendForConflictingPrefixedLabels(t *testing.T) { testcases := map[string]struct { targetLabels []string @@ -1450,7 +1457,7 @@ func TestScrapeLoopAppendForConflictingPrefixedLabels(t *testing.T) { require.NoError(t, slApp.Commit()) - require.Equal(t, []floatSample{ + requireEqual(t, []floatSample{ { metric: labels.FromStrings(tc.expected...), t: timestamp.FromTime(time.Date(2000, 1, 1, 1, 0, 0, 0, time.UTC)), @@ -1544,7 +1551,7 @@ func TestScrapeLoopAppendSampleLimit(t *testing.T) { f: 1, }, } - require.Equal(t, want, resApp.rolledbackFloats, "Appended samples not as expected:\n%s", appender) + requireEqual(t, want, resApp.rolledbackFloats, "Appended samples not as expected:\n%s", appender) now = time.Now() slApp = sl.appender(context.Background()) @@ -2158,9 +2165,9 @@ metric: < _, _, _, err := sl.append(app, buf.Bytes(), test.contentType, now) require.NoError(t, err) require.NoError(t, app.Commit()) - require.Equal(t, test.floats, app.resultFloats) - require.Equal(t, test.histograms, app.resultHistograms) - require.Equal(t, test.exemplars, app.resultExemplars) + requireEqual(t, test.floats, app.resultFloats) + requireEqual(t, test.histograms, app.resultHistograms) + requireEqual(t, test.exemplars, app.resultExemplars) }) } } @@ -2215,8 +2222,8 @@ func TestScrapeLoopAppendExemplarSeries(t *testing.T) { require.NoError(t, app.Commit()) } - require.Equal(t, samples, app.resultFloats) - require.Equal(t, exemplars, app.resultExemplars) + requireEqual(t, samples, app.resultFloats) + requireEqual(t, exemplars, app.resultExemplars) } func TestScrapeLoopRunReportsTargetDownOnScrapeError(t *testing.T) { @@ -2292,7 +2299,7 @@ func TestScrapeLoopAppendGracefullyIfAmendOrOutOfOrderOrOutOfBounds(t *testing.T f: 1, }, } - require.Equal(t, want, app.resultFloats, "Appended samples not as expected:\n%s", appender) + requireEqual(t, want, app.resultFloats, "Appended samples not as expected:\n%s", appender) require.Equal(t, 4, total) require.Equal(t, 4, added) require.Equal(t, 1, seriesAdded) diff --git a/tsdb/db_test.go b/tsdb/db_test.go index 70b12d487a..fd20c0e205 100644 --- a/tsdb/db_test.go +++ b/tsdb/db_test.go @@ -4058,11 +4058,11 @@ func TestOOOWALWrite(t *testing.T) { // The normal WAL. actRecs := getRecords(path.Join(dir, "wal")) - require.Equal(t, inOrderRecords, actRecs) + testutil.RequireEqual(t, inOrderRecords, actRecs) // The WBL. actRecs = getRecords(path.Join(dir, wlog.WblDirName)) - require.Equal(t, oooRecords, actRecs) + testutil.RequireEqual(t, oooRecords, actRecs) } // Tests https://github.com/prometheus/prometheus/issues/10291#issuecomment-1044373110. diff --git a/tsdb/head_test.go b/tsdb/head_test.go index cec7c06bb6..c4bb77467b 100644 --- a/tsdb/head_test.go +++ b/tsdb/head_test.go @@ -30,6 +30,7 @@ import ( "testing" "time" + "github.com/google/go-cmp/cmp" "github.com/prometheus/client_golang/prometheus" prom_testutil "github.com/prometheus/client_golang/prometheus/testutil" "github.com/prometheus/common/model" @@ -50,6 +51,7 @@ import ( "github.com/prometheus/prometheus/tsdb/tombstones" "github.com/prometheus/prometheus/tsdb/tsdbutil" "github.com/prometheus/prometheus/tsdb/wlog" + "github.com/prometheus/prometheus/util/testutil" ) // newTestHeadDefaultOptions returns the HeadOptions that should be used by default in unit tests. @@ -3743,7 +3745,7 @@ func TestChunkSnapshot(t *testing.T) { }) require.NoError(t, err) // Verifies both existence of right exemplars and order of exemplars in the buffer. - require.Equal(t, expExemplars, actExemplars) + testutil.RequireEqualWithOptions(t, expExemplars, actExemplars, []cmp.Option{cmp.AllowUnexported(ex{})}) } var ( diff --git a/tsdb/record/record_test.go b/tsdb/record/record_test.go index 57599ef6d5..0cecb43c2d 100644 --- a/tsdb/record/record_test.go +++ b/tsdb/record/record_test.go @@ -24,6 +24,7 @@ import ( "github.com/prometheus/prometheus/model/labels" "github.com/prometheus/prometheus/tsdb/encoding" "github.com/prometheus/prometheus/tsdb/tombstones" + "github.com/prometheus/prometheus/util/testutil" ) func TestRecord_EncodeDecode(t *testing.T) { @@ -44,7 +45,7 @@ func TestRecord_EncodeDecode(t *testing.T) { } decSeries, err := dec.Series(enc.Series(series, nil), nil) require.NoError(t, err) - require.Equal(t, series, decSeries) + testutil.RequireEqual(t, series, decSeries) metadata := []RefMetadata{ { @@ -107,7 +108,7 @@ func TestRecord_EncodeDecode(t *testing.T) { } decExemplars, err := dec.Exemplars(enc.Exemplars(exemplars, nil), nil) require.NoError(t, err) - require.Equal(t, exemplars, decExemplars) + testutil.RequireEqual(t, exemplars, decExemplars) histograms := []RefHistogramSample{ { diff --git a/tsdb/wal_test.go b/tsdb/wal_test.go index 8700a70754..466183e9b8 100644 --- a/tsdb/wal_test.go +++ b/tsdb/wal_test.go @@ -34,6 +34,7 @@ import ( "github.com/prometheus/prometheus/tsdb/record" "github.com/prometheus/prometheus/tsdb/tombstones" "github.com/prometheus/prometheus/tsdb/wlog" + "github.com/prometheus/prometheus/util/testutil" ) func TestSegmentWAL_cut(t *testing.T) { @@ -147,7 +148,7 @@ func TestSegmentWAL_Truncate(t *testing.T) { readSeries = append(readSeries, s...) }, nil, nil)) - require.Equal(t, expected, readSeries) + testutil.RequireEqual(t, expected, readSeries) } // Symmetrical test of reading and writing to the WAL via its main interface. @@ -213,9 +214,9 @@ func TestSegmentWAL_Log_Restore(t *testing.T) { require.NoError(t, r.Read(serf, smplf, delf)) - require.Equal(t, recordedSamples, resultSamples) - require.Equal(t, recordedSeries, resultSeries) - require.Equal(t, recordedDeletes, resultDeletes) + testutil.RequireEqual(t, recordedSamples, resultSamples) + testutil.RequireEqual(t, recordedSeries, resultSeries) + testutil.RequireEqual(t, recordedDeletes, resultDeletes) series := series[k : k+(numMetrics/iterations)] @@ -533,7 +534,7 @@ func TestMigrateWAL_Fuzz(t *testing.T) { } require.NoError(t, r.Err()) - require.Equal(t, []interface{}{ + testutil.RequireEqual(t, []interface{}{ []record.RefSeries{ {Ref: 100, Labels: labels.FromStrings("abc", "def", "123", "456")}, {Ref: 1, Labels: labels.FromStrings("abc", "def2", "1234", "4567")}, diff --git a/tsdb/wlog/checkpoint_test.go b/tsdb/wlog/checkpoint_test.go index bc5b4f20fa..ce9f9ef99f 100644 --- a/tsdb/wlog/checkpoint_test.go +++ b/tsdb/wlog/checkpoint_test.go @@ -29,6 +29,7 @@ import ( "github.com/prometheus/prometheus/model/labels" "github.com/prometheus/prometheus/tsdb/chunks" "github.com/prometheus/prometheus/tsdb/record" + "github.com/prometheus/prometheus/util/testutil" ) func TestLastCheckpoint(t *testing.T) { @@ -286,7 +287,7 @@ func TestCheckpoint(t *testing.T) { {Ref: 2, Labels: labels.FromStrings("a", "b", "c", "2")}, {Ref: 4, Labels: labels.FromStrings("a", "b", "c", "4")}, } - require.Equal(t, expectedRefSeries, series) + testutil.RequireEqual(t, expectedRefSeries, series) expectedRefMetadata := []record.RefMetadata{ {Ref: 0, Unit: fmt.Sprintf("%d", last-100), Help: fmt.Sprintf("%d", last-100)}, diff --git a/web/api/v1/api_test.go b/web/api/v1/api_test.go index eceadb20ae..8c0a9c73bf 100644 --- a/web/api/v1/api_test.go +++ b/web/api/v1/api_test.go @@ -32,6 +32,7 @@ import ( "github.com/prometheus/prometheus/prompb" "github.com/prometheus/prometheus/util/stats" + "github.com/prometheus/prometheus/util/testutil" "github.com/go-kit/log" "github.com/prometheus/client_golang/prometheus" @@ -597,7 +598,7 @@ func TestGetSeries(t *testing.T) { r := res.data.([]labels.Labels) sort.Sort(byLabels(tc.expected)) sort.Sort(byLabels(r)) - require.Equal(t, tc.expected, r) + testutil.RequireEqual(t, tc.expected, r) } }) } @@ -2878,7 +2879,7 @@ func assertAPIError(t *testing.T, got *apiError, exp errorType) { func assertAPIResponse(t *testing.T, got, exp interface{}) { t.Helper() - require.Equal(t, exp, got) + testutil.RequireEqual(t, exp, got) } func assertAPIResponseLength(t *testing.T, got interface{}, expLen int) { diff --git a/web/federate_test.go b/web/federate_test.go index 94783a7399..92b806fe89 100644 --- a/web/federate_test.go +++ b/web/federate_test.go @@ -37,6 +37,7 @@ import ( "github.com/prometheus/prometheus/storage" "github.com/prometheus/prometheus/tsdb" "github.com/prometheus/prometheus/util/teststorage" + "github.com/prometheus/prometheus/util/testutil" ) var scenarios = map[string]struct { @@ -427,5 +428,5 @@ func TestFederationWithNativeHistograms(t *testing.T) { // TODO(codesome): Once PromQL is able to set the CounterResetHint on histograms, // test it with switching histogram types for metric families. require.Equal(t, 4, metricFamilies) - require.Equal(t, expVec, actVec) + testutil.RequireEqual(t, expVec, actVec) } From 5a6c8f9c152dfab5f96f5b6f14703b801b014255 Mon Sep 17 00:00:00 2001 From: Bryan Boreham Date: Wed, 24 Jan 2024 09:52:16 +0000 Subject: [PATCH 077/237] promtool: use go-cmp instead of DeepEqual go-cmp allows more control over unexported fields and implementation details. Signed-off-by: Bryan Boreham --- cmd/promtool/sd.go | 4 ++-- cmd/promtool/unittest.go | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/cmd/promtool/sd.go b/cmd/promtool/sd.go index 4892743fc0..e65262d439 100644 --- a/cmd/promtool/sd.go +++ b/cmd/promtool/sd.go @@ -18,10 +18,10 @@ import ( "encoding/json" "fmt" "os" - "reflect" "time" "github.com/go-kit/log" + "github.com/google/go-cmp/cmp" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/prometheus/config" @@ -153,7 +153,7 @@ func getSDCheckResult(targetGroups []*targetgroup.Group, scrapeConfig *config.Sc duplicateRes := false for _, sdCheckRes := range sdCheckResults { - if reflect.DeepEqual(sdCheckRes, result) { + if cmp.Equal(sdCheckRes, result, cmp.Comparer(labels.Equal)) { duplicateRes = true break } diff --git a/cmd/promtool/unittest.go b/cmd/promtool/unittest.go index a89288c44a..4777b88098 100644 --- a/cmd/promtool/unittest.go +++ b/cmd/promtool/unittest.go @@ -20,13 +20,13 @@ import ( "fmt" "os" "path/filepath" - "reflect" "sort" "strconv" "strings" "time" "github.com/go-kit/log" + "github.com/google/go-cmp/cmp" "github.com/grafana/regexp" "github.com/nsf/jsondiff" "github.com/prometheus/common/model" @@ -340,7 +340,7 @@ func (tg *testGroup) test(evalInterval time.Duration, groupOrderMap map[string]i sort.Sort(gotAlerts) sort.Sort(expAlerts) - if !reflect.DeepEqual(expAlerts, gotAlerts) { + if !cmp.Equal(expAlerts, gotAlerts, cmp.Comparer(labels.Equal)) { var testName string if tg.TestGroupName != "" { testName = fmt.Sprintf(" name: %s,\n", tg.TestGroupName) @@ -448,7 +448,7 @@ Outer: sort.Slice(gotSamples, func(i, j int) bool { return labels.Compare(gotSamples[i].Labels, gotSamples[j].Labels) <= 0 }) - if !reflect.DeepEqual(expSamples, gotSamples) { + if !cmp.Equal(expSamples, gotSamples, cmp.Comparer(labels.Equal)) { errs = append(errs, fmt.Errorf(" expr: %q, time: %s,\n exp: %v\n got: %v", testCase.Expr, testCase.EvalTime.String(), parsedSamplesString(expSamples), parsedSamplesString(gotSamples))) } From d0dee51aacffdc51e6c9f463cb4557074b1ad5c2 Mon Sep 17 00:00:00 2001 From: Bryan Boreham Date: Wed, 24 Jan 2024 11:53:36 +0000 Subject: [PATCH 078/237] scrape tests: check NaN values directly Normally, a NaN value is never equal to any other value. Compare sample values via `Float64bits` so that NaN values which are exactly the same will compare equal. Signed-off-by: Bryan Boreham --- scrape/helpers_test.go | 6 ++++++ scrape/scrape_test.go | 27 ++++++--------------------- 2 files changed, 12 insertions(+), 21 deletions(-) diff --git a/scrape/helpers_test.go b/scrape/helpers_test.go index 43ee0fcecf..116fa5c94b 100644 --- a/scrape/helpers_test.go +++ b/scrape/helpers_test.go @@ -18,6 +18,7 @@ import ( "context" "encoding/binary" "fmt" + "math" "math/rand" "strings" "sync" @@ -71,6 +72,11 @@ type floatSample struct { f float64 } +func equalFloatSamples(a, b floatSample) bool { + // Compare Float64bits so NaN values which are exactly the same will compare equal. + return labels.Equal(a.metric, b.metric) && a.t == b.t && math.Float64bits(a.f) == math.Float64bits(b.f) +} + type histogramSample struct { t int64 h *histogram.Histogram diff --git a/scrape/scrape_test.go b/scrape/scrape_test.go index c1c6960bcb..c95ca37418 100644 --- a/scrape/scrape_test.go +++ b/scrape/scrape_test.go @@ -1342,7 +1342,7 @@ func TestScrapeLoopAppend(t *testing.T) { scrapeLabels: `metric NaN`, discoveryLabels: nil, expLset: labels.FromStrings("__name__", "metric"), - expValue: float64(value.NormalNaN), + expValue: math.Float64frombits(value.NormalNaN), }, } @@ -1376,13 +1376,6 @@ func TestScrapeLoopAppend(t *testing.T) { }, } - // When the expected value is NaN - // DeepEqual will report NaNs as being different, - // so replace it with the expected one. - if test.expValue == float64(value.NormalNaN) { - app.resultFloats[0].f = expected[0].f - } - t.Logf("Test:%s", test.title) requireEqual(t, expected, app.resultFloats) } @@ -1390,7 +1383,7 @@ func TestScrapeLoopAppend(t *testing.T) { func requireEqual(t *testing.T, expected, actual interface{}, msgAndArgs ...interface{}) { testutil.RequireEqualWithOptions(t, expected, actual, - []cmp.Option{cmp.AllowUnexported(floatSample{}), cmp.AllowUnexported(histogramSample{})}, + []cmp.Option{cmp.Comparer(equalFloatSamples), cmp.AllowUnexported(histogramSample{})}, msgAndArgs...) } @@ -1691,7 +1684,6 @@ func TestScrapeLoop_ChangingMetricString(t *testing.T) { require.NoError(t, err) require.NoError(t, slApp.Commit()) - // DeepEqual will report NaNs as being different, so replace with a different value. want := []floatSample{ { metric: labels.FromStrings("__name__", "metric_a", "a", "1", "b", "1"), @@ -1723,11 +1715,6 @@ func TestScrapeLoopAppendStaleness(t *testing.T) { require.NoError(t, err) require.NoError(t, slApp.Commit()) - ingestedNaN := math.Float64bits(app.resultFloats[1].f) - require.Equal(t, value.StaleNaN, ingestedNaN, "Appended stale sample wasn't as expected") - - // DeepEqual will report NaNs as being different, so replace with a different value. - app.resultFloats[1].f = 42 want := []floatSample{ { metric: labels.FromStrings(model.MetricNameLabel, "metric_a"), @@ -1737,10 +1724,10 @@ func TestScrapeLoopAppendStaleness(t *testing.T) { { metric: labels.FromStrings(model.MetricNameLabel, "metric_a"), t: timestamp.FromTime(now.Add(time.Second)), - f: 42, + f: math.Float64frombits(value.StaleNaN), }, } - require.Equal(t, want, app.resultFloats, "Appended samples not as expected:\n%s", appender) + requireEqual(t, want, app.resultFloats, "Appended samples not as expected:\n%s", appender) } func TestScrapeLoopAppendNoStalenessIfTimestamp(t *testing.T) { @@ -1783,8 +1770,6 @@ func TestScrapeLoopAppendStalenessIfTrackTimestampStaleness(t *testing.T) { require.NoError(t, err) require.NoError(t, slApp.Commit()) - // DeepEqual will report NaNs as being different, so replace with a different value. - app.resultFloats[1].f = 42 want := []floatSample{ { metric: labels.FromStrings(model.MetricNameLabel, "metric_a"), @@ -1794,10 +1779,10 @@ func TestScrapeLoopAppendStalenessIfTrackTimestampStaleness(t *testing.T) { { metric: labels.FromStrings(model.MetricNameLabel, "metric_a"), t: timestamp.FromTime(now.Add(time.Second)), - f: 42, + f: math.Float64frombits(value.StaleNaN), }, } - require.Equal(t, want, app.resultFloats, "Appended samples not as expected:\n%s", appender) + requireEqual(t, want, app.resultFloats, "Appended samples not as expected:\n%s", appender) } func TestScrapeLoopAppendExemplar(t *testing.T) { From 17f48f2b3bf075b047006a5b36017d5e2b89614a Mon Sep 17 00:00:00 2001 From: Bryan Boreham Date: Wed, 24 Jan 2024 16:48:22 +0000 Subject: [PATCH 079/237] Tests: use replacement DeepEquals in more places Signed-off-by: Bryan Boreham --- model/relabel/relabel_test.go | 3 ++- model/textparse/openmetricsparse_test.go | 5 +++-- model/textparse/promparse_test.go | 3 ++- model/textparse/protobufparse_test.go | 9 +++++---- promql/parser/parse_test.go | 3 ++- rules/alerting_test.go | 2 +- scrape/manager_test.go | 5 +++-- storage/remote/queue_manager_test.go | 3 ++- storage/remote/read_test.go | 3 ++- storage/remote/write_handler_test.go | 17 +++++++++++++---- tsdb/index/index_test.go | 4 ++-- 11 files changed, 37 insertions(+), 20 deletions(-) diff --git a/model/relabel/relabel_test.go b/model/relabel/relabel_test.go index 517b9b8223..6798fb02a5 100644 --- a/model/relabel/relabel_test.go +++ b/model/relabel/relabel_test.go @@ -22,6 +22,7 @@ import ( "gopkg.in/yaml.v2" "github.com/prometheus/prometheus/model/labels" + "github.com/prometheus/prometheus/util/testutil" ) func TestRelabel(t *testing.T) { @@ -591,7 +592,7 @@ func TestRelabel(t *testing.T) { res, keep := Process(test.input, test.relabel...) require.Equal(t, !test.drop, keep) if keep { - require.Equal(t, test.output, res) + testutil.RequireEqual(t, test.output, res) } } } diff --git a/model/textparse/openmetricsparse_test.go b/model/textparse/openmetricsparse_test.go index 2b1d909f38..29f31664fe 100644 --- a/model/textparse/openmetricsparse_test.go +++ b/model/textparse/openmetricsparse_test.go @@ -23,6 +23,7 @@ import ( "github.com/prometheus/prometheus/model/exemplar" "github.com/prometheus/prometheus/model/labels" + "github.com/prometheus/prometheus/util/testutil" ) func TestOpenMetricsParse(t *testing.T) { @@ -268,12 +269,12 @@ foo_total 17.0 1520879607.789 # {id="counter-test"} 5` require.Equal(t, exp[i].m, string(m)) require.Equal(t, exp[i].t, ts) require.Equal(t, exp[i].v, v) - require.Equal(t, exp[i].lset, res) + testutil.RequireEqual(t, exp[i].lset, res) if exp[i].e == nil { require.False(t, found) } else { require.True(t, found) - require.Equal(t, *exp[i].e, e) + testutil.RequireEqual(t, *exp[i].e, e) } case EntryType: diff --git a/model/textparse/promparse_test.go b/model/textparse/promparse_test.go index d34b26ba5c..ccd7ef9ccc 100644 --- a/model/textparse/promparse_test.go +++ b/model/textparse/promparse_test.go @@ -26,6 +26,7 @@ import ( "github.com/stretchr/testify/require" "github.com/prometheus/prometheus/model/labels" + "github.com/prometheus/prometheus/util/testutil" ) func TestPromParse(t *testing.T) { @@ -191,7 +192,7 @@ testmetric{label="\"bar\""} 1` require.Equal(t, exp[i].m, string(m)) require.Equal(t, exp[i].t, ts) require.Equal(t, exp[i].v, v) - require.Equal(t, exp[i].lset, res) + testutil.RequireEqual(t, exp[i].lset, res) case EntryType: m, typ := p.Type() diff --git a/model/textparse/protobufparse_test.go b/model/textparse/protobufparse_test.go index c623f2f746..aa6316919b 100644 --- a/model/textparse/protobufparse_test.go +++ b/model/textparse/protobufparse_test.go @@ -27,6 +27,7 @@ import ( "github.com/prometheus/prometheus/model/exemplar" "github.com/prometheus/prometheus/model/histogram" "github.com/prometheus/prometheus/model/labels" + "github.com/prometheus/prometheus/util/testutil" dto "github.com/prometheus/prometheus/prompb/io/prometheus/client" ) @@ -1993,12 +1994,12 @@ func TestProtobufParse(t *testing.T) { require.Equal(t, int64(0), exp[i].t, "i: %d", i) } require.Equal(t, exp[i].v, v, "i: %d", i) - require.Equal(t, exp[i].lset, res, "i: %d", i) + testutil.RequireEqual(t, exp[i].lset, res, "i: %d", i) if len(exp[i].e) == 0 { require.False(t, eFound, "i: %d", i) } else { require.True(t, eFound, "i: %d", i) - require.Equal(t, exp[i].e[0], e, "i: %d", i) + testutil.RequireEqual(t, exp[i].e[0], e, "i: %d", i) require.False(t, p.Exemplar(&e), "too many exemplars returned, i: %d", i) } if exp[i].ct != 0 { @@ -2017,7 +2018,7 @@ func TestProtobufParse(t *testing.T) { } else { require.Equal(t, int64(0), exp[i].t, "i: %d", i) } - require.Equal(t, exp[i].lset, res, "i: %d", i) + testutil.RequireEqual(t, exp[i].lset, res, "i: %d", i) require.Equal(t, exp[i].m, string(m), "i: %d", i) if shs != nil { require.Equal(t, exp[i].shs, shs, "i: %d", i) @@ -2026,7 +2027,7 @@ func TestProtobufParse(t *testing.T) { } j := 0 for e := (exemplar.Exemplar{}); p.Exemplar(&e); j++ { - require.Equal(t, exp[i].e[j], e, "i: %d", i) + testutil.RequireEqual(t, exp[i].e[j], e, "i: %d", i) e = exemplar.Exemplar{} } require.Len(t, exp[i].e, j, "not enough exemplars found, i: %d", i) diff --git a/promql/parser/parse_test.go b/promql/parser/parse_test.go index 6c26445e38..fbf9672f37 100644 --- a/promql/parser/parse_test.go +++ b/promql/parser/parse_test.go @@ -26,6 +26,7 @@ import ( "github.com/prometheus/prometheus/model/histogram" "github.com/prometheus/prometheus/model/labels" + "github.com/prometheus/prometheus/util/testutil" "github.com/prometheus/prometheus/promql/parser/posrange" ) @@ -4018,7 +4019,7 @@ func TestParseSeries(t *testing.T) { if !test.fail { require.NoError(t, err) - require.Equal(t, test.expectedMetric, metric, "error on input '%s'", test.input) + testutil.RequireEqual(t, test.expectedMetric, metric, "error on input '%s'", test.input) require.Equal(t, test.expectedValues, vals, "error in input '%s'", test.input) } else { require.Error(t, err) diff --git a/rules/alerting_test.go b/rules/alerting_test.go index 45527dd300..a270731d92 100644 --- a/rules/alerting_test.go +++ b/rules/alerting_test.go @@ -719,7 +719,7 @@ func TestSendAlertsDontAffectActiveAlerts(t *testing.T) { // The relabel rule changes a1=1 to a1=bug. // But the labels with the AlertingRule should not be changed. - require.Equal(t, labels.FromStrings("a1", "1"), rule.active[h].Labels) + testutil.RequireEqual(t, labels.FromStrings("a1", "1"), rule.active[h].Labels) } func TestKeepFiringFor(t *testing.T) { diff --git a/scrape/manager_test.go b/scrape/manager_test.go index a73b730786..7b7a929168 100644 --- a/scrape/manager_test.go +++ b/scrape/manager_test.go @@ -40,6 +40,7 @@ import ( "github.com/prometheus/prometheus/model/labels" "github.com/prometheus/prometheus/model/relabel" "github.com/prometheus/prometheus/util/runutil" + "github.com/prometheus/prometheus/util/testutil" ) func TestPopulateLabels(t *testing.T) { @@ -449,8 +450,8 @@ func TestPopulateLabels(t *testing.T) { require.NoError(t, err) } require.Equal(t, c.in, in) - require.Equal(t, c.res, res) - require.Equal(t, c.resOrig, orig) + testutil.RequireEqual(t, c.res, res) + testutil.RequireEqual(t, c.resOrig, orig) } } diff --git a/storage/remote/queue_manager_test.go b/storage/remote/queue_manager_test.go index a79dc0d617..6e903eb59e 100644 --- a/storage/remote/queue_manager_test.go +++ b/storage/remote/queue_manager_test.go @@ -44,6 +44,7 @@ import ( "github.com/prometheus/prometheus/scrape" "github.com/prometheus/prometheus/tsdb/chunks" "github.com/prometheus/prometheus/tsdb/record" + "github.com/prometheus/prometheus/util/testutil" ) const defaultFlushDeadline = 1 * time.Minute @@ -1077,7 +1078,7 @@ func TestProcessExternalLabels(t *testing.T) { } { b.Reset(tc.labels) processExternalLabels(b, tc.externalLabels) - require.Equal(t, tc.expected, b.Labels(), "test %d", i) + testutil.RequireEqual(t, tc.expected, b.Labels(), "test %d", i) } } diff --git a/storage/remote/read_test.go b/storage/remote/read_test.go index 931bacf050..1bec9dfb65 100644 --- a/storage/remote/read_test.go +++ b/storage/remote/read_test.go @@ -28,6 +28,7 @@ import ( "github.com/prometheus/prometheus/model/labels" "github.com/prometheus/prometheus/prompb" "github.com/prometheus/prometheus/util/annotations" + "github.com/prometheus/prometheus/util/testutil" ) func TestNoDuplicateReadConfigs(t *testing.T) { @@ -484,7 +485,7 @@ func TestSampleAndChunkQueryableClient(t *testing.T) { got = append(got, ss.At().Labels()) } require.NoError(t, ss.Err()) - require.Equal(t, tc.expectedSeries, got) + testutil.RequireEqual(t, tc.expectedSeries, got) }) } } diff --git a/storage/remote/write_handler_test.go b/storage/remote/write_handler_test.go index df92dc6bcc..73e5cb17d6 100644 --- a/storage/remote/write_handler_test.go +++ b/storage/remote/write_handler_test.go @@ -26,6 +26,7 @@ import ( "time" "github.com/go-kit/log" + "github.com/google/go-cmp/cmp" "github.com/stretchr/testify/require" "github.com/prometheus/prometheus/model/exemplar" @@ -35,6 +36,7 @@ import ( "github.com/prometheus/prometheus/prompb" "github.com/prometheus/prometheus/storage" "github.com/prometheus/prometheus/tsdb" + "github.com/prometheus/prometheus/util/testutil" ) func TestRemoteWriteHandler(t *testing.T) { @@ -59,23 +61,23 @@ func TestRemoteWriteHandler(t *testing.T) { for _, ts := range writeRequestFixture.Timeseries { labels := labelProtosToLabels(ts.Labels) for _, s := range ts.Samples { - require.Equal(t, mockSample{labels, s.Timestamp, s.Value}, appendable.samples[i]) + requireEqual(t, mockSample{labels, s.Timestamp, s.Value}, appendable.samples[i]) i++ } for _, e := range ts.Exemplars { exemplarLabels := labelProtosToLabels(e.Labels) - require.Equal(t, mockExemplar{labels, exemplarLabels, e.Timestamp, e.Value}, appendable.exemplars[j]) + requireEqual(t, mockExemplar{labels, exemplarLabels, e.Timestamp, e.Value}, appendable.exemplars[j]) j++ } for _, hp := range ts.Histograms { if hp.IsFloatHistogram() { fh := FloatHistogramProtoToFloatHistogram(hp) - require.Equal(t, mockHistogram{labels, hp.Timestamp, nil, fh}, appendable.histograms[k]) + requireEqual(t, mockHistogram{labels, hp.Timestamp, nil, fh}, appendable.histograms[k]) } else { h := HistogramProtoToHistogram(hp) - require.Equal(t, mockHistogram{labels, hp.Timestamp, h, nil}, appendable.histograms[k]) + requireEqual(t, mockHistogram{labels, hp.Timestamp, h, nil}, appendable.histograms[k]) } k++ @@ -293,6 +295,13 @@ type mockHistogram struct { fh *histogram.FloatHistogram } +// Wrapper to instruct go-cmp package to compare a list of structs with unexported fields. +func requireEqual(t *testing.T, expected, actual interface{}, msgAndArgs ...interface{}) { + testutil.RequireEqualWithOptions(t, expected, actual, + []cmp.Option{cmp.AllowUnexported(mockSample{}), cmp.AllowUnexported(mockExemplar{}), cmp.AllowUnexported(mockHistogram{})}, + msgAndArgs...) +} + func (m *mockAppendable) Appender(_ context.Context) storage.Appender { return m } diff --git a/tsdb/index/index_test.go b/tsdb/index/index_test.go index 5ff48ef191..c451c38dd2 100644 --- a/tsdb/index/index_test.go +++ b/tsdb/index/index_test.go @@ -205,7 +205,7 @@ func TestIndexRW_Postings(t *testing.T) { require.NoError(t, err) require.Empty(t, c) - require.Equal(t, series[i], builder.Labels()) + testutil.RequireEqual(t, series[i], builder.Labels()) } require.NoError(t, p.Err()) @@ -488,7 +488,7 @@ func TestPersistence_index_e2e(t *testing.T) { err = mi.Series(expp.At(), &eBuilder, &expchks) require.NoError(t, err) - require.Equal(t, eBuilder.Labels(), builder.Labels()) + testutil.RequireEqual(t, eBuilder.Labels(), builder.Labels()) require.Equal(t, expchks, chks) } require.False(t, expp.Next(), "Expected no more postings for %q=%q", p.Name, p.Value) From 12cac5bd5c6b1a9526afe52ae6ec46e4992fd4cd Mon Sep 17 00:00:00 2001 From: Bryan Boreham Date: Thu, 25 Jan 2024 22:55:58 +0000 Subject: [PATCH 080/237] tsdb tests: use go-cmp instead of DeepEquals Also one simpler call checking nil. Signed-off-by: Bryan Boreham --- tsdb/head_test.go | 10 +++++----- tsdb/repair_test.go | 3 ++- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/tsdb/head_test.go b/tsdb/head_test.go index c4bb77467b..a9350ecb96 100644 --- a/tsdb/head_test.go +++ b/tsdb/head_test.go @@ -679,10 +679,10 @@ func TestHead_ReadWAL(t *testing.T) { s50 := head.series.getByID(50) s100 := head.series.getByID(100) - require.Equal(t, labels.FromStrings("a", "1"), s10.lset) - require.Equal(t, (*memSeries)(nil), s11) // Series without samples should be garbage collected at head.Init(). - require.Equal(t, labels.FromStrings("a", "4"), s50.lset) - require.Equal(t, labels.FromStrings("a", "3"), s100.lset) + testutil.RequireEqual(t, labels.FromStrings("a", "1"), s10.lset) + require.Nil(t, s11) // Series without samples should be garbage collected at head.Init(). + testutil.RequireEqual(t, labels.FromStrings("a", "4"), s50.lset) + testutil.RequireEqual(t, labels.FromStrings("a", "3"), s100.lset) expandChunk := func(c chunkenc.Iterator) (x []sample) { for c.Next() == chunkenc.ValFloat { @@ -709,7 +709,7 @@ func TestHead_ReadWAL(t *testing.T) { require.NoError(t, err) e, err := q.Select(0, 1000, []*labels.Matcher{labels.MustNewMatcher(labels.MatchEqual, "a", "1")}) require.NoError(t, err) - require.Equal(t, exemplar.Exemplar{Ts: 100, Value: 1, Labels: labels.FromStrings("traceID", "asdf")}, e[0].Exemplars[0]) + require.True(t, exemplar.Exemplar{Ts: 100, Value: 1, Labels: labels.FromStrings("traceID", "asdf")}.Equals(e[0].Exemplars[0])) }) } } diff --git a/tsdb/repair_test.go b/tsdb/repair_test.go index c199ecdd4e..8a70e05f31 100644 --- a/tsdb/repair_test.go +++ b/tsdb/repair_test.go @@ -25,6 +25,7 @@ import ( "github.com/prometheus/prometheus/tsdb/chunks" "github.com/prometheus/prometheus/tsdb/fileutil" "github.com/prometheus/prometheus/tsdb/index" + "github.com/prometheus/prometheus/util/testutil" ) func TestRepairBadIndexVersion(t *testing.T) { @@ -112,7 +113,7 @@ func TestRepairBadIndexVersion(t *testing.T) { } require.NoError(t, p.Err()) - require.Equal(t, []labels.Labels{ + testutil.RequireEqual(t, []labels.Labels{ labels.FromStrings("a", "1", "b", "1"), labels.FromStrings("a", "2", "b", "1"), }, res) From b66b0a52882fc0ba5182f51400465e2f805b7e39 Mon Sep 17 00:00:00 2001 From: pschou Date: Fri, 9 Feb 2024 12:37:14 -0500 Subject: [PATCH 081/237] docs: improve "Querying Prometheus" readability (#8492) Signed-off-by: schou Co-authored-by: George Krajcsovits Co-authored-by: Bryan Boreham --- docs/querying/basics.md | 113 ++++++++++++++++++++++++++-------------- 1 file changed, 73 insertions(+), 40 deletions(-) diff --git a/docs/querying/basics.md b/docs/querying/basics.md index dac5037857..fee7e63c42 100644 --- a/docs/querying/basics.md +++ b/docs/querying/basics.md @@ -14,7 +14,7 @@ systems via the [HTTP API](api.md). ## Examples -This document is meant as a reference. For learning, it might be easier to +This document is a Prometheus basic language reference. For learning, it may be easier to start with a couple of [examples](examples.md). ## Expression language data types @@ -28,9 +28,9 @@ evaluate to one of four types: * **String** - a simple string value; currently unused Depending on the use-case (e.g. when graphing vs. displaying the output of an -expression), only some of these types are legal as the result from a +expression), only some of these types are legal as the result of a user-specified expression. For example, an expression that returns an instant -vector is the only type that can be directly graphed. +vector is the only type which can be graphed. _Notes about the experimental native histograms:_ @@ -46,16 +46,15 @@ _Notes about the experimental native histograms:_ ### String literals -Strings may be specified as literals in single quotes, double quotes or -backticks. +String literals are designated by single quotes, double quotes or backticks. PromQL follows the same [escaping rules as -Go](https://golang.org/ref/spec#String_literals). In single or double quotes a +Go](https://golang.org/ref/spec#String_literals). For string literals in single or double quotes, a backslash begins an escape sequence, which may be followed by `a`, `b`, `f`, -`n`, `r`, `t`, `v` or `\`. Specific characters can be provided using octal -(`\nnn`) or hexadecimal (`\xnn`, `\unnnn` and `\Unnnnnnnn`). +`n`, `r`, `t`, `v` or `\`. Specific characters can be provided using octal +(`\nnn`) or hexadecimal (`\xnn`, `\unnnn` and `\Unnnnnnnn`) notations. -No escaping is processed inside backticks. Unlike Go, Prometheus does not discard newlines inside backticks. +Conversely, escape characters are not parsed in string literals designated by backticks. It is important to note that, unlike Go, Prometheus does not discard newlines inside backticks. Example: @@ -83,13 +82,17 @@ Examples: -Inf NaN -## Time series Selectors +## Time series selectors + +Time series selectors are responsible for selecting the times series and raw or inferred sample timestamps and values. + +Time series *selectors* are not to be confused with higher level concept of instant and range *queries* that can execute the time series *selectors*. A higher level instant query would evaluate the given selector at one point in time, however the range query would evaluate the selector at multiple different times in between a minimum and maximum timestamp at regular steps. ### Instant vector selectors Instant vector selectors allow the selection of a set of time series and a -single sample value for each at a given timestamp (instant): in the simplest -form, only a metric name is specified. This results in an instant vector +single sample value for each at a given timestamp (point in time). In the simplest +form, only a metric name is specified, which results in an instant vector containing elements for all time series that have this metric name. This example selects all time series that have the `http_requests_total` metric @@ -97,7 +100,7 @@ name: http_requests_total -It is possible to filter these time series further by appending a comma separated list of label +It is possible to filter these time series further by appending a comma-separated list of label matchers in curly braces (`{}`). This example selects only those time series with the `http_requests_total` @@ -124,6 +127,33 @@ For example, this selects all `http_requests_total` time series for `staging`, Label matchers that match empty label values also select all time series that do not have the specific label set at all. It is possible to have multiple matchers for the same label name. +For example, given the dataset: + + http_requests_total + http_requests_total{replica="rep-a"} + http_requests_total{replica="rep-b"} + http_requests_total{environment="development"} + +The query `http_requests_total{environment=""}` would match and return: + + http_requests_total + http_requests_total{replica="rep-a"} + http_requests_total{replica="rep-b"} + +and would exclude: + + http_requests_total{environment="development"} + +Multiple matchers can be used for the same label name; they all must pass for a result to be returned. + +The query: + + http_requests_total{replica!="rep-a",replica=~"rep.*"} + +Would then match: + + http_requests_total{replica="rep-b"} + Vector selectors must either specify a name or at least one label matcher that does not match the empty string. The following expression is illegal: @@ -178,11 +208,13 @@ following units: * `s` - seconds * `m` - minutes * `h` - hours -* `d` - days - assuming a day has always 24h -* `w` - weeks - assuming a week has always 7d -* `y` - years - assuming a year has always 365d +* `d` - days - assuming a day always has 24h +* `w` - weeks - assuming a week always has 7d +* `y` - years - assuming a year always has 365d1 -Time durations can be combined, by concatenation. Units must be ordered from the +1 For days in a year, the leap day is ignored, and conversely, for a minute, a leap second is ignored. + +Time durations can be combined by concatenation. Units must be ordered from the longest to the shortest. A given unit must only appear once in a time duration. Here are some examples of valid time durations: @@ -217,8 +249,7 @@ that `http_requests_total` had a week ago: rate(http_requests_total[5m] offset 1w) -For comparisons with temporal shifts forward in time, a negative offset -can be specified: +When querying for samples in the past, a negative offset will enable temporal comparisons forward in time: rate(http_requests_total[5m] offset -1w) @@ -249,11 +280,11 @@ The same works for range vectors. This returns the 5-minute rate that rate(http_requests_total[5m] @ 1609746000) -The `@` modifier supports all representation of float literals described -above within the limits of `int64`. It can also be used along -with the `offset` modifier where the offset is applied relative to the `@` -modifier time irrespective of which modifier is written first. -These 2 queries will produce the same result. +The `@` modifier supports all representations of numeric literals described above. +It works with the `offset` modifier where the offset is applied relative to the `@` +modifier time. The results are the same irrespective of the order of the modifiers. + +For example, these two queries will produce the same result: # offset after @ http_requests_total @ 1609746000 offset 5m @@ -299,33 +330,35 @@ PromQL supports line comments that start with `#`. Example: ### Staleness -When queries are run, timestamps at which to sample data are selected +The timestamps at which to sample data, during a query, are selected independently of the actual present time series data. This is mainly to support cases like aggregation (`sum`, `avg`, and so on), where multiple aggregated -time series do not exactly align in time. Because of their independence, +time series do not precisely align in time. Because of their independence, Prometheus needs to assign a value at those timestamps for each relevant time -series. It does so by simply taking the newest sample before this timestamp. +series. It does so by taking the newest sample before this timestamp within the lookback period. +The lookback period is 5 minutes by default. If a target scrape or rule evaluation no longer returns a sample for a time -series that was previously present, that time series will be marked as stale. -If a target is removed, its previously returned time series will be marked as -stale soon afterwards. +series that was previously present, this time series will be marked as stale. +If a target is removed, the previously retrieved time series will be marked as +stale soon after removal. If a query is evaluated at a sampling timestamp after a time series is marked -stale, then no value is returned for that time series. If new samples are -subsequently ingested for that time series, they will be returned as normal. +as stale, then no value is returned for that time series. If new samples are +subsequently ingested for that time series, they will be returned as expected. -If no sample is found (by default) 5 minutes before a sampling timestamp, -no value is returned for that time series at this point in time. This -effectively means that time series "disappear" from graphs at times where their -latest collected sample is older than 5 minutes or after they are marked stale. +A time series will go stale when it is no longer exported, or the target no +longer exists. Such time series will disappear from graphs +at the times of their latest collected sample, and they will not be returned +in queries after they are marked stale. -Staleness will not be marked for time series that have timestamps included in -their scrapes. Only the 5 minute threshold will be applied in that case. +Some exporters, which put their own timestamps on samples, get a different behaviour: +series that stop being exported take the last value for (by default) 5 minutes before +disappearing. The `track_timestamps_staleness` setting can change this. ### Avoiding slow queries and overloads -If a query needs to operate on a very large amount of data, graphing it might +If a query needs to operate on a substantial amount of data, graphing it might time out or overload the server or browser. Thus, when constructing queries over unknown data, always start building the query in the tabular view of Prometheus's expression browser until the result set seems reasonable @@ -336,7 +369,7 @@ rule](../configuration/recording_rules.md#recording-rules). This is especially relevant for Prometheus's query language, where a bare metric name selector like `api_http_requests_total` could expand to thousands -of time series with different labels. Also keep in mind that expressions which +of time series with different labels. Also, keep in mind that expressions that aggregate over many time series will generate load on the server even if the output is only a small number of time series. This is similar to how it would be slow to sum all values of a column in a relational database, even if the From 452f4c96b790f2ed1d25c2f7daff2ac92a92448c Mon Sep 17 00:00:00 2001 From: Augustin Husson Date: Tue, 13 Feb 2024 10:21:23 +0100 Subject: [PATCH 082/237] Cut v2.50.0-rc.0 (#13465) Signed-off-by: Augustin Husson --- CHANGELOG.md | 27 ++++++++++++++++++++ VERSION | 2 +- web/ui/module/codemirror-promql/package.json | 4 +-- web/ui/module/lezer-promql/package.json | 2 +- web/ui/package-lock.json | 14 +++++----- web/ui/package.json | 2 +- web/ui/react-app/package.json | 4 +-- 7 files changed, 41 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1f71eb49ba..c1f8bf8003 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,32 @@ # Changelog +## 2.50.0-rc.0 / 2024-02-07 + +* [CHANGE] Remote Write: Error `storage.ErrTooOldSample` is now generating HTTP error 400 instead of HTTP error 500. #13335 +* [FEATURE] Remote Write: Drop old inmemory samples. Activated using the config entry `sample_age_limit`. #13002 +* [FEATURE] **Experimental**: Add support for ingesting zeros as created timestamps. (enabled under the feature-flag `created-timestamp-zero-ingestion`). #12733 #13279 +* [FEATURE] Promtool: Add `analyze` histograms command. #12331 +* [FEATURE] TSDB/compaction: Add a way to enable overlapping compaction. #13282 #13393 #13398 +* [FEATURE] Add automatic memory limit handling. Activated using the feature flag. `auto-gomemlimit` #13395 +* [ENHANCEMENT] Promtool: allow specifying multiple matchers in `promtool tsdb dump`. #13296 +* [ENHANCEMENT] PromQL: Restore more efficient version of `NewPossibleNonCounterInfo` annotation. #13022 +* [ENHANCEMENT] Kuma SD: Extend configuration to allow users to specify client ID. #13278 +* [ENHANCEMENT] PromQL: Use natural sort in `sort_by_label` and `sort_by_label_desc`. This is **experimental**. #13411 +* [ENHANCEMENT] Native Histograms: support `native_histogram_min_bucket_factor` in scrape_config. #13222 +* [ENHANCEMENT] Native Histograms: Issue warning if histogramRate is applied to the wrong kind of histogram. #13392 +* [ENHANCEMENT] TSDB: Make transaction isolation data structures smaller. #13015 +* [ENHANCEMENT] TSDB/postings: Optimize merge using Loser Tree. #12878 +* [ENHANCEMENT] TSDB: Simplify internal series delete function. #13261 +* [ENHANCEMENT] Agent: Performance improvement by making the global hash lookup table smaller. #13262 +* [ENHANCEMENT] PromQL: faster execution of metric functions, e.g. abs(), rate() #13446 +* [ENHANCEMENT] TSDB: Optimize label values with matchers by taking shortcuts. #13426 +* [ENHANCEMENT] Kubernetes SD: Check preconditions earlier and avoid unnecessary checks or iterations in kube_sd. #13408 +* [ENHANCEMENT] Promtool: Improve visibility for `promtool test rules` with JSON colored formatting. #13342 +* [ENHANCEMENT] Consoles: Exclude iowait and steal from CPU Utilisation. #9593 +* [ENHANCEMENT] Various improvements and optimizations on Native Histograms. #13267, #13215, #13276 #13289, #13340 +* [BUGFIX] Scraping: Fix quality value in HTTP Accept header. #13313 +* [BUGFIX] UI: Fix usage of the function `time()` that was crashing. #13371 + ## 2.49.1 / 2024-01-15 * [BUGFIX] TSDB: Fixed a wrong `q=` value in scrape accept header #13313 diff --git a/VERSION b/VERSION index f5518081bd..9f02570d13 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -2.49.1 +2.50.0-rc.0 diff --git a/web/ui/module/codemirror-promql/package.json b/web/ui/module/codemirror-promql/package.json index fb13605825..bfbdd56e23 100644 --- a/web/ui/module/codemirror-promql/package.json +++ b/web/ui/module/codemirror-promql/package.json @@ -1,6 +1,6 @@ { "name": "@prometheus-io/codemirror-promql", - "version": "0.49.1", + "version": "0.50.0-rc.0", "description": "a CodeMirror mode for the PromQL language", "types": "dist/esm/index.d.ts", "module": "dist/esm/index.js", @@ -29,7 +29,7 @@ }, "homepage": "https://github.com/prometheus/prometheus/blob/main/web/ui/module/codemirror-promql/README.md", "dependencies": { - "@prometheus-io/lezer-promql": "0.49.1", + "@prometheus-io/lezer-promql": "0.50.0-rc.0", "lru-cache": "^7.18.3" }, "devDependencies": { diff --git a/web/ui/module/lezer-promql/package.json b/web/ui/module/lezer-promql/package.json index a6bb3f3eb8..aca68a67a0 100644 --- a/web/ui/module/lezer-promql/package.json +++ b/web/ui/module/lezer-promql/package.json @@ -1,6 +1,6 @@ { "name": "@prometheus-io/lezer-promql", - "version": "0.49.1", + "version": "0.50.0-rc.0", "description": "lezer-based PromQL grammar", "main": "dist/index.cjs", "type": "module", diff --git a/web/ui/package-lock.json b/web/ui/package-lock.json index 5f1db6ce0a..b2d136ceb9 100644 --- a/web/ui/package-lock.json +++ b/web/ui/package-lock.json @@ -1,12 +1,12 @@ { "name": "prometheus-io", - "version": "0.49.1", + "version": "0.50.0-rc.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "prometheus-io", - "version": "0.49.1", + "version": "0.50.0-rc.0", "workspaces": [ "react-app", "module/*" @@ -30,10 +30,10 @@ }, "module/codemirror-promql": { "name": "@prometheus-io/codemirror-promql", - "version": "0.49.1", + "version": "0.50.0-rc.0", "license": "Apache-2.0", "dependencies": { - "@prometheus-io/lezer-promql": "0.49.1", + "@prometheus-io/lezer-promql": "0.50.0-rc.0", "lru-cache": "^7.18.3" }, "devDependencies": { @@ -69,7 +69,7 @@ }, "module/lezer-promql": { "name": "@prometheus-io/lezer-promql", - "version": "0.49.1", + "version": "0.50.0-rc.0", "license": "Apache-2.0", "devDependencies": { "@lezer/generator": "^1.5.1", @@ -19233,7 +19233,7 @@ }, "react-app": { "name": "@prometheus-io/app", - "version": "0.49.1", + "version": "0.50.0-rc.0", "dependencies": { "@codemirror/autocomplete": "^6.11.1", "@codemirror/commands": "^6.3.2", @@ -19251,7 +19251,7 @@ "@lezer/lr": "^1.3.14", "@nexucis/fuzzy": "^0.4.1", "@nexucis/kvsearch": "^0.8.1", - "@prometheus-io/codemirror-promql": "0.49.1", + "@prometheus-io/codemirror-promql": "0.50.0-rc.0", "bootstrap": "^4.6.2", "css.escape": "^1.5.1", "downshift": "^7.6.2", diff --git a/web/ui/package.json b/web/ui/package.json index 026c1663b3..86a18f466a 100644 --- a/web/ui/package.json +++ b/web/ui/package.json @@ -28,5 +28,5 @@ "ts-jest": "^29.1.1", "typescript": "^4.9.5" }, - "version": "0.49.1" + "version": "0.50.0-rc.0" } diff --git a/web/ui/react-app/package.json b/web/ui/react-app/package.json index 33fccf5c7c..4bd707e98b 100644 --- a/web/ui/react-app/package.json +++ b/web/ui/react-app/package.json @@ -1,6 +1,6 @@ { "name": "@prometheus-io/app", - "version": "0.49.1", + "version": "0.50.0-rc.0", "private": true, "dependencies": { "@codemirror/autocomplete": "^6.11.1", @@ -19,7 +19,7 @@ "@lezer/lr": "^1.3.14", "@nexucis/fuzzy": "^0.4.1", "@nexucis/kvsearch": "^0.8.1", - "@prometheus-io/codemirror-promql": "0.49.1", + "@prometheus-io/codemirror-promql": "0.50.0-rc.0", "bootstrap": "^4.6.2", "css.escape": "^1.5.1", "downshift": "^7.6.2", From a93859a52f1ae151ce4f1a86c69060d495999356 Mon Sep 17 00:00:00 2001 From: Ziqi Zhao Date: Thu, 15 Feb 2024 00:24:40 +0800 Subject: [PATCH 083/237] Prometheus support parse exemplars from native histogram (#13488) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit scrape: support parsing exemplars from native histogram --------- Signed-off-by: Ziqi Zhao Signed-off-by: Björn Rabenstein Co-authored-by: Björn Rabenstein --- model/textparse/protobufparse.go | 57 ++++--- model/textparse/protobufparse_test.go | 229 ++++++++++++++++++++++++++ 2 files changed, 266 insertions(+), 20 deletions(-) diff --git a/model/textparse/protobufparse.go b/model/textparse/protobufparse.go index 2c9c384b6b..8fd89af825 100644 --- a/model/textparse/protobufparse.go +++ b/model/textparse/protobufparse.go @@ -56,6 +56,8 @@ type ProtobufParser struct { fieldPos int fieldsDone bool // true if no more fields of a Summary or (legacy) Histogram to be processed. redoClassic bool // true after parsing a native histogram if we need to parse it again as a classic histogram. + // exemplarPos is the position within the exemplars slice of a native histogram. + exemplarPos int // exemplarReturned is set to true each time an exemplar has been // returned, and set back to false upon each Next() call. @@ -304,8 +306,9 @@ func (p *ProtobufParser) Metric(l *labels.Labels) string { // Exemplar writes the exemplar of the current sample into the passed // exemplar. It returns if an exemplar exists or not. In case of a native -// histogram, the legacy bucket section is still used for exemplars. To ingest -// all exemplars, call the Exemplar method repeatedly until it returns false. +// histogram, the exemplars in the native histogram will be returned. +// If this field is empty, the classic bucket section is still used for exemplars. +// To ingest all exemplars, call the Exemplar method repeatedly until it returns false. func (p *ProtobufParser) Exemplar(ex *exemplar.Exemplar) bool { if p.exemplarReturned && p.state == EntrySeries { // We only ever return one exemplar per (non-native-histogram) series. @@ -317,28 +320,42 @@ func (p *ProtobufParser) Exemplar(ex *exemplar.Exemplar) bool { case dto.MetricType_COUNTER: exProto = m.GetCounter().GetExemplar() case dto.MetricType_HISTOGRAM, dto.MetricType_GAUGE_HISTOGRAM: - bb := m.GetHistogram().GetBucket() isClassic := p.state == EntrySeries - if p.fieldPos < 0 { - if isClassic { - return false // At _count or _sum. + if !isClassic && len(m.GetHistogram().GetExemplars()) > 0 { + exs := m.GetHistogram().GetExemplars() + for p.exemplarPos < len(exs) { + exProto = exs[p.exemplarPos] + p.exemplarPos++ + if exProto != nil && exProto.GetTimestamp() != nil { + break + } } - p.fieldPos = 0 // Start at 1st bucket for native histograms. - } - for p.fieldPos < len(bb) { - exProto = bb[p.fieldPos].GetExemplar() - if isClassic { - break + if exProto != nil && exProto.GetTimestamp() == nil { + return false } - p.fieldPos++ - // We deliberately drop exemplars with no timestamp only for native histograms. - if exProto != nil && (isClassic || exProto.GetTimestamp() != nil) { - break // Found a classic histogram exemplar or a native histogram exemplar with a timestamp. + } else { + bb := m.GetHistogram().GetBucket() + if p.fieldPos < 0 { + if isClassic { + return false // At _count or _sum. + } + p.fieldPos = 0 // Start at 1st bucket for native histograms. + } + for p.fieldPos < len(bb) { + exProto = bb[p.fieldPos].GetExemplar() + if isClassic { + break + } + p.fieldPos++ + // We deliberately drop exemplars with no timestamp only for native histograms. + if exProto != nil && (isClassic || exProto.GetTimestamp() != nil) { + break // Found a classic histogram exemplar or a native histogram exemplar with a timestamp. + } + } + // If the last exemplar for native histograms has no timestamp, ignore it. + if !isClassic && exProto.GetTimestamp() == nil { + return false } - } - // If the last exemplar for native histograms has no timestamp, ignore it. - if !isClassic && exProto.GetTimestamp() == nil { - return false } default: return false diff --git a/model/textparse/protobufparse_test.go b/model/textparse/protobufparse_test.go index aa6316919b..c807ae644c 100644 --- a/model/textparse/protobufparse_test.go +++ b/model/textparse/protobufparse_test.go @@ -596,6 +596,105 @@ metric: < > > +`, + `name: "test_histogram_with_native_histogram_exemplars" +help: "A histogram with native histogram exemplars." +type: HISTOGRAM +metric: < + histogram: < + sample_count: 175 + sample_sum: 0.0008280461746287094 + bucket: < + cumulative_count: 2 + upper_bound: -0.0004899999999999998 + > + bucket: < + cumulative_count: 4 + upper_bound: -0.0003899999999999998 + exemplar: < + label: < + name: "dummyID" + value: "59727" + > + value: -0.00039 + timestamp: < + seconds: 1625851155 + nanos: 146848499 + > + > + > + bucket: < + cumulative_count: 16 + upper_bound: -0.0002899999999999998 + exemplar: < + label: < + name: "dummyID" + value: "5617" + > + value: -0.00029 + > + > + schema: 3 + zero_threshold: 2.938735877055719e-39 + zero_count: 2 + negative_span: < + offset: -162 + length: 1 + > + negative_span: < + offset: 23 + length: 4 + > + negative_delta: 1 + negative_delta: 3 + negative_delta: -2 + negative_delta: -1 + negative_delta: 1 + positive_span: < + offset: -161 + length: 1 + > + positive_span: < + offset: 8 + length: 3 + > + positive_delta: 1 + positive_delta: 2 + positive_delta: -1 + positive_delta: -1 + exemplars: < + label: < + name: "dummyID" + value: "59780" + > + value: -0.00039 + timestamp: < + seconds: 1625851155 + nanos: 146848499 + > + > + exemplars: < + label: < + name: "dummyID" + value: "5617" + > + value: -0.00029 + > + exemplars: < + label: < + name: "dummyID" + value: "59772" + > + value: -0.00052 + timestamp: < + seconds: 1625851160 + nanos: 156848499 + > + > + > + timestamp_ms: 1234568 +> + `, } @@ -1141,6 +1240,42 @@ func TestProtobufParse(t *testing.T) { "__name__", "test_gaugehistogram_with_createdtimestamp", ), }, + { + m: "test_histogram_with_native_histogram_exemplars", + help: "A histogram with native histogram exemplars.", + }, + { + m: "test_histogram_with_native_histogram_exemplars", + typ: model.MetricTypeHistogram, + }, + { + m: "test_histogram_with_native_histogram_exemplars", + t: 1234568, + shs: &histogram.Histogram{ + Count: 175, + ZeroCount: 2, + Sum: 0.0008280461746287094, + ZeroThreshold: 2.938735877055719e-39, + Schema: 3, + PositiveSpans: []histogram.Span{ + {Offset: -161, Length: 1}, + {Offset: 8, Length: 3}, + }, + NegativeSpans: []histogram.Span{ + {Offset: -162, Length: 1}, + {Offset: 23, Length: 4}, + }, + PositiveBuckets: []int64{1, 2, -1, -1}, + NegativeBuckets: []int64{1, 3, -2, -1, 1}, + }, + lset: labels.FromStrings( + "__name__", "test_histogram_with_native_histogram_exemplars", + ), + e: []exemplar.Exemplar{ + {Labels: labels.FromStrings("dummyID", "59780"), Value: -0.00039, HasTs: true, Ts: 1625851155146}, + {Labels: labels.FromStrings("dummyID", "59772"), Value: -0.00052, HasTs: true, Ts: 1625851160156}, + }, + }, }, }, { @@ -1959,6 +2094,100 @@ func TestProtobufParse(t *testing.T) { "__name__", "test_gaugehistogram_with_createdtimestamp", ), }, + { // 94 + m: "test_histogram_with_native_histogram_exemplars", + help: "A histogram with native histogram exemplars.", + }, + { // 95 + m: "test_histogram_with_native_histogram_exemplars", + typ: model.MetricTypeHistogram, + }, + { // 96 + m: "test_histogram_with_native_histogram_exemplars", + t: 1234568, + shs: &histogram.Histogram{ + Count: 175, + ZeroCount: 2, + Sum: 0.0008280461746287094, + ZeroThreshold: 2.938735877055719e-39, + Schema: 3, + PositiveSpans: []histogram.Span{ + {Offset: -161, Length: 1}, + {Offset: 8, Length: 3}, + }, + NegativeSpans: []histogram.Span{ + {Offset: -162, Length: 1}, + {Offset: 23, Length: 4}, + }, + PositiveBuckets: []int64{1, 2, -1, -1}, + NegativeBuckets: []int64{1, 3, -2, -1, 1}, + }, + lset: labels.FromStrings( + "__name__", "test_histogram_with_native_histogram_exemplars", + ), + e: []exemplar.Exemplar{ + {Labels: labels.FromStrings("dummyID", "59780"), Value: -0.00039, HasTs: true, Ts: 1625851155146}, + {Labels: labels.FromStrings("dummyID", "59772"), Value: -0.00052, HasTs: true, Ts: 1625851160156}, + }, + }, + { // 97 + m: "test_histogram_with_native_histogram_exemplars_count", + t: 1234568, + v: 175, + lset: labels.FromStrings( + "__name__", "test_histogram_with_native_histogram_exemplars_count", + ), + }, + { // 98 + m: "test_histogram_with_native_histogram_exemplars_sum", + t: 1234568, + v: 0.0008280461746287094, + lset: labels.FromStrings( + "__name__", "test_histogram_with_native_histogram_exemplars_sum", + ), + }, + { // 99 + m: "test_histogram_with_native_histogram_exemplars_bucket\xffle\xff-0.0004899999999999998", + t: 1234568, + v: 2, + lset: labels.FromStrings( + "__name__", "test_histogram_with_native_histogram_exemplars_bucket", + "le", "-0.0004899999999999998", + ), + }, + { // 100 + m: "test_histogram_with_native_histogram_exemplars_bucket\xffle\xff-0.0003899999999999998", + t: 1234568, + v: 4, + lset: labels.FromStrings( + "__name__", "test_histogram_with_native_histogram_exemplars_bucket", + "le", "-0.0003899999999999998", + ), + e: []exemplar.Exemplar{ + {Labels: labels.FromStrings("dummyID", "59727"), Value: -0.00039, HasTs: true, Ts: 1625851155146}, + }, + }, + { // 101 + m: "test_histogram_with_native_histogram_exemplars_bucket\xffle\xff-0.0002899999999999998", + t: 1234568, + v: 16, + lset: labels.FromStrings( + "__name__", "test_histogram_with_native_histogram_exemplars_bucket", + "le", "-0.0002899999999999998", + ), + e: []exemplar.Exemplar{ + {Labels: labels.FromStrings("dummyID", "5617"), Value: -0.00029, HasTs: false}, + }, + }, + { // 102 + m: "test_histogram_with_native_histogram_exemplars_bucket\xffle\xff+Inf", + t: 1234568, + v: 175, + lset: labels.FromStrings( + "__name__", "test_histogram_with_native_histogram_exemplars_bucket", + "le", "+Inf", + ), + }, }, }, } From 0e8a2e44a4c54f70d1de052d52bb1304148ddfa5 Mon Sep 17 00:00:00 2001 From: Darshan Chaudhary Date: Wed, 14 Feb 2024 22:29:01 +0530 Subject: [PATCH 084/237] Azure SD: check for nil before accessing and dereferencing (#13578) Signed-off-by: darshanime --- discovery/azure/azure.go | 16 +++++- discovery/azure/azure_test.go | 101 ++++++++++++++++++++++++++++++++++ 2 files changed, 115 insertions(+), 2 deletions(-) diff --git a/discovery/azure/azure.go b/discovery/azure/azure.go index e5e6109b25..a5d81f4ff6 100644 --- a/discovery/azure/azure.go +++ b/discovery/azure/azure.go @@ -569,7 +569,7 @@ func (client *azureClient) getScaleSetVMs(ctx context.Context, scaleSet armcompu } func mapFromVM(vm armcompute.VirtualMachine) virtualMachine { - osType := string(*vm.Properties.StorageProfile.OSDisk.OSType) + var osType string tags := map[string]*string{} networkInterfaces := []string{} var computerName string @@ -580,6 +580,12 @@ func mapFromVM(vm armcompute.VirtualMachine) virtualMachine { } if vm.Properties != nil { + if vm.Properties.StorageProfile != nil && + vm.Properties.StorageProfile.OSDisk != nil && + vm.Properties.StorageProfile.OSDisk.OSType != nil { + osType = string(*vm.Properties.StorageProfile.OSDisk.OSType) + } + if vm.Properties.NetworkProfile != nil { for _, vmNIC := range vm.Properties.NetworkProfile.NetworkInterfaces { networkInterfaces = append(networkInterfaces, *vmNIC.ID) @@ -608,7 +614,7 @@ func mapFromVM(vm armcompute.VirtualMachine) virtualMachine { } func mapFromVMScaleSetVM(vm armcompute.VirtualMachineScaleSetVM, scaleSetName string) virtualMachine { - osType := string(*vm.Properties.StorageProfile.OSDisk.OSType) + var osType string tags := map[string]*string{} networkInterfaces := []string{} var computerName string @@ -619,6 +625,12 @@ func mapFromVMScaleSetVM(vm armcompute.VirtualMachineScaleSetVM, scaleSetName st } if vm.Properties != nil { + if vm.Properties.StorageProfile != nil && + vm.Properties.StorageProfile.OSDisk != nil && + vm.Properties.StorageProfile.OSDisk.OSType != nil { + osType = string(*vm.Properties.StorageProfile.OSDisk.OSType) + } + if vm.Properties.NetworkProfile != nil { for _, vmNIC := range vm.Properties.NetworkProfile.NetworkInterfaces { networkInterfaces = append(networkInterfaces, *vmNIC.ID) diff --git a/discovery/azure/azure_test.go b/discovery/azure/azure_test.go index 4ff937e0bc..1e437c75f2 100644 --- a/discovery/azure/azure_test.go +++ b/discovery/azure/azure_test.go @@ -79,6 +79,55 @@ func TestMapFromVMWithEmptyTags(t *testing.T) { require.Equal(t, expectedVM, actualVM) } +func TestMapFromVMWithEmptyOSType(t *testing.T) { + id := "test" + name := "name" + size := "size" + vmSize := armcompute.VirtualMachineSizeTypes(size) + vmType := "type" + location := "westeurope" + computerName := "computer_name" + networkProfile := armcompute.NetworkProfile{ + NetworkInterfaces: []*armcompute.NetworkInterfaceReference{}, + } + properties := &armcompute.VirtualMachineProperties{ + OSProfile: &armcompute.OSProfile{ + ComputerName: &computerName, + }, + StorageProfile: &armcompute.StorageProfile{ + OSDisk: &armcompute.OSDisk{}, + }, + NetworkProfile: &networkProfile, + HardwareProfile: &armcompute.HardwareProfile{ + VMSize: &vmSize, + }, + } + + testVM := armcompute.VirtualMachine{ + ID: &id, + Name: &name, + Type: &vmType, + Location: &location, + Tags: nil, + Properties: properties, + } + + expectedVM := virtualMachine{ + ID: id, + Name: name, + ComputerName: computerName, + Type: vmType, + Location: location, + Tags: map[string]*string{}, + NetworkInterfaces: []string{}, + Size: size, + } + + actualVM := mapFromVM(testVM) + + require.Equal(t, expectedVM, actualVM) +} + func TestMapFromVMWithTags(t *testing.T) { id := "test" name := "name" @@ -193,6 +242,58 @@ func TestMapFromVMScaleSetVMWithEmptyTags(t *testing.T) { require.Equal(t, expectedVM, actualVM) } +func TestMapFromVMScaleSetVMWithEmptyOSType(t *testing.T) { + id := "test" + name := "name" + size := "size" + vmSize := armcompute.VirtualMachineSizeTypes(size) + vmType := "type" + instanceID := "123" + location := "westeurope" + computerName := "computer_name" + networkProfile := armcompute.NetworkProfile{ + NetworkInterfaces: []*armcompute.NetworkInterfaceReference{}, + } + properties := &armcompute.VirtualMachineScaleSetVMProperties{ + OSProfile: &armcompute.OSProfile{ + ComputerName: &computerName, + }, + StorageProfile: &armcompute.StorageProfile{}, + NetworkProfile: &networkProfile, + HardwareProfile: &armcompute.HardwareProfile{ + VMSize: &vmSize, + }, + } + + testVM := armcompute.VirtualMachineScaleSetVM{ + ID: &id, + Name: &name, + Type: &vmType, + InstanceID: &instanceID, + Location: &location, + Tags: nil, + Properties: properties, + } + + scaleSet := "testSet" + expectedVM := virtualMachine{ + ID: id, + Name: name, + ComputerName: computerName, + Type: vmType, + Location: location, + Tags: map[string]*string{}, + NetworkInterfaces: []string{}, + ScaleSet: scaleSet, + InstanceID: instanceID, + Size: size, + } + + actualVM := mapFromVMScaleSetVM(testVM, scaleSet) + + require.Equal(t, expectedVM, actualVM) +} + func TestMapFromVMScaleSetVMWithTags(t *testing.T) { id := "test" name := "name" From 0b38a5b774f7a68848853a22e5d7b9049e7217e2 Mon Sep 17 00:00:00 2001 From: Matthias Loibl Date: Thu, 15 Feb 2024 12:12:36 +0100 Subject: [PATCH 085/237] Update go get buf.build in README.md The old import path stopped working. ``` go get go.buf.build/protocolbuffers/go/prometheus/prometheus go: unrecognized import path "go.buf.build/protocolbuffers/go/prometheus/prometheus": https fetch: Get "https://go.buf.build/protocolbuffers/go/prometheus/prometheus?go-get=1": dial tcp: lookup go.buf.build on 192.168.2.1:53: no such host ``` We should instead tell users to use the new import paths. Signed-off-by: Matthias Loibl --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 0042793ff6..023619a781 100644 --- a/README.md +++ b/README.md @@ -149,7 +149,7 @@ We are publishing our Remote Write protobuf independently at You can use that as a library: ```shell -go get go.buf.build/protocolbuffers/go/prometheus/prometheus +go get buf.build/gen/go/prometheus/prometheus/protocolbuffers/go@latest ``` This is experimental. From c0e36e6bb3c8db3a4864afc12f40789d7f5b684c Mon Sep 17 00:00:00 2001 From: Bryan Boreham Date: Thu, 15 Feb 2024 14:19:54 +0000 Subject: [PATCH 086/237] Standardise exemplar label as "trace_id" This is consistent with the OpenTelemetry standard, and an example in OpenMetrics. https://github.com/open-telemetry/opentelemetry-specification/blob/89aa01348139/specification/metrics/data-model.md#exemplars https://github.com/OpenObservability/OpenMetrics/blob/138654493130/specification/OpenMetrics.md#exemplars-1 Signed-off-by: Bryan Boreham --- docs/feature_flags.md | 2 +- docs/querying/api.md | 6 ++--- rules/group.go | 2 +- storage/remote/queue_manager_test.go | 2 +- tsdb/exemplar_test.go | 22 +++++++++---------- tsdb/head_test.go | 10 ++++----- tsdb/record/record_test.go | 8 +++---- tsdb/wlog/checkpoint_test.go | 2 +- tsdb/wlog/watcher_test.go | 2 +- web/api/v1/json_codec_test.go | 8 +++---- .../react-app/src/pages/graph/Graph.test.tsx | 2 +- 11 files changed, 33 insertions(+), 33 deletions(-) diff --git a/docs/feature_flags.md b/docs/feature_flags.md index c3540cc234..04d9ff3654 100644 --- a/docs/feature_flags.md +++ b/docs/feature_flags.md @@ -34,7 +34,7 @@ Activating the remote write receiver via a feature flag is deprecated. Use `--we [OpenMetrics](https://github.com/OpenObservability/OpenMetrics/blob/main/specification/OpenMetrics.md#exemplars) introduces the ability for scrape targets to add exemplars to certain metrics. Exemplars are references to data outside of the MetricSet. A common use case are IDs of program traces. -Exemplar storage is implemented as a fixed size circular buffer that stores exemplars in memory for all series. Enabling this feature will enable the storage of exemplars scraped by Prometheus. The config file block [storage](configuration/configuration.md#configuration-file)/[exemplars](configuration/configuration.md#exemplars) can be used to control the size of circular buffer by # of exemplars. An exemplar with just a `traceID=` uses roughly 100 bytes of memory via the in-memory exemplar storage. If the exemplar storage is enabled, we will also append the exemplars to WAL for local persistence (for WAL duration). +Exemplar storage is implemented as a fixed size circular buffer that stores exemplars in memory for all series. Enabling this feature will enable the storage of exemplars scraped by Prometheus. The config file block [storage](configuration/configuration.md#configuration-file)/[exemplars](configuration/configuration.md#exemplars) can be used to control the size of circular buffer by # of exemplars. An exemplar with just a `trace_id=` uses roughly 100 bytes of memory via the in-memory exemplar storage. If the exemplar storage is enabled, we will also append the exemplars to WAL for local persistence (for WAL duration). ## Memory snapshot on shutdown diff --git a/docs/querying/api.md b/docs/querying/api.md index a268b1cc93..04ba3e8c6e 100644 --- a/docs/querying/api.md +++ b/docs/querying/api.md @@ -404,7 +404,7 @@ $ curl -g 'http://localhost:9090/api/v1/query_exemplars?query=test_exemplar_metr "exemplars": [ { "labels": { - "traceID": "EpTxMJ40fUus7aGY" + "trace_id": "EpTxMJ40fUus7aGY" }, "value": "6", "timestamp": 1600096945.479 @@ -421,14 +421,14 @@ $ curl -g 'http://localhost:9090/api/v1/query_exemplars?query=test_exemplar_metr "exemplars": [ { "labels": { - "traceID": "Olp9XHlq763ccsfa" + "trace_id": "Olp9XHlq763ccsfa" }, "value": "19", "timestamp": 1600096955.479 }, { "labels": { - "traceID": "hCtjygkIHwAN9vs4" + "trace_id": "hCtjygkIHwAN9vs4" }, "value": "20", "timestamp": 1600096965.489 diff --git a/rules/group.go b/rules/group.go index 6f7844bac9..bc59bf8598 100644 --- a/rules/group.go +++ b/rules/group.go @@ -464,7 +464,7 @@ func (g *Group) Eval(ctx context.Context, ts time.Time) { }(time.Now()) if sp.SpanContext().IsSampled() && sp.SpanContext().HasTraceID() { - logger = log.WithPrefix(logger, "traceID", sp.SpanContext().TraceID()) + logger = log.WithPrefix(logger, "trace_id", sp.SpanContext().TraceID()) } g.metrics.EvalTotal.WithLabelValues(GroupKey(g.File(), g.Name())).Inc() diff --git a/storage/remote/queue_manager_test.go b/storage/remote/queue_manager_test.go index 6e903eb59e..c953266b7f 100644 --- a/storage/remote/queue_manager_test.go +++ b/storage/remote/queue_manager_test.go @@ -585,7 +585,7 @@ func createExemplars(numExemplars, numSeries int) ([]record.RefExemplar, []recor Ref: chunks.HeadSeriesRef(i), T: int64(j), V: float64(i), - Labels: labels.FromStrings("traceID", fmt.Sprintf("trace-%d", i)), + Labels: labels.FromStrings("trace_id", fmt.Sprintf("trace-%d", i)), } exemplars = append(exemplars, e) } diff --git a/tsdb/exemplar_test.go b/tsdb/exemplar_test.go index 24b46e0fae..21030e4577 100644 --- a/tsdb/exemplar_test.go +++ b/tsdb/exemplar_test.go @@ -40,7 +40,7 @@ func TestValidateExemplar(t *testing.T) { l := labels.FromStrings("service", "asdf") e := exemplar.Exemplar{ - Labels: labels.FromStrings("traceID", "qwerty"), + Labels: labels.FromStrings("trace_id", "qwerty"), Value: 0.1, Ts: 1, } @@ -49,7 +49,7 @@ func TestValidateExemplar(t *testing.T) { require.NoError(t, es.AddExemplar(l, e)) e2 := exemplar.Exemplar{ - Labels: labels.FromStrings("traceID", "zxcvb"), + Labels: labels.FromStrings("trace_id", "zxcvb"), Value: 0.1, Ts: 2, } @@ -82,7 +82,7 @@ func TestAddExemplar(t *testing.T) { l := labels.FromStrings("service", "asdf") e := exemplar.Exemplar{ - Labels: labels.FromStrings("traceID", "qwerty"), + Labels: labels.FromStrings("trace_id", "qwerty"), Value: 0.1, Ts: 1, } @@ -91,7 +91,7 @@ func TestAddExemplar(t *testing.T) { require.Equal(t, 0, es.index[string(l.Bytes(nil))].newest, "exemplar was not stored correctly") e2 := exemplar.Exemplar{ - Labels: labels.FromStrings("traceID", "zxcvb"), + Labels: labels.FromStrings("trace_id", "zxcvb"), Value: 0.1, Ts: 2, } @@ -132,7 +132,7 @@ func TestStorageOverflow(t *testing.T) { var eList []exemplar.Exemplar for i := 0; i < len(es.exemplars)+1; i++ { e := exemplar.Exemplar{ - Labels: labels.FromStrings("traceID", "a"), + Labels: labels.FromStrings("trace_id", "a"), Value: float64(i+1) / 10, Ts: int64(101 + i), } @@ -158,7 +158,7 @@ func TestSelectExemplar(t *testing.T) { lName, lValue := "service", "asdf" l := labels.FromStrings(lName, lValue) e := exemplar.Exemplar{ - Labels: labels.FromStrings("traceID", "querty"), + Labels: labels.FromStrings("trace_id", "querty"), Value: 0.1, Ts: 12, } @@ -189,7 +189,7 @@ func TestSelectExemplar_MultiSeries(t *testing.T) { for i := 0; i < len(es.exemplars); i++ { e1 := exemplar.Exemplar{ - Labels: labels.FromStrings("traceID", "a"), + Labels: labels.FromStrings("trace_id", "a"), Value: float64(i+1) / 10, Ts: int64(101 + i), } @@ -197,7 +197,7 @@ func TestSelectExemplar_MultiSeries(t *testing.T) { require.NoError(t, err) e2 := exemplar.Exemplar{ - Labels: labels.FromStrings("traceID", "b"), + Labels: labels.FromStrings("trace_id", "b"), Value: float64(i+1) / 10, Ts: int64(101 + i), } @@ -231,7 +231,7 @@ func TestSelectExemplar_TimeRange(t *testing.T) { for i := 0; int64(i) < lenEs; i++ { err := es.AddExemplar(l, exemplar.Exemplar{ - Labels: labels.FromStrings("traceID", strconv.Itoa(i)), + Labels: labels.FromStrings("trace_id", strconv.Itoa(i)), Value: 0.1, Ts: int64(101 + i), }) @@ -255,7 +255,7 @@ func TestSelectExemplar_DuplicateSeries(t *testing.T) { es := exs.(*CircularExemplarStorage) e := exemplar.Exemplar{ - Labels: labels.FromStrings("traceID", "qwerty"), + Labels: labels.FromStrings("trace_id", "qwerty"), Value: 0.1, Ts: 12, } @@ -413,7 +413,7 @@ func TestResize(t *testing.T) { func BenchmarkAddExemplar(b *testing.B) { // We need to include these labels since we do length calculation // before adding. - exLabels := labels.FromStrings("traceID", "89620921") + exLabels := labels.FromStrings("trace_id", "89620921") for _, n := range []int{10000, 100000, 1000000} { b.Run(fmt.Sprintf("%d", n), func(b *testing.B) { diff --git a/tsdb/head_test.go b/tsdb/head_test.go index a9350ecb96..bc81e64570 100644 --- a/tsdb/head_test.go +++ b/tsdb/head_test.go @@ -375,7 +375,7 @@ func BenchmarkLoadWLs(b *testing.B) { Ref: chunks.HeadSeriesRef(k) * 101, T: int64(i) * 10, V: float64(i) * 100, - Labels: labels.FromStrings("traceID", fmt.Sprintf("trace-%d", i)), + Labels: labels.FromStrings("trace_id", fmt.Sprintf("trace-%d", i)), }) } populateTestWL(b, wal, []interface{}{refExemplars}) @@ -660,7 +660,7 @@ func TestHead_ReadWAL(t *testing.T) { {Ref: 0, Intervals: []tombstones.Interval{{Mint: 99, Maxt: 101}}}, }, []record.RefExemplar{ - {Ref: 10, T: 100, V: 1, Labels: labels.FromStrings("traceID", "asdf")}, + {Ref: 10, T: 100, V: 1, Labels: labels.FromStrings("trace_id", "asdf")}, }, } @@ -709,7 +709,7 @@ func TestHead_ReadWAL(t *testing.T) { require.NoError(t, err) e, err := q.Select(0, 1000, []*labels.Matcher{labels.MustNewMatcher(labels.MatchEqual, "a", "1")}) require.NoError(t, err) - require.True(t, exemplar.Exemplar{Ts: 100, Value: 1, Labels: labels.FromStrings("traceID", "asdf")}.Equals(e[0].Exemplars[0])) + require.True(t, exemplar.Exemplar{Ts: 100, Value: 1, Labels: labels.FromStrings("trace_id", "asdf")}.Equals(e[0].Exemplars[0])) }) } } @@ -3049,7 +3049,7 @@ func TestHeadExemplars(t *testing.T) { head, _ := newTestHead(t, chunkRange, wlog.CompressionNone, false) app := head.Appender(context.Background()) - l := labels.FromStrings("traceId", "123") + l := labels.FromStrings("trace_id", "123") // It is perfectly valid to add Exemplars before the current start time - // histogram buckets that haven't been update in a while could still be // exported exemplars from an hour ago. @@ -3694,7 +3694,7 @@ func TestChunkSnapshot(t *testing.T) { e := ex{ seriesLabels: lbls, e: exemplar.Exemplar{ - Labels: labels.FromStrings("traceID", fmt.Sprintf("%d", rand.Int())), + Labels: labels.FromStrings("trace_id", fmt.Sprintf("%d", rand.Int())), Value: rand.Float64(), Ts: ts, }, diff --git a/tsdb/record/record_test.go b/tsdb/record/record_test.go index 0cecb43c2d..773c3abfc9 100644 --- a/tsdb/record/record_test.go +++ b/tsdb/record/record_test.go @@ -102,9 +102,9 @@ func TestRecord_EncodeDecode(t *testing.T) { }, decTstones) exemplars := []RefExemplar{ - {Ref: 0, T: 12423423, V: 1.2345, Labels: labels.FromStrings("traceID", "qwerty")}, - {Ref: 123, T: -1231, V: -123, Labels: labels.FromStrings("traceID", "asdf")}, - {Ref: 2, T: 0, V: 99999, Labels: labels.FromStrings("traceID", "zxcv")}, + {Ref: 0, T: 12423423, V: 1.2345, Labels: labels.FromStrings("trace_id", "qwerty")}, + {Ref: 123, T: -1231, V: -123, Labels: labels.FromStrings("trace_id", "asdf")}, + {Ref: 2, T: 0, V: 99999, Labels: labels.FromStrings("trace_id", "zxcv")}, } decExemplars, err := dec.Exemplars(enc.Exemplars(exemplars, nil), nil) require.NoError(t, err) @@ -227,7 +227,7 @@ func TestRecord_Corrupted(t *testing.T) { t.Run("Test corrupted exemplar record", func(t *testing.T) { exemplars := []RefExemplar{ - {Ref: 0, T: 12423423, V: 1.2345, Labels: labels.FromStrings("traceID", "asdf")}, + {Ref: 0, T: 12423423, V: 1.2345, Labels: labels.FromStrings("trace_id", "asdf")}, } corrupted := enc.Exemplars(exemplars, nil)[:8] diff --git a/tsdb/wlog/checkpoint_test.go b/tsdb/wlog/checkpoint_test.go index ce9f9ef99f..89b1c7f339 100644 --- a/tsdb/wlog/checkpoint_test.go +++ b/tsdb/wlog/checkpoint_test.go @@ -202,7 +202,7 @@ func TestCheckpoint(t *testing.T) { histogramsInWAL += 4 b = enc.Exemplars([]record.RefExemplar{ - {Ref: 1, T: last, V: float64(i), Labels: labels.FromStrings("traceID", fmt.Sprintf("trace-%d", i))}, + {Ref: 1, T: last, V: float64(i), Labels: labels.FromStrings("trace_id", fmt.Sprintf("trace-%d", i))}, }, nil) require.NoError(t, w.Log(b)) diff --git a/tsdb/wlog/watcher_test.go b/tsdb/wlog/watcher_test.go index b30dce91a3..b10f8f8f8c 100644 --- a/tsdb/wlog/watcher_test.go +++ b/tsdb/wlog/watcher_test.go @@ -169,7 +169,7 @@ func TestTailSamples(t *testing.T) { Ref: chunks.HeadSeriesRef(inner), T: now.UnixNano() + 1, V: float64(i), - Labels: labels.FromStrings("traceID", fmt.Sprintf("trace-%d", inner)), + Labels: labels.FromStrings("trace_id", fmt.Sprintf("trace-%d", inner)), }, }, nil) require.NoError(t, w.Log(exemplar)) diff --git a/web/api/v1/json_codec_test.go b/web/api/v1/json_codec_test.go index 6b97b55e57..b8384baaa8 100644 --- a/web/api/v1/json_codec_test.go +++ b/web/api/v1/json_codec_test.go @@ -134,14 +134,14 @@ func TestJsonCodec_Encode(t *testing.T) { SeriesLabels: labels.FromStrings("foo", "bar"), Exemplars: []exemplar.Exemplar{ { - Labels: labels.FromStrings("traceID", "abc"), + Labels: labels.FromStrings("trace_id", "abc"), Value: 100.123, Ts: 1234, }, }, }, }, - expected: `{"status":"success","data":[{"seriesLabels":{"foo":"bar"},"exemplars":[{"labels":{"traceID":"abc"},"value":"100.123","timestamp":1.234}]}]}`, + expected: `{"status":"success","data":[{"seriesLabels":{"foo":"bar"},"exemplars":[{"labels":{"trace_id":"abc"},"value":"100.123","timestamp":1.234}]}]}`, }, { response: []exemplar.QueryResult{ @@ -149,14 +149,14 @@ func TestJsonCodec_Encode(t *testing.T) { SeriesLabels: labels.FromStrings("foo", "bar"), Exemplars: []exemplar.Exemplar{ { - Labels: labels.FromStrings("traceID", "abc"), + Labels: labels.FromStrings("trace_id", "abc"), Value: math.Inf(1), Ts: 1234, }, }, }, }, - expected: `{"status":"success","data":[{"seriesLabels":{"foo":"bar"},"exemplars":[{"labels":{"traceID":"abc"},"value":"+Inf","timestamp":1.234}]}]}`, + expected: `{"status":"success","data":[{"seriesLabels":{"foo":"bar"},"exemplars":[{"labels":{"trace_id":"abc"},"value":"+Inf","timestamp":1.234}]}]}`, }, } diff --git a/web/ui/react-app/src/pages/graph/Graph.test.tsx b/web/ui/react-app/src/pages/graph/Graph.test.tsx index 01625f7c2f..69f51d06a2 100644 --- a/web/ui/react-app/src/pages/graph/Graph.test.tsx +++ b/web/ui/react-app/src/pages/graph/Graph.test.tsx @@ -80,7 +80,7 @@ describe('Graph', () => { exemplars: [ { labels: { - traceID: '12345', + trace_id: '12345', }, timestamp: 1572130580, value: '9', From a28d7865adf6542cbac3b908124ae07846a0d4a8 Mon Sep 17 00:00:00 2001 From: Owen Williams Date: Thu, 15 Feb 2024 14:25:12 -0500 Subject: [PATCH 087/237] UTF-8: Add support for parsing UTF8 metric and label names This adds support for the new grammar of `{"metric_name", "l1"="val"}` to promql and some of the exposition formats. This grammar will also be valid for non-UTF-8 names. UTF-8 names will not be considered valid unless model.NameValidationScheme is changed. This does not update the go expfmt parser in text_parse.go, which will be addressed by https://github.com/prometheus/common/issues/554/. Part of https://github.com/prometheus/prometheus/issues/13095 Signed-off-by: Owen Williams --- go.mod | 2 +- go.sum | 4 +- model/textparse/openmetricslex.l | 3 + model/textparse/openmetricslex.l.go | 590 +++++++------ model/textparse/openmetricsparse.go | 195 +++-- model/textparse/openmetricsparse_test.go | 165 +++- model/textparse/promlex.l | 3 + model/textparse/promlex.l.go | 352 +++++--- model/textparse/promparse.go | 155 +++- model/textparse/promparse_test.go | 142 ++- promql/parser/generated_parser.y | 22 +- promql/parser/generated_parser.y.go | 1013 ++++++++++------------ promql/parser/parse.go | 23 + promql/parser/parse_test.go | 65 ++ scrape/scrape_test.go | 8 +- storage/remote/write_handler.go | 2 +- web/federate.go | 3 +- 17 files changed, 1652 insertions(+), 1095 deletions(-) diff --git a/go.mod b/go.mod index 0ba4deb40d..faf9c26274 100644 --- a/go.mod +++ b/go.mod @@ -52,7 +52,7 @@ require ( github.com/prometheus/alertmanager v0.26.0 github.com/prometheus/client_golang v1.18.0 github.com/prometheus/client_model v0.5.0 - github.com/prometheus/common v0.46.0 + github.com/prometheus/common v0.47.0 github.com/prometheus/common/assets v0.2.0 github.com/prometheus/common/sigv4 v0.1.0 github.com/prometheus/exporter-toolkit v0.11.0 diff --git a/go.sum b/go.sum index fd9c104ca1..8c362eb867 100644 --- a/go.sum +++ b/go.sum @@ -670,8 +670,8 @@ github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8b github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= github.com/prometheus/common v0.29.0/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= -github.com/prometheus/common v0.46.0 h1:doXzt5ybi1HBKpsZOL0sSkaNHJJqkyfEWZGGqqScV0Y= -github.com/prometheus/common v0.46.0/go.mod h1:Tp0qkxpb9Jsg54QMe+EAmqXkSV7Evdy1BTn+g2pa/hQ= +github.com/prometheus/common v0.47.0 h1:p5Cz0FNHo7SnWOmWmoRozVcjEp0bIVU8cV7OShpjL1k= +github.com/prometheus/common v0.47.0/go.mod h1:0/KsvlIEfPQCQ5I2iNSAWKPZziNCvRs5EC6ILDTlAPc= github.com/prometheus/common/assets v0.2.0 h1:0P5OrzoHrYBOSM1OigWL3mY8ZvV2N4zIE/5AahrSrfM= github.com/prometheus/common/assets v0.2.0/go.mod h1:D17UVUE12bHbim7HzwUvtqm6gwBEaDQ0F+hIGbFbccI= github.com/prometheus/common/sigv4 v0.1.0 h1:qoVebwtwwEhS85Czm2dSROY5fTo2PAPEVdDeppTwGX4= diff --git a/model/textparse/openmetricslex.l b/model/textparse/openmetricslex.l index 91e4439423..9afbbbd8bd 100644 --- a/model/textparse/openmetricslex.l +++ b/model/textparse/openmetricslex.l @@ -50,12 +50,15 @@ S [ ] TYPE{S} l.state = sMeta1; return tType UNIT{S} l.state = sMeta1; return tUnit "EOF"\n? l.state = sInit; return tEOFWord +\"(\\.|[^\\"])*\" l.state = sMeta2; return tMName {M}({M}|{D})* l.state = sMeta2; return tMName {S}{C}*\n l.state = sInit; return tText {M}({M}|{D})* l.state = sValue; return tMName \{ l.state = sLabels; return tBraceOpen +\{ l.state = sLabels; return tBraceOpen {L}({L}|{D})* return tLName +\"(\\.|[^\\"])*\" l.state = sLabels; return tQString \} l.state = sValue; return tBraceClose = l.state = sLValue; return tEqual , return tComma diff --git a/model/textparse/openmetricslex.l.go b/model/textparse/openmetricslex.l.go index 9f17cfd436..c8789ef60d 100644 --- a/model/textparse/openmetricslex.l.go +++ b/model/textparse/openmetricslex.l.go @@ -37,25 +37,25 @@ yystate0: case 0: // start condition: INITIAL goto yystart1 case 1: // start condition: sComment - goto yystart5 + goto yystart6 case 2: // start condition: sMeta1 - goto yystart25 + goto yystart26 case 3: // start condition: sMeta2 - goto yystart27 + goto yystart31 case 4: // start condition: sLabels - goto yystart30 + goto yystart34 case 5: // start condition: sLValue - goto yystart35 + goto yystart42 case 6: // start condition: sValue - goto yystart39 + goto yystart46 case 7: // start condition: sTimestamp - goto yystart43 - case 8: // start condition: sExemplar goto yystart50 + case 8: // start condition: sExemplar + goto yystart57 case 9: // start condition: sEValue - goto yystart55 + goto yystart62 case 10: // start condition: sETimestamp - goto yystart61 + goto yystart68 } yystate1: @@ -68,6 +68,8 @@ yystart1: goto yystate2 case c == ':' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'z': goto yystate4 + case c == '{': + goto yystate5 } yystate2: @@ -87,34 +89,29 @@ yystate4: c = l.next() switch { default: - goto yyrule8 + goto yyrule9 case c >= '0' && c <= ':' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'z': goto yystate4 } yystate5: c = l.next() -yystart5: + goto yyrule11 + +yystate6: + c = l.next() +yystart6: switch { default: goto yyabort case c == 'E': - goto yystate6 - case c == 'H': - goto yystate10 - case c == 'T': - goto yystate15 - case c == 'U': - goto yystate20 - } - -yystate6: - c = l.next() - switch { - default: - goto yyabort - case c == 'O': goto yystate7 + case c == 'H': + goto yystate11 + case c == 'T': + goto yystate16 + case c == 'U': + goto yystate21 } yystate7: @@ -122,7 +119,7 @@ yystate7: switch { default: goto yyabort - case c == 'F': + case c == 'O': goto yystate8 } @@ -130,30 +127,30 @@ yystate8: c = l.next() switch { default: - goto yyrule5 - case c == '\n': + goto yyabort + case c == 'F': goto yystate9 } yystate9: c = l.next() - goto yyrule5 + switch { + default: + goto yyrule5 + case c == '\n': + goto yystate10 + } yystate10: c = l.next() - switch { - default: - goto yyabort - case c == 'E': - goto yystate11 - } + goto yyrule5 yystate11: c = l.next() switch { default: goto yyabort - case c == 'L': + case c == 'E': goto yystate12 } @@ -162,7 +159,7 @@ yystate12: switch { default: goto yyabort - case c == 'P': + case c == 'L': goto yystate13 } @@ -171,29 +168,29 @@ yystate13: switch { default: goto yyabort - case c == ' ': + case c == 'P': goto yystate14 } yystate14: - c = l.next() - goto yyrule2 - -yystate15: c = l.next() switch { default: goto yyabort - case c == 'Y': - goto yystate16 + case c == ' ': + goto yystate15 } +yystate15: + c = l.next() + goto yyrule2 + yystate16: c = l.next() switch { default: goto yyabort - case c == 'P': + case c == 'Y': goto yystate17 } @@ -202,7 +199,7 @@ yystate17: switch { default: goto yyabort - case c == 'E': + case c == 'P': goto yystate18 } @@ -211,29 +208,29 @@ yystate18: switch { default: goto yyabort - case c == ' ': + case c == 'E': goto yystate19 } yystate19: - c = l.next() - goto yyrule3 - -yystate20: c = l.next() switch { default: goto yyabort - case c == 'N': - goto yystate21 + case c == ' ': + goto yystate20 } +yystate20: + c = l.next() + goto yyrule3 + yystate21: c = l.next() switch { default: goto yyabort - case c == 'I': + case c == 'N': goto yystate22 } @@ -242,7 +239,7 @@ yystate22: switch { default: goto yyabort - case c == 'T': + case c == 'I': goto yystate23 } @@ -251,175 +248,181 @@ yystate23: switch { default: goto yyabort - case c == ' ': + case c == 'T': goto yystate24 } yystate24: c = l.next() - goto yyrule4 - -yystate25: - c = l.next() -yystart25: - switch { - default: - goto yyabort - case c == ':' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'z': - goto yystate26 - } - -yystate26: - c = l.next() - switch { - default: - goto yyrule6 - case c >= '0' && c <= ':' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'z': - goto yystate26 - } - -yystate27: - c = l.next() -yystart27: switch { default: goto yyabort case c == ' ': + goto yystate25 + } + +yystate25: + c = l.next() + goto yyrule4 + +yystate26: + c = l.next() +yystart26: + switch { + default: + goto yyabort + case c == '"': + goto yystate27 + case c == ':' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'z': + goto yystate30 + } + +yystate27: + c = l.next() + switch { + default: + goto yyabort + case c == '"': goto yystate28 + case c == '\\': + goto yystate29 + case c >= '\x01' && c <= '!' || c >= '#' && c <= '[' || c >= ']' && c <= 'ÿ': + goto yystate27 } yystate28: + c = l.next() + goto yyrule6 + +yystate29: + c = l.next() + switch { + default: + goto yyabort + case c >= '\x01' && c <= '\t' || c >= '\v' && c <= 'ÿ': + goto yystate27 + } + +yystate30: + c = l.next() + switch { + default: + goto yyrule7 + case c >= '0' && c <= ':' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'z': + goto yystate30 + } + +yystate31: + c = l.next() +yystart31: + switch { + default: + goto yyabort + case c == ' ': + goto yystate32 + } + +yystate32: c = l.next() switch { default: goto yyabort case c == '\n': - goto yystate29 - case c >= '\x01' && c <= '\t' || c >= '\v' && c <= 'ÿ': - goto yystate28 - } - -yystate29: - c = l.next() - goto yyrule7 - -yystate30: - c = l.next() -yystart30: - switch { - default: - goto yyabort - case c == ',': - goto yystate31 - case c == '=': - goto yystate32 - case c == '}': - goto yystate34 - case c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'z': goto yystate33 + case c >= '\x01' && c <= '\t' || c >= '\v' && c <= 'ÿ': + goto yystate32 } -yystate31: - c = l.next() - goto yyrule13 - -yystate32: - c = l.next() - goto yyrule12 - yystate33: c = l.next() - switch { - default: - goto yyrule10 - case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'z': - goto yystate33 - } + goto yyrule8 yystate34: c = l.next() - goto yyrule11 +yystart34: + switch { + default: + goto yyabort + case c == '"': + goto yystate35 + case c == ',': + goto yystate38 + case c == '=': + goto yystate39 + case c == '}': + goto yystate41 + case c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'z': + goto yystate40 + } yystate35: c = l.next() -yystart35: switch { default: goto yyabort case c == '"': goto yystate36 + case c == '\\': + goto yystate37 + case c >= '\x01' && c <= '!' || c >= '#' && c <= '[' || c >= ']' && c <= 'ÿ': + goto yystate35 } yystate36: c = l.next() - switch { - default: - goto yyabort - case c == '"': - goto yystate37 - case c == '\\': - goto yystate38 - case c >= '\x01' && c <= '\t' || c >= '\v' && c <= '!' || c >= '#' && c <= '[' || c >= ']' && c <= 'ÿ': - goto yystate36 - } + goto yyrule13 yystate37: - c = l.next() - goto yyrule14 - -yystate38: c = l.next() switch { default: goto yyabort case c >= '\x01' && c <= '\t' || c >= '\v' && c <= 'ÿ': - goto yystate36 + goto yystate35 } +yystate38: + c = l.next() + goto yyrule16 + yystate39: c = l.next() -yystart39: - switch { - default: - goto yyabort - case c == ' ': - goto yystate40 - case c == '{': - goto yystate42 - } + goto yyrule15 yystate40: c = l.next() switch { default: - goto yyabort - case c >= '\x01' && c <= '\t' || c >= '\v' && c <= '\x1f' || c >= '!' && c <= 'ÿ': - goto yystate41 + goto yyrule12 + case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'z': + goto yystate40 } yystate41: c = l.next() - switch { - default: - goto yyrule15 - case c >= '\x01' && c <= '\t' || c >= '\v' && c <= '\x1f' || c >= '!' && c <= 'ÿ': - goto yystate41 - } + goto yyrule14 yystate42: c = l.next() - goto yyrule9 - -yystate43: - c = l.next() -yystart43: +yystart42: switch { default: goto yyabort - case c == ' ': - goto yystate45 - case c == '\n': + case c == '"': + goto yystate43 + } + +yystate43: + c = l.next() + switch { + default: + goto yyabort + case c == '"': goto yystate44 + case c == '\\': + goto yystate45 + case c >= '\x01' && c <= '\t' || c >= '\v' && c <= '!' || c >= '#' && c <= '[' || c >= ']' && c <= 'ÿ': + goto yystate43 } yystate44: @@ -431,44 +434,43 @@ yystate45: switch { default: goto yyabort - case c == '#': - goto yystate47 - case c >= '\x01' && c <= '\t' || c >= '\v' && c <= '\x1f' || c == '!' || c == '"' || c >= '$' && c <= 'ÿ': - goto yystate46 + case c >= '\x01' && c <= '\t' || c >= '\v' && c <= 'ÿ': + goto yystate43 } yystate46: c = l.next() +yystart46: switch { default: - goto yyrule16 - case c >= '\x01' && c <= '\t' || c >= '\v' && c <= '\x1f' || c >= '!' && c <= 'ÿ': - goto yystate46 + goto yyabort + case c == ' ': + goto yystate47 + case c == '{': + goto yystate49 } yystate47: c = l.next() switch { default: - goto yyrule16 - case c == ' ': - goto yystate48 + goto yyabort case c >= '\x01' && c <= '\t' || c >= '\v' && c <= '\x1f' || c >= '!' && c <= 'ÿ': - goto yystate46 + goto yystate48 } yystate48: c = l.next() switch { default: - goto yyabort - case c == '{': - goto yystate49 + goto yyrule18 + case c >= '\x01' && c <= '\t' || c >= '\v' && c <= '\x1f' || c >= '!' && c <= 'ÿ': + goto yystate48 } yystate49: c = l.next() - goto yyrule18 + goto yyrule10 yystate50: c = l.next() @@ -476,109 +478,109 @@ yystart50: switch { default: goto yyabort - case c == ',': - goto yystate51 - case c == '=': + case c == ' ': goto yystate52 - case c == '}': - goto yystate54 - case c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'z': - goto yystate53 + case c == '\n': + goto yystate51 } yystate51: c = l.next() - goto yyrule23 + goto yyrule20 yystate52: c = l.next() - goto yyrule21 + switch { + default: + goto yyabort + case c == '#': + goto yystate54 + case c >= '\x01' && c <= '\t' || c >= '\v' && c <= '\x1f' || c == '!' || c == '"' || c >= '$' && c <= 'ÿ': + goto yystate53 + } yystate53: c = l.next() switch { default: goto yyrule19 - case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'z': + case c >= '\x01' && c <= '\t' || c >= '\v' && c <= '\x1f' || c >= '!' && c <= 'ÿ': goto yystate53 } yystate54: c = l.next() - goto yyrule20 + switch { + default: + goto yyrule19 + case c == ' ': + goto yystate55 + case c >= '\x01' && c <= '\t' || c >= '\v' && c <= '\x1f' || c >= '!' && c <= 'ÿ': + goto yystate53 + } yystate55: c = l.next() -yystart55: switch { default: goto yyabort - case c == ' ': + case c == '{': goto yystate56 - case c == '"': - goto yystate58 } yystate56: c = l.next() - switch { - default: - goto yyabort - case c >= '\x01' && c <= '\t' || c >= '\v' && c <= '\x1f' || c >= '!' && c <= 'ÿ': - goto yystate57 - } + goto yyrule21 yystate57: c = l.next() +yystart57: switch { default: - goto yyrule24 - case c >= '\x01' && c <= '\t' || c >= '\v' && c <= '\x1f' || c >= '!' && c <= 'ÿ': - goto yystate57 + goto yyabort + case c == ',': + goto yystate58 + case c == '=': + goto yystate59 + case c == '}': + goto yystate61 + case c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'z': + goto yystate60 } yystate58: c = l.next() - switch { - default: - goto yyabort - case c == '"': - goto yystate59 - case c == '\\': - goto yystate60 - case c >= '\x01' && c <= '\t' || c >= '\v' && c <= '!' || c >= '#' && c <= '[' || c >= ']' && c <= 'ÿ': - goto yystate58 - } + goto yyrule26 yystate59: c = l.next() - goto yyrule22 + goto yyrule24 yystate60: c = l.next() switch { default: - goto yyabort - case c >= '\x01' && c <= '\t' || c >= '\v' && c <= 'ÿ': - goto yystate58 + goto yyrule22 + case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'z': + goto yystate60 } yystate61: c = l.next() -yystart61: + goto yyrule23 + +yystate62: + c = l.next() +yystart62: switch { default: goto yyabort case c == ' ': goto yystate63 - case c == '\n': - goto yystate62 + case c == '"': + goto yystate65 } -yystate62: - c = l.next() - goto yyrule26 - yystate63: c = l.next() switch { @@ -592,11 +594,71 @@ yystate64: c = l.next() switch { default: - goto yyrule25 + goto yyrule27 case c >= '\x01' && c <= '\t' || c >= '\v' && c <= '\x1f' || c >= '!' && c <= 'ÿ': goto yystate64 } +yystate65: + c = l.next() + switch { + default: + goto yyabort + case c == '"': + goto yystate66 + case c == '\\': + goto yystate67 + case c >= '\x01' && c <= '\t' || c >= '\v' && c <= '!' || c >= '#' && c <= '[' || c >= ']' && c <= 'ÿ': + goto yystate65 + } + +yystate66: + c = l.next() + goto yyrule25 + +yystate67: + c = l.next() + switch { + default: + goto yyabort + case c >= '\x01' && c <= '\t' || c >= '\v' && c <= 'ÿ': + goto yystate65 + } + +yystate68: + c = l.next() +yystart68: + switch { + default: + goto yyabort + case c == ' ': + goto yystate70 + case c == '\n': + goto yystate69 + } + +yystate69: + c = l.next() + goto yyrule29 + +yystate70: + c = l.next() + switch { + default: + goto yyabort + case c >= '\x01' && c <= '\t' || c >= '\v' && c <= '\x1f' || c >= '!' && c <= 'ÿ': + goto yystate71 + } + +yystate71: + c = l.next() + switch { + default: + goto yyrule28 + case c >= '\x01' && c <= '\t' || c >= '\v' && c <= '\x1f' || c >= '!' && c <= 'ÿ': + goto yystate71 + } + yyrule1: // #{S} { l.state = sComment @@ -626,115 +688,133 @@ yyrule5: // "EOF"\n? return tEOFWord goto yystate0 } -yyrule6: // {M}({M}|{D})* +yyrule6: // \"(\\.|[^\\"])*\" { l.state = sMeta2 return tMName goto yystate0 } -yyrule7: // {S}{C}*\n +yyrule7: // {M}({M}|{D})* + { + l.state = sMeta2 + return tMName + goto yystate0 + } +yyrule8: // {S}{C}*\n { l.state = sInit return tText goto yystate0 } -yyrule8: // {M}({M}|{D})* +yyrule9: // {M}({M}|{D})* { l.state = sValue return tMName goto yystate0 } -yyrule9: // \{ +yyrule10: // \{ { l.state = sLabels return tBraceOpen goto yystate0 } -yyrule10: // {L}({L}|{D})* +yyrule11: // \{ + { + l.state = sLabels + return tBraceOpen + goto yystate0 + } +yyrule12: // {L}({L}|{D})* { return tLName } -yyrule11: // \} +yyrule13: // \"(\\.|[^\\"])*\" + { + l.state = sLabels + return tQString + goto yystate0 + } +yyrule14: // \} { l.state = sValue return tBraceClose goto yystate0 } -yyrule12: // = +yyrule15: // = { l.state = sLValue return tEqual goto yystate0 } -yyrule13: // , +yyrule16: // , { return tComma } -yyrule14: // \"(\\.|[^\\"\n])*\" +yyrule17: // \"(\\.|[^\\"\n])*\" { l.state = sLabels return tLValue goto yystate0 } -yyrule15: // {S}[^ \n]+ +yyrule18: // {S}[^ \n]+ { l.state = sTimestamp return tValue goto yystate0 } -yyrule16: // {S}[^ \n]+ +yyrule19: // {S}[^ \n]+ { return tTimestamp } -yyrule17: // \n +yyrule20: // \n { l.state = sInit return tLinebreak goto yystate0 } -yyrule18: // {S}#{S}\{ +yyrule21: // {S}#{S}\{ { l.state = sExemplar return tComment goto yystate0 } -yyrule19: // {L}({L}|{D})* +yyrule22: // {L}({L}|{D})* { return tLName } -yyrule20: // \} +yyrule23: // \} { l.state = sEValue return tBraceClose goto yystate0 } -yyrule21: // = +yyrule24: // = { l.state = sEValue return tEqual goto yystate0 } -yyrule22: // \"(\\.|[^\\"\n])*\" +yyrule25: // \"(\\.|[^\\"\n])*\" { l.state = sExemplar return tLValue goto yystate0 } -yyrule23: // , +yyrule26: // , { return tComma } -yyrule24: // {S}[^ \n]+ +yyrule27: // {S}[^ \n]+ { l.state = sETimestamp return tValue goto yystate0 } -yyrule25: // {S}[^ \n]+ +yyrule28: // {S}[^ \n]+ { return tTimestamp } -yyrule26: // \n +yyrule29: // \n if true { // avoid go vet determining the below panic will not be reached l.state = sInit return tLinebreak @@ -743,9 +823,7 @@ yyrule26: // \n panic("unreachable") yyabort: // no lexem recognized - // // silence unused label errors for build and satisfy go vet reachability analysis - // { if false { goto yyabort @@ -757,34 +835,34 @@ yyabort: // no lexem recognized goto yystate1 } if false { - goto yystate5 + goto yystate6 } if false { - goto yystate25 + goto yystate26 } if false { - goto yystate27 + goto yystate31 } if false { - goto yystate30 + goto yystate34 } if false { - goto yystate35 + goto yystate42 } if false { - goto yystate39 - } - if false { - goto yystate43 + goto yystate46 } if false { goto yystate50 } if false { - goto yystate55 + goto yystate57 } if false { - goto yystate61 + goto yystate62 + } + if false { + goto yystate68 } } diff --git a/model/textparse/openmetricsparse.go b/model/textparse/openmetricsparse.go index 4c15ff5fc0..2a7eae080f 100644 --- a/model/textparse/openmetricsparse.go +++ b/model/textparse/openmetricsparse.go @@ -81,6 +81,12 @@ type OpenMetricsParser struct { ts int64 hasTS bool start int + // offsets is a list of offsets into series that describe the positions + // of the metric name and label names and values for this series. + // p.offsets[0] is the start character of the metric name. + // p.offsets[1] is the end of the metric name. + // Subsequently, p.offsets is a pair of pair of offsets for the positions + // of the label name and value start and end characters. offsets []int eOffsets []int @@ -153,20 +159,18 @@ func (p *OpenMetricsParser) Metric(l *labels.Labels) string { s := string(p.series) p.builder.Reset() - p.builder.Add(labels.MetricName, s[:p.offsets[0]-p.start]) + metricName := unreplace(s[p.offsets[0]-p.start : p.offsets[1]-p.start]) + p.builder.Add(labels.MetricName, metricName) - for i := 1; i < len(p.offsets); i += 4 { + for i := 2; i < len(p.offsets); i += 4 { a := p.offsets[i] - p.start b := p.offsets[i+1] - p.start + label := unreplace(s[a:b]) c := p.offsets[i+2] - p.start d := p.offsets[i+3] - p.start + value := unreplace(s[c:d]) - value := s[c:d] - // Replacer causes allocations. Replace only when necessary. - if strings.IndexByte(s[c:d], byte('\\')) >= 0 { - value = lvalReplacer.Replace(value) - } - p.builder.Add(s[a:b], value) + p.builder.Add(label, value) } p.builder.Sort() @@ -255,7 +259,13 @@ func (p *OpenMetricsParser) Next() (Entry, error) { case tHelp, tType, tUnit: switch t2 := p.nextToken(); t2 { case tMName: - p.offsets = append(p.offsets, p.l.start, p.l.i) + mStart := p.l.start + mEnd := p.l.i + if p.l.b[mStart] == '"' && p.l.b[mEnd-1] == '"' { + mStart++ + mEnd-- + } + p.offsets = append(p.offsets, mStart, mEnd) default: return EntryInvalid, p.parseError("expected metric name after "+t.String(), t2) } @@ -312,58 +322,33 @@ func (p *OpenMetricsParser) Next() (Entry, error) { return EntryUnit, nil } + case tBraceOpen: + // We found a brace, so make room for the eventual metric name. If these + // values aren't updated, then the metric name was not set inside the + // braces and we can return an error. + if len(p.offsets) == 0 { + p.offsets = []int{-1, -1} + } + if p.offsets, err = p.parseLVals(p.offsets, false); err != nil { + return EntryInvalid, err + } + + p.series = p.l.b[p.start:p.l.i] + return p.parseMetricSuffix(p.nextToken()) case tMName: - p.offsets = append(p.offsets, p.l.i) + p.offsets = append(p.offsets, p.start, p.l.i) p.series = p.l.b[p.start:p.l.i] t2 := p.nextToken() if t2 == tBraceOpen { - p.offsets, err = p.parseLVals(p.offsets) + p.offsets, err = p.parseLVals(p.offsets, false) if err != nil { return EntryInvalid, err } p.series = p.l.b[p.start:p.l.i] t2 = p.nextToken() } - p.val, err = p.getFloatValue(t2, "metric") - if err != nil { - return EntryInvalid, err - } - - p.hasTS = false - switch t2 := p.nextToken(); t2 { - case tEOF: - return EntryInvalid, errors.New("data does not end with # EOF") - case tLinebreak: - break - case tComment: - if err := p.parseComment(); err != nil { - return EntryInvalid, err - } - case tTimestamp: - p.hasTS = true - var ts float64 - // A float is enough to hold what we need for millisecond resolution. - if ts, err = parseFloat(yoloString(p.l.buf()[1:])); err != nil { - return EntryInvalid, fmt.Errorf("%w while parsing: %q", err, p.l.b[p.start:p.l.i]) - } - if math.IsNaN(ts) || math.IsInf(ts, 0) { - return EntryInvalid, fmt.Errorf("invalid timestamp %f", ts) - } - p.ts = int64(ts * 1000) - switch t3 := p.nextToken(); t3 { - case tLinebreak: - case tComment: - if err := p.parseComment(); err != nil { - return EntryInvalid, err - } - default: - return EntryInvalid, p.parseError("expected next entry after timestamp", t3) - } - default: - return EntryInvalid, p.parseError("expected timestamp or # symbol", t2) - } - return EntrySeries, nil + return p.parseMetricSuffix(t2) default: err = p.parseError("expected a valid start token", t) @@ -374,7 +359,7 @@ func (p *OpenMetricsParser) Next() (Entry, error) { func (p *OpenMetricsParser) parseComment() error { var err error // Parse the labels. - p.eOffsets, err = p.parseLVals(p.eOffsets) + p.eOffsets, err = p.parseLVals(p.eOffsets, true) if err != nil { return err } @@ -415,38 +400,47 @@ func (p *OpenMetricsParser) parseComment() error { return nil } -func (p *OpenMetricsParser) parseLVals(offsets []int) ([]int, error) { - first := true +func (p *OpenMetricsParser) parseLVals(offsets []int, isExemplar bool) ([]int, error) { + t := p.nextToken() for { - t := p.nextToken() + curTStart := p.l.start + curTI := p.l.i switch t { case tBraceClose: return offsets, nil - case tComma: - if first { - return nil, p.parseError("expected label name or left brace", t) - } - t = p.nextToken() - if t != tLName { + case tLName: + case tQString: + default: + return nil, p.parseError("expected label name", t) + } + + t = p.nextToken() + // A quoted string followed by a comma or brace is a metric name. Set the + // offsets and continue processing. If this is an exemplar, this format + // is not allowed. + if t == tComma || t == tBraceClose { + if isExemplar { return nil, p.parseError("expected label name", t) } - case tLName: - if !first { - return nil, p.parseError("expected comma", t) + if offsets[0] != -1 || offsets[1] != -1 { + return nil, fmt.Errorf("metric name already set while parsing: %q", p.l.b[p.start:p.l.i]) } - default: - if first { - return nil, p.parseError("expected label name or left brace", t) + offsets[0] = curTStart + 1 + offsets[1] = curTI - 1 + if t == tBraceClose { + return offsets, nil } - return nil, p.parseError("expected comma or left brace", t) - + t = p.nextToken() + continue } - first = false - // t is now a label name. + // We have a label name, and it might be quoted. + if p.l.b[curTStart] == '"' { + curTStart++ + curTI-- + } + offsets = append(offsets, curTStart, curTI) - offsets = append(offsets, p.l.start, p.l.i) - - if t := p.nextToken(); t != tEqual { + if t != tEqual { return nil, p.parseError("expected equal", t) } if t := p.nextToken(); t != tLValue { @@ -459,9 +453,64 @@ func (p *OpenMetricsParser) parseLVals(offsets []int) ([]int, error) { // The openMetricsLexer ensures the value string is quoted. Strip first // and last character. offsets = append(offsets, p.l.start+1, p.l.i-1) + + // Free trailing commas are allowed. + t = p.nextToken() + if t == tComma { + t = p.nextToken() + } else if t != tBraceClose { + return nil, p.parseError("expected comma or brace close", t) + } } } +// parseMetricSuffix parses the end of the line after the metric name and +// labels. It starts parsing with the provided token. +func (p *OpenMetricsParser) parseMetricSuffix(t token) (Entry, error) { + if p.offsets[0] == -1 { + return EntryInvalid, fmt.Errorf("metric name not set while parsing: %q", p.l.b[p.start:p.l.i]) + } + + var err error + p.val, err = p.getFloatValue(t, "metric") + if err != nil { + return EntryInvalid, err + } + + p.hasTS = false + switch t2 := p.nextToken(); t2 { + case tEOF: + return EntryInvalid, errors.New("data does not end with # EOF") + case tLinebreak: + break + case tComment: + if err := p.parseComment(); err != nil { + return EntryInvalid, err + } + case tTimestamp: + p.hasTS = true + var ts float64 + // A float is enough to hold what we need for millisecond resolution. + if ts, err = parseFloat(yoloString(p.l.buf()[1:])); err != nil { + return EntryInvalid, fmt.Errorf("%w while parsing: %q", err, p.l.b[p.start:p.l.i]) + } + if math.IsNaN(ts) || math.IsInf(ts, 0) { + return EntryInvalid, fmt.Errorf("invalid timestamp %f", ts) + } + p.ts = int64(ts * 1000) + switch t3 := p.nextToken(); t3 { + case tLinebreak: + case tComment: + if err := p.parseComment(); err != nil { + return EntryInvalid, err + } + default: + return EntryInvalid, p.parseError("expected next entry after timestamp", t3) + } + } + return EntrySeries, nil +} + func (p *OpenMetricsParser) getFloatValue(t token, after string) (float64, error) { if t != tValue { return 0, p.parseError(fmt.Sprintf("expected value after %v", after), t) diff --git a/model/textparse/openmetricsparse_test.go b/model/textparse/openmetricsparse_test.go index 29f31664fe..d128761e39 100644 --- a/model/textparse/openmetricsparse_test.go +++ b/model/textparse/openmetricsparse_test.go @@ -301,6 +301,137 @@ foo_total 17.0 1520879607.789 # {id="counter-test"} 5` require.Len(t, exp, i) } +func TestUTF8OpenMetricsParse(t *testing.T) { + oldValidationScheme := model.NameValidationScheme + model.NameValidationScheme = model.UTF8Validation + defer func() { + model.NameValidationScheme = oldValidationScheme + }() + + input := `# HELP "go.gc_duration_seconds" A summary of the GC invocation durations. +# TYPE "go.gc_duration_seconds" summary +# UNIT "go.gc_duration_seconds" seconds +{"go.gc_duration_seconds",quantile="0"} 4.9351e-05 +{"go.gc_duration_seconds",quantile="0.25"} 7.424100000000001e-05 +{"go.gc_duration_seconds",quantile="0.5",a="b"} 8.3835e-05 +{"http.status",q="0.9",a="b"} 8.3835e-05 +{"http.status",q="0.9",a="b"} 8.3835e-05 +{q="0.9","http.status",a="b"} 8.3835e-05 +{"go.gc_duration_seconds_sum"} 0.004304266 +{"Heizölrückstoßabdämpfung 10€ metric with \"interesting\" {character\nchoices}","strange©™\n'quoted' \"name\""="6"} 10.0` + + input += "\n# EOF\n" + + exp := []struct { + lset labels.Labels + m string + t *int64 + v float64 + typ model.MetricType + help string + unit string + comment string + e *exemplar.Exemplar + }{ + { + m: "go.gc_duration_seconds", + help: "A summary of the GC invocation durations.", + }, { + m: "go.gc_duration_seconds", + typ: model.MetricTypeSummary, + }, { + m: "go.gc_duration_seconds", + unit: "seconds", + }, { + m: `{"go.gc_duration_seconds",quantile="0"}`, + v: 4.9351e-05, + lset: labels.FromStrings("__name__", "go.gc_duration_seconds", "quantile", "0"), + }, { + m: `{"go.gc_duration_seconds",quantile="0.25"}`, + v: 7.424100000000001e-05, + lset: labels.FromStrings("__name__", "go.gc_duration_seconds", "quantile", "0.25"), + }, { + m: `{"go.gc_duration_seconds",quantile="0.5",a="b"}`, + v: 8.3835e-05, + lset: labels.FromStrings("__name__", "go.gc_duration_seconds", "quantile", "0.5", "a", "b"), + }, { + m: `{"http.status",q="0.9",a="b"}`, + v: 8.3835e-05, + lset: labels.FromStrings("__name__", "http.status", "q", "0.9", "a", "b"), + }, { + m: `{"http.status",q="0.9",a="b"}`, + v: 8.3835e-05, + lset: labels.FromStrings("__name__", "http.status", "q", "0.9", "a", "b"), + }, { + m: `{q="0.9","http.status",a="b"}`, + v: 8.3835e-05, + lset: labels.FromStrings("__name__", "http.status", "q", "0.9", "a", "b"), + }, { + m: `{"go.gc_duration_seconds_sum"}`, + v: 0.004304266, + lset: labels.FromStrings("__name__", "go.gc_duration_seconds_sum"), + }, { + m: `{"Heizölrückstoßabdämpfung 10€ metric with \"interesting\" {character\nchoices}","strange©™\n'quoted' \"name\""="6"}`, + v: 10.0, + lset: labels.FromStrings("__name__", `Heizölrückstoßabdämpfung 10€ metric with "interesting" {character +choices}`, "strange©™\n'quoted' \"name\"", "6"), + }, + } + + p := NewOpenMetricsParser([]byte(input)) + i := 0 + + var res labels.Labels + + for { + et, err := p.Next() + if errors.Is(err, io.EOF) { + break + } + require.NoError(t, err) + + switch et { + case EntrySeries: + m, ts, v := p.Series() + + var e exemplar.Exemplar + p.Metric(&res) + found := p.Exemplar(&e) + require.Equal(t, exp[i].m, string(m)) + require.Equal(t, exp[i].t, ts) + require.Equal(t, exp[i].v, v) + require.Equal(t, exp[i].lset, res) + if exp[i].e == nil { + require.False(t, found) + } else { + require.True(t, found) + require.Equal(t, *exp[i].e, e) + } + + case EntryType: + m, typ := p.Type() + require.Equal(t, exp[i].m, string(m)) + require.Equal(t, exp[i].typ, typ) + + case EntryHelp: + m, h := p.Help() + require.Equal(t, exp[i].m, string(m)) + require.Equal(t, exp[i].help, string(h)) + + case EntryUnit: + m, u := p.Unit() + require.Equal(t, exp[i].m, string(m)) + require.Equal(t, exp[i].unit, string(u)) + + case EntryComment: + require.Equal(t, exp[i].comment, string(p.Comment())) + } + + i++ + } + require.Len(t, exp, i) +} + func TestOpenMetricsParseErrors(t *testing.T) { cases := []struct { input string @@ -457,17 +588,13 @@ func TestOpenMetricsParseErrors(t *testing.T) { input: "a{b='c'} 1\n# EOF\n", err: "expected label value, got \"'\" (\"INVALID\") while parsing: \"a{b='\"", }, - { - input: "a{b=\"c\",} 1\n# EOF\n", - err: "expected label name, got \"} \" (\"BCLOSE\") while parsing: \"a{b=\\\"c\\\",} \"", - }, { input: "a{,b=\"c\"} 1\n# EOF\n", - err: "expected label name or left brace, got \",b\" (\"COMMA\") while parsing: \"a{,b\"", + err: "expected label name, got \",b\" (\"COMMA\") while parsing: \"a{,b\"", }, { input: "a{b=\"c\"d=\"e\"} 1\n# EOF\n", - err: "expected comma, got \"d=\" (\"LNAME\") while parsing: \"a{b=\\\"c\\\"d=\"", + err: "expected comma or brace close, got \"d=\" (\"LNAME\") while parsing: \"a{b=\\\"c\\\"d=\"", }, { input: "a{b=\"c\",,d=\"e\"} 1\n# EOF\n", @@ -479,12 +606,24 @@ func TestOpenMetricsParseErrors(t *testing.T) { }, { input: "a{\xff=\"foo\"} 1\n# EOF\n", - err: "expected label name or left brace, got \"\\xff\" (\"INVALID\") while parsing: \"a{\\xff\"", + err: "expected label name, got \"\\xff\" (\"INVALID\") while parsing: \"a{\\xff\"", }, { input: "a{b=\"\xff\"} 1\n# EOF\n", err: "invalid UTF-8 label value: \"\\\"\\xff\\\"\"", }, + { + input: `{"a","b = "c"} +# EOF +`, + err: "expected equal, got \"c\\\"\" (\"LNAME\") while parsing: \"{\\\"a\\\",\\\"b = \\\"c\\\"\"", + }, + { + input: `{"a",b\nc="d"} 1 +# EOF +`, + err: "expected equal, got \"\\\\\" (\"INVALID\") while parsing: \"{\\\"a\\\",b\\\\\"", + }, { input: "a true\n", err: "strconv.ParseFloat: parsing \"true\": invalid syntax while parsing: \"a true\"", @@ -495,7 +634,7 @@ func TestOpenMetricsParseErrors(t *testing.T) { }, { input: "empty_label_name{=\"\"} 0\n# EOF\n", - err: "expected label name or left brace, got \"=\\\"\" (\"EQUAL\") while parsing: \"empty_label_name{=\\\"\"", + err: "expected label name, got \"=\\\"\" (\"EQUAL\") while parsing: \"empty_label_name{=\\\"\"", }, { input: "foo 1_2\n\n# EOF\n", @@ -525,6 +664,14 @@ func TestOpenMetricsParseErrors(t *testing.T) { input: `custom_metric_total 1 # {aa="bb"}`, err: "expected value after exemplar labels, got \"}\" (\"EOF\") while parsing: \"custom_metric_total 1 # {aa=\\\"bb\\\"}\"", }, + { + input: `custom_metric_total 1 # {bb}`, + err: "expected label name, got \"}\" (\"BCLOSE\") while parsing: \"custom_metric_total 1 # {bb}\"", + }, + { + input: `custom_metric_total 1 # {bb, a="dd"}`, + err: "expected label name, got \", \" (\"COMMA\") while parsing: \"custom_metric_total 1 # {bb, \"", + }, { input: `custom_metric_total 1 # {aa="bb",,cc="dd"} 1`, err: "expected label name, got \",c\" (\"COMMA\") while parsing: \"custom_metric_total 1 # {aa=\\\"bb\\\",,c\"", @@ -551,7 +698,7 @@ func TestOpenMetricsParseErrors(t *testing.T) { }, { input: `{b="c",} 1`, - err: "expected a valid start token, got \"{\" (\"INVALID\") while parsing: \"{\"", + err: "metric name not set while parsing: \"{b=\\\"c\\\",} 1\"", }, { input: `a 1 NaN`, diff --git a/model/textparse/promlex.l b/model/textparse/promlex.l index c1bc8e7766..e9fa1fb71c 100644 --- a/model/textparse/promlex.l +++ b/model/textparse/promlex.l @@ -66,12 +66,15 @@ C [^\n] # return l.consumeComment() HELP[\t ]+ l.state = sMeta1; return tHelp TYPE[\t ]+ l.state = sMeta1; return tType +\"(\\.|[^\\"])*\" l.state = sMeta2; return tMName {M}({M}|{D})* l.state = sMeta2; return tMName {C}* l.state = sInit; return tText {M}({M}|{D})* l.state = sValue; return tMName \{ l.state = sLabels; return tBraceOpen +\{ l.state = sLabels; return tBraceOpen {L}({L}|{D})* return tLName +\"(\\.|[^\\"])*\" l.state = sLabels; return tQString \} l.state = sValue; return tBraceClose = l.state = sLValue; return tEqual , return tComma diff --git a/model/textparse/promlex.l.go b/model/textparse/promlex.l.go index 4076aae610..a083e5549b 100644 --- a/model/textparse/promlex.l.go +++ b/model/textparse/promlex.l.go @@ -51,19 +51,19 @@ yystate0: case 0: // start condition: INITIAL goto yystart1 case 1: // start condition: sComment - goto yystart8 + goto yystart9 case 2: // start condition: sMeta1 - goto yystart19 + goto yystart20 case 3: // start condition: sMeta2 - goto yystart21 + goto yystart25 case 4: // start condition: sLabels - goto yystart24 + goto yystart28 case 5: // start condition: sLValue - goto yystart29 - case 6: // start condition: sValue - goto yystart33 - case 7: // start condition: sTimestamp goto yystart36 + case 6: // start condition: sValue + goto yystart40 + case 7: // start condition: sTimestamp + goto yystart43 } yystate1: @@ -82,6 +82,8 @@ yystart1: goto yystate3 case c == '\x00': goto yystate2 + case c == '{': + goto yystate8 } yystate2: @@ -123,40 +125,35 @@ yystate7: c = l.next() switch { default: - goto yyrule10 + goto yyrule11 case c >= '0' && c <= ':' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'z': goto yystate7 } yystate8: c = l.next() -yystart8: + goto yyrule13 + +yystate9: + c = l.next() +yystart9: switch { default: goto yyabort case c == 'H': - goto yystate9 + goto yystate10 case c == 'T': - goto yystate14 + goto yystate15 case c == '\t' || c == ' ': goto yystate3 } -yystate9: - c = l.next() - switch { - default: - goto yyabort - case c == 'E': - goto yystate10 - } - yystate10: c = l.next() switch { default: goto yyabort - case c == 'L': + case c == 'E': goto yystate11 } @@ -165,7 +162,7 @@ yystate11: switch { default: goto yyabort - case c == 'P': + case c == 'L': goto yystate12 } @@ -174,7 +171,7 @@ yystate12: switch { default: goto yyabort - case c == '\t' || c == ' ': + case c == 'P': goto yystate13 } @@ -182,18 +179,18 @@ yystate13: c = l.next() switch { default: - goto yyrule6 + goto yyabort case c == '\t' || c == ' ': - goto yystate13 + goto yystate14 } yystate14: c = l.next() switch { default: - goto yyabort - case c == 'Y': - goto yystate15 + goto yyrule6 + case c == '\t' || c == ' ': + goto yystate14 } yystate15: @@ -201,7 +198,7 @@ yystate15: switch { default: goto yyabort - case c == 'P': + case c == 'Y': goto yystate16 } @@ -210,7 +207,7 @@ yystate16: switch { default: goto yyabort - case c == 'E': + case c == 'P': goto yystate17 } @@ -219,7 +216,7 @@ yystate17: switch { default: goto yyabort - case c == '\t' || c == ' ': + case c == 'E': goto yystate18 } @@ -227,167 +224,167 @@ yystate18: c = l.next() switch { default: - goto yyrule7 + goto yyabort case c == '\t' || c == ' ': - goto yystate18 + goto yystate19 } yystate19: c = l.next() -yystart19: switch { default: - goto yyabort - case c == ':' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'z': - goto yystate20 + goto yyrule7 case c == '\t' || c == ' ': - goto yystate3 + goto yystate19 } yystate20: c = l.next() +yystart20: switch { default: - goto yyrule8 - case c >= '0' && c <= ':' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'z': - goto yystate20 + goto yyabort + case c == '"': + goto yystate21 + case c == ':' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'z': + goto yystate24 + case c == '\t' || c == ' ': + goto yystate3 } yystate21: c = l.next() -yystart21: switch { default: - goto yyrule9 - case c == '\t' || c == ' ': - goto yystate23 - case c >= '\x01' && c <= '\b' || c >= '\v' && c <= '\x1f' || c >= '!' && c <= 'ÿ': + goto yyabort + case c == '"': goto yystate22 + case c == '\\': + goto yystate23 + case c >= '\x01' && c <= '!' || c >= '#' && c <= '[' || c >= ']' && c <= 'ÿ': + goto yystate21 } yystate22: c = l.next() - switch { - default: - goto yyrule9 - case c >= '\x01' && c <= '\t' || c >= '\v' && c <= 'ÿ': - goto yystate22 - } + goto yyrule8 yystate23: c = l.next() switch { default: - goto yyrule3 - case c == '\t' || c == ' ': - goto yystate23 - case c >= '\x01' && c <= '\b' || c >= '\v' && c <= '\x1f' || c >= '!' && c <= 'ÿ': - goto yystate22 + goto yyabort + case c >= '\x01' && c <= '\t' || c >= '\v' && c <= 'ÿ': + goto yystate21 } yystate24: c = l.next() -yystart24: switch { default: - goto yyabort - case c == ',': - goto yystate25 - case c == '=': - goto yystate26 - case c == '\t' || c == ' ': - goto yystate3 - case c == '}': - goto yystate28 - case c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'z': - goto yystate27 + goto yyrule9 + case c >= '0' && c <= ':' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'z': + goto yystate24 } yystate25: c = l.next() - goto yyrule15 +yystart25: + switch { + default: + goto yyrule10 + case c == '\t' || c == ' ': + goto yystate27 + case c >= '\x01' && c <= '\b' || c >= '\v' && c <= '\x1f' || c >= '!' && c <= 'ÿ': + goto yystate26 + } yystate26: c = l.next() - goto yyrule14 + switch { + default: + goto yyrule10 + case c >= '\x01' && c <= '\t' || c >= '\v' && c <= 'ÿ': + goto yystate26 + } yystate27: c = l.next() switch { default: - goto yyrule12 - case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'z': + goto yyrule3 + case c == '\t' || c == ' ': goto yystate27 + case c >= '\x01' && c <= '\b' || c >= '\v' && c <= '\x1f' || c >= '!' && c <= 'ÿ': + goto yystate26 } yystate28: c = l.next() - goto yyrule13 +yystart28: + switch { + default: + goto yyabort + case c == '"': + goto yystate29 + case c == ',': + goto yystate32 + case c == '=': + goto yystate33 + case c == '\t' || c == ' ': + goto yystate3 + case c == '}': + goto yystate35 + case c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'z': + goto yystate34 + } yystate29: c = l.next() -yystart29: switch { default: goto yyabort case c == '"': goto yystate30 - case c == '\t' || c == ' ': - goto yystate3 + case c == '\\': + goto yystate31 + case c >= '\x01' && c <= '!' || c >= '#' && c <= '[' || c >= ']' && c <= 'ÿ': + goto yystate29 } yystate30: c = l.next() - switch { - default: - goto yyabort - case c == '"': - goto yystate31 - case c == '\\': - goto yystate32 - case c >= '\x01' && c <= '!' || c >= '#' && c <= '[' || c >= ']' && c <= 'ÿ': - goto yystate30 - } + goto yyrule15 yystate31: - c = l.next() - goto yyrule16 - -yystate32: c = l.next() switch { default: goto yyabort case c >= '\x01' && c <= '\t' || c >= '\v' && c <= 'ÿ': - goto yystate30 + goto yystate29 } +yystate32: + c = l.next() + goto yyrule18 + yystate33: c = l.next() -yystart33: - switch { - default: - goto yyabort - case c == '\t' || c == ' ': - goto yystate3 - case c == '{': - goto yystate35 - case c >= '\x01' && c <= '\b' || c >= '\v' && c <= '\x1f' || c >= '!' && c <= 'z' || c >= '|' && c <= 'ÿ': - goto yystate34 - } + goto yyrule17 yystate34: c = l.next() switch { default: - goto yyrule17 - case c >= '\x01' && c <= '\b' || c >= '\v' && c <= '\x1f' || c >= '!' && c <= 'z' || c >= '|' && c <= 'ÿ': + goto yyrule14 + case c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'z': goto yystate34 } yystate35: c = l.next() - goto yyrule11 + goto yyrule16 yystate36: c = l.next() @@ -395,25 +392,90 @@ yystart36: switch { default: goto yyabort - case c == '\n': + case c == '"': goto yystate37 case c == '\t' || c == ' ': goto yystate3 - case c >= '0' && c <= '9': - goto yystate38 } yystate37: c = l.next() - goto yyrule19 + switch { + default: + goto yyabort + case c == '"': + goto yystate38 + case c == '\\': + goto yystate39 + case c >= '\x01' && c <= '!' || c >= '#' && c <= '[' || c >= ']' && c <= 'ÿ': + goto yystate37 + } yystate38: + c = l.next() + goto yyrule19 + +yystate39: c = l.next() switch { default: - goto yyrule18 + goto yyabort + case c >= '\x01' && c <= '\t' || c >= '\v' && c <= 'ÿ': + goto yystate37 + } + +yystate40: + c = l.next() +yystart40: + switch { + default: + goto yyabort + case c == '\t' || c == ' ': + goto yystate3 + case c == '{': + goto yystate42 + case c >= '\x01' && c <= '\b' || c >= '\v' && c <= '\x1f' || c >= '!' && c <= 'z' || c >= '|' && c <= 'ÿ': + goto yystate41 + } + +yystate41: + c = l.next() + switch { + default: + goto yyrule20 + case c >= '\x01' && c <= '\b' || c >= '\v' && c <= '\x1f' || c >= '!' && c <= 'z' || c >= '|' && c <= 'ÿ': + goto yystate41 + } + +yystate42: + c = l.next() + goto yyrule12 + +yystate43: + c = l.next() +yystart43: + switch { + default: + goto yyabort + case c == '\n': + goto yystate44 + case c == '\t' || c == ' ': + goto yystate3 case c >= '0' && c <= '9': - goto yystate38 + goto yystate45 + } + +yystate44: + c = l.next() + goto yyrule22 + +yystate45: + c = l.next() + switch { + default: + goto yyrule21 + case c >= '0' && c <= '9': + goto yystate45 } yyrule1: // \0 @@ -451,67 +513,85 @@ yyrule7: // TYPE[\t ]+ return tType goto yystate0 } -yyrule8: // {M}({M}|{D})* +yyrule8: // \"(\\.|[^\\"])*\" { l.state = sMeta2 return tMName goto yystate0 } -yyrule9: // {C}* +yyrule9: // {M}({M}|{D})* + { + l.state = sMeta2 + return tMName + goto yystate0 + } +yyrule10: // {C}* { l.state = sInit return tText goto yystate0 } -yyrule10: // {M}({M}|{D})* +yyrule11: // {M}({M}|{D})* { l.state = sValue return tMName goto yystate0 } -yyrule11: // \{ +yyrule12: // \{ { l.state = sLabels return tBraceOpen goto yystate0 } -yyrule12: // {L}({L}|{D})* +yyrule13: // \{ + { + l.state = sLabels + return tBraceOpen + goto yystate0 + } +yyrule14: // {L}({L}|{D})* { return tLName } -yyrule13: // \} +yyrule15: // \"(\\.|[^\\"])*\" + { + l.state = sLabels + return tQString + goto yystate0 + } +yyrule16: // \} { l.state = sValue return tBraceClose goto yystate0 } -yyrule14: // = +yyrule17: // = { l.state = sLValue return tEqual goto yystate0 } -yyrule15: // , +yyrule18: // , { return tComma } -yyrule16: // \"(\\.|[^\\"])*\" +yyrule19: // \"(\\.|[^\\"])*\" { l.state = sLabels return tLValue goto yystate0 } -yyrule17: // [^{ \t\n]+ +yyrule20: // [^{ \t\n]+ { l.state = sTimestamp return tValue goto yystate0 } -yyrule18: // {D}+ +yyrule21: // {D}+ { return tTimestamp } -yyrule19: // \n +yyrule22: // \n if true { // avoid go vet determining the below panic will not be reached l.state = sInit return tLinebreak @@ -520,9 +600,7 @@ yyrule19: // \n panic("unreachable") yyabort: // no lexem recognized - // // silence unused label errors for build and satisfy go vet reachability analysis - // { if false { goto yyabort @@ -534,26 +612,26 @@ yyabort: // no lexem recognized goto yystate1 } if false { - goto yystate8 + goto yystate9 } if false { - goto yystate19 + goto yystate20 } if false { - goto yystate21 + goto yystate25 } if false { - goto yystate24 - } - if false { - goto yystate29 - } - if false { - goto yystate33 + goto yystate28 } if false { goto yystate36 } + if false { + goto yystate40 + } + if false { + goto yystate43 + } } // Workaround to gobble up comments that started with a HELP or TYPE diff --git a/model/textparse/promparse.go b/model/textparse/promparse.go index 7123e52c33..1de783b0d0 100644 --- a/model/textparse/promparse.go +++ b/model/textparse/promparse.go @@ -57,6 +57,7 @@ const ( tComment tBlank tMName + tQString tBraceOpen tBraceClose tLName @@ -93,6 +94,8 @@ func (t token) String() string { return "BLANK" case tMName: return "MNAME" + case tQString: + return "QSTRING" case tBraceOpen: return "BOPEN" case tBraceClose: @@ -153,6 +156,12 @@ type PromParser struct { ts int64 hasTS bool start int + // offsets is a list of offsets into series that describe the positions + // of the metric name and label names and values for this series. + // p.offsets[0] is the start character of the metric name. + // p.offsets[1] is the end of the metric name. + // Subsequently, p.offsets is a pair of pair of offsets for the positions + // of the label name and value start and end characters. offsets []int } @@ -218,20 +227,17 @@ func (p *PromParser) Metric(l *labels.Labels) string { s := string(p.series) p.builder.Reset() - p.builder.Add(labels.MetricName, s[:p.offsets[0]-p.start]) + metricName := unreplace(s[p.offsets[0]-p.start : p.offsets[1]-p.start]) + p.builder.Add(labels.MetricName, metricName) - for i := 1; i < len(p.offsets); i += 4 { + for i := 2; i < len(p.offsets); i += 4 { a := p.offsets[i] - p.start b := p.offsets[i+1] - p.start + label := unreplace(s[a:b]) c := p.offsets[i+2] - p.start d := p.offsets[i+3] - p.start - - value := s[c:d] - // Replacer causes allocations. Replace only when necessary. - if strings.IndexByte(s[c:d], byte('\\')) >= 0 { - value = lvalReplacer.Replace(value) - } - p.builder.Add(s[a:b], value) + value := unreplace(s[c:d]) + p.builder.Add(label, value) } p.builder.Sort() @@ -289,7 +295,13 @@ func (p *PromParser) Next() (Entry, error) { case tHelp, tType: switch t2 := p.nextToken(); t2 { case tMName: - p.offsets = append(p.offsets, p.l.start, p.l.i) + mStart := p.l.start + mEnd := p.l.i + if p.l.b[mStart] == '"' && p.l.b[mEnd-1] == '"' { + mStart++ + mEnd-- + } + p.offsets = append(p.offsets, mStart, mEnd) default: return EntryInvalid, p.parseError("expected metric name after "+t.String(), t2) } @@ -301,7 +313,7 @@ func (p *PromParser) Next() (Entry, error) { p.text = []byte{} } default: - return EntryInvalid, fmt.Errorf("expected text in %s", t.String()) + return EntryInvalid, fmt.Errorf("expected text in %s, got %v", t.String(), t2.String()) } switch t { case tType: @@ -339,12 +351,24 @@ func (p *PromParser) Next() (Entry, error) { return EntryInvalid, p.parseError("linebreak expected after comment", t) } return EntryComment, nil + case tBraceOpen: + // We found a brace, so make room for the eventual metric name. If these + // values aren't updated, then the metric name was not set inside the + // braces and we can return an error. + if len(p.offsets) == 0 { + p.offsets = []int{-1, -1} + } + if err := p.parseLVals(); err != nil { + return EntryInvalid, err + } - case tMName: - p.offsets = append(p.offsets, p.l.i) p.series = p.l.b[p.start:p.l.i] - + return p.parseMetricSuffix(p.nextToken()) + case tMName: + p.offsets = append(p.offsets, p.start, p.l.i) + p.series = p.l.b[p.start:p.l.i] t2 := p.nextToken() + // If there's a brace, consume and parse the label values. if t2 == tBraceOpen { if err := p.parseLVals(); err != nil { return EntryInvalid, err @@ -352,32 +376,7 @@ func (p *PromParser) Next() (Entry, error) { p.series = p.l.b[p.start:p.l.i] t2 = p.nextToken() } - if t2 != tValue { - return EntryInvalid, p.parseError("expected value after metric", t2) - } - if p.val, err = parseFloat(yoloString(p.l.buf())); err != nil { - return EntryInvalid, fmt.Errorf("%w while parsing: %q", err, p.l.b[p.start:p.l.i]) - } - // Ensure canonical NaN value. - if math.IsNaN(p.val) { - p.val = math.Float64frombits(value.NormalNaN) - } - p.hasTS = false - switch t := p.nextToken(); t { - case tLinebreak: - break - case tTimestamp: - p.hasTS = true - if p.ts, err = strconv.ParseInt(yoloString(p.l.buf()), 10, 64); err != nil { - return EntryInvalid, fmt.Errorf("%w while parsing: %q", err, p.l.b[p.start:p.l.i]) - } - if t2 := p.nextToken(); t2 != tLinebreak { - return EntryInvalid, p.parseError("expected next entry after timestamp", t2) - } - default: - return EntryInvalid, p.parseError("expected timestamp or new record", t) - } - return EntrySeries, nil + return p.parseMetricSuffix(t2) default: err = p.parseError("expected a valid start token", t) @@ -385,19 +384,43 @@ func (p *PromParser) Next() (Entry, error) { return EntryInvalid, err } +// parseLVals parses the contents inside the braces. func (p *PromParser) parseLVals() error { t := p.nextToken() for { + curTStart := p.l.start + curTI := p.l.i switch t { case tBraceClose: return nil case tLName: + case tQString: default: return p.parseError("expected label name", t) } - p.offsets = append(p.offsets, p.l.start, p.l.i) - if t := p.nextToken(); t != tEqual { + t = p.nextToken() + // A quoted string followed by a comma or brace is a metric name. Set the + // offsets and continue processing. + if t == tComma || t == tBraceClose { + if p.offsets[0] != -1 || p.offsets[1] != -1 { + return fmt.Errorf("metric name already set while parsing: %q", p.l.b[p.start:p.l.i]) + } + p.offsets[0] = curTStart + 1 + p.offsets[1] = curTI - 1 + if t == tBraceClose { + return nil + } + t = p.nextToken() + continue + } + // We have a label name, and it might be quoted. + if p.l.b[curTStart] == '"' { + curTStart++ + curTI-- + } + p.offsets = append(p.offsets, curTStart, curTI) + if t != tEqual { return p.parseError("expected equal", t) } if t := p.nextToken(); t != tLValue { @@ -411,13 +434,51 @@ func (p *PromParser) parseLVals() error { // and last character. p.offsets = append(p.offsets, p.l.start+1, p.l.i-1) - // Free trailing commas are allowed. + // Free trailing commas are allowed. NOTE: this allows spaces between label + // names, unlike in OpenMetrics. It is not clear if this is intended or an + // accidental bug. if t = p.nextToken(); t == tComma { t = p.nextToken() } } } +// parseMetricSuffix parses the end of the line after the metric name and +// labels. It starts parsing with the provided token. +func (p *PromParser) parseMetricSuffix(t token) (Entry, error) { + if p.offsets[0] == -1 { + return EntryInvalid, fmt.Errorf("metric name not set while parsing: %q", p.l.b[p.start:p.l.i]) + } + if t != tValue { + return EntryInvalid, p.parseError("expected value after metric", t) + } + var err error + if p.val, err = parseFloat(yoloString(p.l.buf())); err != nil { + return EntryInvalid, fmt.Errorf("%w while parsing: %q", err, p.l.b[p.start:p.l.i]) + } + // Ensure canonical NaN value. + if math.IsNaN(p.val) { + p.val = math.Float64frombits(value.NormalNaN) + } + p.hasTS = false + switch t := p.nextToken(); t { + case tLinebreak: + break + case tTimestamp: + p.hasTS = true + if p.ts, err = strconv.ParseInt(yoloString(p.l.buf()), 10, 64); err != nil { + return EntryInvalid, fmt.Errorf("%w while parsing: %q", err, p.l.b[p.start:p.l.i]) + } + if t2 := p.nextToken(); t2 != tLinebreak { + return EntryInvalid, p.parseError("expected next entry after timestamp", t2) + } + default: + return EntryInvalid, p.parseError("expected timestamp or new record", t) + } + + return EntrySeries, nil +} + var lvalReplacer = strings.NewReplacer( `\"`, "\"", `\\`, "\\", @@ -429,6 +490,14 @@ var helpReplacer = strings.NewReplacer( `\n`, "\n", ) +func unreplace(s string) string { + // Replacer causes allocations. Replace only when necessary. + if strings.IndexByte(s, byte('\\')) >= 0 { + return lvalReplacer.Replace(s) + } + return s +} + func yoloString(b []byte) string { return *((*string)(unsafe.Pointer(&b))) } diff --git a/model/textparse/promparse_test.go b/model/textparse/promparse_test.go index ccd7ef9ccc..d82bfe598d 100644 --- a/model/textparse/promparse_test.go +++ b/model/textparse/promparse_test.go @@ -48,6 +48,7 @@ go_gc_duration_seconds{ quantile="1.0", a="b" } 8.3835e-05 go_gc_duration_seconds { quantile="1.0", a="b" } 8.3835e-05 go_gc_duration_seconds { quantile= "1.0", a= "b", } 8.3835e-05 go_gc_duration_seconds { quantile = "1.0", a = "b" } 8.3835e-05 +go_gc_duration_seconds { quantile = "2.0" a = "b" } 8.3835e-05 go_gc_duration_seconds_count 99 some:aggregate:rate5m{a_b="c"} 1 # HELP go_goroutines Number of goroutines that currently exist. @@ -130,6 +131,11 @@ testmetric{label="\"bar\""} 1` m: `go_gc_duration_seconds { quantile = "1.0", a = "b" }`, v: 8.3835e-05, lset: labels.FromStrings("__name__", "go_gc_duration_seconds", "quantile", "1.0", "a", "b"), + }, { + // NOTE: Unlike OpenMetrics, Promparse allows spaces between label terms. This appears to be unintended and should probably be fixed. + m: `go_gc_duration_seconds { quantile = "2.0" a = "b" }`, + v: 8.3835e-05, + lset: labels.FromStrings("__name__", "go_gc_duration_seconds", "quantile", "2.0", "a", "b"), }, { m: `go_gc_duration_seconds_count`, v: 99, @@ -213,6 +219,132 @@ testmetric{label="\"bar\""} 1` require.Len(t, exp, i) } +func TestUTF8PromParse(t *testing.T) { + oldValidationScheme := model.NameValidationScheme + model.NameValidationScheme = model.UTF8Validation + defer func() { + model.NameValidationScheme = oldValidationScheme + }() + + input := `# HELP "go.gc_duration_seconds" A summary of the GC invocation durations. +# TYPE "go.gc_duration_seconds" summary +{"go.gc_duration_seconds",quantile="0"} 4.9351e-05 +{"go.gc_duration_seconds",quantile="0.25",} 7.424100000000001e-05 +{"go.gc_duration_seconds",quantile="0.5",a="b"} 8.3835e-05 +{"go.gc_duration_seconds",quantile="0.8", a="b"} 8.3835e-05 +{"go.gc_duration_seconds", quantile="0.9", a="b"} 8.3835e-05 +{"go.gc_duration_seconds", quantile="1.0", a="b" } 8.3835e-05 +{ "go.gc_duration_seconds", quantile="1.0", a="b" } 8.3835e-05 +{ "go.gc_duration_seconds", quantile= "1.0", a= "b", } 8.3835e-05 +{ "go.gc_duration_seconds", quantile = "1.0", a = "b" } 8.3835e-05 +{"go.gc_duration_seconds_count"} 99 +{"Heizölrückstoßabdämpfung 10€ metric with \"interesting\" {character\nchoices}","strange©™\n'quoted' \"name\""="6"} 10.0` + + exp := []struct { + lset labels.Labels + m string + t *int64 + v float64 + typ model.MetricType + help string + comment string + }{ + { + m: "go.gc_duration_seconds", + help: "A summary of the GC invocation durations.", + }, { + m: "go.gc_duration_seconds", + typ: model.MetricTypeSummary, + }, { + m: `{"go.gc_duration_seconds",quantile="0"}`, + v: 4.9351e-05, + lset: labels.FromStrings("__name__", "go.gc_duration_seconds", "quantile", "0"), + }, { + m: `{"go.gc_duration_seconds",quantile="0.25",}`, + v: 7.424100000000001e-05, + lset: labels.FromStrings("__name__", "go.gc_duration_seconds", "quantile", "0.25"), + }, { + m: `{"go.gc_duration_seconds",quantile="0.5",a="b"}`, + v: 8.3835e-05, + lset: labels.FromStrings("__name__", "go.gc_duration_seconds", "quantile", "0.5", "a", "b"), + }, { + m: `{"go.gc_duration_seconds",quantile="0.8", a="b"}`, + v: 8.3835e-05, + lset: labels.FromStrings("__name__", "go.gc_duration_seconds", "quantile", "0.8", "a", "b"), + }, { + m: `{"go.gc_duration_seconds", quantile="0.9", a="b"}`, + v: 8.3835e-05, + lset: labels.FromStrings("__name__", "go.gc_duration_seconds", "quantile", "0.9", "a", "b"), + }, { + m: `{"go.gc_duration_seconds", quantile="1.0", a="b" }`, + v: 8.3835e-05, + lset: labels.FromStrings("__name__", "go.gc_duration_seconds", "quantile", "1.0", "a", "b"), + }, { + m: `{ "go.gc_duration_seconds", quantile="1.0", a="b" }`, + v: 8.3835e-05, + lset: labels.FromStrings("__name__", "go.gc_duration_seconds", "quantile", "1.0", "a", "b"), + }, { + m: `{ "go.gc_duration_seconds", quantile= "1.0", a= "b", }`, + v: 8.3835e-05, + lset: labels.FromStrings("__name__", "go.gc_duration_seconds", "quantile", "1.0", "a", "b"), + }, { + m: `{ "go.gc_duration_seconds", quantile = "1.0", a = "b" }`, + v: 8.3835e-05, + lset: labels.FromStrings("__name__", "go.gc_duration_seconds", "quantile", "1.0", "a", "b"), + }, { + m: `{"go.gc_duration_seconds_count"}`, + v: 99, + lset: labels.FromStrings("__name__", "go.gc_duration_seconds_count"), + }, { + m: `{"Heizölrückstoßabdämpfung 10€ metric with \"interesting\" {character\nchoices}","strange©™\n'quoted' \"name\""="6"}`, + v: 10.0, + lset: labels.FromStrings("__name__", `Heizölrückstoßabdämpfung 10€ metric with "interesting" {character +choices}`, "strange©™\n'quoted' \"name\"", "6"), + }, + } + + p := NewPromParser([]byte(input)) + i := 0 + + var res labels.Labels + + for { + et, err := p.Next() + if errors.Is(err, io.EOF) { + break + } + require.NoError(t, err) + + switch et { + case EntrySeries: + m, ts, v := p.Series() + + p.Metric(&res) + + require.Equal(t, exp[i].m, string(m)) + require.Equal(t, exp[i].t, ts) + require.Equal(t, exp[i].v, v) + require.Equal(t, exp[i].lset, res) + + case EntryType: + m, typ := p.Type() + require.Equal(t, exp[i].m, string(m)) + require.Equal(t, exp[i].typ, typ) + + case EntryHelp: + m, h := p.Help() + require.Equal(t, exp[i].m, string(m)) + require.Equal(t, exp[i].help, string(h)) + + case EntryComment: + require.Equal(t, exp[i].comment, string(p.Comment())) + } + + i++ + } + require.Len(t, exp, i) +} + func TestPromParseErrors(t *testing.T) { cases := []struct { input string @@ -238,6 +370,14 @@ func TestPromParseErrors(t *testing.T) { input: "a{b=\"\xff\"} 1\n", err: "invalid UTF-8 label value: \"\\\"\\xff\\\"\"", }, + { + input: `{"a", "b = "c"}`, + err: "expected equal, got \"c\\\"\" (\"LNAME\") while parsing: \"{\\\"a\\\", \\\"b = \\\"c\\\"\"", + }, + { + input: `{"a",b\nc="d"} 1`, + err: "expected equal, got \"\\\\\" (\"INVALID\") while parsing: \"{\\\"a\\\",b\\\\\"", + }, { input: "a true\n", err: "strconv.ParseFloat: parsing \"true\": invalid syntax while parsing: \"a true\"", @@ -268,7 +408,7 @@ func TestPromParseErrors(t *testing.T) { }, { input: `{a="ok"} 1`, - err: "expected a valid start token, got \"{\" (\"INVALID\") while parsing: \"{\"", + err: "metric name not set while parsing: \"{a=\\\"ok\\\"} 1\"", }, { input: "# TYPE #\n#EOF\n", diff --git a/promql/parser/generated_parser.y b/promql/parser/generated_parser.y index dce79f7693..841bd31c19 100644 --- a/promql/parser/generated_parser.y +++ b/promql/parser/generated_parser.y @@ -161,7 +161,7 @@ START_METRIC_SELECTOR // Type definitions for grammar rules. %type label_match_list %type label_matcher -%type aggregate_op grouping_label match_op maybe_label metric_identifier unary_op at_modifier_preprocessors +%type aggregate_op grouping_label match_op maybe_label metric_identifier unary_op at_modifier_preprocessors string_identifier %type label_set metric %type label_set_list %type