diff --git a/.github/stale.yml b/.github/stale.yml deleted file mode 100644 index 66a72af5334..00000000000 --- a/.github/stale.yml +++ /dev/null @@ -1,56 +0,0 @@ -# Configuration for probot-stale - https://github.com/probot/stale - -# Number of days of inactivity before an Issue or Pull Request becomes stale -daysUntilStale: 60 - -# Number of days of inactivity before an Issue or Pull Request with the stale label is closed. -# Set to false to disable. If disabled, issues still need to be closed manually, but will remain marked as stale. -daysUntilClose: false - -# Only issues or pull requests with all of these labels are check if stale. Defaults to `[]` (disabled) -onlyLabels: [] - -# Issues or Pull Requests with these labels will never be considered stale. Set to `[]` to disable -exemptLabels: - - keepalive - -# Set to true to ignore issues in a project (defaults to false) -exemptProjects: false - -# Set to true to ignore issues in a milestone (defaults to false) -exemptMilestones: false - -# Set to true to ignore issues with an assignee (defaults to false) -exemptAssignees: false - -# Label to use when marking as stale -staleLabel: stale - -# Comment to post when marking as stale. Set to `false` to disable -markComment: false - -# Comment to post when removing the stale label. -# unmarkComment: > -# Your comment here. - -# Comment to post when closing a stale Issue or Pull Request. -# closeComment: > -# Your comment here. - -# Limit the number of actions per hour, from 1-30. Default is 30 -limitPerRun: 30 - -# Limit to only `issues` or `pulls` -only: pulls - -# Optionally, specify configuration settings that are specific to just 'issues' or 'pulls': -# pulls: -# daysUntilStale: 30 -# markComment: > -# This pull request has been automatically marked as stale because it has not had -# recent activity. It will be closed if no further activity occurs. Thank you -# for your contributions. - -# issues: -# exemptLabels: -# - confirmed diff --git a/.github/workflows/buf-lint.yml b/.github/workflows/buf-lint.yml index acf91ea12ab..3f6cf76e16f 100644 --- a/.github/workflows/buf-lint.yml +++ b/.github/workflows/buf-lint.yml @@ -13,7 +13,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 - - uses: bufbuild/buf-setup-action@dde0b9351db90fbf78e345f41a57de8514bf1091 # v1.32.2 + - uses: bufbuild/buf-setup-action@54abbed4fe8d8d45173eca4798b0c39a53a7b658 # v1.39.0 with: github_token: ${{ secrets.GITHUB_TOKEN }} - uses: bufbuild/buf-lint-action@06f9dd823d873146471cfaaf108a993fe00e5325 # v1.1.1 diff --git a/.github/workflows/buf.yml b/.github/workflows/buf.yml index f52d20785fd..632d38cb009 100644 --- a/.github/workflows/buf.yml +++ b/.github/workflows/buf.yml @@ -13,7 +13,7 @@ jobs: if: github.repository_owner == 'prometheus' steps: - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 - - uses: bufbuild/buf-setup-action@dde0b9351db90fbf78e345f41a57de8514bf1091 # v1.32.2 + - uses: bufbuild/buf-setup-action@54abbed4fe8d8d45173eca4798b0c39a53a7b658 # v1.39.0 with: github_token: ${{ secrets.GITHUB_TOKEN }} - uses: bufbuild/buf-lint-action@06f9dd823d873146471cfaaf108a993fe00e5325 # v1.1.1 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8b3624383cb..98d3d9a754f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -11,10 +11,10 @@ jobs: container: # Whenever the Go version is updated here, .promu.yml # should also be updated. - image: quay.io/prometheus/golang-builder:1.22-base + image: quay.io/prometheus/golang-builder:1.23-base steps: - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 - - uses: prometheus/promci@3cb0c3871f223bd5ce1226995bd52ffb314798b6 # v0.1.0 + - uses: prometheus/promci@45166329da36d74895901808f1c8c97efafc7f84 # v0.3.0 - uses: ./.github/promci/actions/setup_environment - run: make GOOPTS=--tags=stringlabels GO_ONLY=1 SKIP_GOLANGCI_LINT=1 - run: go test --tags=stringlabels ./tsdb/ -test.tsdb-isolation=false @@ -25,10 +25,10 @@ jobs: name: More Go tests runs-on: ubuntu-latest container: - image: quay.io/prometheus/golang-builder:1.22-base + image: quay.io/prometheus/golang-builder:1.23-base steps: - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 - - uses: prometheus/promci@3cb0c3871f223bd5ce1226995bd52ffb314798b6 # v0.1.0 + - uses: prometheus/promci@45166329da36d74895901808f1c8c97efafc7f84 # v0.3.0 - uses: ./.github/promci/actions/setup_environment - run: go test --tags=dedupelabels ./... - run: GOARCH=386 go test ./cmd/prometheus @@ -39,9 +39,12 @@ jobs: test_go_oldest: name: Go tests with previous Go version runs-on: ubuntu-latest + env: + # Enforce the Go version. + GOTOOLCHAIN: local container: # The go version in this image should be N-1 wrt test_go. - image: quay.io/prometheus/golang-builder:1.21-base + image: quay.io/prometheus/golang-builder:1.22-base steps: - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 - run: make build @@ -54,11 +57,11 @@ jobs: # Whenever the Go version is updated here, .promu.yml # should also be updated. container: - image: quay.io/prometheus/golang-builder:1.22-base + image: quay.io/prometheus/golang-builder:1.23-base steps: - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 - - uses: prometheus/promci@3cb0c3871f223bd5ce1226995bd52ffb314798b6 # v0.1.0 + - uses: prometheus/promci@45166329da36d74895901808f1c8c97efafc7f84 # v0.3.0 - uses: ./.github/promci/actions/setup_environment with: enable_go: false @@ -75,9 +78,9 @@ jobs: runs-on: windows-latest steps: - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 - - uses: actions/setup-go@cdcb36043654635271a94b9a6d1392de5bb323a7 # v5.0.1 + - uses: actions/setup-go@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32 # v5.0.2 with: - go-version: 1.22.x + go-version: 1.23.x - run: | $TestTargets = go list ./... | Where-Object { $_ -NotMatch "(github.com/prometheus/prometheus/discovery.*|github.com/prometheus/prometheus/config|github.com/prometheus/prometheus/web)"} go test $TestTargets -vet=off -v @@ -89,7 +92,7 @@ jobs: # Whenever the Go version is updated here, .promu.yml # should also be updated. container: - image: quay.io/prometheus/golang-builder:1.22-base + image: quay.io/prometheus/golang-builder:1.23-base steps: - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 - run: go install ./cmd/promtool/. @@ -115,7 +118,7 @@ jobs: thread: [ 0, 1, 2 ] steps: - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 - - uses: prometheus/promci@3cb0c3871f223bd5ce1226995bd52ffb314798b6 # v0.1.0 + - uses: prometheus/promci@45166329da36d74895901808f1c8c97efafc7f84 # v0.3.0 - uses: ./.github/promci/actions/build with: promu_opts: "-p linux/amd64 -p windows/amd64 -p linux/arm64 -p darwin/amd64 -p darwin/arm64 -p linux/386" @@ -138,11 +141,23 @@ jobs: # should also be updated. steps: - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 - - uses: prometheus/promci@3cb0c3871f223bd5ce1226995bd52ffb314798b6 # v0.1.0 + - uses: prometheus/promci@45166329da36d74895901808f1c8c97efafc7f84 # v0.3.0 - uses: ./.github/promci/actions/build with: parallelism: 12 thread: ${{ matrix.thread }} + build_all_status: + name: Report status of build Prometheus for all architectures + runs-on: ubuntu-latest + needs: [build_all] + if: github.event_name == 'pull_request' && startsWith(github.event.pull_request.base.ref, 'release-') + steps: + - name: Successful build + if: ${{ !(contains(needs.*.result, 'failure')) && !(contains(needs.*.result, 'cancelled')) }} + run: exit 0 + - name: Failing or cancelled build + if: ${{ contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled') }} + run: exit 1 check_generated_parser: name: Check generated parser runs-on: ubuntu-latest @@ -150,10 +165,10 @@ jobs: - name: Checkout repository uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 - name: Install Go - uses: actions/setup-go@cdcb36043654635271a94b9a6d1392de5bb323a7 # v5.0.1 + uses: actions/setup-go@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32 # v5.0.2 with: cache: false - go-version: 1.22.x + go-version: 1.23.x - name: Run goyacc and check for diff run: make install-goyacc check-generated-parser golangci: @@ -163,18 +178,18 @@ jobs: - name: Checkout repository uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 - name: Install Go - uses: actions/setup-go@cdcb36043654635271a94b9a6d1392de5bb323a7 # v5.0.1 + uses: actions/setup-go@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32 # v5.0.2 with: - go-version: 1.22.x + go-version: 1.23.x - name: Install snmp_exporter/generator dependencies run: sudo apt-get update && sudo apt-get -y install libsnmp-dev if: github.repository == 'prometheus/snmp_exporter' - name: Lint - uses: golangci/golangci-lint-action@a4f60bb28d35aeee14e6880718e0c85ff1882e64 # v6.0.1 + uses: golangci/golangci-lint-action@aaa42aa0628b4ae2578232a66b541047968fac86 # v6.1.0 with: args: --verbose # Make sure to sync this with Makefile.common and scripts/golangci-lint.yml. - version: v1.59.1 + version: v1.60.2 fuzzing: uses: ./.github/workflows/fuzzing.yml if: github.event_name == 'pull_request' @@ -188,7 +203,7 @@ jobs: if: github.event_name == 'push' && github.event.ref == 'refs/heads/main' steps: - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 - - uses: prometheus/promci@3cb0c3871f223bd5ce1226995bd52ffb314798b6 # v0.1.0 + - uses: prometheus/promci@45166329da36d74895901808f1c8c97efafc7f84 # v0.3.0 - uses: ./.github/promci/actions/publish_main with: docker_hub_login: ${{ secrets.docker_hub_login }} @@ -202,7 +217,7 @@ jobs: if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v2.') steps: - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 - - uses: prometheus/promci@3cb0c3871f223bd5ce1226995bd52ffb314798b6 # v0.1.0 + - uses: prometheus/promci@45166329da36d74895901808f1c8c97efafc7f84 # v0.3.0 - uses: ./.github/promci/actions/publish_release with: docker_hub_login: ${{ secrets.docker_hub_login }} @@ -217,9 +232,9 @@ jobs: steps: - name: Checkout uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 - - uses: prometheus/promci@3cb0c3871f223bd5ce1226995bd52ffb314798b6 # v0.1.0 + - uses: prometheus/promci@45166329da36d74895901808f1c8c97efafc7f84 # v0.3.0 - name: Install nodejs - uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 + uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3 with: node-version-file: "web/ui/.nvmrc" registry-url: "https://registry.npmjs.org" diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 1ea1f5efae6..89aa2ba29b1 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -27,12 +27,12 @@ jobs: uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 - name: Initialize CodeQL - uses: github/codeql-action/init@2e230e8fe0ad3a14a340ad0815ddb96d599d2aff # v3.25.8 + uses: github/codeql-action/init@4dd16135b69a43b6c8efb853346f8437d92d3c93 # v3.26.6 with: languages: ${{ matrix.language }} - name: Autobuild - uses: github/codeql-action/autobuild@2e230e8fe0ad3a14a340ad0815ddb96d599d2aff # v3.25.8 + uses: github/codeql-action/autobuild@4dd16135b69a43b6c8efb853346f8437d92d3c93 # v3.26.6 - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@2e230e8fe0ad3a14a340ad0815ddb96d599d2aff # v3.25.8 + uses: github/codeql-action/analyze@4dd16135b69a43b6c8efb853346f8437d92d3c93 # v3.26.6 diff --git a/.github/workflows/fuzzing.yml b/.github/workflows/fuzzing.yml index dc510e5966c..f3953cb2a48 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@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3 + uses: actions/upload-artifact@0b2256b8c012f0828dc542b3febcab082c67f72b # v4.3.4 if: failure() && steps.build.outcome == 'success' with: name: artifacts diff --git a/.github/workflows/scorecards.yml b/.github/workflows/scorecards.yml index 078084888ab..82cccb2bc13 100644 --- a/.github/workflows/scorecards.yml +++ b/.github/workflows/scorecards.yml @@ -26,7 +26,7 @@ jobs: persist-credentials: false - name: "Run analysis" - uses: ossf/scorecard-action@dc50aa9510b46c811795eb24b2f1ba02a914e534 # tag=v2.3.3 + uses: ossf/scorecard-action@62b2cac7ed8198b15735ed49ab1e5cf35480ba46 # tag=v2.4.0 with: results_file: results.sarif results_format: sarif @@ -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@65462800fd760344b1a7b4382951275a0abb4808 # tag=v4.3.3 + uses: actions/upload-artifact@0b2256b8c012f0828dc542b3febcab082c67f72b # tag=v4.3.4 with: name: SARIF file path: results.sarif @@ -45,6 +45,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard. - name: "Upload to code-scanning" - uses: github/codeql-action/upload-sarif@2e230e8fe0ad3a14a340ad0815ddb96d599d2aff # tag=v3.25.8 + uses: github/codeql-action/upload-sarif@4dd16135b69a43b6c8efb853346f8437d92d3c93 # tag=v3.26.6 with: sarif_file: results.sarif diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml new file mode 100644 index 00000000000..d71bcbc9d83 --- /dev/null +++ b/.github/workflows/stale.yml @@ -0,0 +1,31 @@ +name: Stale Check +on: + workflow_dispatch: {} + schedule: + - cron: '16 22 * * *' +permissions: + issues: write + pull-requests: write +jobs: + stale: + if: github.repository_owner == 'prometheus' || github.repository_owner == 'prometheus-community' # Don't run this workflow on forks. + runs-on: ubuntu-latest + steps: + - uses: actions/stale@28ca1036281a5e5922ead5184a1bbf96e5fc984e # v9.0.0 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} + # opt out of defaults to avoid marking issues as stale and closing them + # https://github.com/actions/stale#days-before-close + # https://github.com/actions/stale#days-before-stale + days-before-stale: -1 + days-before-close: -1 + # Setting it to empty string to skip comments. + # https://github.com/actions/stale#stale-pr-message + # https://github.com/actions/stale#stale-issue-message + stale-pr-message: '' + stale-issue-message: '' + operations-per-run: 30 + # override days-before-stale, for only marking the pull requests as stale + days-before-pr-stale: 60 + stale-pr-label: stale + exempt-pr-labels: keepalive diff --git a/.golangci.yml b/.golangci.yml index 026d68a3132..303cd33d8b0 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -1,12 +1,5 @@ run: timeout: 15m - skip-files: - # Skip autogenerated files. - - ^.*\.(pb|y)\.go$ - skip-dirs: - # Copied it from a different source - - storage/remote/otlptranslator/prometheusremotewrite - - storage/remote/otlptranslator/prometheus output: sort-results: true @@ -32,8 +25,34 @@ linters: - loggercheck issues: + max-issues-per-linter: 0 max-same-issues: 0 + # The default exclusions are too aggressive. For one, they + # essentially disable any linting on doc comments. We disable + # default exclusions here and add exclusions fitting our codebase + # further down. + exclude-use-default: false + exclude-files: + # Skip autogenerated files. + - ^.*\.(pb|y)\.go$ + exclude-dirs: + # Copied it from a different source. + - storage/remote/otlptranslator/prometheusremotewrite + - storage/remote/otlptranslator/prometheus exclude-rules: + - linters: + - errcheck + # Taken from the default exclusions (that are otherwise disabled above). + text: Error return value of .((os\.)?std(out|err)\..*|.*Close|.*Flush|os\.Remove(All)?|.*print(f|ln)?|os\.(Un)?Setenv). is not checked + - linters: + - govet + # We use many Seek methods that do not follow the usual pattern. + text: "stdmethods: method Seek.* should have signature Seek" + - linters: + - revive + # We have stopped at some point to write doc comments on exported symbols. + # TODO(beorn7): Maybe we should enforce this again? There are ~500 offenders right now. + text: exported (.+) should have comment( \(or a comment on this block\))? or be unexported - linters: - gocritic text: "appendAssign" @@ -94,15 +113,14 @@ linters-settings: errorf: false revive: # By default, revive will enable only the linting rules that are named in the configuration file. - # So, it's needed to explicitly set in configuration all required rules. - # The following configuration enables all the rules from the defaults.toml - # https://github.com/mgechev/revive/blob/master/defaults.toml + # So, it's needed to explicitly enable all required rules here. rules: # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md - name: blank-imports + - name: comment-spacings - name: context-as-argument arguments: - # allow functions with test or bench signatures + # Allow functions with test or bench signatures. - allowTypesBefore: "*testing.T,testing.TB" - name: context-keys-type - name: dot-imports @@ -118,6 +136,8 @@ linters-settings: - name: increment-decrement - name: indent-error-flow - name: package-comments + # TODO(beorn7): Currently, we have a lot of missing package doc comments. Maybe we should have them. + disabled: true - name: range - name: receiver-naming - name: redefines-builtin-id diff --git a/.promu.yml b/.promu.yml index 0aa51d6d315..699a7fa5859 100644 --- a/.promu.yml +++ b/.promu.yml @@ -1,7 +1,7 @@ go: # Whenever the Go version is updated here, # .github/workflows should also be updated. - version: 1.22 + version: 1.23 repository: path: github.com/prometheus/prometheus build: diff --git a/CHANGELOG.md b/CHANGELOG.md index d5a91e90099..19af3f460fc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,95 @@ ## unreleased +## 2.55.1 / 2024-01-04 + +* [BUGFIX] `round()` function did not remove `__name__` label. #15250 + +## 2.55.0 / 2024-10-22 + +* [FEATURE] PromQL: Add experimental `info` function. #14495 +* [FEATURE] Support UTF-8 characters in label names - feature flag `utf8-names`. #14482, #14880, #14736, #14727 +* [FEATURE] Scraping: Add the ability to set custom `http_headers` in config. #14817 +* [FEATURE] Scraping: Support feature flag `created-timestamp-zero-ingestion` in OpenMetrics. #14356, #14815 +* [FEATURE] Scraping: `scrape_failure_log_file` option to log failures to a file. #14734 +* [FEATURE] OTLP receiver: Optional promotion of resource attributes to series labels. #14200 +* [FEATURE] Remote-Write: Support Google Cloud Monitoring authorization. #14346 +* [FEATURE] Promtool: `tsdb create-blocks` new option to add labels. #14403 +* [FEATURE] Promtool: `promtool test` adds `--junit` flag to format results. #14506 +* [FEATURE] TSDB: Add `delayed-compaction` feature flag, for people running many Prometheus to randomize timing. #12532 +* [ENHANCEMENT] OTLP receiver: Warn on exponential histograms with zero count and non-zero sum. #14706 +* [ENHANCEMENT] OTLP receiver: Interrupt translation on context cancellation/timeout. #14612 +* [ENHANCEMENT] Remote Read client: Enable streaming remote read if the server supports it. #11379 +* [ENHANCEMENT] Remote-Write: Don't reshard if we haven't successfully sent a sample since last update. #14450 +* [ENHANCEMENT] PromQL: Delay deletion of `__name__` label to the end of the query evaluation. This is **experimental** and enabled under the feature-flag `promql-delayed-name-removal`. #14477 +* [ENHANCEMENT] PromQL: Experimental `sort_by_label` and `sort_by_label_desc` sort by all labels when label is equal. #14655, #14985 +* [ENHANCEMENT] PromQL: Clarify error message logged when Go runtime panic occurs during query evaluation. #14621 +* [ENHANCEMENT] PromQL: Use Kahan summation for better accuracy in `avg` and `avg_over_time`. #14413 +* [ENHANCEMENT] Tracing: Improve PromQL tracing, including showing the operation performed for aggregates, operators, and calls. #14816 +* [ENHANCEMENT] API: Support multiple listening addresses. #14665 +* [ENHANCEMENT] TSDB: Backward compatibility with upcoming index v3. #14934 +* [PERF] TSDB: Query in-order and out-of-order series together. #14354, #14693, #14714, #14831, #14874, #14948, #15120 +* [PERF] TSDB: Streamline reading of overlapping out-of-order head chunks. #14729 +* [BUGFIX] PromQL: make sort_by_label stable. #14985 +* [BUGFIX] SD: Fix dropping targets (with feature flag `new-service-discovery-manager`). #13147 +* [BUGFIX] SD: Stop storing stale targets (with feature flag `new-service-discovery-manager`). #13622 +* [BUGFIX] Scraping: exemplars could be dropped in protobuf scraping. #14810 +* [BUGFIX] Remote-Write: fix metadata sending for experimental Remote-Write V2. #14766 +* [BUGFIX] Remote-Write: Return 4xx not 5xx when timeseries has duplicate label. #14716 +* [BUGFIX] Experimental Native Histograms: many fixes for incorrect results, panics, warnings. #14513, #14575, #14598, #14609, #14611, #14771, #14821 +* [BUGFIX] TSDB: Only count unknown record types in `record_decode_failures_total` metric. #14042 + +## 2.54.1 / 2024-08-27 + +* [BUGFIX] Scraping: allow multiple samples on same series, with explicit timestamps. #14685 +* [BUGFIX] Docker SD: fix crash in `match_first_network` mode when container is reconnected to a new network. #14654 +* [BUGFIX] PromQL: fix experimental native histograms getting corrupted due to vector selector bug in range queries. #14538 +* [BUGFIX] PromQL: fix experimental native histogram counter reset detection on stale samples. #14514 +* [BUGFIX] PromQL: fix native histograms getting corrupted due to vector selector bug in range queries. #14605 + +## 2.54.0 / 2024-08-09 + +Release 2.54 brings a release candidate of a major new version of [Remote Write: 2.0](https://prometheus.io/docs/specs/remote_write_spec_2_0/). +This is experimental at this time and may still change. +Remote-write v2 is enabled by default, but can be disabled via feature-flag `web.remote-write-receiver.accepted-protobuf-messages`. + +* [CHANGE] Remote-Write: `highest_timestamp_in_seconds` and `queue_highest_sent_timestamp_seconds` metrics now initialized to 0. #14437 +* [CHANGE] API: Split warnings from info annotations in API response. #14327 +* [FEATURE] Remote-Write: Version 2.0 experimental, plus metadata in WAL via feature flag `metadata-wal-records` (defaults on). #14395,#14427,#14444 +* [FEATURE] PromQL: add limitk() and limit_ratio() aggregation operators. #12503 +* [ENHANCEMENT] PromQL: Accept underscores in literal numbers, e.g. 1_000_000 for 1 million. #12821 +* [ENHANCEMENT] PromQL: float literal numbers and durations are now interchangeable (experimental). Example: `time() - my_timestamp > 10m`. #9138 +* [ENHANCEMENT] PromQL: use Kahan summation for sum(). #14074,#14362 +* [ENHANCEMENT] PromQL (experimental native histograms): Optimize `histogram_count` and `histogram_sum` functions. #14097 +* [ENHANCEMENT] TSDB: Better support for out-of-order experimental native histogram samples. #14438 +* [ENHANCEMENT] TSDB: Optimise seek within index. #14393 +* [ENHANCEMENT] TSDB: Optimise deletion of stale series. #14307 +* [ENHANCEMENT] TSDB: Reduce locking to optimise adding and removing series. #13286,#14286 +* [ENHANCEMENT] TSDB: Small optimisation: streamline special handling for out-of-order data. #14396,#14584 +* [ENHANCEMENT] Regexps: Optimize patterns with multiple prefixes. #13843,#14368 +* [ENHANCEMENT] Regexps: Optimize patterns containing multiple literal strings. #14173 +* [ENHANCEMENT] AWS SD: expose Primary IPv6 addresses as __meta_ec2_primary_ipv6_addresses. #14156 +* [ENHANCEMENT] Docker SD: add MatchFirstNetwork for containers with multiple networks. #10490 +* [ENHANCEMENT] OpenStack SD: Use `flavor.original_name` if available. #14312 +* [ENHANCEMENT] UI (experimental native histograms): more accurate representation. #13680,#14430 +* [ENHANCEMENT] Agent: `out_of_order_time_window` config option now applies to agent. #14094 +* [ENHANCEMENT] Notifier: Send any outstanding Alertmanager notifications when shutting down. #14290 +* [ENHANCEMENT] Rules: Add label-matcher support to Rules API. #10194 +* [ENHANCEMENT] HTTP API: Add url to message logged on error while sending response. #14209 +* [BUGFIX] TSDB: Exclude OOO chunks mapped after compaction starts (introduced by #14396). #14584 +* [BUGFIX] CLI: escape `|` characters when generating docs. #14420 +* [BUGFIX] PromQL (experimental native histograms): Fix some binary operators between native histogram values. #14454 +* [BUGFIX] TSDB: LabelNames API could fail during compaction. #14279 +* [BUGFIX] TSDB: Fix rare issue where pending OOO read can be left dangling if creating querier fails. #14341 +* [BUGFIX] TSDB: fix check for context cancellation in LabelNamesFor. #14302 +* [BUGFIX] Rules: Fix rare panic on reload. #14366 +* [BUGFIX] Config: In YAML marshalling, do not output a regexp field if it was never set. #14004 +* [BUGFIX] Remote-Write: reject samples with future timestamps. #14304 +* [BUGFIX] Remote-Write: Fix data corruption in remote write if max_sample_age is applied. #14078 +* [BUGFIX] Notifier: Fix Alertmanager discovery not updating under heavy load. #14174 +* [BUGFIX] Regexes: some Unicode characters were not matched by case-insensitive comparison. #14170,#14299 +* [BUGFIX] Remote-Read: Resolve occasional segmentation fault on query. #14515 + ## 2.53.1 / 2024-07-10 Fix a bug which would drop samples in remote-write if the sending flow stalled diff --git a/Makefile.common b/Makefile.common index e3da72ab476..34d65bb56de 100644 --- a/Makefile.common +++ b/Makefile.common @@ -61,7 +61,7 @@ PROMU_URL := https://github.com/prometheus/promu/releases/download/v$(PROMU_ SKIP_GOLANGCI_LINT := GOLANGCI_LINT := GOLANGCI_LINT_OPTS ?= -GOLANGCI_LINT_VERSION ?= v1.59.1 +GOLANGCI_LINT_VERSION ?= v1.60.2 # 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)) diff --git a/README.md b/README.md index cd14ed2ecbb..df974e1097b 100644 --- a/README.md +++ b/README.md @@ -12,9 +12,10 @@ examples and guides.
[][hub] [](https://goreportcard.com/report/github.com/prometheus/prometheus) [](https://bestpractices.coreinfrastructure.org/projects/486) +[](https://securityscorecards.dev/viewer/?uri=github.com/prometheus/prometheus) +[](https://clomonitor.io/projects/cncf/prometheus) [](https://gitpod.io/#https://github.com/prometheus/prometheus) [](https://bugs.chromium.org/p/oss-fuzz/issues/list?sort=-opened&can=1&q=proj:prometheus) -[](https://securityscorecards.dev/viewer/?uri=github.com/prometheus/prometheus) diff --git a/RELEASE.md b/RELEASE.md index 0d3f7456cd4..b978a3c2261 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -59,6 +59,7 @@ Release cadence of first pre-releases being cut is 6 weeks. | v2.52 | 2024-04-22 | Arthur Silva Sens (GitHub: @ArthurSens) | | v2.53 LTS | 2024-06-03 | George Krajcsovits (GitHub: @krajorama) | | v2.54 | 2024-07-17 | Bryan Boreham (GitHub: @bboreham) | +| v2.55 | 2024-09-17 | Bryan Boreham (GitHub: @bboreham) | If you are interested in volunteering please create a pull request against the [prometheus/prometheus](https://github.com/prometheus/prometheus) repository and propose yourself for the release series of your choice. diff --git a/SECURITY-INSIGHTS.yml b/SECURITY-INSIGHTS.yml new file mode 100644 index 00000000000..009b3562148 --- /dev/null +++ b/SECURITY-INSIGHTS.yml @@ -0,0 +1,48 @@ +header: + schema-version: '1.0.0' + expiration-date: '2025-07-30T01:00:00.000Z' + last-updated: '2024-07-30' + last-reviewed: '2024-07-30' + project-url: https://github.com/prometheus/prometheus + changelog: https://github.com/prometheus/prometheus/blob/main/CHANGELOG.md + license: https://github.com/prometheus/prometheus/blob/main/LICENSE +project-lifecycle: + status: active + bug-fixes-only: false + core-maintainers: + - https://github.com/prometheus/prometheus/blob/main/MAINTAINERS.md +contribution-policy: + accepts-pull-requests: true + accepts-automated-pull-requests: true +dependencies: + third-party-packages: true + dependencies-lists: + - https://github.com/prometheus/prometheus/blob/main/go.mod + - https://github.com/prometheus/prometheus/blob/main/web/ui/package.json + env-dependencies-policy: + policy-url: https://github.com/prometheus/prometheus/blob/main/CONTRIBUTING.md#dependency-management +distribution-points: + - https://github.com/prometheus/prometheus/releases +documentation: + - https://prometheus.io/docs/introduction/overview/ +security-contacts: + - type: email + value: prometheus-team@googlegroups.com +security-testing: + - tool-type: sca + tool-name: Dependabot + tool-version: latest + integration: + ad-hoc: false + ci: true + before-release: true + - tool-type: sast + tool-name: CodeQL + tool-version: latest + integration: + ad-hoc: false + ci: true + before-release: true +vulnerability-reporting: + accepts-vulnerability-reports: true + security-policy: https://github.com/prometheus/prometheus/security/policy diff --git a/VERSION b/VERSION index f419e2c6f15..0a756ea0a78 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -2.53.1 +2.55.1 diff --git a/cmd/prometheus/main.go b/cmd/prometheus/main.go index 1d844ddba68..b3bcb78b78b 100644 --- a/cmd/prometheus/main.go +++ b/cmd/prometheus/main.go @@ -152,6 +152,7 @@ type flagConfig struct { queryConcurrency int queryMaxSamples int RemoteFlushDeadline model.Duration + nameEscapingScheme string featureList []string memlimitRatio float64 @@ -168,6 +169,8 @@ type flagConfig struct { corsRegexString string promlogConfig promlog.Config + + promqlEnableDelayedNameRemoval bool } // setFeatureListOptions sets the corresponding options from the featureList. @@ -234,6 +237,15 @@ func (c *flagConfig) setFeatureListOptions(logger log.Logger) error { config.DefaultConfig.GlobalConfig.ScrapeProtocols = config.DefaultProtoFirstScrapeProtocols config.DefaultGlobalConfig.ScrapeProtocols = config.DefaultProtoFirstScrapeProtocols level.Info(logger).Log("msg", "Experimental created timestamp zero ingestion enabled. Changed default scrape_protocols to prefer PrometheusProto format.", "global.scrape_protocols", fmt.Sprintf("%v", config.DefaultGlobalConfig.ScrapeProtocols)) + case "delayed-compaction": + c.tsdb.EnableDelayedCompaction = true + level.Info(logger).Log("msg", "Experimental delayed compaction is enabled.") + case "promql-delayed-name-removal": + c.promqlEnableDelayedNameRemoval = true + level.Info(logger).Log("msg", "Experimental PromQL delayed name removal enabled.") + case "utf8-names": + model.NameValidationScheme = model.UTF8Validation + level.Info(logger).Log("msg", "Experimental UTF-8 support enabled") case "": continue case "promql-at-modifier", "promql-negative-offset": @@ -290,8 +302,8 @@ func main() { a.Flag("config.file", "Prometheus configuration file path."). Default("prometheus.yml").StringVar(&cfg.configFile) - a.Flag("web.listen-address", "Address to listen on for UI, API, and telemetry."). - Default("0.0.0.0:9090").StringVar(&cfg.web.ListenAddress) + a.Flag("web.listen-address", "Address to listen on for UI, API, and telemetry. Can be repeated."). + Default("0.0.0.0:9090").StringsVar(&cfg.web.ListenAddresses) a.Flag("auto-gomemlimit.ratio", "The ratio of reserved GOMEMLIMIT memory to the detected maximum container or system memory"). Default("0.9").FloatVar(&cfg.memlimitRatio) @@ -305,7 +317,7 @@ func main() { "Maximum duration before timing out read of the request, and closing idle connections."). Default("5m").SetValue(&cfg.webTimeout) - a.Flag("web.max-connections", "Maximum number of simultaneous connections."). + a.Flag("web.max-connections", "Maximum number of simultaneous connections across all listeners."). Default("512").IntVar(&cfg.web.MaxConnections) a.Flag("web.external-url", @@ -381,6 +393,9 @@ func main() { serverOnlyFlag(a, "storage.tsdb.allow-overlapping-blocks", "[DEPRECATED] This flag has no effect. Overlapping blocks are enabled by default now."). Default("true").Hidden().BoolVar(&b) + serverOnlyFlag(a, "storage.tsdb.allow-overlapping-compaction", "Allow compaction of overlapping blocks. If set to false, TSDB stops vertical compaction and leaves overlapping blocks there. The use case is to let another component handle the compaction of overlapping blocks."). + Default("true").Hidden().BoolVar(&cfg.tsdb.EnableOverlappingCompaction) + serverOnlyFlag(a, "storage.tsdb.wal-compression", "Compress the tsdb WAL."). Hidden().Default("true").BoolVar(&cfg.tsdb.WALCompression) @@ -475,7 +490,9 @@ func main() { a.Flag("scrape.discovery-reload-interval", "Interval used by scrape manager to throttle target groups updates."). Hidden().Default("5s").SetValue(&cfg.scrape.DiscoveryReloadInterval) - a.Flag("enable-feature", "Comma separated feature names to enable. Valid options: agent, auto-gomemlimit, exemplar-storage, expand-external-labels, memory-snapshot-on-shutdown, promql-per-step-stats, promql-experimental-functions, remote-write-receiver (DEPRECATED), extra-scrape-metrics, new-service-discovery-manager, auto-gomaxprocs, no-default-scrape-port, native-histograms, otlp-write-receiver, created-timestamp-zero-ingestion, concurrent-rule-eval. See https://prometheus.io/docs/prometheus/latest/feature_flags/ for more details."). + a.Flag("scrape.name-escaping-scheme", `Method for escaping legacy invalid names when sending to Prometheus that does not support UTF-8. Can be one of "values", "underscores", or "dots".`).Default(scrape.DefaultNameEscapingScheme.String()).StringVar(&cfg.nameEscapingScheme) + + a.Flag("enable-feature", "Comma separated feature names to enable. Valid options: agent, auto-gomaxprocs, auto-gomemlimit, concurrent-rule-eval, created-timestamp-zero-ingestion, delayed-compaction, exemplar-storage, expand-external-labels, extra-scrape-metrics, memory-snapshot-on-shutdown, native-histograms, new-service-discovery-manager, no-default-scrape-port, otlp-write-receiver, promql-experimental-functions, promql-delayed-name-removal, promql-per-step-stats, remote-write-receiver (DEPRECATED), utf8-names. See https://prometheus.io/docs/prometheus/latest/feature_flags/ for more details."). Default("").StringsVar(&cfg.featureList) promlogflag.AddFlags(a, &cfg.promlogConfig) @@ -503,6 +520,15 @@ func main() { os.Exit(1) } + if cfg.nameEscapingScheme != "" { + scheme, err := model.ToEscapingScheme(cfg.nameEscapingScheme) + if err != nil { + fmt.Fprintf(os.Stderr, `Invalid name escaping scheme: %q; Needs to be one of "values", "underscores", or "dots"`, cfg.nameEscapingScheme) + os.Exit(1) + } + model.NameEscapingScheme = scheme + } + if agentMode && len(serverOnlyFlags) > 0 { fmt.Fprintf(os.Stderr, "The following flag(s) can not be used in agent mode: %q", serverOnlyFlags) os.Exit(3) @@ -523,7 +549,7 @@ func main() { localStoragePath = cfg.agentStoragePath } - cfg.web.ExternalURL, err = computeExternalURL(cfg.prometheusURL, cfg.web.ListenAddress) + cfg.web.ExternalURL, err = computeExternalURL(cfg.prometheusURL, cfg.web.ListenAddresses[0]) if err != nil { fmt.Fprintln(os.Stderr, fmt.Errorf("parse external URL %q: %w", cfg.prometheusURL, err)) os.Exit(2) @@ -729,6 +755,7 @@ func main() { scrapeManager, err := scrape.NewManager( &cfg.scrape, log.With(logger, "component", "scrape manager"), + func(s string) (log.Logger, error) { return logging.NewJSONFileLogger(s) }, fanoutStorage, prometheus.DefaultRegisterer, ) @@ -778,9 +805,10 @@ func main() { NoStepSubqueryIntervalFn: noStepSubqueryInterval.Get, // EnableAtModifier and EnableNegativeOffset have to be // always on for regular PromQL as of Prometheus v2.33. - EnableAtModifier: true, - EnableNegativeOffset: true, - EnablePerStepStats: cfg.enablePerStepStats, + EnableAtModifier: true, + EnableNegativeOffset: true, + EnablePerStepStats: cfg.enablePerStepStats, + EnableDelayedNameRemoval: cfg.promqlEnableDelayedNameRemoval, } queryEngine = promql.NewEngine(opts) @@ -969,15 +997,21 @@ func main() { }) } - listener, err := webHandler.Listener() + listeners, err := webHandler.Listeners() if err != nil { - level.Error(logger).Log("msg", "Unable to start web listener", "err", err) + level.Error(logger).Log("msg", "Unable to start web listeners", "err", err) + if err := queryEngine.Close(); err != nil { + level.Warn(logger).Log("msg", "Closing query engine failed", "err", err) + } os.Exit(1) } err = toolkit_web.Validate(*webConfig) if err != nil { level.Error(logger).Log("msg", "Unable to validate web configuration file", "err", err) + if err := queryEngine.Close(); err != nil { + level.Warn(logger).Log("msg", "Closing query engine failed", "err", err) + } os.Exit(1) } @@ -999,6 +1033,9 @@ func main() { case <-cancel: reloadReady.Close() } + if err := queryEngine.Close(); err != nil { + level.Warn(logger).Log("msg", "Closing query engine failed", "err", err) + } return nil }, func(err error) { @@ -1266,7 +1303,7 @@ func main() { // Web handler. g.Add( func() error { - if err := webHandler.Run(ctxWeb, listener, *webConfig); err != nil { + if err := webHandler.Run(ctxWeb, listeners, *webConfig); err != nil { return fmt.Errorf("error starting web server: %w", err) } return nil @@ -1715,6 +1752,8 @@ type tsdbOptions struct { MaxExemplars int64 EnableMemorySnapshotOnShutdown bool EnableNativeHistograms bool + EnableDelayedCompaction bool + EnableOverlappingCompaction bool } func (opts tsdbOptions) ToTSDBOptions() tsdb.Options { @@ -1735,7 +1774,8 @@ func (opts tsdbOptions) ToTSDBOptions() tsdb.Options { EnableMemorySnapshotOnShutdown: opts.EnableMemorySnapshotOnShutdown, EnableNativeHistograms: opts.EnableNativeHistograms, OutOfOrderTimeWindow: opts.OutOfOrderTimeWindow, - EnableOverlappingCompaction: true, + EnableDelayedCompaction: opts.EnableDelayedCompaction, + EnableOverlappingCompaction: opts.EnableOverlappingCompaction, } } diff --git a/cmd/prometheus/scrape_failure_log_test.go b/cmd/prometheus/scrape_failure_log_test.go new file mode 100644 index 00000000000..8d86d719f98 --- /dev/null +++ b/cmd/prometheus/scrape_failure_log_test.go @@ -0,0 +1,193 @@ +// Copyright 2024 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 main + +import ( + "bytes" + "fmt" + "net/http" + "net/http/httptest" + "net/url" + "os" + "os/exec" + "path/filepath" + "testing" + "time" + + "github.com/stretchr/testify/require" + "go.uber.org/atomic" + + "github.com/prometheus/prometheus/util/testutil" +) + +func TestScrapeFailureLogFile(t *testing.T) { + if testing.Short() { + t.Skip("skipping test in short mode.") + } + + // Tracks the number of requests made to the mock server. + var requestCount atomic.Int32 + + // Starts a server that always returns HTTP 500 errors. + mockServerAddress := startGarbageServer(t, &requestCount) + + // Create a temporary directory for Prometheus configuration and logs. + tempDir := t.TempDir() + + // Define file paths for the scrape failure log and Prometheus configuration. + // Like other files, the scrape failure log file should be relative to the + // config file. Therefore, we split the name we put in the file and the full + // path used to check the content of the file. + scrapeFailureLogFileName := "scrape_failure.log" + scrapeFailureLogFile := filepath.Join(tempDir, scrapeFailureLogFileName) + promConfigFile := filepath.Join(tempDir, "prometheus.yml") + + // Step 1: Set up an initial Prometheus configuration that globally + // specifies a scrape failure log file. + promConfig := fmt.Sprintf(` +global: + scrape_interval: 500ms + scrape_failure_log_file: %s + +scrape_configs: + - job_name: 'test_job' + static_configs: + - targets: ['%s'] +`, scrapeFailureLogFileName, mockServerAddress) + + err := os.WriteFile(promConfigFile, []byte(promConfig), 0o644) + require.NoError(t, err, "Failed to write Prometheus configuration file") + + // Start Prometheus with the generated configuration and a random port, enabling the lifecycle API. + port := testutil.RandomUnprivilegedPort(t) + params := []string{ + "-test.main", + "--config.file=" + promConfigFile, + "--storage.tsdb.path=" + filepath.Join(tempDir, "data"), + fmt.Sprintf("--web.listen-address=127.0.0.1:%d", port), + "--web.enable-lifecycle", + } + prometheusProcess := exec.Command(promPath, params...) + prometheusProcess.Stdout = os.Stdout + prometheusProcess.Stderr = os.Stderr + + err = prometheusProcess.Start() + require.NoError(t, err, "Failed to start Prometheus") + defer prometheusProcess.Process.Kill() + + // Wait until the mock server receives at least two requests from Prometheus. + require.Eventually(t, func() bool { + return requestCount.Load() >= 2 + }, 30*time.Second, 500*time.Millisecond, "Expected at least two requests to the mock server") + + // Verify that the scrape failures have been logged to the specified file. + content, err := os.ReadFile(scrapeFailureLogFile) + require.NoError(t, err, "Failed to read scrape failure log") + require.Contains(t, string(content), "server returned HTTP status 500 Internal Server Error", "Expected scrape failure log entry not found") + + // Step 2: Update the Prometheus configuration to remove the scrape failure + // log file setting. + promConfig = fmt.Sprintf(` +global: + scrape_interval: 1s + +scrape_configs: + - job_name: 'test_job' + static_configs: + - targets: ['%s'] +`, mockServerAddress) + + err = os.WriteFile(promConfigFile, []byte(promConfig), 0o644) + require.NoError(t, err, "Failed to update Prometheus configuration file") + + // Reload Prometheus with the updated configuration. + reloadPrometheus(t, port) + + // Count the number of lines in the scrape failure log file before any + // further requests. + preReloadLogLineCount := countLinesInFile(scrapeFailureLogFile) + + // Wait for at least two more requests to the mock server to ensure + // Prometheus continues scraping. + requestsBeforeReload := requestCount.Load() + require.Eventually(t, func() bool { + return requestCount.Load() >= requestsBeforeReload+2 + }, 30*time.Second, 500*time.Millisecond, "Expected two more requests to the mock server after configuration reload") + + // Ensure that no new lines were added to the scrape failure log file after + // the configuration change. + require.Equal(t, preReloadLogLineCount, countLinesInFile(scrapeFailureLogFile), "No new lines should be added to the scrape failure log file after removing the log setting") + + // Step 3: Re-add the scrape failure log file setting, but this time under + // scrape_configs, and reload Prometheus. + promConfig = fmt.Sprintf(` +global: + scrape_interval: 1s + +scrape_configs: + - job_name: 'test_job' + scrape_failure_log_file: %s + static_configs: + - targets: ['%s'] +`, scrapeFailureLogFileName, mockServerAddress) + + err = os.WriteFile(promConfigFile, []byte(promConfig), 0o644) + require.NoError(t, err, "Failed to update Prometheus configuration file") + + // Reload Prometheus with the updated configuration. + reloadPrometheus(t, port) + + // Wait for at least two more requests to the mock server and verify that + // new log entries are created. + postReloadLogLineCount := countLinesInFile(scrapeFailureLogFile) + requestsBeforeReAddingLog := requestCount.Load() + require.Eventually(t, func() bool { + return requestCount.Load() >= requestsBeforeReAddingLog+2 + }, 30*time.Second, 500*time.Millisecond, "Expected two additional requests after re-adding the log setting") + + // Confirm that new lines were added to the scrape failure log file. + require.Greater(t, countLinesInFile(scrapeFailureLogFile), postReloadLogLineCount, "New lines should be added to the scrape failure log file after re-adding the log setting") +} + +// reloadPrometheus sends a reload request to the Prometheus server to apply +// updated configurations. +func reloadPrometheus(t *testing.T, port int) { + resp, err := http.Post(fmt.Sprintf("http://127.0.0.1:%d/-/reload", port), "", nil) + require.NoError(t, err, "Failed to reload Prometheus") + require.Equal(t, http.StatusOK, resp.StatusCode, "Unexpected status code when reloading Prometheus") +} + +// startGarbageServer sets up a mock server that returns a 500 Internal Server Error +// for all requests. It also increments the request count each time it's hit. +func startGarbageServer(t *testing.T, requestCount *atomic.Int32) string { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + requestCount.Inc() + w.WriteHeader(http.StatusInternalServerError) + })) + t.Cleanup(server.Close) + + parsedURL, err := url.Parse(server.URL) + require.NoError(t, err, "Failed to parse mock server URL") + + return parsedURL.Host +} + +// countLinesInFile counts and returns the number of lines in the specified file. +func countLinesInFile(filePath string) int { + data, err := os.ReadFile(filePath) + if err != nil { + return 0 // Return 0 if the file doesn't exist or can't be read. + } + return bytes.Count(data, []byte{'\n'}) +} diff --git a/cmd/promtool/backfill.go b/cmd/promtool/backfill.go index 400cae421a2..16491f0416f 100644 --- a/cmd/promtool/backfill.go +++ b/cmd/promtool/backfill.go @@ -85,7 +85,7 @@ func getCompatibleBlockDuration(maxBlockDuration int64) int64 { return blockDuration } -func createBlocks(input []byte, mint, maxt, maxBlockDuration int64, maxSamplesInAppender int, outputDir string, humanReadable, quiet bool) (returnErr error) { +func createBlocks(input []byte, mint, maxt, maxBlockDuration int64, maxSamplesInAppender int, outputDir string, humanReadable, quiet bool, customLabels map[string]string) (returnErr error) { blockDuration := getCompatibleBlockDuration(maxBlockDuration) mint = blockDuration * (mint / blockDuration) @@ -102,6 +102,8 @@ func createBlocks(input []byte, mint, maxt, maxBlockDuration int64, maxSamplesIn nextSampleTs int64 = math.MaxInt64 ) + lb := labels.NewBuilder(labels.EmptyLabels()) + for t := mint; t <= maxt; t += blockDuration { tsUpper := t + blockDuration if nextSampleTs != math.MaxInt64 && nextSampleTs >= tsUpper { @@ -162,7 +164,13 @@ func createBlocks(input []byte, mint, maxt, maxBlockDuration int64, maxSamplesIn l := labels.Labels{} p.Metric(&l) - if _, err := app.Append(0, l, *ts, v); err != nil { + lb.Reset(l) + for name, value := range customLabels { + lb.Set(name, value) + } + lbls := lb.Labels() + + if _, err := app.Append(0, lbls, *ts, v); err != nil { return fmt.Errorf("add sample: %w", err) } @@ -221,13 +229,13 @@ func createBlocks(input []byte, mint, maxt, maxBlockDuration int64, maxSamplesIn return nil } -func backfill(maxSamplesInAppender int, input []byte, outputDir string, humanReadable, quiet bool, maxBlockDuration time.Duration) (err error) { +func backfill(maxSamplesInAppender int, input []byte, outputDir string, humanReadable, quiet bool, maxBlockDuration time.Duration, customLabels map[string]string) (err error) { p := textparse.NewOpenMetricsParser(input, nil) // Don't need a SymbolTable to get max and min timestamps. maxt, mint, err := getMinAndMaxTimestamps(p) if err != nil { return fmt.Errorf("getting min and max timestamp: %w", err) } - if err = createBlocks(input, mint, maxt, int64(maxBlockDuration/time.Millisecond), maxSamplesInAppender, outputDir, humanReadable, quiet); err != nil { + if err = createBlocks(input, mint, maxt, int64(maxBlockDuration/time.Millisecond), maxSamplesInAppender, outputDir, humanReadable, quiet, customLabels); err != nil { return fmt.Errorf("block creation: %w", err) } return nil diff --git a/cmd/promtool/backfill_test.go b/cmd/promtool/backfill_test.go index 32abfa46a8c..b818194e865 100644 --- a/cmd/promtool/backfill_test.go +++ b/cmd/promtool/backfill_test.go @@ -92,6 +92,7 @@ func TestBackfill(t *testing.T) { Description string MaxSamplesInAppender int MaxBlockDuration time.Duration + Labels map[string]string Expected struct { MinTime int64 MaxTime int64 @@ -636,6 +637,49 @@ http_requests_total{code="400"} 1024 7199 }, }, }, + { + ToParse: `# HELP http_requests_total The total number of HTTP requests. +# TYPE http_requests_total counter +http_requests_total{code="200"} 1 1624463088.000 +http_requests_total{code="200"} 2 1629503088.000 +http_requests_total{code="200"} 3 1629863088.000 +# EOF +`, + IsOk: true, + Description: "Sample with external labels.", + MaxSamplesInAppender: 5000, + MaxBlockDuration: 2048 * time.Hour, + Labels: map[string]string{"cluster_id": "123", "org_id": "999"}, + Expected: struct { + MinTime int64 + MaxTime int64 + NumBlocks int + BlockDuration int64 + Samples []backfillSample + }{ + MinTime: 1624463088000, + MaxTime: 1629863088000, + NumBlocks: 2, + BlockDuration: int64(1458 * time.Hour / time.Millisecond), + Samples: []backfillSample{ + { + Timestamp: 1624463088000, + Value: 1, + Labels: labels.FromStrings("__name__", "http_requests_total", "code", "200", "cluster_id", "123", "org_id", "999"), + }, + { + Timestamp: 1629503088000, + Value: 2, + Labels: labels.FromStrings("__name__", "http_requests_total", "code", "200", "cluster_id", "123", "org_id", "999"), + }, + { + Timestamp: 1629863088000, + Value: 3, + Labels: labels.FromStrings("__name__", "http_requests_total", "code", "200", "cluster_id", "123", "org_id", "999"), + }, + }, + }, + }, { ToParse: `# HELP rpc_duration_seconds A summary of the RPC duration in seconds. # TYPE rpc_duration_seconds summary @@ -689,7 +733,7 @@ after_eof 1 2 outputDir := t.TempDir() - err := backfill(test.MaxSamplesInAppender, []byte(test.ToParse), outputDir, false, false, test.MaxBlockDuration) + err := backfill(test.MaxSamplesInAppender, []byte(test.ToParse), outputDir, false, false, test.MaxBlockDuration, test.Labels) if !test.IsOk { require.Error(t, err, test.Description) diff --git a/cmd/promtool/main.go b/cmd/promtool/main.go index e1d275e97ed..d1b6c0fcd98 100644 --- a/cmd/promtool/main.go +++ b/cmd/promtool/main.go @@ -204,6 +204,7 @@ func main() { pushMetricsHeaders := pushMetricsCmd.Flag("header", "Prometheus remote write header.").StringMap() testCmd := app.Command("test", "Unit testing.") + junitOutFile := testCmd.Flag("junit", "File path to store JUnit XML test results.").OpenFile(os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0o644) testRulesCmd := testCmd.Command("rules", "Unit tests for rules.") testRulesRun := testRulesCmd.Flag("run", "If set, will only run test groups whose names match the regular expression. Can be specified multiple times.").Strings() testRulesFiles := testRulesCmd.Arg( @@ -235,14 +236,14 @@ func main() { tsdbDumpCmd := tsdbCmd.Command("dump", "Dump samples from a TSDB.") dumpPath := tsdbDumpCmd.Arg("db path", "Database path (default is "+defaultDBPath+").").Default(defaultDBPath).String() - dumpSandboxDirRoot := tsdbDumpCmd.Flag("sandbox-dir-root", "Root directory where a sandbox directory would be created in case WAL replay generates chunks. The sandbox directory is cleaned up at the end.").Default(defaultDBPath).String() + dumpSandboxDirRoot := tsdbDumpCmd.Flag("sandbox-dir-root", "Root directory where a sandbox directory will be created, this sandbox is used in case WAL replay generates chunks (default is the database path). The sandbox is cleaned up at the end.").String() dumpMinTime := tsdbDumpCmd.Flag("min-time", "Minimum timestamp to dump.").Default(strconv.FormatInt(math.MinInt64, 10)).Int64() dumpMaxTime := tsdbDumpCmd.Flag("max-time", "Maximum timestamp to dump.").Default(strconv.FormatInt(math.MaxInt64, 10)).Int64() dumpMatch := tsdbDumpCmd.Flag("match", "Series selector. Can be specified multiple times.").Default("{__name__=~'(?s:.*)'}").Strings() tsdbDumpOpenMetricsCmd := tsdbCmd.Command("dump-openmetrics", "[Experimental] Dump samples from a TSDB into OpenMetrics text format, excluding native histograms and staleness markers, which are not representable in OpenMetrics.") dumpOpenMetricsPath := tsdbDumpOpenMetricsCmd.Arg("db path", "Database path (default is "+defaultDBPath+").").Default(defaultDBPath).String() - dumpOpenMetricsSandboxDirRoot := tsdbDumpOpenMetricsCmd.Flag("sandbox-dir-root", "Root directory where a sandbox directory would be created in case WAL replay generates chunks. The sandbox directory is cleaned up at the end.").Default(defaultDBPath).String() + dumpOpenMetricsSandboxDirRoot := tsdbDumpOpenMetricsCmd.Flag("sandbox-dir-root", "Root directory where a sandbox directory will be created, this sandbox is used in case WAL replay generates chunks (default is the database path). The sandbox is cleaned up at the end.").String() dumpOpenMetricsMinTime := tsdbDumpOpenMetricsCmd.Flag("min-time", "Minimum timestamp to dump.").Default(strconv.FormatInt(math.MinInt64, 10)).Int64() dumpOpenMetricsMaxTime := tsdbDumpOpenMetricsCmd.Flag("max-time", "Maximum timestamp to dump.").Default(strconv.FormatInt(math.MaxInt64, 10)).Int64() dumpOpenMetricsMatch := tsdbDumpOpenMetricsCmd.Flag("match", "Series selector. Can be specified multiple times.").Default("{__name__=~'(?s:.*)'}").Strings() @@ -252,6 +253,7 @@ func main() { importQuiet := importCmd.Flag("quiet", "Do not print created blocks.").Short('q').Bool() maxBlockDuration := importCmd.Flag("max-block-duration", "Maximum duration created blocks may span. Anything less than 2h is ignored.").Hidden().PlaceHolder("-h, --help | Show context-sensitive help (also try --help-long and --help-man). | |
| --version | Show application version. | |
| --config.file | Prometheus configuration file path. | `prometheus.yml` |
-| --web.listen-address | Address to listen on for UI, API, and telemetry. | `0.0.0.0:9090` |
+| --web.listen-address ... | Address to listen on for UI, API, and telemetry. Can be repeated. | `0.0.0.0:9090` |
| --auto-gomemlimit.ratio | The ratio of reserved GOMEMLIMIT memory to the detected maximum container or system memory | `0.9` |
| --web.config.file | [EXPERIMENTAL] Path to configuration file that can enable TLS or authentication. | |
| --web.read-timeout | Maximum duration before timing out read of the request, and closing idle connections. | `5m` |
-| --web.max-connections | Maximum number of simultaneous connections. | `512` |
+| --web.max-connections | Maximum number of simultaneous connections across all listeners. | `512` |
| --web.external-url | The URL under which Prometheus is externally reachable (for example, if Prometheus is served via a reverse proxy). Used for generating relative and absolute links back to Prometheus itself. If the URL has a path portion, it will be used to prefix all HTTP endpoints served by Prometheus. If omitted, relevant URL components will be derived automatically. | |
| --web.route-prefix | Prefix for the internal routes of web endpoints. Defaults to path of --web.external-url. | |
| --web.user-assets | Path to static asset directory, available at /user. | |
@@ -30,7 +30,7 @@ The Prometheus monitoring server
| --web.console.templates | Path to the console template directory, available at /consoles. | `consoles` |
| --web.console.libraries | Path to the console library directory. | `console_libraries` |
| --web.page-title | Document title of Prometheus instance. | `Prometheus Time Series Collection and Processing Server` |
-| --web.cors.origin | Regex for CORS origin. It is fully anchored. Example: 'https?://(domain1|domain2)\.com' | `.*` |
+| --web.cors.origin | Regex for CORS origin. It is fully anchored. Example: 'https?://(domain1\|domain2)\.com' | `.*` |
| --storage.tsdb.path | Base path for metrics storage. Use with server mode only. | `data/` |
| --storage.tsdb.retention | [DEPRECATED] How long to retain samples in storage. This flag has been deprecated, use "storage.tsdb.retention.time" instead. Use with server mode only. | |
| --storage.tsdb.retention.time | How long to retain samples in storage. When this flag is set it overrides "storage.tsdb.retention". If neither this flag nor "storage.tsdb.retention" nor "storage.tsdb.retention.size" is set, the retention time defaults to 15d. Units Supported: y, w, d, h, m, s, ms. Use with server mode only. | |
@@ -56,7 +56,8 @@ The Prometheus monitoring server
| --query.timeout | Maximum time a query may take before being aborted. Use with server mode only. | `2m` |
| --query.max-concurrency | Maximum number of queries executed concurrently. Use with server mode only. | `20` |
| --query.max-samples | Maximum number of samples a single query can load into memory. Note that queries will fail if they try to load more samples than this into memory, so this also limits the number of samples a query can return. Use with server mode only. | `50000000` |
-| --enable-feature | Comma separated feature names to enable. Valid options: agent, auto-gomemlimit, exemplar-storage, expand-external-labels, memory-snapshot-on-shutdown, promql-per-step-stats, promql-experimental-functions, remote-write-receiver (DEPRECATED), extra-scrape-metrics, new-service-discovery-manager, auto-gomaxprocs, no-default-scrape-port, native-histograms, otlp-write-receiver, created-timestamp-zero-ingestion, concurrent-rule-eval. See https://prometheus.io/docs/prometheus/latest/feature_flags/ for more details. | |
+| --scrape.name-escaping-scheme | Method for escaping legacy invalid names when sending to Prometheus that does not support UTF-8. Can be one of "values", "underscores", or "dots". | `values` |
+| --enable-feature ... | Comma separated feature names to enable. Valid options: agent, auto-gomaxprocs, auto-gomemlimit, concurrent-rule-eval, created-timestamp-zero-ingestion, delayed-compaction, exemplar-storage, expand-external-labels, extra-scrape-metrics, memory-snapshot-on-shutdown, native-histograms, new-service-discovery-manager, no-default-scrape-port, otlp-write-receiver, promql-experimental-functions, promql-delayed-name-removal, promql-per-step-stats, remote-write-receiver (DEPRECATED), utf8-names. See https://prometheus.io/docs/prometheus/latest/feature_flags/ for more details. | |
| --log.level | Only log messages with the given severity or above. One of: [debug, info, warn, error] | `info` |
| --log.format | Output format of log messages. One of: [logfmt, json] | `logfmt` |
diff --git a/docs/command-line/promtool.md b/docs/command-line/promtool.md
index 443cd3f0cbf..e48cede79c1 100644
--- a/docs/command-line/promtool.md
+++ b/docs/command-line/promtool.md
@@ -15,7 +15,7 @@ Tooling for the Prometheus monitoring system.
| -h, --help | Show context-sensitive help (also try --help-long and --help-man). |
| --version | Show application version. |
| --experimental | Enable experimental commands. |
-| --enable-feature | Comma separated feature names to enable (only PromQL related and no-default-scrape-port). See https://prometheus.io/docs/prometheus/latest/feature_flags/ for the options and more details. |
+| --enable-feature ... | Comma separated feature names to enable (only PromQL related and no-default-scrape-port). See https://prometheus.io/docs/prometheus/latest/feature_flags/ for the options and more details. |
@@ -281,7 +281,7 @@ Run series query.
| Flag | Description |
| --- | --- |
-| --match | Series selector. Can be specified multiple times. |
+| --match ... | Series selector. Can be specified multiple times. |
| --start | Start time (RFC3339 or Unix timestamp). |
| --end | End time (RFC3339 or Unix timestamp). |
@@ -309,7 +309,7 @@ Run labels query.
| --- | --- |
| --start | Start time (RFC3339 or Unix timestamp). |
| --end | End time (RFC3339 or Unix timestamp). |
-| --match | Series selector. Can be specified multiple times. |
+| --match ... | Series selector. Can be specified multiple times. |
@@ -338,7 +338,7 @@ Run queries against your Prometheus to analyze the usage pattern of certain metr
| --type | Type of metric: histogram. | |
| --duration | Time frame to analyze. | `1h` |
| --time | Query time (RFC3339 or Unix timestamp), defaults to now. | |
-| --match | Series selector. Can be specified multiple times. | |
+| --match ... | Series selector. Can be specified multiple times. | |
@@ -442,6 +442,15 @@ Unit testing.
+#### Flags
+
+| Flag | Description |
+| --- | --- |
+| --junit | File path to store JUnit XML test results. |
+
+
+
+
##### `promtool test rules`
Unit tests for rules.
@@ -452,7 +461,7 @@ Unit tests for rules.
| Flag | Description | Default |
| --- | --- | --- |
-| --run | If set, will only run test groups whose names match the regular expression. Can be specified multiple times. | |
+| --run ... | If set, will only run test groups whose names match the regular expression. Can be specified multiple times. | |
| --diff | [Experimental] Print colored differential output between expected & received output. | `false` |
@@ -566,10 +575,10 @@ Dump samples from a TSDB.
| Flag | Description | Default |
| --- | --- | --- |
-| --sandbox-dir-root | Root directory where a sandbox directory would be created in case WAL replay generates chunks. The sandbox directory is cleaned up at the end. | `data/` |
+| --sandbox-dir-root | Root directory where a sandbox directory will be created, this sandbox is used in case WAL replay generates chunks (default is the database path). The sandbox is cleaned up at the end. | |
| --min-time | Minimum timestamp to dump. | `-9223372036854775808` |
| --max-time | Maximum timestamp to dump. | `9223372036854775807` |
-| --match | Series selector. Can be specified multiple times. | `{__name__=~'(?s:.*)'}` |
+| --match ... | Series selector. Can be specified multiple times. | `{__name__=~'(?s:.*)'}` |
@@ -593,10 +602,10 @@ Dump samples from a TSDB.
| Flag | Description | Default |
| --- | --- | --- |
-| --sandbox-dir-root | Root directory where a sandbox directory would be created in case WAL replay generates chunks. The sandbox directory is cleaned up at the end. | `data/` |
+| --sandbox-dir-root | Root directory where a sandbox directory will be created, this sandbox is used in case WAL replay generates chunks (default is the database path). The sandbox is cleaned up at the end. | |
| --min-time | Minimum timestamp to dump. | `-9223372036854775808` |
| --max-time | Maximum timestamp to dump. | `9223372036854775807` |
-| --match | Series selector. Can be specified multiple times. | `{__name__=~'(?s:.*)'}` |
+| --match ... | Series selector. Can be specified multiple times. | `{__name__=~'(?s:.*)'}` |
@@ -632,6 +641,15 @@ Import samples from OpenMetrics input and produce TSDB blocks. Please refer to t
+###### Flags
+
+| Flag | Description |
+| --- | --- |
+| --label | Label to attach to metrics. Can be specified multiple times. Example --label=label_name=label_value |
+
+
+
+
###### Arguments
| Argument | Description | Default | Required |
diff --git a/docs/configuration/configuration.md b/docs/configuration/configuration.md
index 35976871b92..1f2db6ee82e 100644
--- a/docs/configuration/configuration.md
+++ b/docs/configuration/configuration.md
@@ -70,7 +70,7 @@ global:
# How frequently to evaluate rules.
[ evaluation_interval: | default = 1m ]
-
+
# Offset the rule evaluation timestamp of this particular group by the specified duration into the past to ensure the underlying metrics have been received.
# Metric availability delays are more likely to occur when Prometheus is running as a remote write target, but can also occur when there's anomalies with scraping.
[ rule_query_offset: | default = 0s ]
@@ -84,6 +84,10 @@ global:
# Reloading the configuration will reopen the file.
[ query_log_file: ]
+ # File to which scrape failures are logged.
+ # Reloading the configuration will reopen the file.
+ [ scrape_failure_log_file: ]
+
# An uncompressed response body larger than this many bytes will cause the
# scrape to fail. 0 means no limit. Example: 100MB.
# This is an experimental feature, this behaviour could
@@ -121,6 +125,11 @@ global:
# that will be kept in memory. 0 means no limit.
[ keep_dropped_targets: | default = 0 ]
+ # Specifies the validation scheme for metric and label names. Either blank or
+ # "legacy" for letters, numbers, colons, and underscores; or "utf8" for full
+ # UTF-8 support.
+ [ metric_name_validation_scheme | default "legacy" ]
+
runtime:
# Configure the Go garbage collector GOGC parameter
# See: https://tip.golang.org/doc/gc-guide#GOGC
@@ -152,6 +161,10 @@ alerting:
remote_write:
[ - ... ]
+# Settings related to the OTLP receiver feature.
+otlp:
+ [ promote_resource_attributes: [, ...] | default = [ ] ]
+
# Settings related to the remote read feature.
remote_read:
[ - ... ]
@@ -298,6 +311,21 @@ tls_config:
[ proxy_connect_header:
[ : [, ...] ] ]
+# Custom HTTP headers to be sent along with each request.
+# Headers that are set by Prometheus itself can't be overwritten.
+http_headers:
+ # Header name.
+ [ :
+ # Header values.
+ [ values: [, ...] ]
+ # Headers values. Hidden in configuration page.
+ [ secrets: [, ...] ]
+ # Files to read header values from.
+ [ files: [, ...] ] ]
+
+# File to which scrape failures are logged.
+# Reloading the configuration will reopen the file.
+[ scrape_failure_log_file: ]
# List of Azure service discovery configurations.
azure_sd_configs:
@@ -457,14 +485,21 @@ metric_relabel_configs:
# that will be kept in memory. 0 means no limit.
[ keep_dropped_targets: | default = 0 ]
+# Specifies the validation scheme for metric and label names. Either blank or
+# "legacy" for letters, numbers, colons, and underscores; or "utf8" for full
+# UTF-8 support.
+[ metric_name_validation_scheme | default "legacy" ]
+
# Limit on total number of positive and negative buckets allowed in a single
-# native histogram. If this is exceeded, the entire scrape will be treated as
-# failed. 0 means no limit.
+# native histogram. The resolution of a histogram with more buckets will be
+# reduced until the number of buckets is within the limit. If the limit cannot
+# be reached, the scrape will fail.
+# 0 means no limit.
[ native_histogram_bucket_limit: | default = 0 ]
# Lower limit for the growth factor of one bucket to the next in each native
# histogram. The resolution of a histogram with a lower growth factor will be
-# reduced until it is within the limit.
+# reduced as much as possible until it is within the limit.
# To set an upper limit for the schema (equivalent to "scale" in OTel's
# exponential histograms), use the following factor limits:
#
@@ -581,6 +616,18 @@ tls_config:
# Specifies headers to send to proxies during CONNECT requests.
[ proxy_connect_header:
[ : [, ...] ] ]
+
+# Custom HTTP headers to be sent along with each request.
+# Headers that are set by Prometheus itself can't be overwritten.
+http_headers:
+ # Header name.
+ [ :
+ # Header values.
+ [ values: [, ...] ]
+ # Headers values. Hidden in configuration page.
+ [ secrets: [, ...] ]
+ # Files to read header values from.
+ [ files: [, ...] ] ]
```
### ``
@@ -672,6 +719,18 @@ oauth2:
[ proxy_connect_header:
[ : [, ...] ] ]
+# Custom HTTP headers to be sent along with each request.
+# Headers that are set by Prometheus itself can't be overwritten.
+http_headers:
+ # Header name.
+ [ :
+ # Header values.
+ [ values: [, ...] ]
+ # Headers values. Hidden in configuration page.
+ [ secrets: [, ...] ]
+ # Files to read header values from.
+ [ files: [, ...] ] ]
+
# Configure whether HTTP requests follow HTTP 3xx redirects.
[ follow_redirects: | default = true ]
@@ -785,6 +844,18 @@ oauth2:
[ proxy_connect_header:
[ : [, ...] ] ]
+# Custom HTTP headers to be sent along with each request.
+# Headers that are set by Prometheus itself can't be overwritten.
+http_headers:
+ # Header name.
+ [ :
+ # Header values.
+ [ values: [, ...] ]
+ # Headers values. Hidden in configuration page.
+ [ secrets: [, ...] ]
+ # Files to read header values from.
+ [ files: [, ...] ] ]
+
# Configure whether HTTP requests follow HTTP 3xx redirects.
[ follow_redirects: | default = true ]
@@ -872,6 +943,18 @@ oauth2:
[ proxy_connect_header:
[ : [, ...] ] ]
+# Custom HTTP headers to be sent along with each request.
+# Headers that are set by Prometheus itself can't be overwritten.
+http_headers:
+ # Header name.
+ [ :
+ # Header values.
+ [ values: [, ...] ]
+ # Headers values. Hidden in configuration page.
+ [ secrets: [, ...] ]
+ # Files to read header values from.
+ [ files: [, ...] ] ]
+
# Configure whether HTTP requests follow HTTP 3xx redirects.
[ follow_redirects: | default = true ]
@@ -930,6 +1013,18 @@ host:
[ proxy_connect_header:
[ : [, ...] ] ]
+# Custom HTTP headers to be sent along with each request.
+# Headers that are set by Prometheus itself can't be overwritten.
+http_headers:
+ # Header name.
+ [ :
+ # Header values.
+ [ values: [, ...] ]
+ # Headers values. Hidden in configuration page.
+ [ secrets: [, ...] ]
+ # Files to read header values from.
+ [ files: [, ...] ] ]
+
# TLS configuration.
tls_config:
[ ]
@@ -941,7 +1036,9 @@ tls_config:
# The host to use if the container is in host networking mode.
[ host_networking_host: | default = "localhost" ]
-# Match the first network if the container has multiple networks defined, thus avoiding collecting duplicate targets.
+# Sort all non-nil networks in ascending order based on network name and
+# get the first network if the container has multiple networks defined,
+# thus avoiding collecting duplicate targets.
[ match_first_network: | default = true ]
# Optional filters to limit the discovery process to a subset of available
@@ -1108,6 +1205,18 @@ host:
[ proxy_connect_header:
[ : [, ...] ] ]
+# Custom HTTP headers to be sent along with each request.
+# Headers that are set by Prometheus itself can't be overwritten.
+http_headers:
+ # Header name.
+ [ :
+ # Header values.
+ [ values: [, ...] ]
+ # Headers values. Hidden in configuration page.
+ [ secrets: [, ...] ]
+ # Files to read header values from.
+ [ files: [, ...] ] ]
+
# TLS configuration.
tls_config:
[ ]
@@ -1317,6 +1426,18 @@ oauth2:
[ proxy_connect_header:
[ : [, ...] ] ]
+# Custom HTTP headers to be sent along with each request.
+# Headers that are set by Prometheus itself can't be overwritten.
+http_headers:
+ # Header name.
+ [ :
+ # Header values.
+ [ values: [, ...] ]
+ # Headers values. Hidden in configuration page.
+ [ secrets: [, ...] ]
+ # Files to read header values from.
+ [ files: [, ...] ] ]
+
# Configure whether HTTP requests follow HTTP 3xx redirects.
[ follow_redirects: | default = true ]
@@ -1594,6 +1715,18 @@ oauth2:
[ proxy_connect_header:
[ : [, ...] ] ]
+# Custom HTTP headers to be sent along with each request.
+# Headers that are set by Prometheus itself can't be overwritten.
+http_headers:
+ # Header name.
+ [ :
+ # Header values.
+ [ values: [, ...] ]
+ # Headers values. Hidden in configuration page.
+ [ secrets: [, ...] ]
+ # Files to read header values from.
+ [ files: [, ...] ] ]
+
# Configure whether HTTP requests follow HTTP 3xx redirects.
[ follow_redirects: | default = true ]
@@ -1820,6 +1953,18 @@ oauth2:
[ proxy_connect_header:
[ : [, ...] ] ]
+# Custom HTTP headers to be sent along with each request.
+# Headers that are set by Prometheus itself can't be overwritten.
+http_headers:
+ # Header name.
+ [ :
+ # Header values.
+ [ values: [, ...] ]
+ # Headers values. Hidden in configuration page.
+ [ secrets: [, ...] ]
+ # Files to read header values from.
+ [ files: [, ...] ] ]
+
# Configure whether HTTP requests follow HTTP 3xx redirects.
[ follow_redirects: | default = true ]
@@ -1914,6 +2059,18 @@ oauth2:
[ proxy_connect_header:
[ : [, ...] ] ]
+# Custom HTTP headers to be sent along with each request.
+# Headers that are set by Prometheus itself can't be overwritten.
+http_headers:
+ # Header name.
+ [ :
+ # Header values.
+ [ values: [, ...] ]
+ # Headers values. Hidden in configuration page.
+ [ secrets: [, ...] ]
+ # Files to read header values from.
+ [ files: [, ...] ] ]
+
# Configure whether HTTP requests follow HTTP 3xx redirects.
[ follow_redirects: | default = true ]
@@ -1997,6 +2154,18 @@ oauth2:
[ proxy_connect_header:
[ : [, ...] ] ]
+# Custom HTTP headers to be sent along with each request.
+# Headers that are set by Prometheus itself can't be overwritten.
+http_headers:
+ # Header name.
+ [ :
+ # Header values.
+ [ values: [, ...] ]
+ # Headers values. Hidden in configuration page.
+ [ secrets: [, ...] ]
+ # Files to read header values from.
+ [ files: [, ...] ] ]
+
# Configure whether HTTP requests follow HTTP 3xx redirects.
[ follow_redirects: | default = true ]
@@ -2231,6 +2400,18 @@ oauth2:
[ proxy_connect_header:
[ : [, ...] ] ]
+# Custom HTTP headers to be sent along with each request.
+# Headers that are set by Prometheus itself can't be overwritten.
+http_headers:
+ # Header name.
+ [ :
+ # Header values.
+ [ values: [, ...] ]
+ # Headers values. Hidden in configuration page.
+ [ secrets: [, ...] ]
+ # Files to read header values from.
+ [ files: [, ...] ] ]
+
# Configure whether HTTP requests follow HTTP 3xx redirects.
[ follow_redirects: | default = true ]
@@ -2322,6 +2503,18 @@ server:
[ proxy_connect_header:
[ : [, ...] ] ]
+# Custom HTTP headers to be sent along with each request.
+# Headers that are set by Prometheus itself can't be overwritten.
+http_headers:
+ # Header name.
+ [ :
+ # Header values.
+ [ values: [, ...] ]
+ # Headers values. Hidden in configuration page.
+ [ secrets: [, ...] ]
+ # Files to read header values from.
+ [ files: [, ...] ] ]
+
# TLS configuration.
tls_config:
[ ]
@@ -2449,6 +2642,18 @@ oauth2:
[ proxy_connect_header:
[ : [, ...] ] ]
+# Custom HTTP headers to be sent along with each request.
+# Headers that are set by Prometheus itself can't be overwritten.
+http_headers:
+ # Header name.
+ [ :
+ # Header values.
+ [ values: [, ...] ]
+ # Headers values. Hidden in configuration page.
+ [ secrets: [, ...] ]
+ # Files to read header values from.
+ [ files: [, ...] ] ]
+
# Configure whether HTTP requests follow HTTP 3xx redirects.
[ follow_redirects: | default = true ]
@@ -2538,6 +2743,18 @@ oauth2:
[ proxy_connect_header:
[ : [, ...] ] ]
+# Custom HTTP headers to be sent along with each request.
+# Headers that are set by Prometheus itself can't be overwritten.
+http_headers:
+ # Header name.
+ [ :
+ # Header values.
+ [ values: [, ...] ]
+ # Headers values. Hidden in configuration page.
+ [ secrets: [, ...] ]
+ # Files to read header values from.
+ [ files: [, ...] ] ]
+
# Configure whether HTTP requests follow HTTP 3xx redirects.
[ follow_redirects: | default = true ]
@@ -2645,6 +2862,18 @@ tls_config:
# Specifies headers to send to proxies during CONNECT requests.
[ proxy_connect_header:
[ : [, ...] ] ]
+
+# Custom HTTP headers to be sent along with each request.
+# Headers that are set by Prometheus itself can't be overwritten.
+http_headers:
+ # Header name.
+ [ :
+ # Header values.
+ [ values: [, ...] ]
+ # Headers values. Hidden in configuration page.
+ [ secrets: [, ...] ]
+ # Files to read header values from.
+ [ files: [, ...] ] ]
```
By default every app listed in Marathon will be scraped by Prometheus. If not all
@@ -2744,6 +2973,18 @@ oauth2:
[ proxy_connect_header:
[ : [, ...] ] ]
+# Custom HTTP headers to be sent along with each request.
+# Headers that are set by Prometheus itself can't be overwritten.
+http_headers:
+ # Header name.
+ [ :
+ # Header values.
+ [ values: [, ...] ]
+ # Headers values. Hidden in configuration page.
+ [ secrets: [, ...] ]
+ # Files to read header values from.
+ [ files: [, ...] ] ]
+
# Configure whether HTTP requests follow HTTP 3xx redirects.
[ follow_redirects: | default = true ]
@@ -2930,6 +3171,18 @@ tls_config:
[ proxy_connect_header:
[ : [, ...] ] ]
+# Custom HTTP headers to be sent along with each request.
+# Headers that are set by Prometheus itself can't be overwritten.
+http_headers:
+ # Header name.
+ [ :
+ # Header values.
+ [ values: [, ...] ]
+ # Headers values. Hidden in configuration page.
+ [ secrets: [, ...] ]
+ # Files to read header values from.
+ [ files: [, ...] ] ]
+
# Configure whether HTTP requests follow HTTP 3xx redirects.
[ follow_redirects: | default = true ]
@@ -3056,6 +3309,18 @@ tags_filter:
[ proxy_connect_header:
[ : [, ...] ] ]
+# Custom HTTP headers to be sent along with each request.
+# Headers that are set by Prometheus itself can't be overwritten.
+http_headers:
+ # Header name.
+ [ :
+ # Header values.
+ [ values: [, ...] ]
+ # Headers values. Hidden in configuration page.
+ [ secrets: [, ...] ]
+ # Files to read header values from.
+ [ files: [, ...] ] ]
+
# TLS configuration.
tls_config:
[ ]
@@ -3132,6 +3397,18 @@ oauth2:
[ proxy_connect_header:
[ : [, ...] ] ]
+# Custom HTTP headers to be sent along with each request.
+# Headers that are set by Prometheus itself can't be overwritten.
+http_headers:
+ # Header name.
+ [ :
+ # Header values.
+ [ values: [, ...] ]
+ # Headers values. Hidden in configuration page.
+ [ secrets: [, ...] ]
+ # Files to read header values from.
+ [ files: [, ...] ] ]
+
# Configure whether HTTP requests follow HTTP 3xx redirects.
[ follow_redirects: | default = true ]
@@ -3214,6 +3491,18 @@ oauth2:
[ proxy_connect_header:
[ : [, ...] ] ]
+# Custom HTTP headers to be sent along with each request.
+# Headers that are set by Prometheus itself can't be overwritten.
+http_headers:
+ # Header name.
+ [ :
+ # Header values.
+ [ values: [, ...] ]
+ # Headers values. Hidden in configuration page.
+ [ secrets: [, ...] ]
+ # Files to read header values from.
+ [ files: [, ...] ] ]
+
# Configure whether HTTP requests follow HTTP 3xx redirects.
[ follow_redirects: | default = true ]
@@ -3259,12 +3548,16 @@ Initially, aside from the configured per-target labels, a target's `job`
label is set to the `job_name` value of the respective scrape configuration.
The `__address__` label is set to the `:` address of the target.
After relabeling, the `instance` label is set to the value of `__address__` by default if
-it was not set during relabeling. The `__scheme__` and `__metrics_path__` labels
-are set to the scheme and metrics path of the target respectively. The `__param_`
-label is set to the value of the first passed URL parameter called ``.
+it was not set during relabeling.
+
+The `__scheme__` and `__metrics_path__` labels
+are set to the scheme and metrics path of the target respectively, as specified in `scrape_config`.
+
+The `__param_`
+label is set to the value of the first passed URL parameter called ``, as defined in `scrape_config`.
The `__scrape_interval__` and `__scrape_timeout__` labels are set to the target's
-interval and timeout.
+interval and timeout, as specified in `scrape_config`.
Additional labels prefixed with `__meta_` may be available during the
relabeling phase. They are set by the service discovery mechanism that provided
@@ -3395,8 +3688,8 @@ authorization:
# It is mutually exclusive with `credentials`.
[ credentials_file: ]
-# Optionally configures AWS's Signature Verification 4 signing process to
-# sign requests. Cannot be set at the same time as basic_auth, authorization, or oauth2.
+# Optionally configures AWS's Signature Verification 4 signing process to sign requests.
+# Cannot be set at the same time as basic_auth, authorization, oauth2, azuread or google_iam.
# To use the default credentials from the AWS SDK, use `sigv4: {}`.
sigv4:
# The AWS region. If blank, the region from the default credentials chain
@@ -3435,6 +3728,18 @@ tls_config:
[ proxy_connect_header:
[ : [, ...] ] ]
+# Custom HTTP headers to be sent along with each request.
+# Headers that are set by Prometheus itself can't be overwritten.
+http_headers:
+ # Header name.
+ [ :
+ # Header values.
+ [ values: [, ...] ]
+ # Headers values. Hidden in configuration page.
+ [ secrets: [, ...] ]
+ # Files to read header values from.
+ [ files: [, ...] ] ]
+
# Configure whether HTTP requests follow HTTP 3xx redirects.
[ follow_redirects: | default = true ]
@@ -3649,12 +3954,12 @@ sigv4:
[ role_arn: ]
# Optional OAuth 2.0 configuration.
-# Cannot be used at the same time as basic_auth, authorization, sigv4, or azuread.
+# Cannot be used at the same time as basic_auth, authorization, sigv4, azuread or google_iam.
oauth2:
[ ]
# Optional AzureAD configuration.
-# Cannot be used at the same time as basic_auth, authorization, oauth2, or sigv4.
+# Cannot be used at the same time as basic_auth, authorization, oauth2, sigv4 or google_iam.
azuread:
# The Azure Cloud. Options are 'AzurePublic', 'AzureChina', or 'AzureGovernment'.
[ cloud: | default = AzurePublic ]
@@ -3674,6 +3979,14 @@ azuread:
[ sdk:
[ tenant_id: ] ]
+# WARNING: Remote write is NOT SUPPORTED by Google Cloud. This configuration is reserved for future use.
+# Optional Google Cloud Monitoring configuration.
+# Cannot be used at the same time as basic_auth, authorization, oauth2, sigv4 or azuread.
+# To use the default credentials from the Google Cloud SDK, use `google_iam: {}`.
+google_iam:
+ # Service account key with monitoring write permessions.
+ credentials_file:
+
# Configures the remote write request's TLS settings.
tls_config:
[ ]
@@ -3690,6 +4003,18 @@ tls_config:
[ proxy_connect_header:
[ : [, ...] ] ]
+# Custom HTTP headers to be sent along with each request.
+# Headers that are set by Prometheus itself can't be overwritten.
+http_headers:
+ # Header name.
+ [ :
+ # Header values.
+ [ values: [, ...] ]
+ # Headers values. Hidden in configuration page.
+ [ secrets: [, ...] ]
+ # Files to read header values from.
+ [ files: [, ...] ] ]
+
# Configure whether HTTP requests follow HTTP 3xx redirects.
[ follow_redirects: | default = true ]
@@ -3811,6 +4136,18 @@ tls_config:
[ proxy_connect_header:
[ : [, ...] ] ]
+# Custom HTTP headers to be sent along with each request.
+# Headers that are set by Prometheus itself can't be overwritten.
+http_headers:
+ # Header name.
+ [ :
+ # Header values.
+ [ values: [, ...] ]
+ # Headers values. Hidden in configuration page.
+ [ secrets: [, ...] ]
+ # Files to read header values from.
+ [ files: [, ...] ] ]
+
# Configure whether HTTP requests follow HTTP 3xx redirects.
[ follow_redirects: | default = true ]
diff --git a/docs/configuration/unit_testing_rules.md b/docs/configuration/unit_testing_rules.md
index 163fcb91f12..7fc676a2515 100644
--- a/docs/configuration/unit_testing_rules.md
+++ b/docs/configuration/unit_testing_rules.md
@@ -92,7 +92,7 @@ series:
#
# Native histogram notation:
# Native histograms can be used instead of floating point numbers using the following notation:
-# {{schema:1 sum:-0.3 count:3.1 z_bucket:7.1 z_bucket_w:0.05 buckets:[5.1 10 7] offset:-3 n_buckets:[4.1 5] n_offset:-5}}
+# {{schema:1 sum:-0.3 count:3.1 z_bucket:7.1 z_bucket_w:0.05 buckets:[5.1 10 7] offset:-3 n_buckets:[4.1 5] n_offset:-5 counter_reset_hint:gauge}}
# Native histograms support the same expanding notation as floating point numbers, i.e. 'axn', 'a+bxn' and 'a-bxn'.
# All properties are optional and default to 0. The order is not important. The following properties are supported:
# - schema (int):
@@ -119,6 +119,8 @@ series:
# Observation counts in negative buckets. Each represents an absolute count.
# - n_offset (int):
# The starting index of the first entry in the negative buckets.
+# - counter_reset_hint (one of 'unknown', 'reset', 'not_reset' or 'gauge')
+# The counter reset hint associated with this histogram. Defaults to 'unknown' if not set.
values:
```
diff --git a/docs/feature_flags.md b/docs/feature_flags.md
index 24d70647fda..7b07a04d0e2 100644
--- a/docs/feature_flags.md
+++ b/docs/feature_flags.md
@@ -200,8 +200,9 @@ won't work when you push OTLP metrics.
`--enable-feature=promql-experimental-functions`
-Enables PromQL functions that are considered experimental and whose name or
-semantics could change.
+Enables PromQL functions that are considered experimental. These functions
+might change their name, syntax, or semantics. They might also get removed
+entirely.
## Created Timestamps Zero Injection
@@ -234,3 +235,33 @@ metadata changes as WAL records on a per-series basis.
This must be used if
you are also using remote write 2.0 as it will only gather metadata from the WAL.
+
+## Delay compaction start time
+
+`--enable-feature=delayed-compaction`
+
+A random offset, up to `10%` of the chunk range, is added to the Head compaction start time. This assists Prometheus instances in avoiding simultaneous compactions and reduces the load on shared resources.
+
+Only auto Head compactions and the operations directly resulting from them are subject to this delay.
+
+In the event of multiple consecutive Head compactions being possible, only the first compaction experiences this delay.
+
+Note that during this delay, the Head continues its usual operations, which include serving and appending series.
+
+Despite the delay in compaction, the blocks produced are time-aligned in the same manner as they would be if the delay was not in place.
+
+## Delay __name__ label removal for PromQL engine
+
+`--enable-feature=promql-delayed-name-removal`
+
+When enabled, Prometheus will change the way in which the `__name__` label is removed from PromQL query results (for functions and expressions for which this is necessary). Specifically, it will delay the removal to the last step of the query evaluation, instead of every time an expression or function creating derived metrics is evaluated.
+
+This allows optionally preserving the `__name__` label via the `label_replace` and `label_join` functions, and helps prevent the "vector cannot contain metrics with the same labelset" error, which can happen when applying a regex-matcher to the `__name__` label.
+
+## UTF-8 Name Support
+
+`--enable-feature=utf8-names`
+
+When enabled, changes the metric and label name validation scheme inside Prometheus to allow the full UTF-8 character set.
+By itself, this flag does not enable the request of UTF-8 names via content negotiation.
+Users will also have to set `metric_name_validation_scheme` in scrape configs to enable the feature either on the global config or on a per-scrape config basis.
diff --git a/docs/querying/api.md b/docs/querying/api.md
index f71bfc15863..efa244fbc8b 100644
--- a/docs/querying/api.md
+++ b/docs/querying/api.md
@@ -260,7 +260,7 @@ URL query parameters:
series to return. At least one `match[]` argument must be provided.
- `start=`: Start timestamp.
- `end=`: End timestamp.
-- `limit=`: Maximum number of returned series. Optional.
+- `limit=`: Maximum number of returned series. Optional. 0 means disabled.
You can URL-encode these parameters directly in the request body by using the `POST` method and
`Content-Type: application/x-www-form-urlencoded` header. This is useful when specifying a large
@@ -311,7 +311,7 @@ URL query parameters:
- `end=`: End timestamp. Optional.
- `match[]=`: Repeated series selector argument that selects the
series from which to read the label names. Optional.
-- `limit=`: Maximum number of returned series. Optional.
+- `limit=`: Maximum number of returned series. Optional. 0 means disabled.
The `data` section of the JSON response is a list of string label names.
@@ -362,7 +362,7 @@ URL query parameters:
- `end=`: End timestamp. Optional.
- `match[]=`: Repeated series selector argument that selects the
series from which to read the label values. Optional.
-- `limit=`: Maximum number of returned series. Optional.
+- `limit=`: Maximum number of returned series. Optional. 0 means disabled.
The `data` section of the JSON response is a list of string label values.
diff --git a/docs/querying/basics.md b/docs/querying/basics.md
index fee7e63c42e..81ffb4e0f39 100644
--- a/docs/querying/basics.md
+++ b/docs/querying/basics.md
@@ -8,9 +8,15 @@ sort_rank: 1
Prometheus provides a functional query language called PromQL (Prometheus Query
Language) that lets the user select and aggregate time series data in real
-time. The result of an expression can either be shown as a graph, viewed as
-tabular data in Prometheus's expression browser, or consumed by external
-systems via the [HTTP API](api.md).
+time.
+
+When you send a query request to Prometheus, it can be an _instant query_, evaluated at one point in time,
+or a _range query_ at equally-spaced steps between a start and an end time. PromQL works exactly the same
+in each cases; the range query is just like an instant query run multiple times at different timestamps.
+
+In the Prometheus UI, the "Table" tab is for instant queries and the "Graph" tab is for range queries.
+
+Other programs can fetch the result of a PromQL expression via the [HTTP API](api.md).
## Examples
@@ -35,7 +41,7 @@ vector is the only type which can be graphed.
_Notes about the experimental native histograms:_
* Ingesting native histograms has to be enabled via a [feature
- flag](../../feature_flags.md#native-histograms).
+ flag](../feature_flags.md#native-histograms).
* Once native histograms have been ingested into the TSDB (and even after
disabling the feature flag again), both instant vectors and range vectors may
now contain samples that aren't simple floating point numbers (float samples)
@@ -81,12 +87,20 @@ Examples:
0x8f
-Inf
NaN
+
-## Time series selectors
+As of version 2.54, float literals can also be represented using the syntax of time durations, where the time duration is converted into a float value corresponding to the number of seconds the time duration represents. This is an experimental feature and might still change.
-Time series selectors are responsible for selecting the times series and raw or inferred sample timestamps and values.
+Examples:
+
+ 1s # Equivalent to 1.0
+ 2m # Equivalent to 120.0
+ 1ms # Equivalent to 0.001
+
-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.
+## Time series selectors
+
+These are the basic building-blocks that instruct PromQL what data to fetch.
### Instant vector selectors
@@ -224,6 +238,15 @@ Here are some examples of valid time durations:
5m
10s
+
+As of version 2.54, time durations can also be represented using the syntax of float literals, implying the number of seconds of the time duration. This is an experimental feature and might still change.
+
+Examples:
+
+ 1.0 # Equivalent to 1s
+ 0.001 # Equivalent to 1ms
+ 120 # Equivalent to 2m
+
### Offset modifier
The `offset` modifier allows changing the time offset for individual
diff --git a/docs/querying/functions.md b/docs/querying/functions.md
index 9a552f697a9..c6e22019fc6 100644
--- a/docs/querying/functions.md
+++ b/docs/querying/functions.md
@@ -98,8 +98,9 @@ vector.
clamps the sample values of all elements in `v` to have a lower limit of `min` and an upper limit of `max`.
Special cases:
-- Return an empty vector if `min > max`
-- Return `NaN` if `min` or `max` is `NaN`
+
+* Return an empty vector if `min > max`
+* Return `NaN` if `min` or `max` is `NaN`
## `clamp_max()`
@@ -349,8 +350,8 @@ a histogram.
Buckets of classic histograms are cumulative. Therefore, the following should always be the case:
-- The counts in the buckets are monotonically increasing (strictly non-decreasing).
-- A lack of observations between the upper limits of two consecutive buckets results in equal counts
+* The counts in the buckets are monotonically increasing (strictly non-decreasing).
+* A lack of observations between the upper limits of two consecutive buckets results in equal counts
in those two buckets.
However, floating point precision issues (e.g. small discrepancies introduced by computing of buckets
@@ -431,6 +432,97 @@ by the number of seconds under the specified time range window, and should be
used primarily for human readability. Use `rate` in recording rules so that
increases are tracked consistently on a per-second basis.
+## `info()` (experimental)
+
+_The `info` function is an experiment to improve UX
+around including labels from [info metrics](https://grafana.com/blog/2021/08/04/how-to-use-promql-joins-for-more-effective-queries-of-prometheus-metrics-at-scale/#info-metrics).
+The behavior of this function may change in future versions of Prometheus,
+including its removal from PromQL. `info` has to be enabled via the
+[feature flag](../feature_flags.md#experimental-promql-functions) `--enable-feature=promql-experimental-functions`._
+
+`info(v instant-vector, [data-label-selector instant-vector])` finds, for each time
+series in `v`, all info series with matching _identifying_ labels (more on
+this later), and adds the union of their _data_ (i.e., non-identifying) labels
+to the time series. The second argument `data-label-selector` is optional.
+It is not a real instant vector, but uses a subset of its syntax.
+It must start and end with curly braces (`{ ... }`) and may only contain label matchers.
+The label matchers are used to constrain which info series to consider
+and which data labels to add to `v`.
+
+Identifying labels of an info series are the subset of labels that uniquely
+identify the info series. The remaining labels are considered
+_data labels_ (also called non-identifying). (Note that Prometheus's concept
+of time series identity always includes _all_ the labels. For the sake of the `info`
+function, we “logically” define info series identity in a different way than
+in the conventional Prometheus view.) The identifying labels of an info series
+are used to join it to regular (non-info) series, i.e. those series that have
+the same labels as the identifying labels of the info series. The data labels, which are
+the ones added to the regular series by the `info` function, effectively encode
+metadata key value pairs. (This implies that a change in the data labels
+in the conventional Prometheus view constitutes the end of one info series and
+the beginning of a new info series, while the “logical” view of the `info` function is
+that the same info series continues to exist, just with different “data”.)
+
+The conventional approach of adding data labels is sometimes called a “join query”,
+as illustrated by the following example:
+
+```
+ rate(http_server_request_duration_seconds_count[2m])
+* on (job, instance) group_left (k8s_cluster_name)
+ target_info
+```
+
+The core of the query is the expression `rate(http_server_request_duration_seconds_count[2m])`.
+But to add data labels from an info metric, the user has to use elaborate
+(and not very obvious) syntax to specify which info metric to use (`target_info`), what the
+identifying labels are (`on (job, instance)`), and which data labels to add
+(`group_left (k8s_cluster_name)`).
+
+This query is not only verbose and hard to write, it might also run into an “identity crisis”:
+If any of the data labels of `target_info` changes, Prometheus sees that as a change of series
+(as alluded to above, Prometheus just has no native concept of non-identifying labels).
+If the old `target_info` series is not properly marked as stale (which can happen with certain ingestion paths),
+the query above will fail for up to 5m (the lookback delta) because it will find a conflicting
+match with both the old and the new version of `target_info`.
+
+The `info` function not only resolves this conflict in favor of the newer series, it also simplifies the syntax
+because it knows about the available info series and what their identifying labels are. The example query
+looks like this with the `info` function:
+
+```
+info(
+ rate(http_server_request_duration_seconds_count[2m]),
+ {k8s_cluster_name=~".+"}
+)
+```
+
+The common case of adding _all_ data labels can be achieved by
+omitting the 2nd argument of the `info` function entirely, simplifying
+the example even more:
+
+```
+info(rate(http_server_request_duration_seconds_count[2m]))
+```
+
+While `info` normally automatically finds all matching info series, it's possible to
+restrict them by providing a `__name__` label matcher, e.g.
+`{__name__="target_info"}`.
+
+### Limitations
+
+In its current iteration, `info` defaults to considering only info series with
+the name `target_info`. It also assumes that the identifying info series labels are
+`instance` and `job`. `info` does support other info series names however, through
+`__name__` label matchers. E.g., one can explicitly say to consider both
+`target_info` and `build_info` as follows:
+`{__name__=~"(target|build)_info"}`. However, the identifying labels always
+have to be `instance` and `job`.
+
+These limitations are partially defeating the purpose of the `info` function.
+At the current stage, this is an experiment to find out how useful the approach
+turns out to be in practice. A final version of the `info` function will indeed
+consider all matching info series and with their appropriate identifying labels.
+
## `irate()`
`irate(v range-vector)` calculates the per-second instant rate of increase of
@@ -616,9 +708,9 @@ Like `sort`, `sort_desc` only affects the results of instant queries, as range q
## `sort_by_label()`
-**This function has to be enabled via the [feature flag](../feature_flags/) `--enable-feature=promql-experimental-functions`.**
+**This function has to be enabled via the [feature flag](../feature_flags.md#experimental-promql-functions) `--enable-feature=promql-experimental-functions`.**
-`sort_by_label(v instant-vector, label string, ...)` returns vector elements sorted by their label values and sample value in case of label values being equal, in ascending order.
+`sort_by_label(v instant-vector, label string, ...)` returns vector elements sorted by the values of the given labels in ascending order. In case these label values are equal, elements are sorted by their full label sets.
Please note that the sort by label functions only affect the results of instant queries, as range query results always have a fixed output ordering.
@@ -626,7 +718,7 @@ This function uses [natural sort order](https://en.wikipedia.org/wiki/Natural_so
## `sort_by_label_desc()`
-**This function has to be enabled via the [feature flag](../feature_flags/) `--enable-feature=promql-experimental-functions`.**
+**This function has to be enabled via the [feature flag](../feature_flags.md#experimental-promql-functions) `--enable-feature=promql-experimental-functions`.**
Same as `sort_by_label`, but sorts in descending order.
@@ -675,7 +767,7 @@ over time and return an instant vector with per-series aggregation results:
* `last_over_time(range-vector)`: the most recent point value in the specified interval.
* `present_over_time(range-vector)`: the value 1 for any series in the specified interval.
-If the [feature flag](../feature_flags/)
+If the [feature flag](../feature_flags.md#experimental-promql-functions)
`--enable-feature=promql-experimental-functions` is set, the following
additional functions are available:
@@ -692,21 +784,21 @@ ignore histogram samples.
The trigonometric functions work in radians:
-- `acos(v instant-vector)`: calculates the arccosine of all elements in `v` ([special cases](https://pkg.go.dev/math#Acos)).
-- `acosh(v instant-vector)`: calculates the inverse hyperbolic cosine of all elements in `v` ([special cases](https://pkg.go.dev/math#Acosh)).
-- `asin(v instant-vector)`: calculates the arcsine of all elements in `v` ([special cases](https://pkg.go.dev/math#Asin)).
-- `asinh(v instant-vector)`: calculates the inverse hyperbolic sine of all elements in `v` ([special cases](https://pkg.go.dev/math#Asinh)).
-- `atan(v instant-vector)`: calculates the arctangent of all elements in `v` ([special cases](https://pkg.go.dev/math#Atan)).
-- `atanh(v instant-vector)`: calculates the inverse hyperbolic tangent of all elements in `v` ([special cases](https://pkg.go.dev/math#Atanh)).
-- `cos(v instant-vector)`: calculates the cosine of all elements in `v` ([special cases](https://pkg.go.dev/math#Cos)).
-- `cosh(v instant-vector)`: calculates the hyperbolic cosine of all elements in `v` ([special cases](https://pkg.go.dev/math#Cosh)).
-- `sin(v instant-vector)`: calculates the sine of all elements in `v` ([special cases](https://pkg.go.dev/math#Sin)).
-- `sinh(v instant-vector)`: calculates the hyperbolic sine of all elements in `v` ([special cases](https://pkg.go.dev/math#Sinh)).
-- `tan(v instant-vector)`: calculates the tangent of all elements in `v` ([special cases](https://pkg.go.dev/math#Tan)).
-- `tanh(v instant-vector)`: calculates the hyperbolic tangent of all elements in `v` ([special cases](https://pkg.go.dev/math#Tanh)).
+* `acos(v instant-vector)`: calculates the arccosine of all elements in `v` ([special cases](https://pkg.go.dev/math#Acos)).
+* `acosh(v instant-vector)`: calculates the inverse hyperbolic cosine of all elements in `v` ([special cases](https://pkg.go.dev/math#Acosh)).
+* `asin(v instant-vector)`: calculates the arcsine of all elements in `v` ([special cases](https://pkg.go.dev/math#Asin)).
+* `asinh(v instant-vector)`: calculates the inverse hyperbolic sine of all elements in `v` ([special cases](https://pkg.go.dev/math#Asinh)).
+* `atan(v instant-vector)`: calculates the arctangent of all elements in `v` ([special cases](https://pkg.go.dev/math#Atan)).
+* `atanh(v instant-vector)`: calculates the inverse hyperbolic tangent of all elements in `v` ([special cases](https://pkg.go.dev/math#Atanh)).
+* `cos(v instant-vector)`: calculates the cosine of all elements in `v` ([special cases](https://pkg.go.dev/math#Cos)).
+* `cosh(v instant-vector)`: calculates the hyperbolic cosine of all elements in `v` ([special cases](https://pkg.go.dev/math#Cosh)).
+* `sin(v instant-vector)`: calculates the sine of all elements in `v` ([special cases](https://pkg.go.dev/math#Sin)).
+* `sinh(v instant-vector)`: calculates the hyperbolic sine of all elements in `v` ([special cases](https://pkg.go.dev/math#Sinh)).
+* `tan(v instant-vector)`: calculates the tangent of all elements in `v` ([special cases](https://pkg.go.dev/math#Tan)).
+* `tanh(v instant-vector)`: calculates the hyperbolic tangent of all elements in `v` ([special cases](https://pkg.go.dev/math#Tanh)).
The following are useful for converting between degrees and radians:
-- `deg(v instant-vector)`: converts radians to degrees for all elements in `v`.
-- `pi()`: returns pi.
-- `rad(v instant-vector)`: converts degrees to radians for all elements in `v`.
+* `deg(v instant-vector)`: converts radians to degrees for all elements in `v`.
+* `pi()`: returns pi.
+* `rad(v instant-vector)`: converts degrees to radians for all elements in `v`.
diff --git a/docs/storage.md b/docs/storage.md
index 947960fe12f..8f5f42b8c6a 100644
--- a/docs/storage.md
+++ b/docs/storage.md
@@ -137,6 +137,18 @@ will be used.
Expired block cleanup happens in the background. It may take up to two hours
to remove expired blocks. Blocks must be fully expired before they are removed.
+## Right-Sizing Retention Size
+
+If you are utilizing `storage.tsdb.retention.size` to set a size limit, you
+will want to consider the right size for this value relative to the storage you
+have allocated for Prometheus. It is wise to reduce the retention size to provide
+a buffer, ensuring that older entries will be removed before the allocated storage
+for Prometheus becomes full.
+
+At present, we recommend setting the retention size to, at most, 80-85% of your
+allocated Prometheus disk space. This increases the likelihood that older entires
+will be removed prior to hitting any disk limitations.
+
## Remote storage integrations
Prometheus's local storage is limited to a single node's scalability and durability.
@@ -145,31 +157,27 @@ a set of interfaces that allow integrating with remote storage systems.
### Overview
-Prometheus integrates with remote storage systems in three ways:
+Prometheus integrates with remote storage systems in four ways:
-- Prometheus can write samples that it ingests to a remote URL in a standardized format.
-- Prometheus can receive samples from other Prometheus servers in a standardized format.
-- Prometheus can read (back) sample data from a remote URL in a standardized format.
+- Prometheus can write samples that it ingests to a remote URL in a [Remote Write format](https://prometheus.io/docs/specs/remote_write_spec_2_0/).
+- Prometheus can receive samples from other clients in a [Remote Write format](https://prometheus.io/docs/specs/remote_write_spec_2_0/).
+- Prometheus can read (back) sample data from a remote URL in a [Remote Read format](https://github.com/prometheus/prometheus/blob/main/prompb/remote.proto#L31).
+- Prometheus can return sample data requested by clients in a [Remote Read format](https://github.com/prometheus/prometheus/blob/main/prompb/remote.proto#L31).

-The read and write protocols both use a snappy-compressed protocol buffer encoding over
-HTTP. The protocols are not considered as stable APIs yet and may change to use gRPC
-over HTTP/2 in the future, when all hops between Prometheus and the remote storage can
-safely be assumed to support HTTP/2.
+The remote read and write protocols both use a snappy-compressed protocol buffer encoding over
+HTTP. The read protocol is not yet considered as stable API.
+
+The write protocol has a [stable specification for 1.0 version](https://prometheus.io/docs/specs/remote_write_spec/)
+and [experimental specification for 2.0 version](https://prometheus.io/docs/specs/remote_write_spec_2_0/),
+both supported by Prometheus server.
-For details on configuring remote storage integrations in Prometheus, see the
+For details on configuring remote storage integrations in Prometheus as a client, see the
[remote write](configuration/configuration.md#remote_write) and
[remote read](configuration/configuration.md#remote_read) sections of the Prometheus
configuration documentation.
-The built-in remote write receiver can be enabled by setting the
-`--web.enable-remote-write-receiver` command line flag. When enabled,
-the remote write receiver endpoint is `/api/v1/write`.
-
-For details on the request and response messages, see the
-[remote storage protocol buffer definitions](https://github.com/prometheus/prometheus/blob/main/prompb/remote.proto).
-
Note that on the read path, Prometheus only fetches raw series data for a set of
label selectors and time ranges from the remote end. All PromQL evaluation on the
raw data still happens in Prometheus itself. This means that remote read queries
@@ -177,6 +185,11 @@ have some scalability limit, since all necessary data needs to be loaded into th
querying Prometheus server first and then processed there. However, supporting
fully distributed evaluation of PromQL was deemed infeasible for the time being.
+Prometheus also serves both protocols. The built-in remote write receiver can be enabled
+by setting the `--web.enable-remote-write-receiver` command line flag. When enabled,
+the remote write receiver endpoint is `/api/v1/write`. The remote read endpoint is
+available on [`/api/v1/read`](https://prometheus.io/docs/prometheus/latest/querying/remote_read_api/).
+
### Existing integrations
To learn more about existing integrations with remote storage systems, see the
diff --git a/documentation/examples/remote_storage/example_write_adapter/README.md b/documentation/examples/remote_storage/example_write_adapter/README.md
index 739cf3be366..968d2b25cba 100644
--- a/documentation/examples/remote_storage/example_write_adapter/README.md
+++ b/documentation/examples/remote_storage/example_write_adapter/README.md
@@ -19,7 +19,7 @@ remote_write:
protobuf_message: "io.prometheus.write.v2.Request"
```
-or for deprecated Remote Write 1.0 message:
+or for the eventually deprecated Remote Write 1.0 message:
```yaml
remote_write:
diff --git a/documentation/examples/remote_storage/go.mod b/documentation/examples/remote_storage/go.mod
index 2491bbe2db7..8ed5084d913 100644
--- a/documentation/examples/remote_storage/go.mod
+++ b/documentation/examples/remote_storage/go.mod
@@ -1,26 +1,26 @@
module github.com/prometheus/prometheus/documentation/examples/remote_storage
-go 1.21
+go 1.22.0
require (
github.com/alecthomas/kingpin/v2 v2.4.0
github.com/go-kit/log v0.2.1
github.com/gogo/protobuf v1.3.2
github.com/golang/snappy v0.0.4
- github.com/influxdata/influxdb v1.11.5
- github.com/prometheus/client_golang v1.19.1
- github.com/prometheus/common v0.55.0
- github.com/prometheus/prometheus v0.52.1
+ github.com/influxdata/influxdb v1.11.6
+ github.com/prometheus/client_golang v1.20.2
+ github.com/prometheus/common v0.57.0
+ github.com/prometheus/prometheus v0.53.1
github.com/stretchr/testify v1.9.0
)
require (
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.11.1 // indirect
- github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.5.2 // indirect
- github.com/Azure/azure-sdk-for-go/sdk/internal v1.6.0 // indirect
+ github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.6.0 // indirect
+ github.com/Azure/azure-sdk-for-go/sdk/internal v1.8.0 // indirect
github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2 // indirect
github.com/alecthomas/units v0.0.0-20231202071711-9a357b53e9c9 // indirect
- github.com/aws/aws-sdk-go v1.53.16 // indirect
+ github.com/aws/aws-sdk-go v1.55.5 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
@@ -35,8 +35,7 @@ require (
github.com/jmespath/go-jmespath v0.4.0 // indirect
github.com/jpillora/backoff v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
- github.com/klauspost/compress v1.17.8 // indirect
- github.com/kr/text v0.2.0 // indirect
+ github.com/klauspost/compress v1.17.9 // indirect
github.com/kylelemons/godebug v1.1.0 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
@@ -56,14 +55,14 @@ require (
go.opentelemetry.io/otel/trace v1.27.0 // indirect
go.uber.org/atomic v1.11.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
- golang.org/x/crypto v0.24.0 // indirect
- golang.org/x/net v0.26.0 // indirect
+ golang.org/x/crypto v0.25.0 // indirect
+ golang.org/x/net v0.27.0 // indirect
golang.org/x/oauth2 v0.21.0 // indirect
- golang.org/x/sys v0.21.0 // indirect
+ golang.org/x/sys v0.22.0 // indirect
golang.org/x/text v0.16.0 // indirect
golang.org/x/time v0.5.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240528184218-531527333157 // indirect
- google.golang.org/grpc v1.64.0 // indirect
+ google.golang.org/grpc v1.65.0 // indirect
google.golang.org/protobuf v1.34.2 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
diff --git a/documentation/examples/remote_storage/go.sum b/documentation/examples/remote_storage/go.sum
index 9898d75d70b..1abeff7eb16 100644
--- a/documentation/examples/remote_storage/go.sum
+++ b/documentation/examples/remote_storage/go.sum
@@ -1,9 +1,9 @@
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.11.1 h1:E+OJmp2tPvt1W+amx48v1eqbjDYsgN+RzP4q16yV5eM=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.11.1/go.mod h1:a6xsAQUZg+VsS3TJ05SRp524Hs4pZ/AeFSr5ENf0Yjo=
-github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.5.2 h1:FDif4R1+UUR+00q6wquyX90K7A8dN+R5E8GEadoP7sU=
-github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.5.2/go.mod h1:aiYBYui4BJ/BJCAIKs92XiPyQfTaBWqvHujDwKb6CBU=
-github.com/Azure/azure-sdk-for-go/sdk/internal v1.6.0 h1:sUFnFjzDUie80h24I7mrKtwCKgLY9L8h5Tp2x9+TWqk=
-github.com/Azure/azure-sdk-for-go/sdk/internal v1.6.0/go.mod h1:52JbnQTp15qg5mRkMBHwp0j0ZFwHJ42Sx3zVV5RE9p0=
+github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.6.0 h1:U2rTu3Ef+7w9FHKIAXM6ZyqF3UOWJZ12zIm8zECAFfg=
+github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.6.0/go.mod h1:9kIvujWAA58nmPmWB1m23fyWic1kYZMxD9CxaWn4Qpg=
+github.com/Azure/azure-sdk-for-go/sdk/internal v1.8.0 h1:jBQA3cKT4L2rWMpgE7Yt3Hwh2aUj8KXjIGLxjHeYNNo=
+github.com/Azure/azure-sdk-for-go/sdk/internal v1.8.0/go.mod h1:4OG6tQ9EOP/MT0NMjDlRzWoVFxfu9rN9B2X+tlSVktg=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute/v5 v5.7.0 h1:LkHbJbgF3YyvC53aqYGR+wWQDn2Rdp9AQdGndf9QvY4=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute/v5 v5.7.0/go.mod h1:QyiQdW4f4/BIfB8ZutZ2s+28RAgfa/pT+zS++ZHyM1I=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork/v4 v4.3.0 h1:bXwSugBiSbgtz7rOtbfGf+woewp4f06orW9OP5BjHLA=
@@ -26,8 +26,8 @@ github.com/alecthomas/units v0.0.0-20231202071711-9a357b53e9c9/go.mod h1:OMCwj8V
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.53.16 h1:8oZjKQO/ml1WLUZw5hvF7pvYjPf8o9f57Wldoy/q9Qc=
-github.com/aws/aws-sdk-go v1.53.16/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk=
+github.com/aws/aws-sdk-go v1.55.5 h1:KKUZBfBoyqy5d3swXyiC7Q76ic40rYcbqH7qjh59kzU=
+github.com/aws/aws-sdk-go v1.55.5/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU=
github.com/bboreham/go-loser v0.0.0-20230920113527-fcc2c21820a3 h1:6df1vn4bBlDDo4tARvBm7l6KA9iVMnE3NWizDeWSrps=
github.com/bboreham/go-loser v0.0.0-20230920113527-fcc2c21820a3/go.mod h1:CIWtjkly68+yqLPbvwwR/fjNJA/idrtULjZWh2v1ys0=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
@@ -37,9 +37,8 @@ github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6r
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
-github.com/cncf/xds/go v0.0.0-20240318125728-8a4994d93e50 h1:DBmgJDC9dTfkVyGgipamEh2BpGYxScCH1TOF1LL1cXc=
-github.com/cncf/xds/go v0.0.0-20240318125728-8a4994d93e50/go.mod h1:5e1+Vvlzido69INQaVO6d87Qn543Xr6nooe9Kz7oBFM=
-github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
+github.com/cncf/xds/go v0.0.0-20240423153145-555b57ec207b h1:ga8SEFjZ60pxLcmhnThWgvH2wg8376yUJmPhEH4H3kw=
+github.com/cncf/xds/go v0.0.0-20240423153145-555b57ec207b/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8=
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=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
@@ -50,8 +49,6 @@ github.com/digitalocean/godo v1.117.0 h1:WVlTe09melDYTd7VCVyvHcNWbgB+uI1O115+5LO
github.com/digitalocean/godo v1.117.0/go.mod h1:Vk0vpCot2HOAJwc5WE8wljZGtJ3ZtWIc8MQ8rF38sdo=
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 v26.1.3+incompatible h1:lLCzRbrVZrljpVNobJu1J2FHk8V0s4BawoZippkc+xo=
github.com/docker/docker v26.1.3+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ=
@@ -169,8 +166,8 @@ github.com/hetznercloud/hcloud-go/v2 v2.9.0 h1:s0N6R7Zoi2DPfMtUF5o9VeUBzTtHVY6MI
github.com/hetznercloud/hcloud-go/v2 v2.9.0/go.mod h1:qtW/TuU7Bs16ibXl/ktJarWqU2LwHr7eGlwoilHxtgg=
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.5 h1:+em5VOl6lhAZubXj5o6SobCwvrRs3XDlBx/MUI4schI=
-github.com/influxdata/influxdb v1.11.5/go.mod h1:k8sWREQl1/9t46VrkrH5adUM4UNGIt206ipO3plbkw8=
+github.com/influxdata/influxdb v1.11.6 h1:zS5MRY+RQ5/XFTer5R8xQRnY17JYSbacvO6OaP164wU=
+github.com/influxdata/influxdb v1.11.6/go.mod h1:F10NoQb9qa04lME3pTPWQrYt4JZ/ke1Eei+1ttgHHrg=
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/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
@@ -190,8 +187,8 @@ github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7V
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
-github.com/klauspost/compress v1.17.8 h1:YcnTYrq7MikUT7k0Yb5eceMmALQPYBW/Xltxn0NAMnU=
-github.com/klauspost/compress v1.17.8/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
+github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA=
+github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
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=
@@ -256,8 +253,8 @@ github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXP
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=
-github.com/prometheus/client_golang v1.19.1 h1:wZWJDwK+NameRJuPGDhlnFgx8e8HN3XHQeLaYJFJBOE=
-github.com/prometheus/client_golang v1.19.1/go.mod h1:mP78NwGzrVks5S2H6ab8+ZZGJLZUq1hoULYBAYBw1Ho=
+github.com/prometheus/client_golang v1.20.2 h1:5ctymQzZlyOON1666svgwn3s6IKWgfbjsejTMiXIyjg=
+github.com/prometheus/client_golang v1.20.2/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
@@ -267,8 +264,8 @@ github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y8
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.55.0 h1:KEi6DK7lXW/m7Ig5i47x0vRzuBsHuvJdi5ee6Y3G1dc=
-github.com/prometheus/common v0.55.0/go.mod h1:2SECS4xJG1kd8XF9IcM1gMX6510RAEL65zxzNImwdc8=
+github.com/prometheus/common v0.57.0 h1:Ro/rKjwdq9mZn1K5QPctzh+MA4Lp0BuYk5ZZEVhoNcY=
+github.com/prometheus/common v0.57.0/go.mod h1:7uRPFSUTbfZWsJ7MHY56sqt7hLQu3bxXHDnNhl8E9qI=
github.com/prometheus/common/sigv4 v0.1.0 h1:qoVebwtwwEhS85Czm2dSROY5fTo2PAPEVdDeppTwGX4=
github.com/prometheus/common/sigv4 v0.1.0/go.mod h1:2Jkxxk9yYvCkE5G1sQT7GuEXm57JrvHu9k5YwTjsNtI=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
@@ -279,8 +276,8 @@ github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0leargg
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
github.com/prometheus/prometheus v0.53.1-0.20240704074759-c137febfcf8c h1:6GEA48LnonkYZhQ654v7QTIP5uBTbCEVm49oIhif5lc=
github.com/prometheus/prometheus v0.53.1-0.20240704074759-c137febfcf8c/go.mod h1:FcNs5wa7M9yV8IlxlB/05s5oy9vULUIlu/tZsviRIT8=
-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/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
+github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
github.com/scaleway/scaleway-sdk-go v1.0.0-beta.27 h1:yGAraK1uUjlhSXgNMIy8o/J4LFNcy7yeipBqt9N9mVg=
github.com/scaleway/scaleway-sdk-go v1.0.0-beta.27/go.mod h1:fCa7OJZ/9DRTnOKmxvT6pn+LPWUptQAmHF/SBJUGEcg=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
@@ -326,8 +323,8 @@ golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnf
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
-golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI=
-golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM=
+golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30=
+golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M=
golang.org/x/exp v0.0.0-20240119083558-1b970713d09a h1:Q8/wZp0KX97QFTc2ywcOE0YRjZPVIx+MXInMzdvQqcA=
golang.org/x/exp v0.0.0-20240119083558-1b970713d09a/go.mod h1:idGWGoKP1toJGkd5/ig9ZLuPcZBC3ewk7SzmH0uou08=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
@@ -347,8 +344,8 @@ golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81R
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
-golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ=
-golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE=
+golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys=
+golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.21.0 h1:tsimM75w1tF/uws5rbeHzIWxEqElMehnc+iW793zsZs=
@@ -376,11 +373,11 @@ golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws=
-golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI=
+golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
-golang.org/x/term v0.21.0 h1:WVXCp+/EBEHOj53Rvu+7KiT/iElMrO8ACK16SMZ3jaA=
-golang.org/x/term v0.21.0/go.mod h1:ooXLefLobQVslOqselCNF4SxFAaoS6KujMbsGzSDmX0=
+golang.org/x/term v0.22.0 h1:BbsgPEJULsl2fV/AT3v15Mjva5yXKQDyKf+TbDz7QJk=
+golang.org/x/term v0.22.0/go.mod h1:F3qCibpT5AMpCRfhfT53vVJwhLtIVHhB9XDjfFvnMI4=
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=
@@ -405,8 +402,8 @@ google.golang.org/genproto/googleapis/api v0.0.0-20240528184218-531527333157 h1:
google.golang.org/genproto/googleapis/api v0.0.0-20240528184218-531527333157/go.mod h1:99sLkeliLXfdj2J75X3Ho+rrVCaJze0uwN7zDDkjPVU=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240528184218-531527333157 h1:Zy9XzmMEflZ/MAaA7vNcoebnRAld7FsPW1EeBB7V0m8=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240528184218-531527333157/go.mod h1:EfXuqaE1J41VCDicxHzUDm+8rk+7ZdXzHV0IhO/I6s0=
-google.golang.org/grpc v1.64.0 h1:KH3VH9y/MgNQg1dE7b3XfVK0GsPSIzJwdF617gUSbvY=
-google.golang.org/grpc v1.64.0/go.mod h1:oxjF8E3FBnjp+/gVFYdWacaLDx9na1aqy9oovLpxQYg=
+google.golang.org/grpc v1.65.0 h1:bs/cUb4lp1G5iImFFd3u5ixQzweKizoZJAwBNLR42lc=
+google.golang.org/grpc v1.65.0/go.mod h1:WgYC2ypjlB0EiQi6wdKixMqukr6lBc0Vo+oOgjrM5ZQ=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
diff --git a/go.mod b/go.mod
index ce2f0714a0a..0631611234b 100644
--- a/go.mod
+++ b/go.mod
@@ -1,25 +1,27 @@
module github.com/prometheus/prometheus
-go 1.21
+go 1.22.0
+
+toolchain go1.23.0
require (
- github.com/Azure/azure-sdk-for-go/sdk/azcore v1.11.1
- github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.5.2
+ github.com/Azure/azure-sdk-for-go/sdk/azcore v1.14.0
+ github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.7.0
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute/v5 v5.7.0
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork/v4 v4.3.0
github.com/Code-Hex/go-generics-cache v1.5.1
github.com/KimMachineGun/automemlimit v0.6.1
github.com/alecthomas/kingpin/v2 v2.4.0
- github.com/alecthomas/units v0.0.0-20231202071711-9a357b53e9c9
- github.com/aws/aws-sdk-go v1.53.16
+ github.com/alecthomas/units v0.0.0-20240626203959-61d1e3462e30
+ github.com/aws/aws-sdk-go v1.55.5
github.com/bboreham/go-loser v0.0.0-20230920113527-fcc2c21820a3
github.com/cespare/xxhash/v2 v2.3.0
github.com/dennwc/varint v1.0.0
- github.com/digitalocean/godo v1.117.0
- github.com/docker/docker v26.1.3+incompatible
+ github.com/digitalocean/godo v1.122.0
+ github.com/docker/docker v27.2.0+incompatible
github.com/edsrzf/mmap-go v1.1.0
- github.com/envoyproxy/go-control-plane v0.12.0
- github.com/envoyproxy/protoc-gen-validate v1.0.4
+ github.com/envoyproxy/go-control-plane v0.13.0
+ github.com/envoyproxy/protoc-gen-validate v1.1.0
github.com/facette/natsort v0.0.0-20181210072756-2cd4dd1e2dcb
github.com/fsnotify/fsnotify v1.7.0
github.com/go-kit/log v0.2.1
@@ -29,75 +31,74 @@ require (
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-20240528025155-186aa0362fba
+ github.com/google/pprof v0.0.0-20240711041743-f6c9dda6c6da
github.com/google/uuid v1.6.0
- github.com/gophercloud/gophercloud v1.12.0
+ github.com/gophercloud/gophercloud v1.14.0
github.com/grafana/regexp v0.0.0-20240518133315-a468a5bfb3bc
github.com/grpc-ecosystem/grpc-gateway v1.16.0
- github.com/hashicorp/consul/api v1.29.1
- github.com/hashicorp/nomad/api v0.0.0-20240604134157-e73d8bb1140d
- github.com/hetznercloud/hcloud-go/v2 v2.9.0
- github.com/ionos-cloud/sdk-go/v6 v6.1.11
+ github.com/hashicorp/consul/api v1.29.4
+ github.com/hashicorp/nomad/api v0.0.0-20240717122358-3d93bd3778f3
+ github.com/hetznercloud/hcloud-go/v2 v2.13.1
+ github.com/ionos-cloud/sdk-go/v6 v6.2.1
github.com/json-iterator/go v1.1.12
- github.com/klauspost/compress v1.17.8
+ github.com/klauspost/compress v1.17.9
github.com/kolo/xmlrpc v0.0.0-20220921171641-a4b6fa1dd06b
- github.com/linode/linodego v1.35.0
- github.com/miekg/dns v1.1.59
+ github.com/linode/linodego v1.40.0
+ github.com/miekg/dns v1.1.62
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f
github.com/nsf/jsondiff v0.0.0-20230430225905-43f6cf3098c1
github.com/oklog/run v1.1.0
github.com/oklog/ulid v1.3.1
- github.com/ovh/go-ovh v1.5.1
+ github.com/ovh/go-ovh v1.6.0
github.com/prometheus/alertmanager v0.27.0
- github.com/prometheus/client_golang v1.19.1
+ github.com/prometheus/client_golang v1.20.3
github.com/prometheus/client_model v0.6.1
- github.com/prometheus/common v0.54.0
+ github.com/prometheus/common v0.59.1
github.com/prometheus/common/assets v0.2.0
github.com/prometheus/common/sigv4 v0.1.0
- github.com/prometheus/exporter-toolkit v0.11.0
- github.com/scaleway/scaleway-sdk-go v1.0.0-beta.27
+ github.com/prometheus/exporter-toolkit v0.12.0
+ github.com/scaleway/scaleway-sdk-go v1.0.0-beta.30
github.com/shurcooL/httpfs v0.0.0-20230704072500-f1e31cf0ba5c
github.com/stretchr/testify v1.9.0
github.com/vultr/govultr/v2 v2.17.2
- go.opentelemetry.io/collector/pdata v1.8.0
- go.opentelemetry.io/collector/semconv v0.101.0
- go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.52.0
- go.opentelemetry.io/otel v1.27.0
- go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.27.0
- go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.27.0
- go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.27.0
- go.opentelemetry.io/otel/sdk v1.27.0
- go.opentelemetry.io/otel/trace v1.27.0
+ go.opentelemetry.io/collector/pdata v1.14.1
+ go.opentelemetry.io/collector/semconv v0.108.1
+ go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0
+ go.opentelemetry.io/otel v1.29.0
+ go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.29.0
+ go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.29.0
+ go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.29.0
+ go.opentelemetry.io/otel/sdk v1.29.0
+ go.opentelemetry.io/otel/trace v1.29.0
go.uber.org/atomic v1.11.0
go.uber.org/automaxprocs v1.5.3
go.uber.org/goleak v1.3.0
go.uber.org/multierr v1.11.0
- golang.org/x/net v0.26.0
- golang.org/x/oauth2 v0.21.0
- golang.org/x/sync v0.7.0
- golang.org/x/sys v0.21.0
- golang.org/x/text v0.16.0
- golang.org/x/time v0.5.0
- golang.org/x/tools v0.22.0
- google.golang.org/api v0.183.0
- google.golang.org/genproto/googleapis/api v0.0.0-20240528184218-531527333157
- google.golang.org/grpc v1.64.0
- google.golang.org/protobuf v1.34.1
+ golang.org/x/oauth2 v0.23.0
+ golang.org/x/sync v0.8.0
+ golang.org/x/sys v0.25.0
+ golang.org/x/text v0.18.0
+ golang.org/x/time v0.6.0
+ golang.org/x/tools v0.24.0
+ google.golang.org/api v0.195.0
+ google.golang.org/genproto/googleapis/api v0.0.0-20240827150818-7e3bb234dfed
+ google.golang.org/grpc v1.66.0
+ google.golang.org/protobuf v1.34.2
gopkg.in/yaml.v2 v2.4.0
gopkg.in/yaml.v3 v3.0.1
- k8s.io/api v0.29.3
- k8s.io/apimachinery v0.29.3
- k8s.io/client-go v0.29.3
+ k8s.io/api v0.31.0
+ k8s.io/apimachinery v0.31.0
+ k8s.io/client-go v0.31.0
k8s.io/klog v1.0.0
- k8s.io/klog/v2 v2.120.1
+ k8s.io/klog/v2 v2.130.1
)
require (
- cloud.google.com/go/auth v0.5.1 // indirect
- cloud.google.com/go/auth/oauth2adapt v0.2.2 // indirect
- cloud.google.com/go/compute/metadata v0.3.0 // indirect
- github.com/Azure/azure-sdk-for-go/sdk/internal v1.6.0 // indirect
+ cloud.google.com/go/auth v0.9.3 // indirect
+ cloud.google.com/go/auth/oauth2adapt v0.2.4 // indirect
+ cloud.google.com/go/compute/metadata v0.5.0 // indirect
+ github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 // indirect
github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2 // indirect
github.com/Microsoft/go-winio v0.6.1 // indirect
github.com/armon/go-metrics v0.4.1 // indirect
@@ -105,7 +106,7 @@ require (
github.com/beorn7/perks v1.0.1 // indirect
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
github.com/cilium/ebpf v0.11.0 // indirect
- github.com/cncf/xds/go v0.0.0-20240318125728-8a4994d93e50 // indirect
+ github.com/cncf/xds/go v0.0.0-20240423153145-555b57ec207b // indirect
github.com/containerd/cgroups/v3 v3.0.3 // indirect
github.com/containerd/log v0.1.0 // indirect
github.com/coreos/go-systemd/v22 v22.5.0 // indirect
@@ -114,12 +115,12 @@ require (
github.com/docker/go-connections v0.4.0 // indirect
github.com/docker/go-units v0.5.0 // indirect
github.com/emicklei/go-restful/v3 v3.11.0 // indirect
- github.com/evanphx/json-patch v5.6.0+incompatible // indirect
github.com/fatih/color v1.16.0 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
+ github.com/fxamacker/cbor/v2 v2.7.0 // indirect
github.com/ghodss/yaml v1.0.0 // indirect
github.com/go-kit/kit v0.12.0 // indirect
- github.com/go-logr/logr v1.4.1 // indirect
+ github.com/go-logr/logr v1.4.2 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-openapi/analysis v0.22.2 // indirect
github.com/go-openapi/errors v0.22.0 // indirect
@@ -132,17 +133,17 @@ require (
github.com/go-resty/resty/v2 v2.13.1 // indirect
github.com/godbus/dbus/v5 v5.0.4 // indirect
github.com/golang-jwt/jwt/v5 v5.2.1 // indirect
- github.com/golang/glog v1.2.0 // indirect
+ github.com/golang/glog v1.2.1 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/golang/protobuf v1.5.4 // indirect
github.com/google/gnostic-models v0.6.8 // 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
- github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect
- github.com/googleapis/gax-go/v2 v2.12.4 // indirect
+ github.com/google/s2a-go v0.1.8 // indirect
+ github.com/googleapis/enterprise-certificate-proxy v0.3.4 // indirect
+ github.com/googleapis/gax-go/v2 v2.13.0 // indirect
github.com/gorilla/websocket v1.5.0 // indirect
- github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 // indirect
+ github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 // indirect
github.com/hashicorp/cronexpr v1.1.2 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
@@ -162,6 +163,8 @@ require (
github.com/mailru/easyjson v0.7.7 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
+ github.com/mdlayher/socket v0.4.1 // indirect
+ github.com/mdlayher/vsock v1.2.1 // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/moby/docker-image-spec v1.3.1 // indirect
@@ -175,34 +178,38 @@ require (
github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 // indirect
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect
github.com/pkg/errors v0.9.1 // indirect
+ github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
- github.com/prometheus/procfs v0.12.0 // indirect
+ github.com/prometheus/procfs v0.15.1 // indirect
github.com/sirupsen/logrus v1.9.3 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/stretchr/objx v0.5.2 // indirect
+ github.com/x448/float16 v0.8.4 // indirect
github.com/xhit/go-str2duration/v2 v2.1.0 // indirect
go.mongodb.org/mongo-driver v1.14.0 // indirect
go.opencensus.io v0.24.0 // indirect
- go.opentelemetry.io/otel/metric v1.27.0 // indirect
- go.opentelemetry.io/proto/otlp v1.2.0 // indirect
- golang.org/x/crypto v0.24.0 // indirect
+ go.opentelemetry.io/otel/metric v1.29.0 // indirect
+ go.opentelemetry.io/proto/otlp v1.3.1 // indirect
+ golang.org/x/crypto v0.26.0 // indirect
golang.org/x/exp v0.0.0-20240119083558-1b970713d09a // indirect
- golang.org/x/mod v0.18.0 // indirect
- golang.org/x/term v0.21.0 // indirect
- google.golang.org/genproto/googleapis/rpc v0.0.0-20240528184218-531527333157 // indirect
+ golang.org/x/mod v0.20.0 // indirect
+ golang.org/x/net v0.28.0 // indirect
+ golang.org/x/term v0.23.0 // indirect
+ google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 // indirect
+ gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gotest.tools/v3 v3.0.3 // indirect
k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 // indirect
- k8s.io/utils v0.0.0-20230726121419-3b25d923346b // indirect
+ k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 // indirect
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect
sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect
- sigs.k8s.io/yaml v1.3.0 // indirect
+ sigs.k8s.io/yaml v1.4.0 // indirect
)
replace (
k8s.io/klog => github.com/simonpasquier/klog-gokit v0.3.0
- k8s.io/klog/v2 => github.com/simonpasquier/klog-gokit/v3 v3.3.0
+ k8s.io/klog/v2 => github.com/simonpasquier/klog-gokit/v3 v3.5.0
)
// Exclude linodego v1.0.0 as it is no longer published on github.
diff --git a/go.sum b/go.sum
index 956b9d89492..0246a377d26 100644
--- a/go.sum
+++ b/go.sum
@@ -12,18 +12,18 @@ cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bP
cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=
cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
-cloud.google.com/go/auth v0.5.1 h1:0QNO7VThG54LUzKiQxv8C6x1YX7lUrzlAa1nVLF8CIw=
-cloud.google.com/go/auth v0.5.1/go.mod h1:vbZT8GjzDf3AVqCcQmqeeM32U9HBFc32vVVAbwDsa6s=
-cloud.google.com/go/auth/oauth2adapt v0.2.2 h1:+TTV8aXpjeChS9M+aTtN/TjdQnzJvmzKFt//oWu7HX4=
-cloud.google.com/go/auth/oauth2adapt v0.2.2/go.mod h1:wcYjgpZI9+Yu7LyYBg4pqSiaRkfEK3GQcpb7C/uyF1Q=
+cloud.google.com/go/auth v0.9.3 h1:VOEUIAADkkLtyfr3BLa3R8Ed/j6w1jTBmARx+wb5w5U=
+cloud.google.com/go/auth v0.9.3/go.mod h1:7z6VY+7h3KUdRov5F1i8NDP5ZzWKYmEPO842BgCsmTk=
+cloud.google.com/go/auth/oauth2adapt v0.2.4 h1:0GWE/FUsXhf6C+jAkWgYm7X9tK8cuEIfy19DBn6B6bY=
+cloud.google.com/go/auth/oauth2adapt v0.2.4/go.mod h1:jC/jOpwFP6JBxhB3P5Rr0a9HLMC/Pe3eaL4NmdvqPtc=
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
-cloud.google.com/go/compute/metadata v0.3.0 h1:Tz+eQXMEqDIKRsmY3cHTL6FVaynIjX2QxYC4trgAKZc=
-cloud.google.com/go/compute/metadata v0.3.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k=
+cloud.google.com/go/compute/metadata v0.5.0 h1:Zr0eK8JbFv6+Wi4ilXAR8FJ3wyNdpxHKJNPos6LTZOY=
+cloud.google.com/go/compute/metadata v0.5.0/go.mod h1:aHnloV2TPI38yx4s9+wAZhHykWvVCfu7hQbF+9CWoiY=
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
@@ -36,12 +36,12 @@ cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohl
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
-github.com/Azure/azure-sdk-for-go/sdk/azcore v1.11.1 h1:E+OJmp2tPvt1W+amx48v1eqbjDYsgN+RzP4q16yV5eM=
-github.com/Azure/azure-sdk-for-go/sdk/azcore v1.11.1/go.mod h1:a6xsAQUZg+VsS3TJ05SRp524Hs4pZ/AeFSr5ENf0Yjo=
-github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.5.2 h1:FDif4R1+UUR+00q6wquyX90K7A8dN+R5E8GEadoP7sU=
-github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.5.2/go.mod h1:aiYBYui4BJ/BJCAIKs92XiPyQfTaBWqvHujDwKb6CBU=
-github.com/Azure/azure-sdk-for-go/sdk/internal v1.6.0 h1:sUFnFjzDUie80h24I7mrKtwCKgLY9L8h5Tp2x9+TWqk=
-github.com/Azure/azure-sdk-for-go/sdk/internal v1.6.0/go.mod h1:52JbnQTp15qg5mRkMBHwp0j0ZFwHJ42Sx3zVV5RE9p0=
+github.com/Azure/azure-sdk-for-go/sdk/azcore v1.14.0 h1:nyQWyZvwGTvunIMxi1Y9uXkcyr+I7TeNrr/foo4Kpk8=
+github.com/Azure/azure-sdk-for-go/sdk/azcore v1.14.0/go.mod h1:l38EPgmsp71HHLq9j7De57JcKOWPyhrsW1Awm1JS6K0=
+github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.7.0 h1:tfLQ34V6F7tVSwoTf/4lH5sE0o6eCJuNDTmH09nDpbc=
+github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.7.0/go.mod h1:9kIvujWAA58nmPmWB1m23fyWic1kYZMxD9CxaWn4Qpg=
+github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 h1:ywEEhmNahHBihViHepv3xPBn1663uRv2t2q/ESv9seY=
+github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0/go.mod h1:iZDifYGJTIgIIkYRNWPENUnqx6bJ2xnSDFI2tjwZNuY=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute/v5 v5.7.0 h1:LkHbJbgF3YyvC53aqYGR+wWQDn2Rdp9AQdGndf9QvY4=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute/v5 v5.7.0/go.mod h1:QyiQdW4f4/BIfB8ZutZ2s+28RAgfa/pT+zS++ZHyM1I=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/internal/v2 v2.0.0 h1:PTFGRSlMKCQelWwxUyYVEUqseBJVemLyqWJjvMyt0do=
@@ -75,8 +75,8 @@ github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuy
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
-github.com/alecthomas/units v0.0.0-20231202071711-9a357b53e9c9 h1:ez/4by2iGztzR4L0zgAOR8lTQK9VlyBVVd7G4omaOQs=
-github.com/alecthomas/units v0.0.0-20231202071711-9a357b53e9c9/go.mod h1:OMCwj8VM1Kc9e19TLln2VL61YJF0x1XFtfdL4JdbSyE=
+github.com/alecthomas/units v0.0.0-20240626203959-61d1e3462e30 h1:t3eaIm0rUkzbrIewtiFmMK5RXHej2XnoXNhxVsAYUfg=
+github.com/alecthomas/units v0.0.0-20240626203959-61d1e3462e30/go.mod h1:fvzegU4vN3H1qMT+8wDmzjAcDONcgo2/SZ/TyfdUOFs=
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
@@ -92,8 +92,8 @@ github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:W
github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU=
github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
github.com/aws/aws-sdk-go v1.38.35/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro=
-github.com/aws/aws-sdk-go v1.53.16 h1:8oZjKQO/ml1WLUZw5hvF7pvYjPf8o9f57Wldoy/q9Qc=
-github.com/aws/aws-sdk-go v1.53.16/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk=
+github.com/aws/aws-sdk-go v1.55.5 h1:KKUZBfBoyqy5d3swXyiC7Q76ic40rYcbqH7qjh59kzU=
+github.com/aws/aws-sdk-go v1.55.5/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU=
github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g=
github.com/bboreham/go-loser v0.0.0-20230920113527-fcc2c21820a3 h1:6df1vn4bBlDDo4tARvBm7l6KA9iVMnE3NWizDeWSrps=
github.com/bboreham/go-loser v0.0.0-20230920113527-fcc2c21820a3/go.mod h1:CIWtjkly68+yqLPbvwwR/fjNJA/idrtULjZWh2v1ys0=
@@ -120,8 +120,8 @@ github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp
github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
-github.com/cncf/xds/go v0.0.0-20240318125728-8a4994d93e50 h1:DBmgJDC9dTfkVyGgipamEh2BpGYxScCH1TOF1LL1cXc=
-github.com/cncf/xds/go v0.0.0-20240318125728-8a4994d93e50/go.mod h1:5e1+Vvlzido69INQaVO6d87Qn543Xr6nooe9Kz7oBFM=
+github.com/cncf/xds/go v0.0.0-20240423153145-555b57ec207b h1:ga8SEFjZ60pxLcmhnThWgvH2wg8376yUJmPhEH4H3kw=
+github.com/cncf/xds/go v0.0.0-20240423153145-555b57ec207b/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8=
github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8=
github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI=
github.com/containerd/cgroups/v3 v3.0.3 h1:S5ByHZ/h9PMe5IOQoN7E+nMc2UcLEM/V48DGDJ9kip0=
@@ -143,14 +143,14 @@ 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/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
-github.com/digitalocean/godo v1.117.0 h1:WVlTe09melDYTd7VCVyvHcNWbgB+uI1O115+5LOtdSw=
-github.com/digitalocean/godo v1.117.0/go.mod h1:Vk0vpCot2HOAJwc5WE8wljZGtJ3ZtWIc8MQ8rF38sdo=
+github.com/digitalocean/godo v1.122.0 h1:ziytLQi8QKtDp2K1A+YrYl2dWLHLh2uaMzWvcz9HkKg=
+github.com/digitalocean/godo v1.122.0/go.mod h1:WQVH83OHUy6gC4gXpEVQKtxTd4L5oCp+5OialidkPLY=
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 v26.1.3+incompatible h1:lLCzRbrVZrljpVNobJu1J2FHk8V0s4BawoZippkc+xo=
-github.com/docker/docker v26.1.3+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
+github.com/docker/docker v27.2.0+incompatible h1:Rk9nIVdfH3+Vz4cyI/uhbINhEZ/oLmc+CBXmH6fbNk4=
+github.com/docker/docker v27.2.0+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=
@@ -168,13 +168,11 @@ github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4s
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
-github.com/envoyproxy/go-control-plane v0.12.0 h1:4X+VP1GHd1Mhj6IB5mMeGbLCleqxjletLK6K0rbxyZI=
-github.com/envoyproxy/go-control-plane v0.12.0/go.mod h1:ZBTaoJ23lqITozF0M6G4/IragXCQKCnYbmlmtHvwRG0=
+github.com/envoyproxy/go-control-plane v0.13.0 h1:HzkeUz1Knt+3bK+8LG1bxOO/jzWZmdxpwC51i202les=
+github.com/envoyproxy/go-control-plane v0.13.0/go.mod h1:GRaKG3dwvFoTg4nj7aXdZnvMg4d7nvT/wl9WgVXn3Q8=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
-github.com/envoyproxy/protoc-gen-validate v1.0.4 h1:gVPz/FMfvh57HdSJQyvBtF00j8JU4zdyUgIUNhlgg0A=
-github.com/envoyproxy/protoc-gen-validate v1.0.4/go.mod h1:qys6tmnRsYrQqIhm2bvKZH4Blx/1gTIZ2UKVY1M+Yew=
-github.com/evanphx/json-patch v5.6.0+incompatible h1:jBYDEEiFBPxA0v50tFdvOzQQTCvpL6mnFh5mB2/l16U=
-github.com/evanphx/json-patch v5.6.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
+github.com/envoyproxy/protoc-gen-validate v1.1.0 h1:tntQDh69XqOCOZsDz0lVJQez/2L6Uu2PdjCQwWCJ3bM=
+github.com/envoyproxy/protoc-gen-validate v1.1.0/go.mod h1:sXRDRVmzEbkM7CVcM06s9shE/m23dg3wzjl0UWqJ2q4=
github.com/facette/natsort v0.0.0-20181210072756-2cd4dd1e2dcb h1:IT4JYU7k4ikYg1SCxNI1/Tieq/NFvh6dzLdgi7eu0tM=
github.com/facette/natsort v0.0.0-20181210072756-2cd4dd1e2dcb/go.mod h1:bH6Xx7IW64qjjJq8M2u4dxNaBiDfKK+z/3eGDpXEQhc=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
@@ -191,6 +189,8 @@ github.com/frankban/quicktest v1.14.5/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7z
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=
+github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E=
+github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ=
github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
@@ -210,8 +210,8 @@ github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG
github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4=
github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
-github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ=
-github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
+github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
+github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
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/analysis v0.22.2 h1:ZBmNoP2h5omLKr/srIC9bfqrUGzT6g6gNv03HE9Vpj0=
@@ -236,8 +236,8 @@ github.com/go-resty/resty/v2 v2.13.1 h1:x+LHXBI2nMB1vqndymf26quycC4aggYJ7DECYbiz
github.com/go-resty/resty/v2 v2.13.1/go.mod h1:GznXlLxkq6Nh4sU59rPmUw3VtgpO3aS96ORAI6Q7d+0=
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-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
+github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
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/godbus/dbus/v5 v5.0.4 h1:9349emZab16e7zQvpmsbtjc18ykshndd8y2PG3sgJbA=
@@ -251,8 +251,8 @@ github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69
github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk=
github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
-github.com/golang/glog v1.2.0 h1:uCdmnmatrKCgMBlM4rMuJZWOkPDqdbZPnrMXDY4gI68=
-github.com/golang/glog v1.2.0/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w=
+github.com/golang/glog v1.2.1 h1:OptwRhECazUx5ix5TTWC3EZhsZEHWcYWY4FQHTIubm4=
+github.com/golang/glog v1.2.1/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w=
github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
@@ -319,23 +319,23 @@ github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hf
github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
-github.com/google/pprof v0.0.0-20240528025155-186aa0362fba h1:ql1qNgCyOB7iAEk8JTNM+zJrgIbnyCKX/wdlyPufP5g=
-github.com/google/pprof v0.0.0-20240528025155-186aa0362fba/go.mod h1:K1liHPHnj73Fdn/EKuT8nrFqBihUSKXoLYU0BuatOYo=
+github.com/google/pprof v0.0.0-20240711041743-f6c9dda6c6da h1:xRmpO92tb8y+Z85iUOMOicpCfaYcv7o3Cg3wKrIpg8g=
+github.com/google/pprof v0.0.0-20240711041743-f6c9dda6c6da/go.mod h1:K1liHPHnj73Fdn/EKuT8nrFqBihUSKXoLYU0BuatOYo=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
-github.com/google/s2a-go v0.1.7 h1:60BLSyTrOV4/haCDW4zb1guZItoSq8foHCXrAnjBo/o=
-github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw=
+github.com/google/s2a-go v0.1.8 h1:zZDs9gcbt9ZPLV0ndSyQk6Kacx2g/X+SKYovpnz3SMM=
+github.com/google/s2a-go v0.1.8/go.mod h1:6iNWHTpQ+nfNRN5E00MSdfDwVesa8hhS32PhPO8deJA=
github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
-github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfFxPRy3Bf7vr3h0cechB90XaQs=
-github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0=
+github.com/googleapis/enterprise-certificate-proxy v0.3.4 h1:XYIDZApgAnrN1c855gTgghdIA6Stxb52D5RnLI1SLyw=
+github.com/googleapis/enterprise-certificate-proxy v0.3.4/go.mod h1:YKe7cfqYXjKGpGvmSg28/fFvhNzinZQm8DGnaburhGA=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
-github.com/googleapis/gax-go/v2 v2.12.4 h1:9gWcmF85Wvq4ryPFvGFaOgPIs1AQX0d0bcbGw4Z96qg=
-github.com/googleapis/gax-go/v2 v2.12.4/go.mod h1:KYEYLorsnIGDi/rPC8b5TdlB9kbKoFubselGIoBMCwI=
-github.com/gophercloud/gophercloud v1.12.0 h1:Jrz16vPAL93l80q16fp8NplrTCp93y7rZh2P3Q4Yq7g=
-github.com/gophercloud/gophercloud v1.12.0/go.mod h1:aAVqcocTSXh2vYFZ1JTvx4EQmfgzxRcNupUfxZbBNDM=
+github.com/googleapis/gax-go/v2 v2.13.0 h1:yitjD5f7jQHhyDsnhKEBU52NdvvdSeGzlAnDPT0hH1s=
+github.com/googleapis/gax-go/v2 v2.13.0/go.mod h1:Z/fvTZXF8/uw7Xu5GuslPw+bplx6SS338j1Is2S+B7A=
+github.com/gophercloud/gophercloud v1.14.0 h1:Bt9zQDhPrbd4qX7EILGmy+i7GP35cc+AAL2+wIJpUE8=
+github.com/gophercloud/gophercloud v1.14.0/go.mod h1:aAVqcocTSXh2vYFZ1JTvx4EQmfgzxRcNupUfxZbBNDM=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
@@ -350,13 +350,13 @@ github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgf
github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo=
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
-github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 h1:bkypFPDjIYGfCYD5mRBvpqxfYX1YCS1PXdKYWi8FsN0=
-github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0/go.mod h1:P+Lt/0by1T8bfcF3z737NnSbmxQAppXMRziHUxPOC8k=
+github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 h1:asbCHRVmodnJTuQ3qamDwqVOIjwqUPTYmYuemVOx+Ys=
+github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0/go.mod h1:ggCgvZ2r7uOoQjOyu2Y1NhHmEPPzzuhWgcza5M1Ji1I=
github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE=
-github.com/hashicorp/consul/api v1.29.1 h1:UEwOjYJrd3lG1x5w7HxDRMGiAUPrb3f103EoeKuuEcc=
-github.com/hashicorp/consul/api v1.29.1/go.mod h1:lumfRkY/coLuqMICkI7Fh3ylMG31mQSRZyef2c5YvJI=
-github.com/hashicorp/consul/proto-public v0.6.1 h1:+uzH3olCrksXYWAYHKqK782CtK9scfqH+Unlw3UHhCg=
-github.com/hashicorp/consul/proto-public v0.6.1/go.mod h1:cXXbOg74KBNGajC+o8RlA502Esf0R9prcoJgiOX/2Tg=
+github.com/hashicorp/consul/api v1.29.4 h1:P6slzxDLBOxUSj3fWo2o65VuKtbtOXFi7TSSgtXutuE=
+github.com/hashicorp/consul/api v1.29.4/go.mod h1:HUlfw+l2Zy68ceJavv2zAyArl2fqhGWnMycyt56sBgg=
+github.com/hashicorp/consul/proto-public v0.6.2 h1:+DA/3g/IiKlJZb88NBn0ZgXrxJp2NlvCZdEyl+qxvL0=
+github.com/hashicorp/consul/proto-public v0.6.2/go.mod h1:cXXbOg74KBNGajC+o8RlA502Esf0R9prcoJgiOX/2Tg=
github.com/hashicorp/consul/sdk v0.3.0/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
github.com/hashicorp/consul/sdk v0.16.1 h1:V8TxTnImoPD5cj0U9Spl0TUxcytjcbbJeADFF07KdHg=
github.com/hashicorp/consul/sdk v0.16.1/go.mod h1:fSXvwxB2hmh1FMZCNl6PwX0Q/1wdWtHJcZ7Ea5tns0s=
@@ -396,8 +396,8 @@ github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/b
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=
+github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKeRZfjY=
+github.com/hashicorp/go-version v1.7.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
@@ -409,13 +409,13 @@ github.com/hashicorp/mdns v1.0.4/go.mod h1:mtBihi+LeNXGtG8L9dX59gAEa12BDtBQSp4v/
github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
github.com/hashicorp/memberlist v0.5.0 h1:EtYPN8DpAURiapus508I4n9CzHs2W+8NZGbmmR/prTM=
github.com/hashicorp/memberlist v0.5.0/go.mod h1:yvyXLpo0QaGE59Y7hDTsTzDD25JYBZ4mHgHUZ8lrOI0=
-github.com/hashicorp/nomad/api v0.0.0-20240604134157-e73d8bb1140d h1:KHq+mAzWSkumj4PDoXc5VZbycPGcmYu8tohgVLQ6SIc=
-github.com/hashicorp/nomad/api v0.0.0-20240604134157-e73d8bb1140d/go.mod h1:svtxn6QnrQ69P23VvIWMR34tg3vmwLz4UdUzm1dSCgE=
+github.com/hashicorp/nomad/api v0.0.0-20240717122358-3d93bd3778f3 h1:fgVfQ4AC1avVOnu2cfms8VAiD8lUq3vWI8mTocOXN/w=
+github.com/hashicorp/nomad/api v0.0.0-20240717122358-3d93bd3778f3/go.mod h1:svtxn6QnrQ69P23VvIWMR34tg3vmwLz4UdUzm1dSCgE=
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
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.9.0 h1:s0N6R7Zoi2DPfMtUF5o9VeUBzTtHVY6MIkHOQnfu/AY=
-github.com/hetznercloud/hcloud-go/v2 v2.9.0/go.mod h1:qtW/TuU7Bs16ibXl/ktJarWqU2LwHr7eGlwoilHxtgg=
+github.com/hetznercloud/hcloud-go/v2 v2.13.1 h1:jq0GP4QaYE5d8xR/Zw17s9qoaESRJMXfGmtD1a/qckQ=
+github.com/hetznercloud/hcloud-go/v2 v2.13.1/go.mod h1:dhix40Br3fDiBhwaSG/zgaYOFFddpfBm/6R1Zz0IiF0=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
@@ -423,8 +423,8 @@ 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/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo=
-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/ionos-cloud/sdk-go/v6 v6.2.1 h1:mxxN+frNVmbFrmmFfXnBC3g2USYJrl6mc1LW2iNYbFY=
+github.com/ionos-cloud/sdk-go/v6 v6.2.1/go.mod h1:SXrO9OGyWjd2rZhAhEpdYN6VUAODzzqRdqA9BCviQtI=
github.com/jarcoal/httpmock v1.3.1 h1:iUx3whfZWVf3jT01hQTO/Eo5sAYtB2/rqaUuOtpInww=
github.com/jarcoal/httpmock v1.3.1/go.mod h1:3yb8rc4BI7TCBhFY8ng0gjuLKJNquuDNiPaZjnENuYg=
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
@@ -454,8 +454,8 @@ github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
-github.com/klauspost/compress v1.17.8 h1:YcnTYrq7MikUT7k0Yb5eceMmALQPYBW/Xltxn0NAMnU=
-github.com/klauspost/compress v1.17.8/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
+github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA=
+github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
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=
@@ -472,8 +472,8 @@ github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM=
github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4=
-github.com/linode/linodego v1.35.0 h1:rIhUeCHBLEDlkoRnOTwzSGzljQ3ksXwLxacmXnrV+Do=
-github.com/linode/linodego v1.35.0/go.mod h1:JxuhOEAMfSxun6RU5/MgTKH2GGTmFrhKRj3wL1NFin0=
+github.com/linode/linodego v1.40.0 h1:7ESY0PwK94hoggoCtIroT1Xk6b1flrFBNZ6KwqbTqlI=
+github.com/linode/linodego v1.40.0/go.mod h1:NsUw4l8QrLdIofRg1NYFBbW5ZERnmbZykVBszPZLORM=
github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ=
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
@@ -497,11 +497,15 @@ github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzp
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/mdlayher/socket v0.4.1 h1:eM9y2/jlbs1M615oshPQOHZzj6R6wMT7bX5NPiQvn2U=
+github.com/mdlayher/socket v0.4.1/go.mod h1:cAqeGjoufqdxWkD7DkpyS+wcefOtmu5OQ8KuoJGIReA=
+github.com/mdlayher/vsock v1.2.1 h1:pC1mTJTvjo1r9n9fbm7S1j04rCgCzhCOS5DY0zqHlnQ=
+github.com/mdlayher/vsock v1.2.1/go.mod h1:NRfCibel++DgeMD8z/hP+PPTjlNJsdPOmxcnENvE+SE=
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=
-github.com/miekg/dns v1.1.59 h1:C9EXc/UToRwKLhK5wKU/I4QVsBUc8kE6MkHBkeypWZs=
-github.com/miekg/dns v1.1.59/go.mod h1:nZpewl5p6IvctfgrckopVx2OlSEHPRO/U4SYkRklrEk=
+github.com/miekg/dns v1.1.62 h1:cN8OuEF1/x5Rq6Np+h1epln8OiyPWV+lROx9LxcGgIQ=
+github.com/miekg/dns v1.1.62/go.mod h1:mvDlcItzm+br7MToIKqkglaGhlFMHJ9DTNNWONWXbNQ=
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
github.com/mitchellh/cli v1.1.0/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI=
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
@@ -553,11 +557,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.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.19.0 h1:9Cnnf7UHo57Hy3k6/m5k3dRfGTMXGvxhHFvkDTCTpvA=
+github.com/onsi/ginkgo/v2 v2.19.0/go.mod h1:rlwLi9PilAFJ8jCg9UE1QP6VBpd6/xj3SRC0d6TU0To=
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.19.0 h1:4ieX6qQjPP/BfC3mpsAtIGGlxTWPeA3Inl/7DtXw1tw=
+github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro=
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=
@@ -573,8 +577,8 @@ github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5/go.mod h1:/wsWhb9smxS
github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw=
github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4=
github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4=
-github.com/ovh/go-ovh v1.5.1 h1:P8O+7H+NQuFK9P/j4sFW5C0fvSS2DnHYGPwdVCp45wI=
-github.com/ovh/go-ovh v1.5.1/go.mod h1:cTVDnl94z4tl8pP1uZ/8jlVxntjSIf09bNcQ5TJSC7c=
+github.com/ovh/go-ovh v1.6.0 h1:ixLOwxQdzYDx296sXcgS35TOPEahJkpjMGtzPadCjQI=
+github.com/ovh/go-ovh v1.6.0/go.mod h1:cTVDnl94z4tl8pP1uZ/8jlVxntjSIf09bNcQ5TJSC7c=
github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIwwtUjcrb0b5/5kLM=
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY=
@@ -592,6 +596,8 @@ github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA=
+github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo=
+github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
@@ -608,8 +614,8 @@ github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeD
github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU=
github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=
-github.com/prometheus/client_golang v1.19.1 h1:wZWJDwK+NameRJuPGDhlnFgx8e8HN3XHQeLaYJFJBOE=
-github.com/prometheus/client_golang v1.19.1/go.mod h1:mP78NwGzrVks5S2H6ab8+ZZGJLZUq1hoULYBAYBw1Ho=
+github.com/prometheus/client_golang v1.20.3 h1:oPksm4K8B+Vt35tUhw6GbSNSgVlVSBH0qELP/7u83l4=
+github.com/prometheus/client_golang v1.20.3/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
@@ -625,22 +631,22 @@ 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.54.0 h1:ZlZy0BgJhTwVZUn7dLOkwCZHUkrAqd3WYtcFCWnM1D8=
-github.com/prometheus/common v0.54.0/go.mod h1:/TQgMJP5CuVYveyT7n/0Ix8yLNNXy9yRSkhnLTHPDIQ=
+github.com/prometheus/common v0.59.1 h1:LXb1quJHWm1P6wq/U824uxYi4Sg0oGvNeUm1z5dJoX0=
+github.com/prometheus/common v0.59.1/go.mod h1:GpWM7dewqmVYcd7SmRaiWVe9SSqjf0UrwnYnpEZNuT0=
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=
github.com/prometheus/common/sigv4 v0.1.0/go.mod h1:2Jkxxk9yYvCkE5G1sQT7GuEXm57JrvHu9k5YwTjsNtI=
-github.com/prometheus/exporter-toolkit v0.11.0 h1:yNTsuZ0aNCNFQ3aFTD2uhPOvr4iD7fdBvKPAEGkNf+g=
-github.com/prometheus/exporter-toolkit v0.11.0/go.mod h1:BVnENhnNecpwoTLiABx7mrPB/OLRIgN74qlQbV+FK1Q=
+github.com/prometheus/exporter-toolkit v0.12.0 h1:DkE5RcEZR3lQA2QD5JLVQIf41dFKNsVMXFhgqcif7fo=
+github.com/prometheus/exporter-toolkit v0.12.0/go.mod h1:fQH0KtTn0yrrS0S82kqppRjDDiwMfIQUwT+RBRRhwUc=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
-github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo=
-github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo=
+github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
+github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
@@ -650,8 +656,8 @@ github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99
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=
-github.com/scaleway/scaleway-sdk-go v1.0.0-beta.27 h1:yGAraK1uUjlhSXgNMIy8o/J4LFNcy7yeipBqt9N9mVg=
-github.com/scaleway/scaleway-sdk-go v1.0.0-beta.27/go.mod h1:fCa7OJZ/9DRTnOKmxvT6pn+LPWUptQAmHF/SBJUGEcg=
+github.com/scaleway/scaleway-sdk-go v1.0.0-beta.30 h1:yoKAVkEVwAqbGbR8n87rHQ1dulL25rKloGadb3vm770=
+github.com/scaleway/scaleway-sdk-go v1.0.0-beta.30/go.mod h1:sH0u6fq6x4R5M7WxkoQFY/o7UaiItec0o1LinLCJNq8=
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 v1.7.1 h1:UJcjSAI3aUKx52kfcfhblgyhZceouhvvs3OYdWgn+PY=
@@ -661,8 +667,8 @@ github.com/shurcooL/httpfs v0.0.0-20230704072500-f1e31cf0ba5c/go.mod h1:owqhoLW1
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/simonpasquier/klog-gokit v0.3.0 h1:TkFK21cbwDRS+CiystjqbAiq5ubJcVTk9hLUck5Ntcs=
github.com/simonpasquier/klog-gokit v0.3.0/go.mod h1:+SUlDQNrhVtGt2FieaqNftzzk8P72zpWlACateWxA9k=
-github.com/simonpasquier/klog-gokit/v3 v3.3.0 h1:HMzH999kO5gEgJTaWWO+xjncW5oycspcsBnjn9b853Q=
-github.com/simonpasquier/klog-gokit/v3 v3.3.0/go.mod h1:uSbnWC3T7kt1dQyY9sjv0Ao1SehMAJdVnUNSKhjaDsg=
+github.com/simonpasquier/klog-gokit/v3 v3.5.0 h1:ewnk+ickph0hkQFgdI4pffKIbruAxxWcg0Fe/vQmLOM=
+github.com/simonpasquier/klog-gokit/v3 v3.5.0/go.mod h1:S9flvRzzpaYLYtXI2w8jf9R/IU/Cy14NrbvDUevNP1E=
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=
@@ -694,6 +700,7 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
+github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
@@ -702,6 +709,8 @@ github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijb
github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
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/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
+github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
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/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
@@ -723,28 +732,28 @@ go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
-go.opentelemetry.io/collector/pdata v1.8.0 h1:d/QQgZxB4Y+d3mqLVh2ozvzujUhloD3P/fk7X+In764=
-go.opentelemetry.io/collector/pdata v1.8.0/go.mod h1:/W7clu0wFC4WSRp94Ucn6Vm36Wkrt+tmtlDb1aiNZCY=
-go.opentelemetry.io/collector/semconv v0.101.0 h1:tOe9iTe9dDCnvz/bqgfNRr4w80kXG8505tQJ5h5v08Q=
-go.opentelemetry.io/collector/semconv v0.101.0/go.mod h1:8ElcRZ8Cdw5JnvhTOQOdYizkJaQ10Z2fS+R6djOnj6A=
-go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.52.0 h1:9l89oX4ba9kHbBol3Xin3leYJ+252h0zszDtBwyKe2A=
-go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.52.0/go.mod h1:XLZfZboOJWHNKUv7eH0inh0E9VV6eWDFB/9yJyTLPp0=
-go.opentelemetry.io/otel v1.27.0 h1:9BZoF3yMK/O1AafMiQTVu0YDj5Ea4hPhxCs7sGva+cg=
-go.opentelemetry.io/otel v1.27.0/go.mod h1:DMpAK8fzYRzs+bi3rS5REupisuqTheUlSZJ1WnZaPAQ=
-go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.27.0 h1:R9DE4kQ4k+YtfLI2ULwX82VtNQ2J8yZmA7ZIF/D+7Mc=
-go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.27.0/go.mod h1:OQFyQVrDlbe+R7xrEyDr/2Wr67Ol0hRUgsfA+V5A95s=
-go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.27.0 h1:qFffATk0X+HD+f1Z8lswGiOQYKHRlzfmdJm0wEaVrFA=
-go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.27.0/go.mod h1:MOiCmryaYtc+V0Ei+Tx9o5S1ZjA7kzLucuVuyzBZloQ=
-go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.27.0 h1:QY7/0NeRPKlzusf40ZE4t1VlMKbqSNT7cJRYzWuja0s=
-go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.27.0/go.mod h1:HVkSiDhTM9BoUJU8qE6j2eSWLLXvi1USXjyd2BXT8PY=
-go.opentelemetry.io/otel/metric v1.27.0 h1:hvj3vdEKyeCi4YaYfNjv2NUje8FqKqUY8IlF0FxV/ik=
-go.opentelemetry.io/otel/metric v1.27.0/go.mod h1:mVFgmRlhljgBiuk/MP/oKylr4hs85GZAylncepAX/ak=
-go.opentelemetry.io/otel/sdk v1.27.0 h1:mlk+/Y1gLPLn84U4tI8d3GNJmGT/eXe3ZuOXN9kTWmI=
-go.opentelemetry.io/otel/sdk v1.27.0/go.mod h1:Ha9vbLwJE6W86YstIywK2xFfPjbWlCuwPtMkKdz/Y4A=
-go.opentelemetry.io/otel/trace v1.27.0 h1:IqYb813p7cmbHk0a5y6pD5JPakbVfftRXABGt5/Rscw=
-go.opentelemetry.io/otel/trace v1.27.0/go.mod h1:6RiD1hkAprV4/q+yd2ln1HG9GoPx39SuvvstaLBl+l4=
-go.opentelemetry.io/proto/otlp v1.2.0 h1:pVeZGk7nXDC9O2hncA6nHldxEjm6LByfA2aN8IOkz94=
-go.opentelemetry.io/proto/otlp v1.2.0/go.mod h1:gGpR8txAl5M03pDhMC79G6SdqNV26naRm/KDsgaHD8A=
+go.opentelemetry.io/collector/pdata v1.14.1 h1:wXZjtQA7Vy5HFqco+yA95ENyMQU5heBB1IxMHQf6mUk=
+go.opentelemetry.io/collector/pdata v1.14.1/go.mod h1:z1dTjwwtcoXxZx2/nkHysjxMeaxe9pEmYTEr4SMNIx8=
+go.opentelemetry.io/collector/semconv v0.108.1 h1:Txk9tauUnamZaxS5vlf1O0uZ4VD6nioRBR0nX8L/fU4=
+go.opentelemetry.io/collector/semconv v0.108.1/go.mod h1:zCJ5njhWpejR+A40kiEoeFm1xq1uzyZwMnRNX6/D82A=
+go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0 h1:4K4tsIXefpVJtvA/8srF4V4y0akAoPHkIslgAkjixJA=
+go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0/go.mod h1:jjdQuTGVsXV4vSs+CJ2qYDeDPf9yIJV23qlIzBm73Vg=
+go.opentelemetry.io/otel v1.29.0 h1:PdomN/Al4q/lN6iBJEN3AwPvUiHPMlt93c8bqTG5Llw=
+go.opentelemetry.io/otel v1.29.0/go.mod h1:N/WtXPs1CNCUEx+Agz5uouwCba+i+bJGFicT8SR4NP8=
+go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.29.0 h1:dIIDULZJpgdiHz5tXrTgKIMLkus6jEFa7x5SOKcyR7E=
+go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.29.0/go.mod h1:jlRVBe7+Z1wyxFSUs48L6OBQZ5JwH2Hg/Vbl+t9rAgI=
+go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.29.0 h1:nSiV3s7wiCam610XcLbYOmMfJxB9gO4uK3Xgv5gmTgg=
+go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.29.0/go.mod h1:hKn/e/Nmd19/x1gvIHwtOwVWM+VhuITSWip3JUDghj0=
+go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.29.0 h1:JAv0Jwtl01UFiyWZEMiJZBiTlv5A50zNs8lsthXqIio=
+go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.29.0/go.mod h1:QNKLmUEAq2QUbPQUfvw4fmv0bgbK7UlOSFCnXyfvSNc=
+go.opentelemetry.io/otel/metric v1.29.0 h1:vPf/HFWTNkPu1aYeIsc98l4ktOQaL6LeSoeV2g+8YLc=
+go.opentelemetry.io/otel/metric v1.29.0/go.mod h1:auu/QWieFVWx+DmQOUMgj0F8LHWdgalxXqvp7BII/W8=
+go.opentelemetry.io/otel/sdk v1.29.0 h1:vkqKjk7gwhS8VaWb0POZKmIEDimRCMsopNYnriHyryo=
+go.opentelemetry.io/otel/sdk v1.29.0/go.mod h1:pM8Dx5WKnvxLCb+8lG1PRNIDxu9g9b9g59Qr7hfAAok=
+go.opentelemetry.io/otel/trace v1.29.0 h1:J/8ZNK4XgR7a21DZUAsbF8pZ5Jcw1VhACmnYt39JTi4=
+go.opentelemetry.io/otel/trace v1.29.0/go.mod h1:eHl3w0sp3paPkYstJOmAimxhiFXPg+MMTlEh3nsQgWQ=
+go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0=
+go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8=
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
@@ -773,8 +782,8 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y
golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
-golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI=
-golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM=
+golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw=
+golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@@ -809,8 +818,8 @@ 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.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
-golang.org/x/mod v0.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0=
-golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
+golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0=
+golang.org/x/mod v0.20.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-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -856,16 +865,16 @@ golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
-golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ=
-golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE=
+golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE=
+golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
-golang.org/x/oauth2 v0.21.0 h1:tsimM75w1tF/uws5rbeHzIWxEqElMehnc+iW793zsZs=
-golang.org/x/oauth2 v0.21.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
+golang.org/x/oauth2 v0.23.0 h1:PbgcYx2W7i4LvjJWEbf0ngHV6qJYr86PkAV3bXdLEbs=
+golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -879,8 +888,8 @@ golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/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.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
-golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
+golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
+golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -946,16 +955,16 @@ golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
-golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws=
-golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34=
+golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
-golang.org/x/term v0.21.0 h1:WVXCp+/EBEHOj53Rvu+7KiT/iElMrO8ACK16SMZ3jaA=
-golang.org/x/term v0.21.0/go.mod h1:ooXLefLobQVslOqselCNF4SxFAaoS6KujMbsGzSDmX0=
+golang.org/x/term v0.23.0 h1:F6D4vR+EHoL9/sWAWgAR1H2DcHr4PareCbAaCo1RpuU=
+golang.org/x/term v0.23.0/go.mod h1:DgV24QBUrK6jhZXl+20l6UWznPlwAHm1Q1mGHtydmSk=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -967,14 +976,15 @@ golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
-golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
-golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
+golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224=
+golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
-golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
+golang.org/x/time v0.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U=
+golang.org/x/time v0.6.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
@@ -1025,8 +1035,8 @@ golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc
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.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
-golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA=
-golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c=
+golang.org/x/tools v0.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24=
+golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ=
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=
@@ -1046,8 +1056,8 @@ google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/
google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
-google.golang.org/api v0.183.0 h1:PNMeRDwo1pJdgNcFQ9GstuLe/noWKIc89pRWRLMvLwE=
-google.golang.org/api v0.183.0/go.mod h1:q43adC5/pHoSZTx5h2mSmdF7NcyfW9JuDyIOJAgS9ZQ=
+google.golang.org/api v0.195.0 h1:Ude4N8FvTKnnQJHU48RFI40jOBgIrL8Zqr3/QeST6yU=
+google.golang.org/api v0.195.0/go.mod h1:DOGRWuv3P8TU8Lnz7uQc4hyNqrBpMtD9ppW3wBJurgc=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
@@ -1084,10 +1094,10 @@ google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1m
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
-google.golang.org/genproto/googleapis/api v0.0.0-20240528184218-531527333157 h1:7whR9kGa5LUwFtpLm2ArCEejtnxlGeLbAyjFY8sGNFw=
-google.golang.org/genproto/googleapis/api v0.0.0-20240528184218-531527333157/go.mod h1:99sLkeliLXfdj2J75X3Ho+rrVCaJze0uwN7zDDkjPVU=
-google.golang.org/genproto/googleapis/rpc v0.0.0-20240528184218-531527333157 h1:Zy9XzmMEflZ/MAaA7vNcoebnRAld7FsPW1EeBB7V0m8=
-google.golang.org/genproto/googleapis/rpc v0.0.0-20240528184218-531527333157/go.mod h1:EfXuqaE1J41VCDicxHzUDm+8rk+7ZdXzHV0IhO/I6s0=
+google.golang.org/genproto/googleapis/api v0.0.0-20240827150818-7e3bb234dfed h1:3RgNmBoI9MZhsj3QxC+AP/qQhNwpCLOvYDYYsFrhFt0=
+google.golang.org/genproto/googleapis/api v0.0.0-20240827150818-7e3bb234dfed/go.mod h1:OCdP9MfskevB/rbYvHTsXTtKC+3bHWajPdoKgjcYkfo=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 h1:pPJltXNxVzT4pK9yD8vR9X75DaWYYmLGMsEvBfFQZzQ=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU=
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM=
@@ -1106,8 +1116,8 @@ google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3Iji
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
-google.golang.org/grpc v1.64.0 h1:KH3VH9y/MgNQg1dE7b3XfVK0GsPSIzJwdF617gUSbvY=
-google.golang.org/grpc v1.64.0/go.mod h1:oxjF8E3FBnjp+/gVFYdWacaLDx9na1aqy9oovLpxQYg=
+google.golang.org/grpc v1.66.0 h1:DibZuoBznOxbDQxRINckZcUvnCEvrW9pcWIE2yF9r1c=
+google.golang.org/grpc v1.66.0/go.mod h1:s3/l6xSSCURdVfAnL+TqCNMyTDAGN6+lZeVxnZR128Y=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
@@ -1119,8 +1129,8 @@ google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpAD
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
-google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg=
-google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
+google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
+google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
@@ -1129,6 +1139,8 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntN
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
+gopkg.in/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSPG+6V4=
+gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o=
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
@@ -1162,16 +1174,16 @@ 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.3 h1:2ORfZ7+bGC3YJqGpV0KSDDEVf8hdGQ6A03/50vj8pmw=
-k8s.io/api v0.29.3/go.mod h1:y2yg2NTyHUUkIoTC+phinTnEa3KFM6RZ3szxt014a80=
-k8s.io/apimachinery v0.29.3 h1:2tbx+5L7RNvqJjn7RIuIKu9XTsIZ9Z5wX2G22XAa5EU=
-k8s.io/apimachinery v0.29.3/go.mod h1:hx/S4V2PNW4OMg3WizRrHutyB5la0iCUbZym+W0EQIU=
-k8s.io/client-go v0.29.3 h1:R/zaZbEAxqComZ9FHeQwOh3Y1ZUs7FaHKZdQtIc2WZg=
-k8s.io/client-go v0.29.3/go.mod h1:tkDisCvgPfiRpxGnOORfkljmS+UrW+WtXAy2fTvXJB0=
+k8s.io/api v0.31.0 h1:b9LiSjR2ym/SzTOlfMHm1tr7/21aD7fSkqgD/CVJBCo=
+k8s.io/api v0.31.0/go.mod h1:0YiFF+JfFxMM6+1hQei8FY8M7s1Mth+z/q7eF1aJkTE=
+k8s.io/apimachinery v0.31.0 h1:m9jOiSr3FoSSL5WO9bjm1n6B9KROYYgNZOb4tyZ1lBc=
+k8s.io/apimachinery v0.31.0/go.mod h1:rsPdaZJfTfLsNJSQzNHQvYoTmxhoOEofxtOsF3rtsMo=
+k8s.io/client-go v0.31.0 h1:QqEJzNjbN2Yv1H79SsS+SWnXkBgVu4Pj3CJQgbx0gI8=
+k8s.io/client-go v0.31.0/go.mod h1:Y9wvC76g4fLjmU0BA+rV+h2cncoadjvjjkkIGoTLcGU=
k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 h1:BZqlfIlq5YbRMFko6/PM7FjZpUb45WallggurYhKGag=
k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340/go.mod h1:yD4MZYeKMBwQKVht279WycxKyM84kkAx2DPrTXaeb98=
-k8s.io/utils v0.0.0-20230726121419-3b25d923346b h1:sgn3ZU783SCgtaSJjpcVVlRqd6GSnlTLKgpAAttJvpI=
-k8s.io/utils v0.0.0-20230726121419-3b25d923346b/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
+k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 h1:pUdcCO1Lk/tbT5ztQWOBi5HBgbBP1J8+AsQnQCKsi8A=
+k8s.io/utils v0.0.0-20240711033017-18e509b52bc8/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
@@ -1180,6 +1192,6 @@ sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h6
sigs.k8s.io/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+sGiqlzvrtq4=
sigs.k8s.io/structured-merge-diff/v4 v4.4.1/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08=
sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
-sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo=
-sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8=
+sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E=
+sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY=
sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU=
diff --git a/model/exemplar/exemplar.go b/model/exemplar/exemplar.go
index 08f55374ef6..2c28b172571 100644
--- a/model/exemplar/exemplar.go
+++ b/model/exemplar/exemplar.go
@@ -15,7 +15,9 @@ package exemplar
import "github.com/prometheus/prometheus/model/labels"
-// The combined length of the label names and values of an Exemplar's LabelSet MUST NOT exceed 128 UTF-8 characters
+// ExemplarMaxLabelSetLength is defined by OpenMetrics: "The combined length of
+// the label names and values of an Exemplar's LabelSet MUST NOT exceed 128
+// UTF-8 characters."
// https://github.com/OpenObservability/OpenMetrics/blob/main/specification/OpenMetrics.md#exemplars
const ExemplarMaxLabelSetLength = 128
@@ -49,7 +51,7 @@ func (e Exemplar) Equals(e2 Exemplar) bool {
return e.Value == e2.Value
}
-// Sort first by timestamp, then value, then labels.
+// Compare first timestamps, then values, then labels.
func Compare(a, b Exemplar) int {
if a.Ts < b.Ts {
return -1
diff --git a/model/histogram/float_histogram_test.go b/model/histogram/float_histogram_test.go
index 1558a6d679c..cf370a313e4 100644
--- a/model/histogram/float_histogram_test.go
+++ b/model/histogram/float_histogram_test.go
@@ -131,6 +131,54 @@ func TestFloatHistogramMul(t *testing.T) {
NegativeBuckets: []float64{9, 3, 15, 18},
},
},
+ {
+ "negation",
+ &FloatHistogram{
+ ZeroThreshold: 0.01,
+ ZeroCount: 11,
+ Count: 30,
+ Sum: 23,
+ PositiveSpans: []Span{{-2, 2}, {1, 3}},
+ PositiveBuckets: []float64{1, 0, 3, 4, 7},
+ NegativeSpans: []Span{{3, 2}, {3, 2}},
+ NegativeBuckets: []float64{3, 1, 5, 6},
+ },
+ -1,
+ &FloatHistogram{
+ ZeroThreshold: 0.01,
+ ZeroCount: -11,
+ Count: -30,
+ Sum: -23,
+ PositiveSpans: []Span{{-2, 2}, {1, 3}},
+ PositiveBuckets: []float64{-1, 0, -3, -4, -7},
+ NegativeSpans: []Span{{3, 2}, {3, 2}},
+ NegativeBuckets: []float64{-3, -1, -5, -6},
+ },
+ },
+ {
+ "negative multiplier",
+ &FloatHistogram{
+ ZeroThreshold: 0.01,
+ ZeroCount: 11,
+ Count: 30,
+ Sum: 23,
+ PositiveSpans: []Span{{-2, 2}, {1, 3}},
+ PositiveBuckets: []float64{1, 0, 3, 4, 7},
+ NegativeSpans: []Span{{3, 2}, {3, 2}},
+ NegativeBuckets: []float64{3, 1, 5, 6},
+ },
+ -2,
+ &FloatHistogram{
+ ZeroThreshold: 0.01,
+ ZeroCount: -22,
+ Count: -60,
+ Sum: -46,
+ PositiveSpans: []Span{{-2, 2}, {1, 3}},
+ PositiveBuckets: []float64{-2, 0, -6, -8, -14},
+ NegativeSpans: []Span{{3, 2}, {3, 2}},
+ NegativeBuckets: []float64{-6, -2, -10, -12},
+ },
+ },
{
"no-op with custom buckets",
&FloatHistogram{
@@ -409,6 +457,54 @@ func TestFloatHistogramDiv(t *testing.T) {
NegativeBuckets: []float64{1.5, 0.5, 2.5, 3},
},
},
+ {
+ "negation",
+ &FloatHistogram{
+ ZeroThreshold: 0.01,
+ ZeroCount: 5.5,
+ Count: 3493.3,
+ Sum: 2349209.324,
+ PositiveSpans: []Span{{-2, 1}, {2, 3}},
+ PositiveBuckets: []float64{1, 3.3, 4.2, 0.1},
+ NegativeSpans: []Span{{3, 2}, {3, 2}},
+ NegativeBuckets: []float64{3.1, 3, 1.234e5, 1000},
+ },
+ -1,
+ &FloatHistogram{
+ ZeroThreshold: 0.01,
+ ZeroCount: -5.5,
+ Count: -3493.3,
+ Sum: -2349209.324,
+ PositiveSpans: []Span{{-2, 1}, {2, 3}},
+ PositiveBuckets: []float64{-1, -3.3, -4.2, -0.1},
+ NegativeSpans: []Span{{3, 2}, {3, 2}},
+ NegativeBuckets: []float64{-3.1, -3, -1.234e5, -1000},
+ },
+ },
+ {
+ "negative half",
+ &FloatHistogram{
+ ZeroThreshold: 0.01,
+ ZeroCount: 11,
+ Count: 30,
+ Sum: 23,
+ PositiveSpans: []Span{{-2, 2}, {1, 3}},
+ PositiveBuckets: []float64{1, 0, 3, 4, 7},
+ NegativeSpans: []Span{{3, 2}, {3, 2}},
+ NegativeBuckets: []float64{3, 1, 5, 6},
+ },
+ -2,
+ &FloatHistogram{
+ ZeroThreshold: 0.01,
+ ZeroCount: -5.5,
+ Count: -15,
+ Sum: -11.5,
+ PositiveSpans: []Span{{-2, 2}, {1, 3}},
+ PositiveBuckets: []float64{-0.5, 0, -1.5, -2, -3.5},
+ NegativeSpans: []Span{{3, 2}, {3, 2}},
+ NegativeBuckets: []float64{-1.5, -0.5, -2.5, -3},
+ },
+ },
{
"no-op with custom buckets",
&FloatHistogram{
diff --git a/model/labels/labels.go b/model/labels/labels.go
index 01514abf385..f4de7496ce7 100644
--- a/model/labels/labels.go
+++ b/model/labels/labels.go
@@ -38,10 +38,10 @@ func (ls Labels) Bytes(buf []byte) []byte {
b.WriteByte(labelSep)
for i, l := range ls {
if i > 0 {
- b.WriteByte(seps[0])
+ b.WriteByte(sep)
}
b.WriteString(l.Name)
- b.WriteByte(seps[0])
+ b.WriteByte(sep)
b.WriteString(l.Value)
}
return b.Bytes()
@@ -86,9 +86,9 @@ func (ls Labels) Hash() uint64 {
}
b = append(b, v.Name...)
- b = append(b, seps[0])
+ b = append(b, sep)
b = append(b, v.Value...)
- b = append(b, seps[0])
+ b = append(b, sep)
}
return xxhash.Sum64(b)
}
@@ -106,9 +106,9 @@ func (ls Labels) HashForLabels(b []byte, names ...string) (uint64, []byte) {
i++
default:
b = append(b, ls[i].Name...)
- b = append(b, seps[0])
+ b = append(b, sep)
b = append(b, ls[i].Value...)
- b = append(b, seps[0])
+ b = append(b, sep)
i++
j++
}
@@ -130,9 +130,9 @@ func (ls Labels) HashWithoutLabels(b []byte, names ...string) (uint64, []byte) {
continue
}
b = append(b, ls[i].Name...)
- b = append(b, seps[0])
+ b = append(b, sep)
b = append(b, ls[i].Value...)
- b = append(b, seps[0])
+ b = append(b, sep)
}
return xxhash.Sum64(b), b
}
@@ -151,10 +151,10 @@ func (ls Labels) BytesWithLabels(buf []byte, names ...string) []byte {
i++
default:
if b.Len() > 1 {
- b.WriteByte(seps[0])
+ b.WriteByte(sep)
}
b.WriteString(ls[i].Name)
- b.WriteByte(seps[0])
+ b.WriteByte(sep)
b.WriteString(ls[i].Value)
i++
j++
@@ -177,10 +177,10 @@ func (ls Labels) BytesWithoutLabels(buf []byte, names ...string) []byte {
continue
}
if b.Len() > 1 {
- b.WriteByte(seps[0])
+ b.WriteByte(sep)
}
b.WriteString(ls[i].Name)
- b.WriteByte(seps[0])
+ b.WriteByte(sep)
b.WriteString(ls[i].Value)
}
return b.Bytes()
@@ -315,7 +315,8 @@ func Compare(a, b Labels) int {
return len(a) - len(b)
}
-// Copy labels from b on top of whatever was in ls previously, reusing memory or expanding if needed.
+// CopyFrom copies labels from b on top of whatever was in ls previously,
+// reusing memory or expanding if needed.
func (ls *Labels) CopyFrom(b Labels) {
(*ls) = append((*ls)[:0], b...)
}
@@ -422,7 +423,7 @@ type ScratchBuilder struct {
add Labels
}
-// Symbol-table is no-op, just for api parity with dedupelabels.
+// SymbolTable is no-op, just for api parity with dedupelabels.
type SymbolTable struct{}
func NewSymbolTable() *SymbolTable { return nil }
@@ -458,7 +459,7 @@ func (b *ScratchBuilder) Add(name, value string) {
b.add = append(b.add, Label{Name: name, Value: value})
}
-// Add a name/value pair, using []byte instead of string.
+// UnsafeAddBytes adds a name/value pair, using []byte instead of string.
// The '-tags stringlabels' version of this function is unsafe, hence the name.
// This version is safe - it copies the strings immediately - but we keep the same name so everything compiles.
func (b *ScratchBuilder) UnsafeAddBytes(name, value []byte) {
@@ -475,14 +476,14 @@ func (b *ScratchBuilder) Assign(ls Labels) {
b.add = append(b.add[:0], ls...) // Copy on top of our slice, so we don't retain the input slice.
}
-// Return the name/value pairs added so far as a Labels object.
+// Labels returns the name/value pairs added so far as a Labels object.
// Note: if you want them sorted, call Sort() first.
func (b *ScratchBuilder) Labels() Labels {
// Copy the slice, so the next use of ScratchBuilder doesn't overwrite.
return append([]Label{}, b.add...)
}
-// Write the newly-built Labels out to ls.
+// Overwrite the newly-built Labels out to ls.
// Callers must ensure that there are no other references to ls, or any strings fetched from it.
func (b *ScratchBuilder) Overwrite(ls *Labels) {
*ls = append((*ls)[:0], b.add...)
diff --git a/model/labels/labels_common.go b/model/labels/labels_common.go
index 4bc94f84fe5..d7bdc1e0768 100644
--- a/model/labels/labels_common.go
+++ b/model/labels/labels_common.go
@@ -29,10 +29,11 @@ const (
BucketLabel = "le"
InstanceName = "instance"
- labelSep = '\xfe'
+ labelSep = '\xfe' // Used at beginning of `Bytes` return.
+ sep = '\xff' // Used between labels in `Bytes` and `Hash`.
)
-var seps = []byte{'\xff'}
+var seps = []byte{sep} // Used with Hash, which has no WriteByte method.
// Label is a key/value pair of strings.
type Label struct {
@@ -94,12 +95,23 @@ func (ls *Labels) UnmarshalYAML(unmarshal func(interface{}) error) error {
}
// IsValid checks if the metric name or label names are valid.
-func (ls Labels) IsValid() bool {
+func (ls Labels) IsValid(validationScheme model.ValidationScheme) bool {
err := ls.Validate(func(l Label) error {
- if l.Name == model.MetricNameLabel && !model.IsValidMetricName(model.LabelValue(l.Value)) {
- return strconv.ErrSyntax
+ if l.Name == model.MetricNameLabel {
+ // If the default validation scheme has been overridden with legacy mode,
+ // we need to call the special legacy validation checker.
+ if validationScheme == model.LegacyValidation && model.NameValidationScheme == model.UTF8Validation && !model.IsValidLegacyMetricName(string(model.LabelValue(l.Value))) {
+ return strconv.ErrSyntax
+ }
+ if !model.IsValidMetricName(model.LabelValue(l.Value)) {
+ return strconv.ErrSyntax
+ }
}
- if !model.LabelName(l.Name).IsValid() || !model.LabelValue(l.Value).IsValid() {
+ if validationScheme == model.LegacyValidation && model.NameValidationScheme == model.UTF8Validation {
+ if !model.LabelName(l.Name).IsValidLegacy() || !model.LabelValue(l.Value).IsValid() {
+ return strconv.ErrSyntax
+ }
+ } else if !model.LabelName(l.Name).IsValid() || !model.LabelValue(l.Value).IsValid() {
return strconv.ErrSyntax
}
return nil
diff --git a/model/labels/labels_dedupelabels.go b/model/labels/labels_dedupelabels.go
index 0e5bb048beb..da8a88cc158 100644
--- a/model/labels/labels_dedupelabels.go
+++ b/model/labels/labels_dedupelabels.go
@@ -146,13 +146,13 @@ func (ls Labels) Bytes(buf []byte) []byte {
b := bytes.NewBuffer(buf[:0])
for i := 0; i < len(ls.data); {
if i > 0 {
- b.WriteByte(seps[0])
+ b.WriteByte(sep)
}
var name, value string
name, i = decodeString(ls.syms, ls.data, i)
value, i = decodeString(ls.syms, ls.data, i)
b.WriteString(name)
- b.WriteByte(seps[0])
+ b.WriteByte(sep)
b.WriteString(value)
}
return b.Bytes()
@@ -201,9 +201,9 @@ func (ls Labels) Hash() uint64 {
}
b = append(b, name...)
- b = append(b, seps[0])
+ b = append(b, sep)
b = append(b, value...)
- b = append(b, seps[0])
+ b = append(b, sep)
pos = newPos
}
return xxhash.Sum64(b)
@@ -226,9 +226,9 @@ func (ls Labels) HashForLabels(b []byte, names ...string) (uint64, []byte) {
}
if name == names[j] {
b = append(b, name...)
- b = append(b, seps[0])
+ b = append(b, sep)
b = append(b, value...)
- b = append(b, seps[0])
+ b = append(b, sep)
}
}
@@ -252,9 +252,9 @@ func (ls Labels) HashWithoutLabels(b []byte, names ...string) (uint64, []byte) {
continue
}
b = append(b, name...)
- b = append(b, seps[0])
+ b = append(b, sep)
b = append(b, value...)
- b = append(b, seps[0])
+ b = append(b, sep)
}
return xxhash.Sum64(b), b
}
@@ -275,10 +275,10 @@ func (ls Labels) BytesWithLabels(buf []byte, names ...string) []byte {
}
if lName == names[j] {
if b.Len() > 1 {
- b.WriteByte(seps[0])
+ b.WriteByte(sep)
}
b.WriteString(lName)
- b.WriteByte(seps[0])
+ b.WriteByte(sep)
b.WriteString(lValue)
}
pos = newPos
@@ -299,10 +299,10 @@ func (ls Labels) BytesWithoutLabels(buf []byte, names ...string) []byte {
}
if j == len(names) || lName != names[j] {
if b.Len() > 1 {
- b.WriteByte(seps[0])
+ b.WriteByte(sep)
}
b.WriteString(lName)
- b.WriteByte(seps[0])
+ b.WriteByte(sep)
b.WriteString(lValue)
}
pos = newPos
diff --git a/model/labels/labels_stringlabels.go b/model/labels/labels_stringlabels.go
index bccceb61fe1..c8bce51234a 100644
--- a/model/labels/labels_stringlabels.go
+++ b/model/labels/labels_stringlabels.go
@@ -112,9 +112,9 @@ func (ls Labels) HashForLabels(b []byte, names ...string) (uint64, []byte) {
}
if name == names[j] {
b = append(b, name...)
- b = append(b, seps[0])
+ b = append(b, sep)
b = append(b, value...)
- b = append(b, seps[0])
+ b = append(b, sep)
}
}
@@ -138,9 +138,9 @@ func (ls Labels) HashWithoutLabels(b []byte, names ...string) (uint64, []byte) {
continue
}
b = append(b, name...)
- b = append(b, seps[0])
+ b = append(b, sep)
b = append(b, value...)
- b = append(b, seps[0])
+ b = append(b, sep)
}
return xxhash.Sum64(b), b
}
diff --git a/model/labels/labels_test.go b/model/labels/labels_test.go
index d8910cdc855..9208908311f 100644
--- a/model/labels/labels_test.go
+++ b/model/labels/labels_test.go
@@ -21,6 +21,7 @@ import (
"strings"
"testing"
+ "github.com/prometheus/common/model"
"github.com/stretchr/testify/require"
"gopkg.in/yaml.v2"
)
@@ -272,11 +273,86 @@ func TestLabels_IsValid(t *testing.T) {
},
} {
t.Run("", func(t *testing.T) {
- require.Equal(t, test.expected, test.input.IsValid())
+ require.Equal(t, test.expected, test.input.IsValid(model.LegacyValidation))
})
}
}
+func TestLabels_ValidationModes(t *testing.T) {
+ for _, test := range []struct {
+ input Labels
+ globalMode model.ValidationScheme
+ callMode model.ValidationScheme
+ expected bool
+ }{
+ {
+ input: FromStrings(
+ "__name__", "test.metric",
+ "hostname", "localhost",
+ "job", "check",
+ ),
+ globalMode: model.UTF8Validation,
+ callMode: model.UTF8Validation,
+ expected: true,
+ },
+ {
+ input: FromStrings(
+ "__name__", "test",
+ "\xc5 bad utf8", "localhost",
+ "job", "check",
+ ),
+ globalMode: model.UTF8Validation,
+ callMode: model.UTF8Validation,
+ expected: false,
+ },
+ {
+ // Setting the common model to legacy validation and then trying to check for UTF-8 on a
+ // per-call basis is not supported.
+ input: FromStrings(
+ "__name__", "test.utf8.metric",
+ "hostname", "localhost",
+ "job", "check",
+ ),
+ globalMode: model.LegacyValidation,
+ callMode: model.UTF8Validation,
+ expected: false,
+ },
+ {
+ input: FromStrings(
+ "__name__", "test",
+ "hostname", "localhost",
+ "job", "check",
+ ),
+ globalMode: model.LegacyValidation,
+ callMode: model.LegacyValidation,
+ expected: true,
+ },
+ {
+ input: FromStrings(
+ "__name__", "test.utf8.metric",
+ "hostname", "localhost",
+ "job", "check",
+ ),
+ globalMode: model.UTF8Validation,
+ callMode: model.LegacyValidation,
+ expected: false,
+ },
+ {
+ input: FromStrings(
+ "__name__", "test",
+ "host.name", "localhost",
+ "job", "check",
+ ),
+ globalMode: model.UTF8Validation,
+ callMode: model.LegacyValidation,
+ expected: false,
+ },
+ } {
+ model.NameValidationScheme = test.globalMode
+ require.Equal(t, test.expected, test.input.IsValid(test.callMode))
+ }
+}
+
func TestLabels_Equal(t *testing.T) {
labels := FromStrings(
"aaa", "111",
diff --git a/model/labels/sharding.go b/model/labels/sharding.go
index 5e3e89fbbba..8b3a369397d 100644
--- a/model/labels/sharding.go
+++ b/model/labels/sharding.go
@@ -39,9 +39,9 @@ func StableHash(ls Labels) uint64 {
}
b = append(b, v.Name...)
- b = append(b, seps[0])
+ b = append(b, sep)
b = append(b, v.Value...)
- b = append(b, seps[0])
+ b = append(b, sep)
}
return xxhash.Sum64(b)
}
diff --git a/model/labels/sharding_dedupelabels.go b/model/labels/sharding_dedupelabels.go
index 5912724f9b0..5bf41b05d6a 100644
--- a/model/labels/sharding_dedupelabels.go
+++ b/model/labels/sharding_dedupelabels.go
@@ -43,9 +43,9 @@ func StableHash(ls Labels) uint64 {
}
b = append(b, name...)
- b = append(b, seps[0])
+ b = append(b, sep)
b = append(b, value...)
- b = append(b, seps[0])
+ b = append(b, sep)
pos = newPos
}
return xxhash.Sum64(b)
diff --git a/model/labels/sharding_stringlabels.go b/model/labels/sharding_stringlabels.go
index 3ad2027d8cd..798f268eb97 100644
--- a/model/labels/sharding_stringlabels.go
+++ b/model/labels/sharding_stringlabels.go
@@ -43,9 +43,9 @@ func StableHash(ls Labels) uint64 {
}
b = append(b, v.Name...)
- b = append(b, seps[0])
+ b = append(b, sep)
b = append(b, v.Value...)
- b = append(b, seps[0])
+ b = append(b, sep)
}
if h != nil {
return h.Sum64()
diff --git a/model/relabel/relabel.go b/model/relabel/relabel.go
index 4f33edda432..a880465969a 100644
--- a/model/relabel/relabel.go
+++ b/model/relabel/relabel.go
@@ -213,6 +213,10 @@ func (re Regexp) IsZero() bool {
// String returns the original string used to compile the regular expression.
func (re Regexp) String() string {
+ if re.Regexp == nil {
+ return ""
+ }
+
str := re.Regexp.String()
// Trim the anchor `^(?:` prefix and `)$` suffix.
return str[4 : len(str)-2]
diff --git a/model/relabel/relabel_test.go b/model/relabel/relabel_test.go
index 0f11f7068d3..fc9952134d5 100644
--- a/model/relabel/relabel_test.go
+++ b/model/relabel/relabel_test.go
@@ -900,3 +900,16 @@ action: replace
})
}
}
+
+func TestRegexp_ShouldMarshalAndUnmarshalZeroValue(t *testing.T) {
+ var zero Regexp
+
+ marshalled, err := yaml.Marshal(&zero)
+ require.NoError(t, err)
+ require.Equal(t, "null\n", string(marshalled))
+
+ var unmarshalled Regexp
+ err = yaml.Unmarshal(marshalled, &unmarshalled)
+ require.NoError(t, err)
+ require.Nil(t, unmarshalled.Regexp)
+}
diff --git a/model/textparse/interface.go b/model/textparse/interface.go
index df01dbc34f3..0b5d9281e4d 100644
--- a/model/textparse/interface.go
+++ b/model/textparse/interface.go
@@ -106,8 +106,8 @@ const (
EntryInvalid Entry = -1
EntryType Entry = 0
EntryHelp Entry = 1
- EntrySeries Entry = 2 // A series with a simple float64 as value.
+ EntrySeries Entry = 2 // EntrySeries marks a series with a simple float64 as value.
EntryComment Entry = 3
EntryUnit Entry = 4
- EntryHistogram Entry = 5 // A series with a native histogram as a value.
+ EntryHistogram Entry = 5 // EntryHistogram marks a series with a native histogram as a value.
)
diff --git a/model/textparse/openmetricsparse.go b/model/textparse/openmetricsparse.go
index b7ad1dd85c3..5f0415d3ee9 100644
--- a/model/textparse/openmetricsparse.go
+++ b/model/textparse/openmetricsparse.go
@@ -94,16 +94,46 @@ type OpenMetricsParser struct {
exemplarVal float64
exemplarTs int64
hasExemplarTs bool
+
+ skipCTSeries bool
+}
+
+type openMetricsParserOptions struct {
+ SkipCTSeries bool
}
-// NewOpenMetricsParser returns a new parser of the byte slice.
-func NewOpenMetricsParser(b []byte, st *labels.SymbolTable) Parser {
- return &OpenMetricsParser{
- l: &openMetricsLexer{b: b},
- builder: labels.NewScratchBuilderWithSymbolTable(st, 16),
+type OpenMetricsOption func(*openMetricsParserOptions)
+
+// WithOMParserCTSeriesSkipped turns off exposing _created lines
+// as series, which makes those only used for parsing created timestamp
+// for `CreatedTimestamp` method purposes.
+//
+// It's recommended to use this option to avoid using _created lines for other
+// purposes than created timestamp, but leave false by default for the
+// best-effort compatibility.
+func WithOMParserCTSeriesSkipped() OpenMetricsOption {
+ return func(o *openMetricsParserOptions) {
+ o.SkipCTSeries = true
}
}
+// NewOpenMetricsParser returns a new parser for the byte slice with option to skip CT series parsing.
+func NewOpenMetricsParser(b []byte, st *labels.SymbolTable, opts ...OpenMetricsOption) Parser {
+ options := &openMetricsParserOptions{}
+
+ for _, opt := range opts {
+ opt(options)
+ }
+
+ parser := &OpenMetricsParser{
+ l: &openMetricsLexer{b: b},
+ builder: labels.NewScratchBuilderWithSymbolTable(st, 16),
+ skipCTSeries: options.SkipCTSeries,
+ }
+
+ return parser
+}
+
// Series returns the bytes of the series, the timestamp if set, and the value
// of the current sample.
func (p *OpenMetricsParser) Series() ([]byte, *int64, float64) {
@@ -219,10 +249,90 @@ func (p *OpenMetricsParser) Exemplar(e *exemplar.Exemplar) bool {
return true
}
-// CreatedTimestamp returns nil as it's not implemented yet.
-// TODO(bwplotka): https://github.com/prometheus/prometheus/issues/12980
+// CreatedTimestamp returns the created timestamp for a current Metric if exists or nil.
+// NOTE(Maniktherana): Might use additional CPU/mem resources due to deep copy of parser required for peeking given 1.0 OM specification on _created series.
func (p *OpenMetricsParser) CreatedTimestamp() *int64 {
- return nil
+ if !TypeRequiresCT(p.mtype) {
+ // Not a CT supported metric type, fast path.
+ return nil
+ }
+
+ var (
+ currLset labels.Labels
+ buf []byte
+ peekWithoutNameLsetHash uint64
+ )
+ p.Metric(&currLset)
+ currFamilyLsetHash, buf := currLset.HashWithoutLabels(buf, labels.MetricName, "le", "quantile")
+ // Search for the _created line for the currFamilyLsetHash using ephemeral parser until
+ // we see EOF or new metric family. We have to do it as we don't know where (and if)
+ // that CT line is.
+ // TODO(bwplotka): Make sure OM 1.1/2.0 pass CT via metadata or exemplar-like to avoid this.
+ peek := deepCopy(p)
+ for {
+ eType, err := peek.Next()
+ if err != nil {
+ // This means peek will give error too later on, so def no CT line found.
+ // This might result in partial scrape with wrong/missing CT, but only
+ // spec improvement would help.
+ // TODO(bwplotka): Make sure OM 1.1/2.0 pass CT via metadata or exemplar-like to avoid this.
+ return nil
+ }
+ if eType != EntrySeries {
+ // Assume we hit different family, no CT line found.
+ return nil
+ }
+
+ var peekedLset labels.Labels
+ peek.Metric(&peekedLset)
+ peekedName := peekedLset.Get(model.MetricNameLabel)
+ if !strings.HasSuffix(peekedName, "_created") {
+ // Not a CT line, search more.
+ continue
+ }
+
+ // We got a CT line here, but let's search if CT line is actually for our series, edge case.
+ peekWithoutNameLsetHash, _ = peekedLset.HashWithoutLabels(buf, labels.MetricName, "le", "quantile")
+ if peekWithoutNameLsetHash != currFamilyLsetHash {
+ // CT line for a different series, for our series no CT.
+ return nil
+ }
+ ct := int64(peek.val)
+ return &ct
+ }
+}
+
+// TypeRequiresCT returns true if the metric type requires a _created timestamp.
+func TypeRequiresCT(t model.MetricType) bool {
+ switch t {
+ case model.MetricTypeCounter, model.MetricTypeSummary, model.MetricTypeHistogram:
+ return true
+ default:
+ return false
+ }
+}
+
+// deepCopy creates a copy of a parser without re-using the slices' original memory addresses.
+func deepCopy(p *OpenMetricsParser) OpenMetricsParser {
+ newB := make([]byte, len(p.l.b))
+ copy(newB, p.l.b)
+
+ newLexer := &openMetricsLexer{
+ b: newB,
+ i: p.l.i,
+ start: p.l.start,
+ err: p.l.err,
+ state: p.l.state,
+ }
+
+ newParser := OpenMetricsParser{
+ l: newLexer,
+ builder: p.builder,
+ mtype: p.mtype,
+ val: p.val,
+ skipCTSeries: false,
+ }
+ return newParser
}
// nextToken returns the next token from the openMetricsLexer.
@@ -337,7 +447,13 @@ func (p *OpenMetricsParser) Next() (Entry, error) {
}
p.series = p.l.b[p.start:p.l.i]
- return p.parseMetricSuffix(p.nextToken())
+ if err := p.parseSeriesEndOfLine(p.nextToken()); err != nil {
+ return EntryInvalid, err
+ }
+ if p.skipCTSeries && p.isCreatedSeries() {
+ return p.Next()
+ }
+ return EntrySeries, nil
case tMName:
p.offsets = append(p.offsets, p.start, p.l.i)
p.series = p.l.b[p.start:p.l.i]
@@ -351,8 +467,14 @@ func (p *OpenMetricsParser) Next() (Entry, error) {
p.series = p.l.b[p.start:p.l.i]
t2 = p.nextToken()
}
- return p.parseMetricSuffix(t2)
+ if err := p.parseSeriesEndOfLine(t2); err != nil {
+ return EntryInvalid, err
+ }
+ if p.skipCTSeries && p.isCreatedSeries() {
+ return p.Next()
+ }
+ return EntrySeries, nil
default:
err = p.parseError("expected a valid start token", t)
}
@@ -467,51 +589,64 @@ func (p *OpenMetricsParser) parseLVals(offsets []int, isExemplar bool) ([]int, e
}
}
-// 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) {
+// isCreatedSeries returns true if the current series is a _created series.
+func (p *OpenMetricsParser) isCreatedSeries() bool {
+ var newLbs labels.Labels
+ p.Metric(&newLbs)
+ name := newLbs.Get(model.MetricNameLabel)
+ if TypeRequiresCT(p.mtype) && strings.HasSuffix(name, "_created") {
+ return true
+ }
+ return false
+}
+
+// parseSeriesEndOfLine parses the series end of the line (value, optional
+// timestamp, commentary, etc.) after the metric name and labels.
+// It starts parsing with the provided token.
+func (p *OpenMetricsParser) parseSeriesEndOfLine(t token) 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])
+ return 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
+ return err
}
p.hasTS = false
switch t2 := p.nextToken(); t2 {
case tEOF:
- return EntryInvalid, errors.New("data does not end with # EOF")
+ return errors.New("data does not end with # EOF")
case tLinebreak:
break
case tComment:
if err := p.parseComment(); err != nil {
- return EntryInvalid, err
+ return 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])
+ return 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)
+ return 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
+ return err
}
default:
- return EntryInvalid, p.parseError("expected next entry after timestamp", t3)
+ return p.parseError("expected next entry after timestamp", t3)
}
}
- return EntrySeries, nil
+
+ return nil
}
func (p *OpenMetricsParser) getFloatValue(t token, after string) (float64, error) {
diff --git a/model/textparse/openmetricsparse_test.go b/model/textparse/openmetricsparse_test.go
index bc76a540d3f..cadaabc99f7 100644
--- a/model/textparse/openmetricsparse_test.go
+++ b/model/textparse/openmetricsparse_test.go
@@ -14,6 +14,7 @@
package textparse
import (
+ "errors"
"io"
"testing"
@@ -24,6 +25,8 @@ import (
"github.com/prometheus/prometheus/model/labels"
)
+func int64p(x int64) *int64 { return &x }
+
func TestOpenMetricsParse(t *testing.T) {
input := `# HELP go_gc_duration_seconds A summary of the GC invocation durations.
# TYPE go_gc_duration_seconds summary
@@ -63,15 +66,34 @@ ss{A="a"} 0
_metric_starting_with_underscore 1
testmetric{_label_starting_with_underscore="foo"} 1
testmetric{label="\"bar\""} 1
+# HELP foo Counter with and without labels to certify CT is parsed for both cases
# TYPE foo counter
-foo_total 17.0 1520879607.789 # {id="counter-test"} 5`
+foo_total 17.0 1520879607.789 # {id="counter-test"} 5
+foo_created 1000
+foo_total{a="b"} 17.0 1520879607.789 # {id="counter-test"} 5
+foo_created{a="b"} 1000
+# HELP bar Summary with CT at the end, making sure we find CT even if it's multiple lines a far
+# TYPE bar summary
+bar_count 17.0
+bar_sum 324789.3
+bar{quantile="0.95"} 123.7
+bar{quantile="0.99"} 150.0
+bar_created 1520430000
+# HELP baz Histogram with the same objective as above's summary
+# TYPE baz histogram
+baz_bucket{le="0.0"} 0
+baz_bucket{le="+Inf"} 17
+baz_count 17
+baz_sum 324789.3
+baz_created 1520430000
+# HELP fizz_created Gauge which shouldn't be parsed as CT
+# TYPE fizz_created gauge
+fizz_created 17.0`
input += "\n# HELP metric foo\x00bar"
input += "\nnull_byte_metric{a=\"abc\x00\"} 1"
input += "\n# EOF\n"
- int64p := func(x int64) *int64 { return &x }
-
exp := []expectedParse{
{
m: "go_gc_duration_seconds",
@@ -216,6 +238,9 @@ foo_total 17.0 1520879607.789 # {id="counter-test"} 5`
m: "testmetric{label=\"\\\"bar\\\"\"}",
v: 1,
lset: labels.FromStrings("__name__", "testmetric", "label", `"bar"`),
+ }, {
+ m: "foo",
+ help: "Counter with and without labels to certify CT is parsed for both cases",
}, {
m: "foo",
typ: model.MetricTypeCounter,
@@ -225,6 +250,76 @@ foo_total 17.0 1520879607.789 # {id="counter-test"} 5`
lset: labels.FromStrings("__name__", "foo_total"),
t: int64p(1520879607789),
e: &exemplar.Exemplar{Labels: labels.FromStrings("id", "counter-test"), Value: 5},
+ ct: int64p(1000),
+ }, {
+ m: `foo_total{a="b"}`,
+ v: 17.0,
+ lset: labels.FromStrings("__name__", "foo_total", "a", "b"),
+ t: int64p(1520879607789),
+ e: &exemplar.Exemplar{Labels: labels.FromStrings("id", "counter-test"), Value: 5},
+ ct: int64p(1000),
+ }, {
+ m: "bar",
+ help: "Summary with CT at the end, making sure we find CT even if it's multiple lines a far",
+ }, {
+ m: "bar",
+ typ: model.MetricTypeSummary,
+ }, {
+ m: "bar_count",
+ v: 17.0,
+ lset: labels.FromStrings("__name__", "bar_count"),
+ ct: int64p(1520430000),
+ }, {
+ m: "bar_sum",
+ v: 324789.3,
+ lset: labels.FromStrings("__name__", "bar_sum"),
+ ct: int64p(1520430000),
+ }, {
+ m: `bar{quantile="0.95"}`,
+ v: 123.7,
+ lset: labels.FromStrings("__name__", "bar", "quantile", "0.95"),
+ ct: int64p(1520430000),
+ }, {
+ m: `bar{quantile="0.99"}`,
+ v: 150.0,
+ lset: labels.FromStrings("__name__", "bar", "quantile", "0.99"),
+ ct: int64p(1520430000),
+ }, {
+ m: "baz",
+ help: "Histogram with the same objective as above's summary",
+ }, {
+ m: "baz",
+ typ: model.MetricTypeHistogram,
+ }, {
+ m: `baz_bucket{le="0.0"}`,
+ v: 0,
+ lset: labels.FromStrings("__name__", "baz_bucket", "le", "0.0"),
+ ct: int64p(1520430000),
+ }, {
+ m: `baz_bucket{le="+Inf"}`,
+ v: 17,
+ lset: labels.FromStrings("__name__", "baz_bucket", "le", "+Inf"),
+ ct: int64p(1520430000),
+ }, {
+ m: `baz_count`,
+ v: 17,
+ lset: labels.FromStrings("__name__", "baz_count"),
+ ct: int64p(1520430000),
+ }, {
+ m: `baz_sum`,
+ v: 324789.3,
+ lset: labels.FromStrings("__name__", "baz_sum"),
+ ct: int64p(1520430000),
+ }, {
+ m: "fizz_created",
+ help: "Gauge which shouldn't be parsed as CT",
+ }, {
+ m: "fizz_created",
+ typ: model.MetricTypeGauge,
+ }, {
+ m: `fizz_created`,
+ v: 17,
+ lset: labels.FromStrings("__name__", "fizz_created"),
}, {
m: "metric",
help: "foo\x00bar",
@@ -235,8 +330,8 @@ foo_total 17.0 1520879607.789 # {id="counter-test"} 5`
},
}
- p := NewOpenMetricsParser([]byte(input), labels.NewSymbolTable())
- checkParseResults(t, p, exp)
+ p := NewOpenMetricsParser([]byte(input), labels.NewSymbolTable(), WithOMParserCTSeriesSkipped())
+ checkParseResultsWithCT(t, p, exp, true)
}
func TestUTF8OpenMetricsParse(t *testing.T) {
@@ -251,6 +346,7 @@ func TestUTF8OpenMetricsParse(t *testing.T) {
# 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_created"} 12313
{"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
@@ -274,10 +370,12 @@ func TestUTF8OpenMetricsParse(t *testing.T) {
m: `{"go.gc_duration_seconds",quantile="0"}`,
v: 4.9351e-05,
lset: labels.FromStrings("__name__", "go.gc_duration_seconds", "quantile", "0"),
+ ct: int64p(12313),
}, {
m: `{"go.gc_duration_seconds",quantile="0.25"}`,
v: 7.424100000000001e-05,
lset: labels.FromStrings("__name__", "go.gc_duration_seconds", "quantile", "0.25"),
+ ct: int64p(12313),
}, {
m: `{"go.gc_duration_seconds",quantile="0.5",a="b"}`,
v: 8.3835e-05,
@@ -306,8 +404,8 @@ choices}`, "strange©™\n'quoted' \"name\"", "6"),
},
}
- p := NewOpenMetricsParser([]byte(input), labels.NewSymbolTable())
- checkParseResults(t, p, exp)
+ p := NewOpenMetricsParser([]byte(input), labels.NewSymbolTable(), WithOMParserCTSeriesSkipped())
+ checkParseResultsWithCT(t, p, exp, true)
}
func TestOpenMetricsParseErrors(t *testing.T) {
@@ -598,10 +696,6 @@ func TestOpenMetricsParseErrors(t *testing.T) {
input: "# TYPE hhh histogram\nhhh_bucket{le=\"+Inf\"} 1 # {aa=\"bb\"} 4 -Inf",
err: `invalid exemplar timestamp -Inf`,
},
- {
- input: "# TYPE hhh histogram\nhhh_bucket{le=\"+Inf\"} 1 # {aa=\"bb\"} 4 Inf",
- err: `invalid exemplar timestamp +Inf`,
- },
}
for i, c := range cases {
@@ -684,3 +778,217 @@ func TestOMNullByteHandling(t *testing.T) {
require.Equal(t, c.err, err.Error(), "test %d", i)
}
}
+
+// While not desirable, there are cases were CT fails to parse and
+// these tests show them.
+// TODO(maniktherana): Make sure OM 1.1/2.0 pass CT via metadata or exemplar-like to avoid this.
+func TestCTParseFailures(t *testing.T) {
+ input := `# HELP something Histogram with _created between buckets and summary
+# TYPE something histogram
+something_count 17
+something_sum 324789.3
+something_created 1520430001
+something_bucket{le="0.0"} 0
+something_bucket{le="+Inf"} 17
+# HELP thing Histogram with _created as first line
+# TYPE thing histogram
+thing_created 1520430002
+thing_count 17
+thing_sum 324789.3
+thing_bucket{le="0.0"} 0
+thing_bucket{le="+Inf"} 17
+# HELP yum Summary with _created between sum and quantiles
+# TYPE yum summary
+yum_count 17.0
+yum_sum 324789.3
+yum_created 1520430003
+yum{quantile="0.95"} 123.7
+yum{quantile="0.99"} 150.0
+# HELP foobar Summary with _created as the first line
+# TYPE foobar summary
+foobar_created 1520430004
+foobar_count 17.0
+foobar_sum 324789.3
+foobar{quantile="0.95"} 123.7
+foobar{quantile="0.99"} 150.0`
+
+ input += "\n# EOF\n"
+
+ int64p := func(x int64) *int64 { return &x }
+
+ type expectCT struct {
+ m string
+ ct *int64
+ typ model.MetricType
+ help string
+ isErr bool
+ }
+
+ exp := []expectCT{
+ {
+ m: "something",
+ help: "Histogram with _created between buckets and summary",
+ isErr: false,
+ }, {
+ m: "something",
+ typ: model.MetricTypeHistogram,
+ isErr: false,
+ }, {
+ m: `something_count`,
+ ct: int64p(1520430001),
+ isErr: false,
+ }, {
+ m: `something_sum`,
+ ct: int64p(1520430001),
+ isErr: false,
+ }, {
+ m: `something_bucket{le="0.0"}`,
+ ct: int64p(1520430001),
+ isErr: true,
+ }, {
+ m: `something_bucket{le="+Inf"}`,
+ ct: int64p(1520430001),
+ isErr: true,
+ }, {
+ m: "thing",
+ help: "Histogram with _created as first line",
+ isErr: false,
+ }, {
+ m: "thing",
+ typ: model.MetricTypeHistogram,
+ isErr: false,
+ }, {
+ m: `thing_count`,
+ ct: int64p(1520430002),
+ isErr: true,
+ }, {
+ m: `thing_sum`,
+ ct: int64p(1520430002),
+ isErr: true,
+ }, {
+ m: `thing_bucket{le="0.0"}`,
+ ct: int64p(1520430002),
+ isErr: true,
+ }, {
+ m: `thing_bucket{le="+Inf"}`,
+ ct: int64p(1520430002),
+ isErr: true,
+ }, {
+ m: "yum",
+ help: "Summary with _created between summary and quantiles",
+ isErr: false,
+ }, {
+ m: "yum",
+ typ: model.MetricTypeSummary,
+ isErr: false,
+ }, {
+ m: "yum_count",
+ ct: int64p(1520430003),
+ isErr: false,
+ }, {
+ m: "yum_sum",
+ ct: int64p(1520430003),
+ isErr: false,
+ }, {
+ m: `yum{quantile="0.95"}`,
+ ct: int64p(1520430003),
+ isErr: true,
+ }, {
+ m: `yum{quantile="0.99"}`,
+ ct: int64p(1520430003),
+ isErr: true,
+ }, {
+ m: "foobar",
+ help: "Summary with _created as the first line",
+ isErr: false,
+ }, {
+ m: "foobar",
+ typ: model.MetricTypeSummary,
+ isErr: false,
+ }, {
+ m: "foobar_count",
+ ct: int64p(1520430004),
+ isErr: true,
+ }, {
+ m: "foobar_sum",
+ ct: int64p(1520430004),
+ isErr: true,
+ }, {
+ m: `foobar{quantile="0.95"}`,
+ ct: int64p(1520430004),
+ isErr: true,
+ }, {
+ m: `foobar{quantile="0.99"}`,
+ ct: int64p(1520430004),
+ isErr: true,
+ },
+ }
+
+ p := NewOpenMetricsParser([]byte(input), labels.NewSymbolTable(), WithOMParserCTSeriesSkipped())
+ 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:
+ p.Metric(&res)
+
+ if ct := p.CreatedTimestamp(); exp[i].isErr {
+ require.Nil(t, ct)
+ } else {
+ require.Equal(t, *exp[i].ct, *ct)
+ }
+ default:
+ i++
+ continue
+ }
+ i++
+ }
+}
+
+func TestDeepCopy(t *testing.T) {
+ input := []byte(`# HELP go_goroutines A gauge goroutines.
+# TYPE go_goroutines gauge
+go_goroutines 33 123.123
+# TYPE go_gc_duration_seconds summary
+go_gc_duration_seconds
+go_gc_duration_seconds_created`)
+
+ st := labels.NewSymbolTable()
+ parser := NewOpenMetricsParser(input, st, WithOMParserCTSeriesSkipped()).(*OpenMetricsParser)
+
+ // Modify the original parser state
+ _, err := parser.Next()
+ require.NoError(t, err)
+ require.Equal(t, "go_goroutines", string(parser.l.b[parser.offsets[0]:parser.offsets[1]]))
+ require.True(t, parser.skipCTSeries)
+
+ // Create a deep copy of the parser
+ copyParser := deepCopy(parser)
+ etype, err := copyParser.Next()
+ require.NoError(t, err)
+ require.Equal(t, EntryType, etype)
+ require.True(t, parser.skipCTSeries)
+ require.False(t, copyParser.skipCTSeries)
+
+ // Modify the original parser further
+ parser.Next()
+ parser.Next()
+ parser.Next()
+ require.Equal(t, "go_gc_duration_seconds", string(parser.l.b[parser.offsets[0]:parser.offsets[1]]))
+ require.Equal(t, "summary", string(parser.mtype))
+ require.False(t, copyParser.skipCTSeries)
+ require.True(t, parser.skipCTSeries)
+
+ // Ensure the copy remains unchanged
+ copyParser.Next()
+ copyParser.Next()
+ require.Equal(t, "go_gc_duration_seconds", string(copyParser.l.b[copyParser.offsets[0]:copyParser.offsets[1]]))
+ require.False(t, copyParser.skipCTSeries)
+}
diff --git a/model/textparse/promparse_test.go b/model/textparse/promparse_test.go
index 66986291d7c..7971d23b7e0 100644
--- a/model/textparse/promparse_test.go
+++ b/model/textparse/promparse_test.go
@@ -18,6 +18,7 @@ import (
"errors"
"io"
"os"
+ "strings"
"testing"
"github.com/klauspost/compress/gzip"
@@ -41,6 +42,7 @@ type expectedParse struct {
unit string
comment string
e *exemplar.Exemplar
+ ct *int64
}
func TestPromParse(t *testing.T) {
@@ -188,6 +190,10 @@ testmetric{label="\"bar\""} 1`
}
func checkParseResults(t *testing.T, p Parser, exp []expectedParse) {
+ checkParseResultsWithCT(t, p, exp, false)
+}
+
+func checkParseResultsWithCT(t *testing.T, p Parser, exp []expectedParse, ctLinesRemoved bool) {
i := 0
var res labels.Labels
@@ -205,6 +211,14 @@ func checkParseResults(t *testing.T, p Parser, exp []expectedParse) {
p.Metric(&res)
+ if ctLinesRemoved {
+ // Are CT series skipped?
+ _, typ := p.Type()
+ if TypeRequiresCT(typ) && strings.HasSuffix(res.Get(labels.MetricName), "_created") {
+ t.Fatalf("we exped created lines skipped")
+ }
+ }
+
require.Equal(t, exp[i].m, string(m))
require.Equal(t, exp[i].t, ts)
require.Equal(t, exp[i].v, v)
@@ -218,6 +232,11 @@ func checkParseResults(t *testing.T, p Parser, exp []expectedParse) {
require.True(t, found)
testutil.RequireEqual(t, *exp[i].e, e)
}
+ if ct := p.CreatedTimestamp(); ct != nil {
+ require.Equal(t, *exp[i].ct, *ct)
+ } else {
+ require.Nil(t, exp[i].ct)
+ }
case EntryType:
m, typ := p.Type()
@@ -475,8 +494,10 @@ const (
func BenchmarkParse(b *testing.B) {
for parserName, parser := range map[string]func([]byte, *labels.SymbolTable) Parser{
- "prometheus": NewPromParser,
- "openmetrics": NewOpenMetricsParser,
+ "prometheus": NewPromParser,
+ "openmetrics": func(b []byte, st *labels.SymbolTable) Parser {
+ return NewOpenMetricsParser(b, st)
+ },
} {
for _, fn := range []string{"promtestdata.txt", "promtestdata.nometa.txt"} {
f, err := os.Open(fn)
diff --git a/model/textparse/protobufparse.go b/model/textparse/protobufparse.go
index ea3a2e1a34f..e384a75fca4 100644
--- a/model/textparse/protobufparse.go
+++ b/model/textparse/protobufparse.go
@@ -47,7 +47,7 @@ import (
// the re-arrangement work is actually causing problems (which has to be seen),
// that expectation needs to be changed.
type ProtobufParser struct {
- in []byte // The intput to parse.
+ in []byte // The input to parse.
inPos int // Position within the input.
metricPos int // Position within Metric slice.
// fieldPos is the position within a Summary or (legacy) Histogram. -2
@@ -71,7 +71,7 @@ type ProtobufParser struct {
mf *dto.MetricFamily
- // Wether to also parse a classic histogram that is also present as a
+ // Whether to also parse a classic histogram that is also present as a
// native histogram.
parseClassicHistograms bool
@@ -409,6 +409,7 @@ func (p *ProtobufParser) Next() (Entry, error) {
switch p.state {
case EntryInvalid:
p.metricPos = 0
+ p.exemplarPos = 0
p.fieldPos = -2
n, err := readDelimited(p.in[p.inPos:], p.mf)
p.inPos += n
@@ -485,6 +486,7 @@ func (p *ProtobufParser) Next() (Entry, error) {
p.metricPos++
p.fieldPos = -2
p.fieldsDone = false
+ p.exemplarPos = 0
// If this is a metric family containing native
// histograms, we have to switch back to native
// histograms after parsing a classic histogram.
diff --git a/model/textparse/protobufparse_test.go b/model/textparse/protobufparse_test.go
index e323a6cc8f3..cf34ae52df1 100644
--- a/model/textparse/protobufparse_test.go
+++ b/model/textparse/protobufparse_test.go
@@ -695,6 +695,70 @@ metric: <
timestamp_ms: 1234568
>
+`,
+
+ `name: "test_histogram_with_native_histogram_exemplars2"
+help: "Another 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
+ >
+ bucket: <
+ cumulative_count: 16
+ upper_bound: -0.0002899999999999998
+ >
+ 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
+ >
+ >
+ >
+ timestamp_ms: 1234568
+>
+
`,
}
@@ -1276,6 +1340,41 @@ func TestProtobufParse(t *testing.T) {
{Labels: labels.FromStrings("dummyID", "59772"), Value: -0.00052, HasTs: true, Ts: 1625851160156},
},
},
+ {
+ m: "test_histogram_with_native_histogram_exemplars2",
+ help: "Another histogram with native histogram exemplars.",
+ },
+ {
+ m: "test_histogram_with_native_histogram_exemplars2",
+ typ: model.MetricTypeHistogram,
+ },
+ {
+ m: "test_histogram_with_native_histogram_exemplars2",
+ 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_exemplars2",
+ ),
+ e: []exemplar.Exemplar{
+ {Labels: labels.FromStrings("dummyID", "59780"), Value: -0.00039, HasTs: true, Ts: 1625851155146},
+ },
+ },
},
},
{
@@ -1995,15 +2094,15 @@ func TestProtobufParse(t *testing.T) {
"__name__", "without_quantiles_sum",
),
},
- { // 78
+ { // 81
m: "empty_histogram",
help: "A histogram without observations and with a zero threshold of zero but with a no-op span to identify it as a native histogram.",
},
- { // 79
+ { // 82
m: "empty_histogram",
typ: model.MetricTypeHistogram,
},
- { // 80
+ { // 83
m: "empty_histogram",
shs: &histogram.Histogram{
CounterResetHint: histogram.UnknownCounterReset,
@@ -2014,15 +2113,15 @@ func TestProtobufParse(t *testing.T) {
"__name__", "empty_histogram",
),
},
- { // 81
+ { // 84
m: "test_counter_with_createdtimestamp",
help: "A counter with a created timestamp.",
},
- { // 82
+ { // 85
m: "test_counter_with_createdtimestamp",
typ: model.MetricTypeCounter,
},
- { // 83
+ { // 86
m: "test_counter_with_createdtimestamp",
v: 42,
ct: 1000,
@@ -2030,15 +2129,15 @@ func TestProtobufParse(t *testing.T) {
"__name__", "test_counter_with_createdtimestamp",
),
},
- { // 84
+ { // 87
m: "test_summary_with_createdtimestamp",
help: "A summary with a created timestamp.",
},
- { // 85
+ { // 88
m: "test_summary_with_createdtimestamp",
typ: model.MetricTypeSummary,
},
- { // 86
+ { // 89
m: "test_summary_with_createdtimestamp_count",
v: 42,
ct: 1000,
@@ -2046,7 +2145,7 @@ func TestProtobufParse(t *testing.T) {
"__name__", "test_summary_with_createdtimestamp_count",
),
},
- { // 87
+ { // 90
m: "test_summary_with_createdtimestamp_sum",
v: 1.234,
ct: 1000,
@@ -2054,15 +2153,15 @@ func TestProtobufParse(t *testing.T) {
"__name__", "test_summary_with_createdtimestamp_sum",
),
},
- { // 88
+ { // 91
m: "test_histogram_with_createdtimestamp",
help: "A histogram with a created timestamp.",
},
- { // 89
+ { // 92
m: "test_histogram_with_createdtimestamp",
typ: model.MetricTypeHistogram,
},
- { // 90
+ { // 93
m: "test_histogram_with_createdtimestamp",
ct: 1000,
shs: &histogram.Histogram{
@@ -2074,15 +2173,15 @@ func TestProtobufParse(t *testing.T) {
"__name__", "test_histogram_with_createdtimestamp",
),
},
- { // 91
+ { // 94
m: "test_gaugehistogram_with_createdtimestamp",
help: "A gauge histogram with a created timestamp.",
},
- { // 92
+ { // 95
m: "test_gaugehistogram_with_createdtimestamp",
typ: model.MetricTypeGaugeHistogram,
},
- { // 93
+ { // 96
m: "test_gaugehistogram_with_createdtimestamp",
ct: 1000,
shs: &histogram.Histogram{
@@ -2094,15 +2193,15 @@ func TestProtobufParse(t *testing.T) {
"__name__", "test_gaugehistogram_with_createdtimestamp",
),
},
- { // 94
+ { // 97
m: "test_histogram_with_native_histogram_exemplars",
help: "A histogram with native histogram exemplars.",
},
- { // 95
+ { // 98
m: "test_histogram_with_native_histogram_exemplars",
typ: model.MetricTypeHistogram,
},
- { // 96
+ { // 99
m: "test_histogram_with_native_histogram_exemplars",
t: 1234568,
shs: &histogram.Histogram{
@@ -2130,7 +2229,7 @@ func TestProtobufParse(t *testing.T) {
{Labels: labels.FromStrings("dummyID", "59772"), Value: -0.00052, HasTs: true, Ts: 1625851160156},
},
},
- { // 97
+ { // 100
m: "test_histogram_with_native_histogram_exemplars_count",
t: 1234568,
v: 175,
@@ -2138,7 +2237,7 @@ func TestProtobufParse(t *testing.T) {
"__name__", "test_histogram_with_native_histogram_exemplars_count",
),
},
- { // 98
+ { // 101
m: "test_histogram_with_native_histogram_exemplars_sum",
t: 1234568,
v: 0.0008280461746287094,
@@ -2146,7 +2245,7 @@ func TestProtobufParse(t *testing.T) {
"__name__", "test_histogram_with_native_histogram_exemplars_sum",
),
},
- { // 99
+ { // 102
m: "test_histogram_with_native_histogram_exemplars_bucket\xffle\xff-0.0004899999999999998",
t: 1234568,
v: 2,
@@ -2155,7 +2254,7 @@ func TestProtobufParse(t *testing.T) {
"le", "-0.0004899999999999998",
),
},
- { // 100
+ { // 103
m: "test_histogram_with_native_histogram_exemplars_bucket\xffle\xff-0.0003899999999999998",
t: 1234568,
v: 4,
@@ -2167,7 +2266,7 @@ func TestProtobufParse(t *testing.T) {
{Labels: labels.FromStrings("dummyID", "59727"), Value: -0.00039, HasTs: true, Ts: 1625851155146},
},
},
- { // 101
+ { // 104
m: "test_histogram_with_native_histogram_exemplars_bucket\xffle\xff-0.0002899999999999998",
t: 1234568,
v: 16,
@@ -2179,7 +2278,7 @@ func TestProtobufParse(t *testing.T) {
{Labels: labels.FromStrings("dummyID", "5617"), Value: -0.00029, HasTs: false},
},
},
- { // 102
+ { // 105
m: "test_histogram_with_native_histogram_exemplars_bucket\xffle\xff+Inf",
t: 1234568,
v: 175,
@@ -2188,6 +2287,93 @@ func TestProtobufParse(t *testing.T) {
"le", "+Inf",
),
},
+ { // 106
+ m: "test_histogram_with_native_histogram_exemplars2",
+ help: "Another histogram with native histogram exemplars.",
+ },
+ { // 107
+ m: "test_histogram_with_native_histogram_exemplars2",
+ typ: model.MetricTypeHistogram,
+ },
+ { // 108
+ m: "test_histogram_with_native_histogram_exemplars2",
+ 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_exemplars2",
+ ),
+ e: []exemplar.Exemplar{
+ {Labels: labels.FromStrings("dummyID", "59780"), Value: -0.00039, HasTs: true, Ts: 1625851155146},
+ },
+ },
+ { // 109
+ m: "test_histogram_with_native_histogram_exemplars2_count",
+ t: 1234568,
+ v: 175,
+ lset: labels.FromStrings(
+ "__name__", "test_histogram_with_native_histogram_exemplars2_count",
+ ),
+ },
+ { // 110
+ m: "test_histogram_with_native_histogram_exemplars2_sum",
+ t: 1234568,
+ v: 0.0008280461746287094,
+ lset: labels.FromStrings(
+ "__name__", "test_histogram_with_native_histogram_exemplars2_sum",
+ ),
+ },
+ { // 111
+ m: "test_histogram_with_native_histogram_exemplars2_bucket\xffle\xff-0.0004899999999999998",
+ t: 1234568,
+ v: 2,
+ lset: labels.FromStrings(
+ "__name__", "test_histogram_with_native_histogram_exemplars2_bucket",
+ "le", "-0.0004899999999999998",
+ ),
+ },
+ { // 112
+ m: "test_histogram_with_native_histogram_exemplars2_bucket\xffle\xff-0.0003899999999999998",
+ t: 1234568,
+ v: 4,
+ lset: labels.FromStrings(
+ "__name__", "test_histogram_with_native_histogram_exemplars2_bucket",
+ "le", "-0.0003899999999999998",
+ ),
+ },
+ { // 113
+ m: "test_histogram_with_native_histogram_exemplars2_bucket\xffle\xff-0.0002899999999999998",
+ t: 1234568,
+ v: 16,
+ lset: labels.FromStrings(
+ "__name__", "test_histogram_with_native_histogram_exemplars2_bucket",
+ "le", "-0.0002899999999999998",
+ ),
+ },
+ { // 114
+ m: "test_histogram_with_native_histogram_exemplars2_bucket\xffle\xff+Inf",
+ t: 1234568,
+ v: 175,
+ lset: labels.FromStrings(
+ "__name__", "test_histogram_with_native_histogram_exemplars2_bucket",
+ "le", "+Inf",
+ ),
+ },
},
},
}
diff --git a/notifier/notifier.go b/notifier/notifier.go
index 68b0d4961e1..218e4cb8c74 100644
--- a/notifier/notifier.go
+++ b/notifier/notifier.go
@@ -674,7 +674,6 @@ func (n *Manager) sendOne(ctx context.Context, c *http.Client, url string, b []b
}()
// Any HTTP status 2xx is OK.
- //nolint:usestdlibvars
if resp.StatusCode/100 != 2 {
return fmt.Errorf("bad response status %s", resp.Status)
}
diff --git a/notifier/notifier_test.go b/notifier/notifier_test.go
index 2cdaa9e06df..cf922a537c0 100644
--- a/notifier/notifier_test.go
+++ b/notifier/notifier_test.go
@@ -711,7 +711,7 @@ func TestHangingNotifier(t *testing.T) {
)
var (
- sendTimeout = 10 * time.Millisecond
+ sendTimeout = 100 * time.Millisecond
sdUpdatert = sendTimeout / 2
done = make(chan struct{})
diff --git a/prompb/io/prometheus/write/v2/types.pb.go b/prompb/io/prometheus/write/v2/types.pb.go
index d6ea8398f74..3420d20e25c 100644
--- a/prompb/io/prometheus/write/v2/types.pb.go
+++ b/prompb/io/prometheus/write/v2/types.pb.go
@@ -302,15 +302,10 @@ type Exemplar struct {
// value represents an exact example value. This can be useful when the exemplar
// is attached to a histogram, which only gives an estimated value through buckets.
Value float64 `protobuf:"fixed64,2,opt,name=value,proto3" json:"value,omitempty"`
- // timestamp represents an optional timestamp of the sample in ms.
+ // timestamp represents the timestamp of the exemplar in ms.
//
// For Go, see github.com/prometheus/prometheus/model/timestamp/timestamp.go
// for conversion from/to time.Time to Prometheus timestamp.
- //
- // Note that the "optional" keyword is omitted due to
- // https://cloud.google.com/apis/design/design_patterns.md#optional_primitive_fields
- // Zero value means value not set. If you need to use exactly zero value for
- // the timestamp, use 1 millisecond before or after.
Timestamp int64 `protobuf:"varint,3,opt,name=timestamp,proto3" json:"timestamp,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
diff --git a/prompb/io/prometheus/write/v2/types.proto b/prompb/io/prometheus/write/v2/types.proto
index 0cc7b8bc4a4..ff6c4936bb3 100644
--- a/prompb/io/prometheus/write/v2/types.proto
+++ b/prompb/io/prometheus/write/v2/types.proto
@@ -107,15 +107,10 @@ message Exemplar {
// value represents an exact example value. This can be useful when the exemplar
// is attached to a histogram, which only gives an estimated value through buckets.
double value = 2;
- // timestamp represents an optional timestamp of the sample in ms.
+ // timestamp represents the timestamp of the exemplar in ms.
//
// For Go, see github.com/prometheus/prometheus/model/timestamp/timestamp.go
// for conversion from/to time.Time to Prometheus timestamp.
- //
- // Note that the "optional" keyword is omitted due to
- // https://cloud.google.com/apis/design/design_patterns.md#optional_primitive_fields
- // Zero value means value not set. If you need to use exactly zero value for
- // the timestamp, use 1 millisecond before or after.
int64 timestamp = 3;
}
diff --git a/promql/bench_test.go b/promql/bench_test.go
index bd672802949..a7817bd8498 100644
--- a/promql/bench_test.go
+++ b/promql/bench_test.go
@@ -21,10 +21,13 @@ import (
"testing"
"time"
+ "github.com/stretchr/testify/require"
+
"github.com/prometheus/prometheus/model/histogram"
"github.com/prometheus/prometheus/model/labels"
"github.com/prometheus/prometheus/promql"
"github.com/prometheus/prometheus/promql/parser"
+ "github.com/prometheus/prometheus/promql/promqltest"
"github.com/prometheus/prometheus/storage"
"github.com/prometheus/prometheus/tsdb/tsdbutil"
"github.com/prometheus/prometheus/util/teststorage"
@@ -165,6 +168,9 @@ func rangeQueryCases() []benchCase {
{
expr: "sum(a_X)",
},
+ {
+ expr: "avg(a_X)",
+ },
{
expr: "sum without (l)(h_X)",
},
@@ -271,7 +277,7 @@ func BenchmarkRangeQuery(b *testing.B) {
MaxSamples: 50000000,
Timeout: 100 * time.Second,
}
- engine := promql.NewEngine(opts)
+ engine := promqltest.NewTestEngineWithOpts(b, opts)
const interval = 10000 // 10s interval.
// A day of data plus 10k steps.
@@ -362,7 +368,7 @@ func BenchmarkNativeHistograms(b *testing.B) {
for _, tc := range cases {
b.Run(tc.name, func(b *testing.B) {
- ng := promql.NewEngine(opts)
+ ng := promqltest.NewTestEngineWithOpts(b, opts)
for i := 0; i < b.N; i++ {
qry, err := ng.NewRangeQuery(context.Background(), testStorage, nil, tc.query, start, end, step)
if err != nil {
@@ -376,6 +382,126 @@ func BenchmarkNativeHistograms(b *testing.B) {
}
}
+func BenchmarkInfoFunction(b *testing.B) {
+ // Initialize test storage and generate test series data.
+ testStorage := teststorage.New(b)
+ defer testStorage.Close()
+
+ start := time.Unix(0, 0)
+ end := start.Add(2 * time.Hour)
+ step := 30 * time.Second
+
+ // Generate time series data for the benchmark.
+ generateInfoFunctionTestSeries(b, testStorage, 100, 2000, 3600)
+
+ // Define test cases with queries to benchmark.
+ cases := []struct {
+ name string
+ query string
+ }{
+ {
+ name: "Joining info metrics with other metrics with group_left example 1",
+ query: "rate(http_server_request_duration_seconds_count[2m]) * on (job, instance) group_left (k8s_cluster_name) target_info{k8s_cluster_name=\"us-east\"}",
+ },
+ {
+ name: "Joining info metrics with other metrics with info() example 1",
+ query: `info(rate(http_server_request_duration_seconds_count[2m]), {k8s_cluster_name="us-east"})`,
+ },
+ {
+ name: "Joining info metrics with other metrics with group_left example 2",
+ query: "sum by (k8s_cluster_name, http_status_code) (rate(http_server_request_duration_seconds_count[2m]) * on (job, instance) group_left (k8s_cluster_name) target_info)",
+ },
+ {
+ name: "Joining info metrics with other metrics with info() example 2",
+ query: `sum by (k8s_cluster_name, http_status_code) (info(rate(http_server_request_duration_seconds_count[2m]), {k8s_cluster_name=~".+"}))`,
+ },
+ }
+
+ // Benchmark each query type.
+ for _, tc := range cases {
+ // Initialize the PromQL engine once for all benchmarks.
+ opts := promql.EngineOpts{
+ Logger: nil,
+ Reg: nil,
+ MaxSamples: 50000000,
+ Timeout: 100 * time.Second,
+ EnableAtModifier: true,
+ EnableNegativeOffset: true,
+ }
+ engine := promql.NewEngine(opts)
+ b.Run(tc.name, func(b *testing.B) {
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ b.StopTimer() // Stop the timer to exclude setup time.
+ qry, err := engine.NewRangeQuery(context.Background(), testStorage, nil, tc.query, start, end, step)
+ require.NoError(b, err)
+ b.StartTimer()
+ result := qry.Exec(context.Background())
+ require.NoError(b, result.Err)
+ }
+ })
+ }
+
+ // Report allocations.
+ b.ReportAllocs()
+}
+
+// Helper function to generate target_info and http_server_request_duration_seconds_count series for info function benchmarking.
+func generateInfoFunctionTestSeries(tb testing.TB, stor *teststorage.TestStorage, infoSeriesNum, interval, numIntervals int) {
+ tb.Helper()
+
+ ctx := context.Background()
+ statusCodes := []string{"200", "400", "500"}
+
+ // Generate target_info metrics with instance and job labels, and k8s_cluster_name label.
+ // Generate http_server_request_duration_seconds_count metrics with instance and job labels, and http_status_code label.
+ // the classic target_info metrics is gauge type.
+ metrics := make([]labels.Labels, 0, infoSeriesNum+len(statusCodes))
+ for i := 0; i < infoSeriesNum; i++ {
+ clusterName := "us-east"
+ if i >= infoSeriesNum/2 {
+ clusterName = "eu-south"
+ }
+ metrics = append(metrics, labels.FromStrings(
+ "__name__", "target_info",
+ "instance", "instance"+strconv.Itoa(i),
+ "job", "job"+strconv.Itoa(i),
+ "k8s_cluster_name", clusterName,
+ ))
+ }
+
+ for _, statusCode := range statusCodes {
+ metrics = append(metrics, labels.FromStrings(
+ "__name__", "http_server_request_duration_seconds_count",
+ "instance", "instance0",
+ "job", "job0",
+ "http_status_code", statusCode,
+ ))
+ }
+
+ // Append the generated metrics and samples to the storage.
+ refs := make([]storage.SeriesRef, len(metrics))
+
+ for i := 0; i < numIntervals; i++ {
+ a := stor.Appender(context.Background())
+ ts := int64(i * interval)
+ for j, metric := range metrics[:infoSeriesNum] {
+ ref, _ := a.Append(refs[j], metric, ts, 1)
+ refs[j] = ref
+ }
+
+ for j, metric := range metrics[infoSeriesNum:] {
+ ref, _ := a.Append(refs[j+infoSeriesNum], metric, ts, float64(i))
+ refs[j+infoSeriesNum] = ref
+ }
+
+ require.NoError(tb, a.Commit())
+ }
+
+ stor.DB.ForceHeadMMap() // Ensure we have at most one head chunk for every series.
+ stor.DB.Compact(ctx)
+}
+
func generateNativeHistogramSeries(app storage.Appender, numSeries int) error {
commonLabels := []string{labels.MetricName, "native_histogram_series", "foo", "bar"}
series := make([][]*histogram.Histogram, numSeries)
diff --git a/promql/engine.go b/promql/engine.go
index 579c2693476..882399c5eb5 100644
--- a/promql/engine.go
+++ b/promql/engine.go
@@ -19,6 +19,7 @@ import (
"context"
"errors"
"fmt"
+ "io"
"math"
"reflect"
"runtime"
@@ -271,6 +272,8 @@ func contextErr(err error, env string) error {
//
// 2) Enforcement of the maximum number of concurrent queries.
type QueryTracker interface {
+ io.Closer
+
// GetMaxConcurrent returns maximum number of concurrent queries that are allowed by this tracker.
GetMaxConcurrent() int
@@ -313,6 +316,11 @@ type EngineOpts struct {
// EnablePerStepStats if true allows for per-step stats to be computed on request. Disabled otherwise.
EnablePerStepStats bool
+
+ // EnableDelayedNameRemoval delays the removal of the __name__ label to the last step of the query evaluation.
+ // This is useful in certain scenarios where the __name__ label must be preserved or where applying a
+ // regex-matcher to the __name__ label may otherwise lead to duplicate labelset errors.
+ EnableDelayedNameRemoval bool
}
// Engine handles the lifetime of queries from beginning to end.
@@ -330,6 +338,7 @@ type Engine struct {
enableAtModifier bool
enableNegativeOffset bool
enablePerStepStats bool
+ enableDelayedNameRemoval bool
}
// NewEngine returns a new engine.
@@ -420,7 +429,20 @@ func NewEngine(opts EngineOpts) *Engine {
enableAtModifier: opts.EnableAtModifier,
enableNegativeOffset: opts.EnableNegativeOffset,
enablePerStepStats: opts.EnablePerStepStats,
+ enableDelayedNameRemoval: opts.EnableDelayedNameRemoval,
+ }
+}
+
+// Close closes ng.
+func (ng *Engine) Close() error {
+ if ng == nil {
+ return nil
+ }
+
+ if ng.activeQueryTracker != nil {
+ return ng.activeQueryTracker.Close()
}
+ return nil
}
// SetQueryLogger sets the query logger.
@@ -573,7 +595,7 @@ func (ng *Engine) validateOpts(expr parser.Expr) error {
return validationErr
}
-// NewTestQuery: inject special behaviour into Query for testing.
+// NewTestQuery injects special behaviour into Query for testing.
func (ng *Engine) NewTestQuery(f func(context.Context) error) Query {
qry := &query{
q: "test statement",
@@ -706,16 +728,17 @@ func (ng *Engine) execEvalStmt(ctx context.Context, query *query, s *parser.Eval
startTimestamp: start,
endTimestamp: start,
interval: 1,
- ctx: ctxInnerEval,
maxSamples: ng.maxSamplesPerQuery,
logger: ng.logger,
lookbackDelta: s.LookbackDelta,
samplesStats: query.sampleStats,
noStepSubqueryIntervalFn: ng.noStepSubqueryIntervalFn,
+ enableDelayedNameRemoval: ng.enableDelayedNameRemoval,
+ querier: querier,
}
query.sampleStats.InitStepTracking(start, start, 1)
- val, warnings, err := evaluator.Eval(s.Expr)
+ val, warnings, err := evaluator.Eval(ctxInnerEval, s.Expr)
evalSpanTimer.Finish()
@@ -743,9 +766,9 @@ func (ng *Engine) execEvalStmt(ctx context.Context, query *query, s *parser.Eval
// Point might have a different timestamp, force it to the evaluation
// timestamp as that is when we ran the evaluation.
if len(s.Histograms) > 0 {
- vector[i] = Sample{Metric: s.Metric, H: s.Histograms[0].H, T: start}
+ vector[i] = Sample{Metric: s.Metric, H: s.Histograms[0].H, T: start, DropName: s.DropName}
} else {
- vector[i] = Sample{Metric: s.Metric, F: s.Floats[0].F, T: start}
+ vector[i] = Sample{Metric: s.Metric, F: s.Floats[0].F, T: start, DropName: s.DropName}
}
}
return vector, warnings, nil
@@ -764,15 +787,16 @@ func (ng *Engine) execEvalStmt(ctx context.Context, query *query, s *parser.Eval
startTimestamp: timeMilliseconds(s.Start),
endTimestamp: timeMilliseconds(s.End),
interval: durationMilliseconds(s.Interval),
- ctx: ctxInnerEval,
maxSamples: ng.maxSamplesPerQuery,
logger: ng.logger,
lookbackDelta: s.LookbackDelta,
samplesStats: query.sampleStats,
noStepSubqueryIntervalFn: ng.noStepSubqueryIntervalFn,
+ enableDelayedNameRemoval: ng.enableDelayedNameRemoval,
+ querier: querier,
}
query.sampleStats.InitStepTracking(evaluator.startTimestamp, evaluator.endTimestamp, evaluator.interval)
- val, warnings, err := evaluator.Eval(s.Expr)
+ val, warnings, err := evaluator.Eval(ctxInnerEval, s.Expr)
evalSpanTimer.Finish()
@@ -947,7 +971,7 @@ func (ng *Engine) populateSeries(ctx context.Context, querier storage.Querier, s
// point, this is how far back we need to look for it.
f, ok := parser.Functions[hints.Func]
if ok && f.ExtRange {
- hints.Start = hints.Start - durationMilliseconds(ng.lookbackDelta)
+ hints.Start -= durationMilliseconds(ng.lookbackDelta)
}
n.UnexpandedSeriesSet = querier.Select(ctx, false, hints, n.LabelMatchers...)
@@ -989,6 +1013,8 @@ func extractGroupsFromPath(p []parser.Node) (bool, []string) {
return false, nil
}
+// checkAndExpandSeriesSet expands expr's UnexpandedSeriesSet into expr's Series.
+// If the Series field is already non-nil, it's a no-op.
func checkAndExpandSeriesSet(ctx context.Context, expr parser.Expr) (annotations.Annotations, error) {
switch e := expr.(type) {
case *parser.MatrixSelector:
@@ -997,6 +1023,8 @@ func checkAndExpandSeriesSet(ctx context.Context, expr parser.Expr) (annotations
if e.Series != nil {
return nil, nil
}
+ span := trace.SpanFromContext(ctx)
+ span.AddEvent("expand start", trace.WithAttributes(attribute.String("selector", e.String())))
series, ws, err := expandSeriesSet(ctx, e.UnexpandedSeriesSet)
if e.SkipHistogramBuckets {
for i := range series {
@@ -1004,6 +1032,7 @@ func checkAndExpandSeriesSet(ctx context.Context, expr parser.Expr) (annotations
}
}
e.Series = series
+ span.AddEvent("expand end", trace.WithAttributes(attribute.Int("num_series", len(series))))
return ws, err
}
return nil, nil
@@ -1033,8 +1062,6 @@ func (e errWithWarnings) Error() string { return e.err.Error() }
// querier and reports errors. On timeout or cancellation of its context it
// terminates.
type evaluator struct {
- ctx context.Context
-
startTimestamp int64 // Start time in milliseconds.
endTimestamp int64 // End time in milliseconds.
interval int64 // Interval in milliseconds.
@@ -1045,6 +1072,8 @@ type evaluator struct {
lookbackDelta time.Duration
samplesStats *stats.QuerySamples
noStepSubqueryIntervalFn func(rangeMillis int64) int64
+ enableDelayedNameRemoval bool
+ querier storage.Querier
}
// errorf causes a panic with the input formatted into an error.
@@ -1070,7 +1099,7 @@ func (ev *evaluator) recover(expr parser.Expr, ws *annotations.Annotations, errp
buf := make([]byte, 64<<10)
buf = buf[:runtime.Stack(buf, false)]
- level.Error(ev.logger).Log("msg", "runtime panic in parser", "expr", expr.String(), "err", e, "stacktrace", string(buf))
+ level.Error(ev.logger).Log("msg", "runtime panic during query evaluation", "expr", expr.String(), "err", e, "stacktrace", string(buf))
*errp = fmt.Errorf("unexpected error: %w", err)
case errWithWarnings:
*errp = err.err
@@ -1082,10 +1111,13 @@ func (ev *evaluator) recover(expr parser.Expr, ws *annotations.Annotations, errp
}
}
-func (ev *evaluator) Eval(expr parser.Expr) (v parser.Value, ws annotations.Annotations, err error) {
+func (ev *evaluator) Eval(ctx context.Context, expr parser.Expr) (v parser.Value, ws annotations.Annotations, err error) {
defer ev.recover(expr, &ws, &err)
- v, ws = ev.eval(expr)
+ v, ws = ev.eval(ctx, expr)
+ if ev.enableDelayedNameRemoval {
+ ev.cleanupMetricLabels(v)
+ }
return v, ws, nil
}
@@ -1114,6 +1146,9 @@ type EvalNodeHelper struct {
rightSigs map[string]Sample
matchedSigs map[string]map[uint64]struct{}
resultMetric map[string]labels.Labels
+
+ // Additional options for the evaluation.
+ enableDelayedNameRemoval bool
}
func (enh *EvalNodeHelper) resetBuilder(lbls labels.Labels) {
@@ -1130,7 +1165,7 @@ func (enh *EvalNodeHelper) resetBuilder(lbls labels.Labels) {
// function call results.
// The prepSeries function (if provided) can be used to prepare the helper
// for each series, then passed to each call funcCall.
-func (ev *evaluator) rangeEval(prepSeries func(labels.Labels, *EvalSeriesHelper), funcCall func([]parser.Value, [][]EvalSeriesHelper, *EvalNodeHelper) (Vector, annotations.Annotations), exprs ...parser.Expr) (Matrix, annotations.Annotations) {
+func (ev *evaluator) rangeEval(ctx context.Context, prepSeries func(labels.Labels, *EvalSeriesHelper), funcCall func([]parser.Value, [][]EvalSeriesHelper, *EvalNodeHelper) (Vector, annotations.Annotations), exprs ...parser.Expr) (Matrix, annotations.Annotations) {
numSteps := int((ev.endTimestamp-ev.startTimestamp)/ev.interval) + 1
matrixes := make([]Matrix, len(exprs))
origMatrixes := make([]Matrix, len(exprs))
@@ -1141,7 +1176,7 @@ func (ev *evaluator) rangeEval(prepSeries func(labels.Labels, *EvalSeriesHelper)
// Functions will take string arguments from the expressions, not the values.
if e != nil && e.Type() != parser.ValueTypeString {
// ev.currentSamples will be updated to the correct value within the ev.eval call.
- val, ws := ev.eval(e)
+ val, ws := ev.eval(ctx, e)
warnings.Merge(ws)
matrixes[i] = val.(Matrix)
@@ -1163,7 +1198,7 @@ func (ev *evaluator) rangeEval(prepSeries func(labels.Labels, *EvalSeriesHelper)
biggestLen = len(matrixes[i])
}
}
- enh := &EvalNodeHelper{Out: make(Vector, 0, biggestLen)}
+ enh := &EvalNodeHelper{Out: make(Vector, 0, biggestLen), enableDelayedNameRemoval: ev.enableDelayedNameRemoval}
type seriesAndTimestamp struct {
Series
ts int64
@@ -1193,45 +1228,24 @@ func (ev *evaluator) rangeEval(prepSeries func(labels.Labels, *EvalSeriesHelper)
}
for ts := ev.startTimestamp; ts <= ev.endTimestamp; ts += ev.interval {
- if err := contextDone(ev.ctx, "expression evaluation"); err != nil {
+ if err := contextDone(ctx, "expression evaluation"); err != nil {
ev.error(err)
}
// Reset number of samples in memory after each timestamp.
ev.currentSamples = tempNumSamples
// Gather input vectors for this timestamp.
for i := range exprs {
- vectors[i] = vectors[i][:0]
-
+ var bh []EvalSeriesHelper
+ var sh []EvalSeriesHelper
if prepSeries != nil {
- bufHelpers[i] = bufHelpers[i][:0]
- }
-
- for si, series := range matrixes[i] {
- switch {
- case len(series.Floats) > 0 && series.Floats[0].T == ts:
- vectors[i] = append(vectors[i], Sample{Metric: series.Metric, F: series.Floats[0].F, T: ts})
- // Move input vectors forward so we don't have to re-scan the same
- // past points at the next step.
- matrixes[i][si].Floats = series.Floats[1:]
- case len(series.Histograms) > 0 && series.Histograms[0].T == ts:
- vectors[i] = append(vectors[i], Sample{Metric: series.Metric, H: series.Histograms[0].H, T: ts})
- matrixes[i][si].Histograms = series.Histograms[1:]
- default:
- continue
- }
- if prepSeries != nil {
- bufHelpers[i] = append(bufHelpers[i], seriesHelpers[i][si])
- }
- // Don't add histogram size here because we only
- // copy the pointer above, not the whole
- // histogram.
- ev.currentSamples++
- if ev.currentSamples > ev.maxSamples {
- ev.error(ErrTooManySamples(env))
- }
+ bh = bufHelpers[i][:0]
+ sh = seriesHelpers[i]
}
+ vectors[i], bh = ev.gatherVector(ts, matrixes[i], vectors[i], bh, sh)
args[i] = vectors[i]
- ev.samplesStats.UpdatePeak(ev.currentSamples)
+ if prepSeries != nil {
+ bufHelpers[i] = bh
+ }
}
// Make the function call.
@@ -1253,15 +1267,15 @@ func (ev *evaluator) rangeEval(prepSeries func(labels.Labels, *EvalSeriesHelper)
// If this could be an instant query, shortcut so as not to change sort order.
if ev.endTimestamp == ev.startTimestamp {
- if result.ContainsSameLabelset() {
+ if !ev.enableDelayedNameRemoval && result.ContainsSameLabelset() {
ev.errorf("vector cannot contain metrics with the same labelset")
}
mat := make(Matrix, len(result))
for i, s := range result {
if s.H == nil {
- mat[i] = Series{Metric: s.Metric, Floats: []FPoint{{T: ts, F: s.F}}}
+ mat[i] = Series{Metric: s.Metric, Floats: []FPoint{{T: ts, F: s.F}}, DropName: s.DropName}
} else {
- mat[i] = Series{Metric: s.Metric, Histograms: []HPoint{{T: ts, H: s.H}}}
+ mat[i] = Series{Metric: s.Metric, Histograms: []HPoint{{T: ts, H: s.H}}, DropName: s.DropName}
}
}
ev.currentSamples = originalNumSamples + mat.TotalSamples()
@@ -1279,7 +1293,7 @@ func (ev *evaluator) rangeEval(prepSeries func(labels.Labels, *EvalSeriesHelper)
}
ss.ts = ts
} else {
- ss = seriesAndTimestamp{Series{Metric: sample.Metric}, ts}
+ ss = seriesAndTimestamp{Series{Metric: sample.Metric, DropName: sample.DropName}, ts}
}
addToSeries(&ss.Series, enh.Ts, sample.F, sample.H, numSteps)
seriess[h] = ss
@@ -1303,7 +1317,7 @@ func (ev *evaluator) rangeEval(prepSeries func(labels.Labels, *EvalSeriesHelper)
return mat, warnings
}
-func (ev *evaluator) rangeEvalAgg(aggExpr *parser.AggregateExpr, sortedGrouping []string, inputMatrix Matrix, param float64) (Matrix, annotations.Annotations) {
+func (ev *evaluator) rangeEvalAgg(ctx context.Context, aggExpr *parser.AggregateExpr, sortedGrouping []string, inputMatrix Matrix, param float64) (Matrix, annotations.Annotations) {
// Keep a copy of the original point slice so that it can be returned to the pool.
origMatrix := slices.Clone(inputMatrix)
defer func() {
@@ -1315,7 +1329,7 @@ func (ev *evaluator) rangeEvalAgg(aggExpr *parser.AggregateExpr, sortedGrouping
var warnings annotations.Annotations
- enh := &EvalNodeHelper{}
+ enh := &EvalNodeHelper{enableDelayedNameRemoval: ev.enableDelayedNameRemoval}
tempNumSamples := ev.currentSamples
// Create a mapping from input series to output groups.
@@ -1383,7 +1397,7 @@ func (ev *evaluator) rangeEvalAgg(aggExpr *parser.AggregateExpr, sortedGrouping
}
for ts := ev.startTimestamp; ts <= ev.endTimestamp; ts += ev.interval {
- if err := contextDone(ev.ctx, "expression evaluation"); err != nil {
+ if err := contextDone(ctx, "expression evaluation"); err != nil {
ev.error(err)
}
// Reset number of samples in memory after each timestamp.
@@ -1432,13 +1446,86 @@ func (ev *evaluator) rangeEvalAgg(aggExpr *parser.AggregateExpr, sortedGrouping
return result, warnings
}
+// evalSeries generates a Matrix between ev.startTimestamp and ev.endTimestamp (inclusive), each point spaced ev.interval apart, from series given offset.
+// For every storage.Series iterator in series, the method iterates in ev.interval sized steps from ev.startTimestamp until and including ev.endTimestamp,
+// collecting every corresponding sample (obtained via ev.vectorSelectorSingle) into a Series.
+// All of the generated Series are collected into a Matrix, that gets returned.
+func (ev *evaluator) evalSeries(ctx context.Context, series []storage.Series, offset time.Duration, recordOrigT bool) Matrix {
+ numSteps := int((ev.endTimestamp-ev.startTimestamp)/ev.interval) + 1
+
+ mat := make(Matrix, 0, len(series))
+ var prevSS *Series
+ it := storage.NewMemoizedEmptyIterator(durationMilliseconds(ev.lookbackDelta))
+ var chkIter chunkenc.Iterator
+ for _, s := range series {
+ if err := contextDone(ctx, "expression evaluation"); err != nil {
+ ev.error(err)
+ }
+
+ chkIter = s.Iterator(chkIter)
+ it.Reset(chkIter)
+ ss := Series{
+ Metric: s.Labels(),
+ }
+
+ for ts, step := ev.startTimestamp, -1; ts <= ev.endTimestamp; ts += ev.interval {
+ step++
+ origT, f, h, ok := ev.vectorSelectorSingle(it, offset, ts)
+ if !ok {
+ continue
+ }
+
+ if h == nil {
+ ev.currentSamples++
+ ev.samplesStats.IncrementSamplesAtStep(step, 1)
+ if ev.currentSamples > ev.maxSamples {
+ ev.error(ErrTooManySamples(env))
+ }
+ if ss.Floats == nil {
+ ss.Floats = reuseOrGetFPointSlices(prevSS, numSteps)
+ }
+ if recordOrigT {
+ // This is an info metric, where we want to track the original sample timestamp.
+ // Info metric values should be 1 by convention, therefore we can re-use this
+ // space in the sample.
+ f = float64(origT)
+ }
+ ss.Floats = append(ss.Floats, FPoint{F: f, T: ts})
+ } else {
+ if recordOrigT {
+ ev.error(fmt.Errorf("this should be an info metric, with float samples: %s", ss.Metric))
+ }
+
+ point := HPoint{H: h, T: ts}
+ histSize := point.size()
+ ev.currentSamples += histSize
+ ev.samplesStats.IncrementSamplesAtStep(step, int64(histSize))
+ if ev.currentSamples > ev.maxSamples {
+ ev.error(ErrTooManySamples(env))
+ }
+ if ss.Histograms == nil {
+ ss.Histograms = reuseOrGetHPointSlices(prevSS, numSteps)
+ }
+ ss.Histograms = append(ss.Histograms, point)
+ }
+ }
+
+ if len(ss.Floats)+len(ss.Histograms) > 0 {
+ mat = append(mat, ss)
+ prevSS = &mat[len(mat)-1]
+ }
+ }
+ ev.samplesStats.UpdatePeak(ev.currentSamples)
+ return mat
+}
+
// evalSubquery evaluates given SubqueryExpr and returns an equivalent
// evaluated MatrixSelector in its place. Note that the Name and LabelMatchers are not set.
-func (ev *evaluator) evalSubquery(subq *parser.SubqueryExpr) (*parser.MatrixSelector, int, annotations.Annotations) {
+func (ev *evaluator) evalSubquery(ctx context.Context, subq *parser.SubqueryExpr) (*parser.MatrixSelector, int, annotations.Annotations) {
samplesStats := ev.samplesStats
// Avoid double counting samples when running a subquery, those samples will be counted in later stage.
ev.samplesStats = ev.samplesStats.NewChild()
- val, ws := ev.eval(subq)
+ val, ws := ev.eval(ctx, subq)
// But do incorporate the peak from the subquery
samplesStats.UpdatePeakFromSubquery(ev.samplesStats)
ev.samplesStats = samplesStats
@@ -1465,18 +1552,20 @@ func (ev *evaluator) evalSubquery(subq *parser.SubqueryExpr) (*parser.MatrixSele
}
// eval evaluates the given expression as the given AST expression node requires.
-func (ev *evaluator) eval(expr parser.Expr) (parser.Value, annotations.Annotations) {
+func (ev *evaluator) eval(ctx context.Context, expr parser.Expr) (parser.Value, annotations.Annotations) {
// This is the top-level evaluation method.
// Thus, we check for timeout/cancellation here.
- if err := contextDone(ev.ctx, "expression evaluation"); err != nil {
+ if err := contextDone(ctx, "expression evaluation"); err != nil {
ev.error(err)
}
numSteps := int((ev.endTimestamp-ev.startTimestamp)/ev.interval) + 1
// Create a new span to help investigate inner evaluation performances.
- ctxWithSpan, span := otel.Tracer("").Start(ev.ctx, stats.InnerEvalTime.SpanOperation()+" eval "+reflect.TypeOf(expr).String())
- ev.ctx = ctxWithSpan
+ ctx, span := otel.Tracer("").Start(ctx, stats.InnerEvalTime.SpanOperation()+" eval "+reflect.TypeOf(expr).String())
defer span.End()
+ if ss, ok := expr.(interface{ ShortString() string }); ok {
+ span.SetAttributes(attribute.String("operation", ss.ShortString()))
+ }
switch e := expr.(type) {
case *parser.AggregateExpr:
@@ -1497,7 +1586,7 @@ func (ev *evaluator) eval(expr parser.Expr) (parser.Value, annotations.Annotatio
sortedGrouping = append(sortedGrouping, valueLabel.Val)
slices.Sort(sortedGrouping)
}
- return ev.rangeEval(nil, func(v []parser.Value, _ [][]EvalSeriesHelper, enh *EvalNodeHelper) (Vector, annotations.Annotations) {
+ return ev.rangeEval(ctx, nil, func(v []parser.Value, _ [][]EvalSeriesHelper, enh *EvalNodeHelper) (Vector, annotations.Annotations) {
return ev.aggregationCountValues(e, sortedGrouping, valueLabel.Val, v[0].(Vector), enh)
}, e.Expr)
}
@@ -1507,16 +1596,16 @@ func (ev *evaluator) eval(expr parser.Expr) (parser.Value, annotations.Annotatio
// param is the number k for topk/bottomk, or q for quantile.
var fParam float64
if param != nil {
- val, ws := ev.eval(param)
+ val, ws := ev.eval(ctx, param)
warnings.Merge(ws)
fParam = val.(Matrix)[0].Floats[0].F
}
// Now fetch the data to be aggregated.
- val, ws := ev.eval(e.Expr)
+ val, ws := ev.eval(ctx, e.Expr)
warnings.Merge(ws)
inputMatrix := val.(Matrix)
- result, ws := ev.rangeEvalAgg(e, sortedGrouping, inputMatrix, fParam)
+ result, ws := ev.rangeEvalAgg(ctx, e, sortedGrouping, inputMatrix, fParam)
warnings.Merge(ws)
ev.currentSamples = originalNumSamples + result.TotalSamples()
ev.samplesStats.UpdatePeak(ev.currentSamples)
@@ -1534,7 +1623,7 @@ func (ev *evaluator) eval(expr parser.Expr) (parser.Value, annotations.Annotatio
unwrapParenExpr(&arg)
vs, ok := arg.(*parser.VectorSelector)
if ok {
- return ev.rangeEvalTimestampFunctionOverVectorSelector(vs, call, e)
+ return ev.rangeEvalTimestampFunctionOverVectorSelector(ctx, vs, call, e)
}
}
@@ -1558,7 +1647,7 @@ func (ev *evaluator) eval(expr parser.Expr) (parser.Value, annotations.Annotatio
matrixArgIndex = i
matrixArg = true
// Replacing parser.SubqueryExpr with parser.MatrixSelector.
- val, totalSamples, ws := ev.evalSubquery(subq)
+ val, totalSamples, ws := ev.evalSubquery(ctx, subq)
e.Args[i] = val
warnings.Merge(ws)
defer func() {
@@ -1573,14 +1662,16 @@ func (ev *evaluator) eval(expr parser.Expr) (parser.Value, annotations.Annotatio
// Special handling for functions that work on series not samples.
switch e.Func.Name {
case "label_replace":
- return ev.evalLabelReplace(e.Args)
+ return ev.evalLabelReplace(ctx, e.Args)
case "label_join":
- return ev.evalLabelJoin(e.Args)
+ return ev.evalLabelJoin(ctx, e.Args)
+ case "info":
+ return ev.evalInfo(ctx, e.Args)
}
if !matrixArg {
// Does not have a matrix argument.
- return ev.rangeEval(nil, func(v []parser.Value, _ [][]EvalSeriesHelper, enh *EvalNodeHelper) (Vector, annotations.Annotations) {
+ return ev.rangeEval(ctx, nil, func(v []parser.Value, _ [][]EvalSeriesHelper, enh *EvalNodeHelper) (Vector, annotations.Annotations) {
vec, annos := call(v, e.Args, enh)
return vec, warnings.Merge(annos)
}, e.Args...)
@@ -1592,7 +1683,7 @@ func (ev *evaluator) eval(expr parser.Expr) (parser.Value, annotations.Annotatio
otherInArgs := make([]Vector, len(e.Args))
for i, e := range e.Args {
if i != matrixArgIndex {
- val, ws := ev.eval(e)
+ val, ws := ev.eval(ctx, e)
otherArgs[i] = val.(Matrix)
otherInArgs[i] = Vector{Sample{}}
inArgs[i] = otherInArgs[i]
@@ -1606,7 +1697,7 @@ func (ev *evaluator) eval(expr parser.Expr) (parser.Value, annotations.Annotatio
sel := arg.(*parser.MatrixSelector)
selVS := sel.VectorSelector.(*parser.VectorSelector)
- ws, err := checkAndExpandSeriesSet(ev.ctx, sel)
+ ws, err := checkAndExpandSeriesSet(ctx, sel)
warnings.Merge(ws)
if err != nil {
ev.error(errWithWarnings{fmt.Errorf("expanding series: %w", err), warnings})
@@ -1631,12 +1722,19 @@ func (ev *evaluator) eval(expr parser.Expr) (parser.Value, annotations.Annotatio
var prevSS *Series
inMatrix := make(Matrix, 1)
inArgs[matrixArgIndex] = inMatrix
- enh := &EvalNodeHelper{Out: make(Vector, 0, 1)}
+ enh := &EvalNodeHelper{Out: make(Vector, 0, 1), enableDelayedNameRemoval: ev.enableDelayedNameRemoval}
// Process all the calls for one time series at a time.
it := storage.NewBuffer(bufferRange)
var chkIter chunkenc.Iterator
+
+ // The last_over_time function acts like offset; thus, it
+ // should keep the metric name. For all the other range
+ // vector functions, the only change needed is to drop the
+ // metric name in the output.
+ dropName := e.Func.Name != "last_over_time"
+
for i, s := range selVS.Series {
- if err := contextDone(ev.ctx, "expression evaluation"); err != nil {
+ if err := contextDone(ctx, "expression evaluation"); err != nil {
ev.error(err)
}
ev.currentSamples -= len(floats) + totalHPointSize(histograms)
@@ -1649,15 +1747,12 @@ func (ev *evaluator) eval(expr parser.Expr) (parser.Value, annotations.Annotatio
chkIter = s.Iterator(chkIter)
it.Reset(chkIter)
metric := selVS.Series[i].Labels()
- // The last_over_time function acts like offset; thus, it
- // should keep the metric name. For all the other range
- // vector functions, the only change needed is to drop the
- // metric name in the output.
- if e.Func.Name != "last_over_time" {
+ if !ev.enableDelayedNameRemoval && dropName {
metric = metric.DropMetricName()
}
ss := Series{
- Metric: metric,
+ Metric: metric,
+ DropName: dropName,
}
inMatrix[0].Metric = selVS.Series[i].Labels()
for ts, step := ev.startTimestamp, -1; ts <= ev.endTimestamp; ts += ev.interval {
@@ -1772,32 +1867,38 @@ func (ev *evaluator) eval(expr parser.Expr) (parser.Value, annotations.Annotatio
return Matrix{
Series{
- Metric: createLabelsForAbsentFunction(e.Args[0]),
- Floats: newp,
+ Metric: createLabelsForAbsentFunction(e.Args[0]),
+ Floats: newp,
+ DropName: dropName,
},
}, warnings
}
- if mat.ContainsSameLabelset() {
+ if !ev.enableDelayedNameRemoval && mat.ContainsSameLabelset() {
ev.errorf("vector cannot contain metrics with the same labelset")
}
-
return mat, warnings
case *parser.ParenExpr:
- return ev.eval(e.Expr)
+ return ev.eval(ctx, e.Expr)
case *parser.UnaryExpr:
- val, ws := ev.eval(e.Expr)
+ val, ws := ev.eval(ctx, e.Expr)
mat := val.(Matrix)
if e.Op == parser.SUB {
for i := range mat {
- mat[i].Metric = mat[i].Metric.DropMetricName()
+ if !ev.enableDelayedNameRemoval {
+ mat[i].Metric = mat[i].Metric.DropMetricName()
+ }
+ mat[i].DropName = true
for j := range mat[i].Floats {
mat[i].Floats[j].F = -mat[i].Floats[j].F
}
+ for j := range mat[i].Histograms {
+ mat[i].Histograms[j].H = mat[i].Histograms[j].H.Copy().Mul(-1)
+ }
}
- if mat.ContainsSameLabelset() {
+ if !ev.enableDelayedNameRemoval && mat.ContainsSameLabelset() {
ev.errorf("vector cannot contain metrics with the same labelset")
}
}
@@ -1806,7 +1907,7 @@ func (ev *evaluator) eval(expr parser.Expr) (parser.Value, annotations.Annotatio
case *parser.BinaryExpr:
switch lt, rt := e.LHS.Type(), e.RHS.Type(); {
case lt == parser.ValueTypeScalar && rt == parser.ValueTypeScalar:
- return ev.rangeEval(nil, func(v []parser.Value, _ [][]EvalSeriesHelper, enh *EvalNodeHelper) (Vector, annotations.Annotations) {
+ return ev.rangeEval(ctx, nil, func(v []parser.Value, _ [][]EvalSeriesHelper, enh *EvalNodeHelper) (Vector, annotations.Annotations) {
val := scalarBinop(e.Op, v[0].(Vector)[0].F, v[1].(Vector)[0].F)
return append(enh.Out, Sample{F: val}), nil
}, e.LHS, e.RHS)
@@ -1819,120 +1920,74 @@ func (ev *evaluator) eval(expr parser.Expr) (parser.Value, annotations.Annotatio
}
switch e.Op {
case parser.LAND:
- return ev.rangeEval(initSignatures, func(v []parser.Value, sh [][]EvalSeriesHelper, enh *EvalNodeHelper) (Vector, annotations.Annotations) {
+ return ev.rangeEval(ctx, initSignatures, func(v []parser.Value, sh [][]EvalSeriesHelper, enh *EvalNodeHelper) (Vector, annotations.Annotations) {
return ev.VectorAnd(v[0].(Vector), v[1].(Vector), e.VectorMatching, sh[0], sh[1], enh), nil
}, e.LHS, e.RHS)
case parser.LOR:
- return ev.rangeEval(initSignatures, func(v []parser.Value, sh [][]EvalSeriesHelper, enh *EvalNodeHelper) (Vector, annotations.Annotations) {
+ return ev.rangeEval(ctx, initSignatures, func(v []parser.Value, sh [][]EvalSeriesHelper, enh *EvalNodeHelper) (Vector, annotations.Annotations) {
return ev.VectorOr(v[0].(Vector), v[1].(Vector), e.VectorMatching, sh[0], sh[1], enh), nil
}, e.LHS, e.RHS)
case parser.LUNLESS:
- return ev.rangeEval(initSignatures, func(v []parser.Value, sh [][]EvalSeriesHelper, enh *EvalNodeHelper) (Vector, annotations.Annotations) {
+ return ev.rangeEval(ctx, initSignatures, func(v []parser.Value, sh [][]EvalSeriesHelper, enh *EvalNodeHelper) (Vector, annotations.Annotations) {
return ev.VectorUnless(v[0].(Vector), v[1].(Vector), e.VectorMatching, sh[0], sh[1], enh), nil
}, e.LHS, e.RHS)
default:
- return ev.rangeEval(initSignatures, func(v []parser.Value, sh [][]EvalSeriesHelper, enh *EvalNodeHelper) (Vector, annotations.Annotations) {
+ return ev.rangeEval(ctx, initSignatures, func(v []parser.Value, sh [][]EvalSeriesHelper, enh *EvalNodeHelper) (Vector, annotations.Annotations) {
vec, err := ev.VectorBinop(e.Op, v[0].(Vector), v[1].(Vector), e.VectorMatching, e.ReturnBool, sh[0], sh[1], enh)
return vec, handleVectorBinopError(err, e)
}, e.LHS, e.RHS)
}
case lt == parser.ValueTypeVector && rt == parser.ValueTypeScalar:
- return ev.rangeEval(nil, func(v []parser.Value, _ [][]EvalSeriesHelper, enh *EvalNodeHelper) (Vector, annotations.Annotations) {
+ return ev.rangeEval(ctx, nil, func(v []parser.Value, _ [][]EvalSeriesHelper, enh *EvalNodeHelper) (Vector, annotations.Annotations) {
vec, err := ev.VectorscalarBinop(e.Op, v[0].(Vector), Scalar{V: v[1].(Vector)[0].F}, false, e.ReturnBool, enh)
return vec, handleVectorBinopError(err, e)
}, e.LHS, e.RHS)
case lt == parser.ValueTypeScalar && rt == parser.ValueTypeVector:
- return ev.rangeEval(nil, func(v []parser.Value, _ [][]EvalSeriesHelper, enh *EvalNodeHelper) (Vector, annotations.Annotations) {
+ return ev.rangeEval(ctx, nil, func(v []parser.Value, _ [][]EvalSeriesHelper, enh *EvalNodeHelper) (Vector, annotations.Annotations) {
vec, err := ev.VectorscalarBinop(e.Op, v[1].(Vector), Scalar{V: v[0].(Vector)[0].F}, true, e.ReturnBool, enh)
return vec, handleVectorBinopError(err, e)
}, e.LHS, e.RHS)
}
case *parser.NumberLiteral:
- return ev.rangeEval(nil, func(v []parser.Value, _ [][]EvalSeriesHelper, enh *EvalNodeHelper) (Vector, annotations.Annotations) {
+ span.SetAttributes(attribute.Float64("value", e.Val))
+ return ev.rangeEval(ctx, nil, func(v []parser.Value, _ [][]EvalSeriesHelper, enh *EvalNodeHelper) (Vector, annotations.Annotations) {
return append(enh.Out, Sample{F: e.Val, Metric: labels.EmptyLabels()}), nil
})
case *parser.StringLiteral:
+ span.SetAttributes(attribute.String("value", e.Val))
return String{V: e.Val, T: ev.startTimestamp}, nil
case *parser.VectorSelector:
- ws, err := checkAndExpandSeriesSet(ev.ctx, e)
+ ws, err := checkAndExpandSeriesSet(ctx, e)
if err != nil {
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 {
- if err := contextDone(ev.ctx, "expression evaluation"); err != nil {
- ev.error(err)
- }
- chkIter = s.Iterator(chkIter)
- it.Reset(chkIter)
- ss := Series{
- Metric: e.Series[i].Labels(),
- }
-
- for ts, step := ev.startTimestamp, -1; ts <= ev.endTimestamp; ts += ev.interval {
- step++
- _, f, h, ok := ev.vectorSelectorSingle(it, e, ts)
- if ok {
- if h == nil {
- ev.currentSamples++
- ev.samplesStats.IncrementSamplesAtStep(step, 1)
- if ev.currentSamples > ev.maxSamples {
- ev.error(ErrTooManySamples(env))
- }
- if ss.Floats == nil {
- ss.Floats = reuseOrGetFPointSlices(prevSS, numSteps)
- }
- ss.Floats = append(ss.Floats, FPoint{F: f, T: ts})
- } else {
- point := HPoint{H: h, T: ts}
- histSize := point.size()
- ev.currentSamples += histSize
- ev.samplesStats.IncrementSamplesAtStep(step, int64(histSize))
- if ev.currentSamples > ev.maxSamples {
- ev.error(ErrTooManySamples(env))
- }
- if ss.Histograms == nil {
- ss.Histograms = reuseOrGetHPointSlices(prevSS, numSteps)
- }
- ss.Histograms = append(ss.Histograms, point)
- }
- }
- }
-
- if len(ss.Floats)+len(ss.Histograms) > 0 {
- mat = append(mat, ss)
- prevSS = &mat[len(mat)-1]
- }
- }
- ev.samplesStats.UpdatePeak(ev.currentSamples)
+ mat := ev.evalSeries(ctx, e.Series, e.Offset, false)
return mat, ws
case *parser.MatrixSelector:
if ev.startTimestamp != ev.endTimestamp {
panic(errors.New("cannot do range evaluation of matrix selector"))
}
- return ev.matrixSelector(e)
+ return ev.matrixSelector(ctx, e)
case *parser.SubqueryExpr:
offsetMillis := durationMilliseconds(e.Offset)
rangeMillis := durationMilliseconds(e.Range)
newEv := &evaluator{
endTimestamp: ev.endTimestamp - offsetMillis,
- ctx: ev.ctx,
currentSamples: ev.currentSamples,
maxSamples: ev.maxSamples,
logger: ev.logger,
lookbackDelta: ev.lookbackDelta,
samplesStats: ev.samplesStats.NewChild(),
noStepSubqueryIntervalFn: ev.noStepSubqueryIntervalFn,
+ enableDelayedNameRemoval: ev.enableDelayedNameRemoval,
+ querier: ev.querier,
}
if e.Step != 0 {
@@ -1955,7 +2010,7 @@ func (ev *evaluator) eval(expr parser.Expr) (parser.Value, annotations.Annotatio
setOffsetForAtModifier(newEv.startTimestamp, e.Expr)
}
- res, ws := newEv.eval(e.Expr)
+ res, ws := newEv.eval(ctx, e.Expr)
ev.currentSamples = newEv.currentSamples
ev.samplesStats.UpdatePeakFromSubquery(newEv.samplesStats)
ev.samplesStats.IncrementSamplesAtTimestamp(ev.endTimestamp, newEv.samplesStats.TotalSamples)
@@ -1963,22 +2018,23 @@ func (ev *evaluator) eval(expr parser.Expr) (parser.Value, annotations.Annotatio
case *parser.StepInvariantExpr:
switch ce := e.Expr.(type) {
case *parser.StringLiteral, *parser.NumberLiteral:
- return ev.eval(ce)
+ return ev.eval(ctx, ce)
}
newEv := &evaluator{
startTimestamp: ev.startTimestamp,
endTimestamp: ev.startTimestamp, // Always a single evaluation.
interval: ev.interval,
- ctx: ev.ctx,
currentSamples: ev.currentSamples,
maxSamples: ev.maxSamples,
logger: ev.logger,
lookbackDelta: ev.lookbackDelta,
samplesStats: ev.samplesStats.NewChild(),
noStepSubqueryIntervalFn: ev.noStepSubqueryIntervalFn,
+ enableDelayedNameRemoval: ev.enableDelayedNameRemoval,
+ querier: ev.querier,
}
- res, ws := newEv.eval(e.Expr)
+ res, ws := newEv.eval(ctx, e.Expr)
ev.currentSamples = newEv.currentSamples
ev.samplesStats.UpdatePeakFromSubquery(newEv.samplesStats)
for ts, step := ev.startTimestamp, -1; ts <= ev.endTimestamp; ts += ev.interval {
@@ -2054,8 +2110,8 @@ func reuseOrGetFPointSlices(prevSS *Series, numSteps int) (r []FPoint) {
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)
+func (ev *evaluator) rangeEvalTimestampFunctionOverVectorSelector(ctx context.Context, vs *parser.VectorSelector, call FunctionCall, e *parser.Call) (parser.Value, annotations.Annotations) {
+ ws, err := checkAndExpandSeriesSet(ctx, vs)
if err != nil {
ev.error(errWithWarnings{fmt.Errorf("expanding series: %w", err), ws})
}
@@ -2066,7 +2122,7 @@ func (ev *evaluator) rangeEvalTimestampFunctionOverVectorSelector(vs *parser.Vec
seriesIterators[i] = storage.NewMemoizedIterator(it, durationMilliseconds(ev.lookbackDelta))
}
- return ev.rangeEval(nil, func(v []parser.Value, _ [][]EvalSeriesHelper, enh *EvalNodeHelper) (Vector, annotations.Annotations) {
+ return ev.rangeEval(ctx, nil, func(v []parser.Value, _ [][]EvalSeriesHelper, enh *EvalNodeHelper) (Vector, annotations.Annotations) {
if vs.Timestamp != nil {
// This is a special case for "timestamp()" when the @ modifier is used, to ensure that
// we return a point for each time step in this case.
@@ -2077,7 +2133,7 @@ func (ev *evaluator) rangeEvalTimestampFunctionOverVectorSelector(vs *parser.Vec
vec := make(Vector, 0, len(vs.Series))
for i, s := range vs.Series {
it := seriesIterators[i]
- t, _, _, ok := ev.vectorSelectorSingle(it, vs, enh.Ts)
+ t, _, _, ok := ev.vectorSelectorSingle(it, vs.Offset, enh.Ts)
if !ok {
continue
}
@@ -2101,10 +2157,10 @@ func (ev *evaluator) rangeEvalTimestampFunctionOverVectorSelector(vs *parser.Vec
}
// vectorSelectorSingle evaluates an instant vector for the iterator of one time series.
-func (ev *evaluator) vectorSelectorSingle(it *storage.MemoizedSeriesIterator, node *parser.VectorSelector, ts int64) (
+func (ev *evaluator) vectorSelectorSingle(it *storage.MemoizedSeriesIterator, offset time.Duration, ts int64) (
int64, float64, *histogram.FloatHistogram, bool,
) {
- refTime := ts - durationMilliseconds(node.Offset)
+ refTime := ts - durationMilliseconds(offset)
var t int64
var v float64
var h *histogram.FloatHistogram
@@ -2202,7 +2258,7 @@ func putMatrixSelectorHPointSlice(p []HPoint) {
}
// matrixSelector evaluates a *parser.MatrixSelector expression.
-func (ev *evaluator) matrixSelector(node *parser.MatrixSelector) (Matrix, annotations.Annotations) {
+func (ev *evaluator) matrixSelector(ctx context.Context, node *parser.MatrixSelector) (Matrix, annotations.Annotations) {
var (
vs = node.VectorSelector.(*parser.VectorSelector)
@@ -2213,7 +2269,7 @@ func (ev *evaluator) matrixSelector(node *parser.MatrixSelector) (Matrix, annota
it = storage.NewBuffer(durationMilliseconds(node.Range))
)
- ws, err := checkAndExpandSeriesSet(ev.ctx, node)
+ ws, err := checkAndExpandSeriesSet(ctx, node)
if err != nil {
ev.error(errWithWarnings{fmt.Errorf("expanding series: %w", err), ws})
}
@@ -2221,7 +2277,7 @@ func (ev *evaluator) matrixSelector(node *parser.MatrixSelector) (Matrix, annota
var chkIter chunkenc.Iterator
series := vs.Series
for i, s := range series {
- if err := contextDone(ev.ctx, "expression evaluation"); err != nil {
+ if err := contextDone(ctx, "expression evaluation"); err != nil {
ev.error(err)
}
chkIter = s.Iterator(chkIter)
@@ -2382,7 +2438,7 @@ loop:
// point exists at or before range start, add it and then keep
// replacing it with later points while not yet (strictly)
// inside the range.
- if t > mint || !appendedPointBeforeMint {
+ if t > mintFloats || !appendedPointBeforeMint {
ev.currentSamples++
if ev.currentSamples > ev.maxSamples {
ev.error(ErrTooManySamples(env))
@@ -2413,6 +2469,11 @@ loop:
} else {
histograms = append(histograms, HPoint{H: &histogram.FloatHistogram{}})
}
+ if histograms[n].H == nil {
+ // Make sure to pass non-nil H to AtFloatHistogram so that it does a deep-copy.
+ // Not an issue in the loop above since that uses an intermediate buffer.
+ histograms[n].H = &histogram.FloatHistogram{}
+ }
histograms[n].T, histograms[n].H = it.AtFloatHistogram(histograms[n].H)
if value.IsStaleNaN(histograms[n].H.Sum) {
histograms = histograms[:n]
@@ -2605,7 +2666,7 @@ func (ev *evaluator) VectorBinop(op parser.ItemType, lhs, rhs Vector, matching *
continue
}
metric := resultMetric(ls.Metric, rs.Metric, op, matching, enh)
- if returnBool {
+ if !ev.enableDelayedNameRemoval && returnBool {
metric = metric.DropMetricName()
}
insertedSigs, exists := matchedSigs[sig]
@@ -2630,9 +2691,10 @@ func (ev *evaluator) VectorBinop(op parser.ItemType, lhs, rhs Vector, matching *
}
enh.Out = append(enh.Out, Sample{
- Metric: metric,
- F: floatValue,
- H: histogramValue,
+ Metric: metric,
+ F: floatValue,
+ H: histogramValue,
+ DropName: returnBool,
})
}
return enh.Out, lastErr
@@ -2732,7 +2794,10 @@ func (ev *evaluator) VectorscalarBinop(op parser.ItemType, lhs Vector, rhs Scala
lhsSample.F = float
lhsSample.H = histogram
if shouldDropMetricName(op) || returnBool {
- lhsSample.Metric = lhsSample.Metric.DropMetricName()
+ if !ev.enableDelayedNameRemoval {
+ lhsSample.Metric = lhsSample.Metric.DropMetricName()
+ }
+ lhsSample.DropName = true
}
enh.Out = append(enh.Out, lhsSample)
}
@@ -2830,15 +2895,20 @@ func vectorElemBinop(op parser.ItemType, lhs, rhs float64, hlhs, hrhs *histogram
}
type groupedAggregation struct {
- seen bool // Was this output groups seen in the input at this timestamp.
- hasFloat bool // Has at least 1 float64 sample aggregated.
- hasHistogram bool // Has at least 1 histogram sample aggregated.
- floatValue float64
- histogramValue *histogram.FloatHistogram
- floatMean float64 // Mean, or "compensating value" for Kahan summation.
- groupCount int
- groupAggrComplete bool // Used by LIMITK to short-cut series loop when we've reached K elem on every group
- heap vectorByValueHeap
+ floatValue float64
+ histogramValue *histogram.FloatHistogram
+ floatMean float64
+ floatKahanC float64 // "Compensating value" for Kahan summation.
+ groupCount float64
+ heap vectorByValueHeap
+
+ // All bools together for better packing within the struct.
+ seen bool // Was this output groups seen in the input at this timestamp.
+ hasFloat bool // Has at least 1 float64 sample aggregated.
+ hasHistogram bool // Has at least 1 histogram sample aggregated.
+ incompatibleHistograms bool // If true, group has seen mixed exponential and custom buckets, or incompatible custom buckets.
+ groupAggrComplete bool // Used by LIMITK to short-cut series loop when we've reached K elem on every group.
+ incrementalMean bool // True after reverting to incremental calculation of the mean value.
}
// aggregation evaluates sum, avg, count, stdvar, stddev or quantile at one timestep on inputMatrix.
@@ -2862,15 +2932,14 @@ func (ev *evaluator) aggregation(e *parser.AggregateExpr, q float64, inputMatrix
// Initialize this group if it's the first time we've seen it.
if !group.seen {
*group = groupedAggregation{
- seen: true,
- floatValue: f,
- groupCount: 1,
+ seen: true,
+ floatValue: f,
+ floatMean: f,
+ incompatibleHistograms: false,
+ groupCount: 1,
}
switch op {
- case parser.AVG:
- group.floatMean = f
- fallthrough
- case parser.SUM:
+ case parser.AVG, parser.SUM:
if h == nil {
group.hasFloat = true
} else {
@@ -2878,7 +2947,6 @@ func (ev *evaluator) aggregation(e *parser.AggregateExpr, q float64, inputMatrix
group.hasHistogram = true
}
case parser.STDVAR, parser.STDDEV:
- group.floatMean = f
group.floatValue = 0
case parser.QUANTILE:
group.heap = make(vectorByValueHeap, 1)
@@ -2889,6 +2957,10 @@ func (ev *evaluator) aggregation(e *parser.AggregateExpr, q float64, inputMatrix
continue
}
+ if group.incompatibleHistograms {
+ continue
+ }
+
switch op {
case parser.SUM:
if h != nil {
@@ -2897,6 +2969,7 @@ func (ev *evaluator) aggregation(e *parser.AggregateExpr, q float64, inputMatrix
_, err := group.histogramValue.Add(h)
if err != nil {
handleAggregationError(err, e, inputMatrix[si].Metric.Get(model.MetricNameLabel), &annos)
+ group.incompatibleHistograms = true
}
}
// Otherwise the aggregation contained floats
@@ -2904,7 +2977,7 @@ func (ev *evaluator) aggregation(e *parser.AggregateExpr, q float64, inputMatrix
// point in copying the histogram in that case.
} else {
group.hasFloat = true
- group.floatValue, group.floatMean = kahanSumInc(f, group.floatValue, group.floatMean)
+ group.floatValue, group.floatKahanC = kahanSumInc(f, group.floatValue, group.floatKahanC)
}
case parser.AVG:
@@ -2912,15 +2985,19 @@ func (ev *evaluator) aggregation(e *parser.AggregateExpr, q float64, inputMatrix
if h != nil {
group.hasHistogram = true
if group.histogramValue != nil {
- left := h.Copy().Div(float64(group.groupCount))
- right := group.histogramValue.Copy().Div(float64(group.groupCount))
+ left := h.Copy().Div(group.groupCount)
+ right := group.histogramValue.Copy().Div(group.groupCount)
toAdd, err := left.Sub(right)
if err != nil {
handleAggregationError(err, e, inputMatrix[si].Metric.Get(model.MetricNameLabel), &annos)
+ group.incompatibleHistograms = true
+ continue
}
_, err = group.histogramValue.Add(toAdd)
if err != nil {
handleAggregationError(err, e, inputMatrix[si].Metric.Get(model.MetricNameLabel), &annos)
+ group.incompatibleHistograms = true
+ continue
}
}
// Otherwise the aggregation contained floats
@@ -2928,6 +3005,22 @@ func (ev *evaluator) aggregation(e *parser.AggregateExpr, q float64, inputMatrix
// point in copying the histogram in that case.
} else {
group.hasFloat = true
+ if !group.incrementalMean {
+ newV, newC := kahanSumInc(f, group.floatValue, group.floatKahanC)
+ if !math.IsInf(newV, 0) {
+ // The sum doesn't overflow, so we propagate it to the
+ // group struct and continue with the regular
+ // calculation of the mean value.
+ group.floatValue, group.floatKahanC = newV, newC
+ break
+ }
+ // If we are here, we know that the sum _would_ overflow. So
+ // instead of continue to sum up, we revert to incremental
+ // calculation of the mean value from here on.
+ group.incrementalMean = true
+ group.floatMean = group.floatValue / (group.groupCount - 1)
+ group.floatKahanC /= group.groupCount - 1
+ }
if math.IsInf(group.floatMean, 0) {
if math.IsInf(f, 0) && (group.floatMean > 0) == (f > 0) {
// The `floatMean` and `s.F` values are `Inf` of the same sign. They
@@ -2945,8 +3038,13 @@ func (ev *evaluator) aggregation(e *parser.AggregateExpr, q float64, inputMatrix
break
}
}
- // Divide each side of the `-` by `group.groupCount` to avoid float64 overflows.
- group.floatMean += f/float64(group.groupCount) - group.floatMean/float64(group.groupCount)
+ currentMean := group.floatMean + group.floatKahanC
+ group.floatMean, group.floatKahanC = kahanSumInc(
+ // Divide each side of the `-` by `group.groupCount` to avoid float64 overflows.
+ f/group.groupCount-currentMean/group.groupCount,
+ group.floatMean,
+ group.floatKahanC,
+ )
}
case parser.GROUP:
@@ -2969,7 +3067,7 @@ func (ev *evaluator) aggregation(e *parser.AggregateExpr, q float64, inputMatrix
if h == nil { // Ignore native histograms.
group.groupCount++
delta := f - group.floatMean
- group.floatMean += delta / float64(group.groupCount)
+ group.floatMean += delta / group.groupCount
group.floatValue += delta * (f - group.floatMean)
}
@@ -2995,20 +3093,25 @@ func (ev *evaluator) aggregation(e *parser.AggregateExpr, q float64, inputMatrix
annos.Add(annotations.NewMixedFloatsHistogramsAggWarning(e.Expr.PositionRange()))
continue
}
- if aggr.hasHistogram {
+ switch {
+ case aggr.incompatibleHistograms:
+ continue
+ case aggr.hasHistogram:
aggr.histogramValue = aggr.histogramValue.Compact(0)
- } else {
- aggr.floatValue = aggr.floatMean
+ case aggr.incrementalMean:
+ aggr.floatValue = aggr.floatMean + aggr.floatKahanC
+ default:
+ aggr.floatValue = (aggr.floatValue + aggr.floatKahanC) / aggr.groupCount
}
case parser.COUNT:
- aggr.floatValue = float64(aggr.groupCount)
+ aggr.floatValue = aggr.groupCount
case parser.STDVAR:
- aggr.floatValue /= float64(aggr.groupCount)
+ aggr.floatValue /= aggr.groupCount
case parser.STDDEV:
- aggr.floatValue = math.Sqrt(aggr.floatValue / float64(aggr.groupCount))
+ aggr.floatValue = math.Sqrt(aggr.floatValue / aggr.groupCount)
case parser.QUANTILE:
aggr.floatValue = quantile(q, aggr.heap)
@@ -3019,10 +3122,13 @@ func (ev *evaluator) aggregation(e *parser.AggregateExpr, q float64, inputMatrix
annos.Add(annotations.NewMixedFloatsHistogramsAggWarning(e.Expr.PositionRange()))
continue
}
- if aggr.hasHistogram {
+ switch {
+ case aggr.incompatibleHistograms:
+ continue
+ case aggr.hasHistogram:
aggr.histogramValue.Compact(0)
- } else {
- aggr.floatValue += aggr.floatMean // Add Kahan summation compensating term.
+ default:
+ aggr.floatValue += aggr.floatKahanC
}
default:
// For other aggregations, we already have the right value.
@@ -3030,6 +3136,7 @@ func (ev *evaluator) aggregation(e *parser.AggregateExpr, q float64, inputMatrix
ss := &outputMatrix[ri]
addToSeries(ss, enh.Ts, aggr.floatValue, aggr.histogramValue, numSteps)
+ ss.DropName = inputMatrix[ri].DropName
}
return annos
@@ -3056,7 +3163,7 @@ seriesLoop:
if !ok {
continue
}
- s = Sample{Metric: inputMatrix[si].Metric, F: f}
+ s = Sample{Metric: inputMatrix[si].Metric, F: f, DropName: inputMatrix[si].DropName}
group := &groups[seriesToResult[si]]
// Initialize this group if it's the first time we've seen it.
@@ -3140,16 +3247,16 @@ seriesLoop:
mat = make(Matrix, 0, len(groups))
}
- add := func(lbls labels.Labels, f float64) {
+ add := func(lbls labels.Labels, f float64, dropName bool) {
// If this could be an instant query, add directly to the matrix so the result is in consistent order.
if ev.endTimestamp == ev.startTimestamp {
- mat = append(mat, Series{Metric: lbls, Floats: []FPoint{{T: enh.Ts, F: f}}})
+ mat = append(mat, Series{Metric: lbls, Floats: []FPoint{{T: enh.Ts, F: f}}, DropName: dropName})
} else {
// Otherwise the results are added into seriess elements.
hash := lbls.Hash()
ss, ok := seriess[hash]
if !ok {
- ss = Series{Metric: lbls}
+ ss = Series{Metric: lbls, DropName: dropName}
}
addToSeries(&ss, enh.Ts, f, nil, numSteps)
seriess[hash] = ss
@@ -3166,7 +3273,7 @@ seriesLoop:
sort.Sort(sort.Reverse(aggr.heap))
}
for _, v := range aggr.heap {
- add(v.Metric, v.F)
+ add(v.Metric, v.F, v.DropName)
}
case parser.BOTTOMK:
@@ -3175,12 +3282,12 @@ seriesLoop:
sort.Sort(sort.Reverse((*vectorByReverseValueHeap)(&aggr.heap)))
}
for _, v := range aggr.heap {
- add(v.Metric, v.F)
+ add(v.Metric, v.F, v.DropName)
}
case parser.LIMITK, parser.LIMIT_RATIO:
for _, v := range aggr.heap {
- add(v.Metric, v.F)
+ add(v.Metric, v.F, v.DropName)
}
}
}
@@ -3188,7 +3295,7 @@ seriesLoop:
return mat, annos
}
-// aggregationK evaluates count_values on vec.
+// aggregationCountValues evaluates count_values on vec.
// Outputs as many series per group as there are values in the input.
func (ev *evaluator) aggregationCountValues(e *parser.AggregateExpr, grouping []string, valueLabel string, vec Vector, enh *EvalNodeHelper) (Vector, annotations.Annotations) {
type groupCount struct {
@@ -3232,6 +3339,30 @@ func (ev *evaluator) aggregationCountValues(e *parser.AggregateExpr, grouping []
return enh.Out, nil
}
+func (ev *evaluator) cleanupMetricLabels(v parser.Value) {
+ if v.Type() == parser.ValueTypeMatrix {
+ mat := v.(Matrix)
+ for i := range mat {
+ if mat[i].DropName {
+ mat[i].Metric = mat[i].Metric.DropMetricName()
+ }
+ }
+ if mat.ContainsSameLabelset() {
+ ev.errorf("vector cannot contain metrics with the same labelset")
+ }
+ } else if v.Type() == parser.ValueTypeVector {
+ vec := v.(Vector)
+ for i := range vec {
+ if vec[i].DropName {
+ vec[i].Metric = vec[i].Metric.DropMetricName()
+ }
+ }
+ if vec.ContainsSameLabelset() {
+ ev.errorf("vector cannot contain metrics with the same labelset")
+ }
+ }
+}
+
func addToSeries(ss *Series, ts int64, f float64, h *histogram.FloatHistogram, numSteps int) {
if h == nil {
if ss.Floats == nil {
@@ -3542,14 +3673,14 @@ func makeInt64Pointer(val int64) *int64 {
return valp
}
-// Add RatioSampler interface to allow unit-testing (previously: Randomizer).
+// RatioSampler allows unit-testing (previously: Randomizer).
type RatioSampler interface {
// Return this sample "offset" between [0.0, 1.0]
sampleOffset(ts int64, sample *Sample) float64
AddRatioSample(r float64, sample *Sample) bool
}
-// Use Hash(labels.String()) / maxUint64 as a "deterministic"
+// HashRatioSampler uses Hash(labels.String()) / maxUint64 as a "deterministic"
// value in [0.0, 1.0].
type HashRatioSampler struct{}
@@ -3603,3 +3734,41 @@ func newHistogramStatsSeries(series storage.Series) *histogramStatsSeries {
func (s histogramStatsSeries) Iterator(it chunkenc.Iterator) chunkenc.Iterator {
return NewHistogramStatsIterator(s.Series.Iterator(it))
}
+
+// gatherVector gathers a Vector for ts from the series in input.
+// output is used as a buffer.
+// If bufHelpers and seriesHelpers are provided, seriesHelpers[i] is appended to bufHelpers for every input index i.
+// The gathered Vector and bufHelper are returned.
+func (ev *evaluator) gatherVector(ts int64, input Matrix, output Vector, bufHelpers, seriesHelpers []EvalSeriesHelper) (Vector, []EvalSeriesHelper) {
+ output = output[:0]
+ for i, series := range input {
+ switch {
+ case len(series.Floats) > 0 && series.Floats[0].T == ts:
+ s := series.Floats[0]
+ output = append(output, Sample{Metric: series.Metric, F: s.F, T: ts, DropName: series.DropName})
+ // Move input vectors forward so we don't have to re-scan the same
+ // past points at the next step.
+ input[i].Floats = series.Floats[1:]
+ case len(series.Histograms) > 0 && series.Histograms[0].T == ts:
+ s := series.Histograms[0]
+ output = append(output, Sample{Metric: series.Metric, H: s.H, T: ts, DropName: series.DropName})
+ input[i].Histograms = series.Histograms[1:]
+ default:
+ continue
+ }
+ if len(seriesHelpers) > 0 {
+ bufHelpers = append(bufHelpers, seriesHelpers[i])
+ }
+
+ // Don't add histogram size here because we only
+ // copy the pointer above, not the whole
+ // histogram.
+ ev.currentSamples++
+ if ev.currentSamples > ev.maxSamples {
+ ev.error(ErrTooManySamples(env))
+ }
+ }
+ ev.samplesStats.UpdatePeak(ev.currentSamples)
+
+ return output, bufHelpers
+}
diff --git a/promql/engine_test.go b/promql/engine_test.go
index 99f3fc5bdd8..8da27a73c5c 100644
--- a/promql/engine_test.go
+++ b/promql/engine_test.go
@@ -17,8 +17,6 @@ import (
"context"
"errors"
"fmt"
- "math"
- "os"
"sort"
"strconv"
"sync"
@@ -26,7 +24,6 @@ import (
"time"
"github.com/stretchr/testify/require"
- "go.uber.org/goleak"
"github.com/prometheus/prometheus/model/histogram"
"github.com/prometheus/prometheus/model/labels"
@@ -51,20 +48,13 @@ const (
func TestMain(m *testing.M) {
// Enable experimental functions testing
parser.EnableExperimentalFunctions = true
- goleak.VerifyTestMain(m)
+ testutil.TolerantVerifyLeak(m)
}
func TestQueryConcurrency(t *testing.T) {
maxConcurrency := 10
- dir, err := os.MkdirTemp("", "test_concurrency")
- require.NoError(t, err)
- defer os.RemoveAll(dir)
- queryTracker := promql.NewActiveQueryTracker(dir, maxConcurrency, nil)
- t.Cleanup(func() {
- require.NoError(t, queryTracker.Close())
- })
-
+ queryTracker := promql.NewActiveQueryTracker(t.TempDir(), maxConcurrency, nil)
opts := promql.EngineOpts{
Logger: nil,
Reg: nil,
@@ -72,15 +62,17 @@ func TestQueryConcurrency(t *testing.T) {
Timeout: 100 * time.Second,
ActiveQueryTracker: queryTracker,
}
+ engine := promqltest.NewTestEngineWithOpts(t, opts)
- engine := promql.NewEngine(opts)
ctx, cancelCtx := context.WithCancel(context.Background())
- defer cancelCtx()
+ t.Cleanup(cancelCtx)
block := make(chan struct{})
processing := make(chan struct{})
done := make(chan int)
- defer close(done)
+ t.Cleanup(func() {
+ close(done)
+ })
f := func(context.Context) error {
select {
@@ -165,7 +157,7 @@ func TestQueryTimeout(t *testing.T) {
MaxSamples: 10,
Timeout: 5 * time.Millisecond,
}
- engine := promql.NewEngine(opts)
+ engine := promqltest.NewTestEngineWithOpts(t, opts)
ctx, cancelCtx := context.WithCancel(context.Background())
defer cancelCtx()
@@ -190,7 +182,7 @@ func TestQueryCancel(t *testing.T) {
MaxSamples: 10,
Timeout: 10 * time.Second,
}
- engine := promql.NewEngine(opts)
+ engine := promqltest.NewTestEngineWithOpts(t, opts)
ctx, cancelCtx := context.WithCancel(context.Background())
defer cancelCtx()
@@ -238,11 +230,11 @@ func (q *errQuerier) Select(context.Context, bool, *storage.SelectHints, ...*lab
return errSeriesSet{err: q.err}
}
-func (*errQuerier) LabelValues(context.Context, string, ...*labels.Matcher) ([]string, annotations.Annotations, error) {
+func (*errQuerier) LabelValues(context.Context, string, *storage.LabelHints, ...*labels.Matcher) ([]string, annotations.Annotations, error) {
return nil, nil, nil
}
-func (*errQuerier) LabelNames(context.Context, ...*labels.Matcher) ([]string, annotations.Annotations, error) {
+func (*errQuerier) LabelNames(context.Context, *storage.LabelHints, ...*labels.Matcher) ([]string, annotations.Annotations, error) {
return nil, nil, nil
}
func (*errQuerier) Close() error { return nil }
@@ -264,7 +256,7 @@ func TestQueryError(t *testing.T) {
MaxSamples: 10,
Timeout: 10 * time.Second,
}
- engine := promql.NewEngine(opts)
+ engine := promqltest.NewTestEngineWithOpts(t, opts)
errStorage := promql.ErrStorage{errors.New("storage error")}
queryable := storage.QueryableFunc(func(mint, maxt int64) (storage.Querier, error) {
return &errQuerier{err: errStorage}, nil
@@ -598,7 +590,7 @@ func TestSelectHintsSetCorrectly(t *testing.T) {
},
} {
t.Run(tc.query, func(t *testing.T) {
- engine := promql.NewEngine(opts)
+ engine := promqltest.NewTestEngineWithOpts(t, opts)
hintsRecorder := &noopHintRecordingQueryable{}
var (
@@ -629,7 +621,7 @@ func TestEngineShutdown(t *testing.T) {
MaxSamples: 10,
Timeout: 10 * time.Second,
}
- engine := promql.NewEngine(opts)
+ engine := promqltest.NewTestEngineWithOpts(t, opts)
ctx, cancelCtx := context.WithCancel(context.Background())
block := make(chan struct{})
@@ -765,7 +757,7 @@ load 10s
t.Run(fmt.Sprintf("%d query=%s", i, c.Query), func(t *testing.T) {
var err error
var qry promql.Query
- engine := newTestEngine()
+ engine := newTestEngine(t)
if c.Interval == 0 {
qry, err = engine.NewInstantQuery(context.Background(), storage, nil, c.Query, c.Start)
} else {
@@ -1307,7 +1299,7 @@ load 10s
for _, c := range cases {
t.Run(c.Query, func(t *testing.T) {
opts := promql.NewPrometheusQueryOpts(true, 0)
- engine := promqltest.NewTestEngine(true, 0, promqltest.DefaultMaxSamplesPerQuery)
+ engine := promqltest.NewTestEngine(t, true, 0, promqltest.DefaultMaxSamplesPerQuery)
runQuery := func(expErr error) *stats.Statistics {
var err error
@@ -1334,7 +1326,7 @@ load 10s
if c.SkipMaxCheck {
return
}
- engine = promqltest.NewTestEngine(true, 0, stats.Samples.PeakSamples-1)
+ engine = promqltest.NewTestEngine(t, true, 0, stats.Samples.PeakSamples-1)
runQuery(promql.ErrTooManySamples(env))
})
}
@@ -1487,7 +1479,7 @@ load 10s
for _, c := range cases {
t.Run(c.Query, func(t *testing.T) {
- engine := newTestEngine()
+ engine := newTestEngine(t)
testFunc := func(expError error) {
var err error
var qry promql.Query
@@ -1508,18 +1500,18 @@ load 10s
}
// Within limit.
- engine = promqltest.NewTestEngine(false, 0, c.MaxSamples)
+ engine = promqltest.NewTestEngine(t, false, 0, c.MaxSamples)
testFunc(nil)
// Exceeding limit.
- engine = promqltest.NewTestEngine(false, 0, c.MaxSamples-1)
+ engine = promqltest.NewTestEngine(t, false, 0, c.MaxSamples-1)
testFunc(promql.ErrTooManySamples(env))
})
}
}
func TestAtModifier(t *testing.T) {
- engine := newTestEngine()
+ engine := newTestEngine(t)
storage := promqltest.LoadedStorage(t, `
load 10s
metric{job="1"} 0+1x1000
@@ -1715,7 +1707,8 @@ load 1ms
{F: 3600, T: 6 * 60 * 1000},
{F: 3600, T: 7 * 60 * 1000},
},
- Metric: labels.EmptyLabels(),
+ Metric: labels.EmptyLabels(),
+ DropName: true,
},
},
},
@@ -1931,20 +1924,24 @@ func TestSubquerySelector(t *testing.T) {
nil,
promql.Matrix{
promql.Series{
- Floats: []promql.FPoint{{F: 3, T: 7985000}, {F: 3, T: 7990000}, {F: 3, T: 7995000}, {F: 3, T: 8000000}},
- Metric: labels.FromStrings("job", "api-server", "instance", "0", "group", "canary"),
+ Floats: []promql.FPoint{{F: 3, T: 7985000}, {F: 3, T: 7990000}, {F: 3, T: 7995000}, {F: 3, T: 8000000}},
+ Metric: labels.FromStrings("job", "api-server", "instance", "0", "group", "canary"),
+ DropName: true,
},
promql.Series{
- Floats: []promql.FPoint{{F: 4, T: 7985000}, {F: 4, T: 7990000}, {F: 4, T: 7995000}, {F: 4, T: 8000000}},
- Metric: labels.FromStrings("job", "api-server", "instance", "1", "group", "canary"),
+ Floats: []promql.FPoint{{F: 4, T: 7985000}, {F: 4, T: 7990000}, {F: 4, T: 7995000}, {F: 4, T: 8000000}},
+ Metric: labels.FromStrings("job", "api-server", "instance", "1", "group", "canary"),
+ DropName: true,
},
promql.Series{
- Floats: []promql.FPoint{{F: 1, T: 7985000}, {F: 1, T: 7990000}, {F: 1, T: 7995000}, {F: 1, T: 8000000}},
- Metric: labels.FromStrings("job", "api-server", "instance", "0", "group", "production"),
+ Floats: []promql.FPoint{{F: 1, T: 7985000}, {F: 1, T: 7990000}, {F: 1, T: 7995000}, {F: 1, T: 8000000}},
+ Metric: labels.FromStrings("job", "api-server", "instance", "0", "group", "production"),
+ DropName: true,
},
promql.Series{
- Floats: []promql.FPoint{{F: 2, T: 7985000}, {F: 2, T: 7990000}, {F: 2, T: 7995000}, {F: 2, T: 8000000}},
- Metric: labels.FromStrings("job", "api-server", "instance", "1", "group", "production"),
+ Floats: []promql.FPoint{{F: 2, T: 7985000}, {F: 2, T: 7990000}, {F: 2, T: 7995000}, {F: 2, T: 8000000}},
+ Metric: labels.FromStrings("job", "api-server", "instance", "1", "group", "production"),
+ DropName: true,
},
},
nil,
@@ -1997,7 +1994,7 @@ func TestSubquerySelector(t *testing.T) {
},
} {
t.Run("", func(t *testing.T) {
- engine := newTestEngine()
+ engine := newTestEngine(t)
storage := promqltest.LoadedStorage(t, tst.loadString)
t.Cleanup(func() { storage.Close() })
@@ -2046,7 +2043,7 @@ func TestQueryLogger_basic(t *testing.T) {
MaxSamples: 10,
Timeout: 10 * time.Second,
}
- engine := promql.NewEngine(opts)
+ engine := promqltest.NewTestEngineWithOpts(t, opts)
queryExec := func() {
ctx, cancelCtx := context.WithCancel(context.Background())
@@ -2097,7 +2094,7 @@ func TestQueryLogger_fields(t *testing.T) {
MaxSamples: 10,
Timeout: 10 * time.Second,
}
- engine := promql.NewEngine(opts)
+ engine := promqltest.NewTestEngineWithOpts(t, opts)
f1 := NewFakeQueryLogger()
engine.SetQueryLogger(f1)
@@ -2126,7 +2123,7 @@ func TestQueryLogger_error(t *testing.T) {
MaxSamples: 10,
Timeout: 10 * time.Second,
}
- engine := promql.NewEngine(opts)
+ engine := promqltest.NewTestEngineWithOpts(t, opts)
f1 := NewFakeQueryLogger()
engine.SetQueryLogger(f1)
@@ -3009,7 +3006,7 @@ func TestEngineOptsValidation(t *testing.T) {
}
for _, c := range cases {
- eng := promql.NewEngine(c.opts)
+ eng := promqltest.NewTestEngineWithOpts(t, c.opts)
_, err1 := eng.NewInstantQuery(context.Background(), nil, nil, c.query, time.Unix(10, 0))
_, err2 := eng.NewRangeQuery(context.Background(), nil, nil, c.query, time.Unix(0, 0), time.Unix(10, 0), time.Second)
if c.fail {
@@ -3022,8 +3019,31 @@ func TestEngineOptsValidation(t *testing.T) {
}
}
+func TestEngine_Close(t *testing.T) {
+ t.Run("nil engine", func(t *testing.T) {
+ var ng *promql.Engine
+ require.NoError(t, ng.Close())
+ })
+
+ t.Run("non-nil engine", func(t *testing.T) {
+ ng := promql.NewEngine(promql.EngineOpts{
+ Logger: nil,
+ Reg: nil,
+ MaxSamples: 0,
+ Timeout: 100 * time.Second,
+ NoStepSubqueryIntervalFn: nil,
+ EnableAtModifier: true,
+ EnableNegativeOffset: true,
+ EnablePerStepStats: false,
+ LookbackDelta: 0,
+ EnableDelayedNameRemoval: true,
+ })
+ require.NoError(t, ng.Close())
+ })
+}
+
func TestInstantQueryWithRangeVectorSelector(t *testing.T) {
- engine := newTestEngine()
+ engine := newTestEngine(t)
baseT := timestamp.Time(0)
storage := promqltest.LoadedStorage(t, `
@@ -3098,217 +3118,6 @@ func TestInstantQueryWithRangeVectorSelector(t *testing.T) {
}
}
-func TestNativeHistogram_Sum_Count_Add_AvgOperator(t *testing.T) {
- // TODO(codesome): Integrate histograms into the PromQL testing framework
- // and write more tests there.
- cases := []struct {
- histograms []histogram.Histogram
- expected histogram.FloatHistogram
- expectedAvg histogram.FloatHistogram
- }{
- {
- histograms: []histogram.Histogram{
- {
- CounterResetHint: histogram.GaugeType,
- Schema: 0,
- Count: 25,
- Sum: 1234.5,
- ZeroThreshold: 0.001,
- ZeroCount: 4,
- PositiveSpans: []histogram.Span{
- {Offset: 0, Length: 2},
- {Offset: 1, Length: 2},
- },
- PositiveBuckets: []int64{1, 1, -1, 0},
- NegativeSpans: []histogram.Span{
- {Offset: 0, Length: 2},
- {Offset: 2, Length: 2},
- },
- NegativeBuckets: []int64{2, 2, -3, 8},
- },
- {
- CounterResetHint: histogram.GaugeType,
- Schema: 0,
- Count: 41,
- Sum: 2345.6,
- ZeroThreshold: 0.001,
- ZeroCount: 5,
- PositiveSpans: []histogram.Span{
- {Offset: 0, Length: 4},
- {Offset: 0, Length: 0},
- {Offset: 0, Length: 3},
- },
- PositiveBuckets: []int64{1, 2, -2, 1, -1, 0, 0},
- NegativeSpans: []histogram.Span{
- {Offset: 1, Length: 4},
- {Offset: 2, Length: 0},
- {Offset: 2, Length: 3},
- },
- NegativeBuckets: []int64{1, 3, -2, 5, -2, 0, -3},
- },
- {
- CounterResetHint: histogram.GaugeType,
- Schema: 0,
- Count: 41,
- Sum: 1111.1,
- ZeroThreshold: 0.001,
- ZeroCount: 5,
- PositiveSpans: []histogram.Span{
- {Offset: 0, Length: 4},
- {Offset: 0, Length: 0},
- {Offset: 0, Length: 3},
- },
- PositiveBuckets: []int64{1, 2, -2, 1, -1, 0, 0},
- NegativeSpans: []histogram.Span{
- {Offset: 1, Length: 4},
- {Offset: 2, Length: 0},
- {Offset: 2, Length: 3},
- },
- NegativeBuckets: []int64{1, 3, -2, 5, -2, 0, -3},
- },
- {
- CounterResetHint: histogram.GaugeType,
- Schema: 1, // Everything is 0 just to make the count 4 so avg has nicer numbers.
- },
- },
- expected: histogram.FloatHistogram{
- CounterResetHint: histogram.GaugeType,
- Schema: 0,
- ZeroThreshold: 0.001,
- ZeroCount: 14,
- Count: 107,
- Sum: 4691.2,
- PositiveSpans: []histogram.Span{
- {Offset: 0, Length: 7},
- },
- PositiveBuckets: []float64{3, 8, 2, 5, 3, 2, 2},
- NegativeSpans: []histogram.Span{
- {Offset: 0, Length: 6},
- {Offset: 3, Length: 3},
- },
- NegativeBuckets: []float64{2, 6, 8, 4, 15, 9, 10, 10, 4},
- },
- expectedAvg: histogram.FloatHistogram{
- CounterResetHint: histogram.GaugeType,
- Schema: 0,
- ZeroThreshold: 0.001,
- ZeroCount: 3.5,
- Count: 26.75,
- Sum: 1172.8,
- PositiveSpans: []histogram.Span{
- {Offset: 0, Length: 7},
- },
- PositiveBuckets: []float64{0.75, 2, 0.5, 1.25, 0.75, 0.5, 0.5},
- NegativeSpans: []histogram.Span{
- {Offset: 0, Length: 6},
- {Offset: 3, Length: 3},
- },
- NegativeBuckets: []float64{0.5, 1.5, 2, 1, 3.75, 2.25, 2.5, 2.5, 1},
- },
- },
- }
-
- idx0 := int64(0)
- for _, c := range cases {
- for _, floatHisto := range []bool{true, false} {
- t.Run(fmt.Sprintf("floatHistogram=%t %d", floatHisto, idx0), func(t *testing.T) {
- storage := teststorage.New(t)
- t.Cleanup(func() { storage.Close() })
-
- seriesName := "sparse_histogram_series"
- seriesNameOverTime := "sparse_histogram_series_over_time"
-
- engine := newTestEngine()
-
- ts := idx0 * int64(10*time.Minute/time.Millisecond)
- app := storage.Appender(context.Background())
- _, err := app.Append(0, labels.FromStrings("__name__", "float_series", "idx", "0"), ts, 42)
- require.NoError(t, err)
- for idx1, h := range c.histograms {
- lbls := labels.FromStrings("__name__", seriesName, "idx", strconv.Itoa(idx1))
- // Since we mutate h later, we need to create a copy here.
- var err error
- if floatHisto {
- _, err = app.AppendHistogram(0, lbls, ts, nil, h.Copy().ToFloat(nil))
- } else {
- _, err = app.AppendHistogram(0, lbls, ts, h.Copy(), nil)
- }
- require.NoError(t, err)
-
- lbls = labels.FromStrings("__name__", seriesNameOverTime)
- newTs := ts + int64(idx1)*int64(time.Minute/time.Millisecond)
- // Since we mutate h later, we need to create a copy here.
- if floatHisto {
- _, err = app.AppendHistogram(0, lbls, newTs, nil, h.Copy().ToFloat(nil))
- } else {
- _, err = app.AppendHistogram(0, lbls, newTs, h.Copy(), nil)
- }
- require.NoError(t, err)
- }
- require.NoError(t, app.Commit())
-
- queryAndCheck := func(queryString string, ts int64, exp promql.Vector) {
- qry, err := engine.NewInstantQuery(context.Background(), storage, nil, queryString, timestamp.Time(ts))
- require.NoError(t, err)
-
- res := qry.Exec(context.Background())
- require.NoError(t, res.Err)
- require.Empty(t, res.Warnings)
-
- vector, err := res.Vector()
- require.NoError(t, err)
-
- testutil.RequireEqual(t, exp, vector)
- }
- queryAndCheckAnnotations := func(queryString string, ts int64, expWarnings annotations.Annotations) {
- qry, err := engine.NewInstantQuery(context.Background(), storage, nil, queryString, timestamp.Time(ts))
- require.NoError(t, err)
-
- res := qry.Exec(context.Background())
- require.NoError(t, res.Err)
- require.Equal(t, expWarnings, res.Warnings)
- }
-
- // sum().
- queryString := fmt.Sprintf("sum(%s)", seriesName)
- queryAndCheck(queryString, ts, []promql.Sample{{T: ts, H: &c.expected, Metric: labels.EmptyLabels()}})
-
- queryString = `sum({idx="0"})`
- var annos annotations.Annotations
- annos.Add(annotations.NewMixedFloatsHistogramsAggWarning(posrange.PositionRange{Start: 4, End: 13}))
- queryAndCheckAnnotations(queryString, ts, annos)
-
- // + operator.
- queryString = fmt.Sprintf(`%s{idx="0"}`, seriesName)
- for idx := 1; idx < len(c.histograms); idx++ {
- queryString += fmt.Sprintf(` + ignoring(idx) %s{idx="%d"}`, seriesName, idx)
- }
- queryAndCheck(queryString, ts, []promql.Sample{{T: ts, H: &c.expected, Metric: labels.EmptyLabels()}})
-
- // count().
- queryString = fmt.Sprintf("count(%s)", seriesName)
- queryAndCheck(queryString, ts, []promql.Sample{{T: ts, F: 4, Metric: labels.EmptyLabels()}})
-
- // avg().
- queryString = fmt.Sprintf("avg(%s)", seriesName)
- queryAndCheck(queryString, ts, []promql.Sample{{T: ts, H: &c.expectedAvg, Metric: labels.EmptyLabels()}})
-
- offset := int64(len(c.histograms) - 1)
- newTs := ts + offset*int64(time.Minute/time.Millisecond)
-
- // sum_over_time().
- queryString = fmt.Sprintf("sum_over_time(%s[%dm:1m])", seriesNameOverTime, offset)
- queryAndCheck(queryString, newTs, []promql.Sample{{T: newTs, H: &c.expected, Metric: labels.EmptyLabels()}})
-
- // avg_over_time().
- queryString = fmt.Sprintf("avg_over_time(%s[%dm:1m])", seriesNameOverTime, offset)
- queryAndCheck(queryString, newTs, []promql.Sample{{T: newTs, H: &c.expectedAvg, Metric: labels.EmptyLabels()}})
- })
- idx0++
- }
- }
-}
-
func TestNativeHistogram_SubOperator(t *testing.T) {
// TODO(codesome): Integrate histograms into the PromQL testing framework
// and write more tests there.
@@ -3488,7 +3297,7 @@ func TestNativeHistogram_SubOperator(t *testing.T) {
for _, c := range cases {
for _, floatHisto := range []bool{true, false} {
t.Run(fmt.Sprintf("floatHistogram=%t %d", floatHisto, idx0), func(t *testing.T) {
- engine := newTestEngine()
+ engine := newTestEngine(t)
storage := teststorage.New(t)
t.Cleanup(func() { storage.Close() })
@@ -3544,171 +3353,6 @@ func TestNativeHistogram_SubOperator(t *testing.T) {
}
}
-func TestNativeHistogram_MulDivOperator(t *testing.T) {
- // TODO(codesome): Integrate histograms into the PromQL testing framework
- // and write more tests there.
- originalHistogram := histogram.Histogram{
- Schema: 0,
- Count: 21,
- Sum: 33,
- ZeroThreshold: 0.001,
- ZeroCount: 3,
- PositiveSpans: []histogram.Span{
- {Offset: 0, Length: 3},
- },
- PositiveBuckets: []int64{3, 0, 0},
- NegativeSpans: []histogram.Span{
- {Offset: 0, Length: 3},
- },
- NegativeBuckets: []int64{3, 0, 0},
- }
-
- cases := []struct {
- scalar float64
- histogram histogram.Histogram
- expectedMul histogram.FloatHistogram
- expectedDiv histogram.FloatHistogram
- }{
- {
- scalar: 3,
- histogram: originalHistogram,
- expectedMul: histogram.FloatHistogram{
- Schema: 0,
- Count: 63,
- Sum: 99,
- ZeroThreshold: 0.001,
- ZeroCount: 9,
- PositiveSpans: []histogram.Span{
- {Offset: 0, Length: 3},
- },
- PositiveBuckets: []float64{9, 9, 9},
- NegativeSpans: []histogram.Span{
- {Offset: 0, Length: 3},
- },
- NegativeBuckets: []float64{9, 9, 9},
- },
- expectedDiv: histogram.FloatHistogram{
- Schema: 0,
- Count: 7,
- Sum: 11,
- ZeroThreshold: 0.001,
- ZeroCount: 1,
- PositiveSpans: []histogram.Span{
- {Offset: 0, Length: 3},
- },
- PositiveBuckets: []float64{1, 1, 1},
- NegativeSpans: []histogram.Span{
- {Offset: 0, Length: 3},
- },
- NegativeBuckets: []float64{1, 1, 1},
- },
- },
- {
- scalar: 0,
- histogram: originalHistogram,
- expectedMul: histogram.FloatHistogram{
- Schema: 0,
- Count: 0,
- Sum: 0,
- ZeroThreshold: 0.001,
- ZeroCount: 0,
- PositiveSpans: []histogram.Span{
- {Offset: 0, Length: 3},
- },
- PositiveBuckets: []float64{0, 0, 0},
- NegativeSpans: []histogram.Span{
- {Offset: 0, Length: 3},
- },
- NegativeBuckets: []float64{0, 0, 0},
- },
- expectedDiv: histogram.FloatHistogram{
- Schema: 0,
- Count: math.Inf(1),
- Sum: math.Inf(1),
- ZeroThreshold: 0.001,
- ZeroCount: math.Inf(1),
- PositiveSpans: []histogram.Span{
- {Offset: 0, Length: 3},
- },
- PositiveBuckets: []float64{math.Inf(1), math.Inf(1), math.Inf(1)},
- NegativeSpans: []histogram.Span{
- {Offset: 0, Length: 3},
- },
- NegativeBuckets: []float64{math.Inf(1), math.Inf(1), math.Inf(1)},
- },
- },
- }
-
- idx0 := int64(0)
- for _, c := range cases {
- for _, floatHisto := range []bool{true, false} {
- t.Run(fmt.Sprintf("floatHistogram=%t %d", floatHisto, idx0), func(t *testing.T) {
- storage := teststorage.New(t)
- t.Cleanup(func() { storage.Close() })
-
- seriesName := "sparse_histogram_series"
- floatSeriesName := "float_series"
-
- engine := newTestEngine()
-
- ts := idx0 * int64(10*time.Minute/time.Millisecond)
- app := storage.Appender(context.Background())
- h := c.histogram
- lbls := labels.FromStrings("__name__", seriesName)
- // Since we mutate h later, we need to create a copy here.
- var err error
- if floatHisto {
- _, err = app.AppendHistogram(0, lbls, ts, nil, h.Copy().ToFloat(nil))
- } else {
- _, err = app.AppendHistogram(0, lbls, ts, h.Copy(), nil)
- }
- require.NoError(t, err)
- _, err = app.Append(0, labels.FromStrings("__name__", floatSeriesName), ts, c.scalar)
- require.NoError(t, err)
- require.NoError(t, app.Commit())
-
- queryAndCheck := func(queryString string, exp promql.Vector) {
- qry, err := engine.NewInstantQuery(context.Background(), storage, nil, queryString, timestamp.Time(ts))
- require.NoError(t, err)
-
- res := qry.Exec(context.Background())
- require.NoError(t, res.Err)
-
- vector, err := res.Vector()
- require.NoError(t, err)
-
- testutil.RequireEqual(t, exp, vector)
- }
-
- // histogram * scalar.
- queryString := fmt.Sprintf(`%s * %f`, seriesName, c.scalar)
- queryAndCheck(queryString, []promql.Sample{{T: ts, H: &c.expectedMul, Metric: labels.EmptyLabels()}})
-
- // scalar * histogram.
- queryString = fmt.Sprintf(`%f * %s`, c.scalar, seriesName)
- queryAndCheck(queryString, []promql.Sample{{T: ts, H: &c.expectedMul, Metric: labels.EmptyLabels()}})
-
- // histogram * float.
- queryString = fmt.Sprintf(`%s * %s`, seriesName, floatSeriesName)
- queryAndCheck(queryString, []promql.Sample{{T: ts, H: &c.expectedMul, Metric: labels.EmptyLabels()}})
-
- // float * histogram.
- queryString = fmt.Sprintf(`%s * %s`, floatSeriesName, seriesName)
- queryAndCheck(queryString, []promql.Sample{{T: ts, H: &c.expectedMul, Metric: labels.EmptyLabels()}})
-
- // histogram / scalar.
- queryString = fmt.Sprintf(`%s / %f`, seriesName, c.scalar)
- queryAndCheck(queryString, []promql.Sample{{T: ts, H: &c.expectedDiv, Metric: labels.EmptyLabels()}})
-
- // histogram / float.
- queryString = fmt.Sprintf(`%s / %s`, seriesName, floatSeriesName)
- queryAndCheck(queryString, []promql.Sample{{T: ts, H: &c.expectedDiv, Metric: labels.EmptyLabels()}})
- })
- idx0++
- }
- }
-}
-
func TestQueryLookbackDelta(t *testing.T) {
var (
load = `load 5m
@@ -3772,7 +3416,7 @@ metric 0 1 2
for _, c := range cases {
c := c
t.Run(c.name, func(t *testing.T) {
- engine := promqltest.NewTestEngine(false, c.engineLookback, promqltest.DefaultMaxSamplesPerQuery)
+ engine := promqltest.NewTestEngine(t, false, c.engineLookback, promqltest.DefaultMaxSamplesPerQuery)
storage := promqltest.LoadedStorage(t, load)
t.Cleanup(func() { storage.Close() })
@@ -3798,3 +3442,124 @@ func makeInt64Pointer(val int64) *int64 {
*valp = val
return valp
}
+
+func TestHistogramCopyFromIteratorRegression(t *testing.T) {
+ // Loading the following histograms creates two chunks because there's a
+ // counter reset. Not only the counter is lower in the last histogram
+ // but also there's missing buckets.
+ // This in turns means that chunk iterators will have different spans.
+ load := `load 1m
+histogram {{sum:4 count:4 buckets:[2 2]}} {{sum:6 count:6 buckets:[3 3]}} {{sum:1 count:1 buckets:[1]}}
+`
+ storage := promqltest.LoadedStorage(t, load)
+ t.Cleanup(func() { storage.Close() })
+ engine := promqltest.NewTestEngine(t, false, 0, promqltest.DefaultMaxSamplesPerQuery)
+
+ verify := func(t *testing.T, qry promql.Query, expected []histogram.FloatHistogram) {
+ res := qry.Exec(context.Background())
+ require.NoError(t, res.Err)
+
+ m, ok := res.Value.(promql.Matrix)
+ require.True(t, ok)
+
+ require.Len(t, m, 1)
+ series := m[0]
+
+ require.Empty(t, series.Floats)
+ require.Len(t, series.Histograms, len(expected))
+ for i, e := range expected {
+ series.Histograms[i].H.CounterResetHint = histogram.UnknownCounterReset // Don't care.
+ require.Equal(t, &e, series.Histograms[i].H)
+ }
+ }
+
+ qry, err := engine.NewRangeQuery(context.Background(), storage, nil, "increase(histogram[60s])", time.Unix(0, 0), time.Unix(0, 0).Add(1*time.Minute), time.Minute)
+ require.NoError(t, err)
+ verify(t, qry, []histogram.FloatHistogram{
+ {
+ Count: 2,
+ Sum: 2, // Increase from 4 to 6 is 2.
+ PositiveSpans: []histogram.Span{{Offset: 0, Length: 2}}, // Two buckets changed between the first and second histogram.
+ PositiveBuckets: []float64{1, 1}, // Increase from 2 to 3 is 1 in both buckets.
+ },
+ })
+
+ qry, err = engine.NewInstantQuery(context.Background(), storage, nil, "histogram[60s]", time.Unix(0, 0).Add(2*time.Minute))
+ require.NoError(t, err)
+ verify(t, qry, []histogram.FloatHistogram{
+ {
+ Count: 6,
+ Sum: 6,
+ PositiveSpans: []histogram.Span{{Offset: 0, Length: 2}},
+ PositiveBuckets: []float64{3, 3},
+ },
+ {
+ Count: 1,
+ Sum: 1,
+ PositiveSpans: []histogram.Span{{Offset: 0, Length: 1}},
+ PositiveBuckets: []float64{1},
+ },
+ })
+}
+
+func TestEvaluationWithDelayedNameRemovalDisabled(t *testing.T) {
+ opts := promql.EngineOpts{
+ Logger: nil,
+ Reg: nil,
+ EnableAtModifier: true,
+ MaxSamples: 10000,
+ Timeout: 10 * time.Second,
+ EnableDelayedNameRemoval: false,
+ }
+ engine := promqltest.NewTestEngineWithOpts(t, opts)
+
+ promqltest.RunTest(t, `
+load 5m
+ metric{env="1"} 0 60 120
+ another_metric{env="1"} 60 120 180
+
+# Does not drop __name__ for vector selector
+eval instant at 15m metric{env="1"}
+ metric{env="1"} 120
+
+# Drops __name__ for unary operators
+eval instant at 15m -metric
+ {env="1"} -120
+
+# Drops __name__ for binary operators
+eval instant at 15m metric + another_metric
+ {env="1"} 300
+
+# Does not drop __name__ for binary comparison operators
+eval instant at 15m metric <= another_metric
+ metric{env="1"} 120
+
+# Drops __name__ for binary comparison operators with "bool" modifier
+eval instant at 15m metric <= bool another_metric
+ {env="1"} 1
+
+# Drops __name__ for vector-scalar operations
+eval instant at 15m metric * 2
+ {env="1"} 240
+
+# Drops __name__ for instant-vector functions
+eval instant at 15m clamp(metric, 0, 100)
+ {env="1"} 100
+
+# Drops __name__ for round function
+eval instant at 15m round(metric)
+ {env="1"} 120
+
+# Drops __name__ for range-vector functions
+eval instant at 15m rate(metric{env="1"}[10m])
+ {env="1"} 0.2
+
+# Does not drop __name__ for last_over_time function
+eval instant at 15m last_over_time(metric{env="1"}[10m])
+ metric{env="1"} 120
+
+# Drops name for other _over_time functions
+eval instant at 15m max_over_time(metric{env="1"}[10m])
+ {env="1"} 120
+`, engine)
+}
diff --git a/promql/functions.go b/promql/functions.go
index 0a1460932e3..ed35bef663a 100644
--- a/promql/functions.go
+++ b/promql/functions.go
@@ -14,6 +14,7 @@
package promql
import (
+ "context"
"errors"
"fmt"
"math"
@@ -98,9 +99,10 @@ func extrapolatedRate(vals []parser.Value, args parser.Expressions, enh *EvalNod
lastT = samples.Histograms[numSamplesMinusOne].T
var newAnnos annotations.Annotations
resultHistogram, newAnnos = histogramRate(samples.Histograms, isCounter, metricName, args[0].PositionRange())
+ annos.Merge(newAnnos)
if resultHistogram == nil {
// The histograms are not compatible with each other.
- return enh.Out, annos.Merge(newAnnos)
+ return enh.Out, annos
}
case len(samples.Floats) > 1:
numSamplesMinusOne = len(samples.Floats) - 1
@@ -130,10 +132,18 @@ func extrapolatedRate(vals []parser.Value, args parser.Expressions, enh *EvalNod
sampledInterval := float64(lastT-firstT) / 1000
averageDurationBetweenSamples := sampledInterval / float64(numSamplesMinusOne)
- // If the first/last samples are close to the boundaries of the range,
- // extrapolate the result. This is as we expect that another sample
- // will exist given the spacing between samples we've seen thus far,
- // with an allowance for noise.
+ // If samples are close enough to the (lower or upper) boundary of the
+ // range, we extrapolate the rate all the way to the boundary in
+ // question. "Close enough" is defined as "up to 10% more than the
+ // average duration between samples within the range", see
+ // extrapolationThreshold below. Essentially, we are assuming a more or
+ // less regular spacing between samples, and if we don't see a sample
+ // where we would expect one, we assume the series does not cover the
+ // whole range, but starts and/or ends within the range. We still
+ // extrapolate the rate in this case, but not all the way to the
+ // boundary, but only by half of the average duration between samples
+ // (which is our guess for where the series actually starts or ends).
+
extrapolationThreshold := averageDurationBetweenSamples * 1.1
extrapolateToInterval := sampledInterval
@@ -179,17 +189,29 @@ func extrapolatedRate(vals []parser.Value, args parser.Expressions, enh *EvalNod
// Otherwise, it returns the calculated histogram and an empty annotation.
func histogramRate(points []HPoint, isCounter bool, metricName string, pos posrange.PositionRange) (*histogram.FloatHistogram, annotations.Annotations) {
prev := points[0].H
+ usingCustomBuckets := prev.UsesCustomBuckets()
last := points[len(points)-1].H
if last == nil {
return nil, annotations.New().Add(annotations.NewMixedFloatsHistogramsWarning(metricName, pos))
}
+
minSchema := prev.Schema
if last.Schema < minSchema {
minSchema = last.Schema
}
+ if last.UsesCustomBuckets() != usingCustomBuckets {
+ return nil, annotations.New().Add(annotations.NewMixedExponentialCustomHistogramsWarning(metricName, pos))
+ }
+
var annos annotations.Annotations
+ // We check for gauge type histograms in the loop below, but the loop below does not run on the first and last point,
+ // so check the first and last point now.
+ if isCounter && (prev.CounterResetHint == histogram.GaugeType || last.CounterResetHint == histogram.GaugeType) {
+ annos.Add(annotations.NewNativeHistogramNotCounterWarning(metricName, pos))
+ }
+
// First iteration to find out two things:
// - What's the smallest relevant schema?
// - Are all data points histograms?
@@ -209,6 +231,9 @@ func histogramRate(points []HPoint, isCounter bool, metricName string, pos posra
if curr.Schema < minSchema {
minSchema = curr.Schema
}
+ if curr.UsesCustomBuckets() != usingCustomBuckets {
+ return nil, annotations.New().Add(annotations.NewMixedExponentialCustomHistogramsWarning(metricName, pos))
+ }
}
h := last.CopyToSchema(minSchema)
@@ -242,7 +267,7 @@ func histogramRate(points []HPoint, isCounter bool, metricName string, pos posra
}
h.CounterResetHint = histogram.GaugeType
- return h.Compact(0), nil
+ return h.Compact(0), annos
}
// extendedRate is a utility function for xrate/xincrease/xdelta.
@@ -300,11 +325,11 @@ func extendedRate(vals []parser.Value, args parser.Expressions, enh *EvalNodeHel
// the sampled range to the requested range.
if points[firstPoint].T <= rangeStart && durationToEnd < averageInterval {
adjustToRange := float64(durationMilliseconds(ms.Range))
- resultValue = resultValue * (adjustToRange / sampledRange)
+ resultValue *= (adjustToRange / sampledRange)
}
if isRate {
- resultValue = resultValue / ms.Range.Seconds()
+ resultValue /= ms.Range.Seconds()
}
return append(enh.Out, Sample{F: resultValue}), nil
@@ -471,14 +496,9 @@ func funcSortDesc(vals []parser.Value, args parser.Expressions, enh *EvalNodeHel
// === sort_by_label(vector parser.ValueTypeVector, label parser.ValueTypeString...) (Vector, Annotations) ===
func funcSortByLabel(vals []parser.Value, args parser.Expressions, enh *EvalNodeHelper) (Vector, annotations.Annotations) {
- // In case the labels are the same, NaN should sort to the bottom, so take
- // ascending sort with NaN first and reverse it.
- var anno annotations.Annotations
- vals[0], anno = funcSort(vals, args, enh)
- labels := stringSliceFromArgs(args[1:])
+ lbls := stringSliceFromArgs(args[1:])
slices.SortFunc(vals[0].(Vector), func(a, b Sample) int {
- // Iterate over each given label
- for _, label := range labels {
+ for _, label := range lbls {
lv1 := a.Metric.Get(label)
lv2 := b.Metric.Get(label)
@@ -493,22 +513,18 @@ func funcSortByLabel(vals []parser.Value, args parser.Expressions, enh *EvalNode
return +1
}
- return 0
+ // If all labels provided as arguments were equal, sort by the full label set. This ensures a consistent ordering.
+ return labels.Compare(a.Metric, b.Metric)
})
- return vals[0].(Vector), anno
+ return vals[0].(Vector), nil
}
// === sort_by_label_desc(vector parser.ValueTypeVector, label parser.ValueTypeString...) (Vector, Annotations) ===
func funcSortByLabelDesc(vals []parser.Value, args parser.Expressions, enh *EvalNodeHelper) (Vector, annotations.Annotations) {
- // In case the labels are the same, NaN should sort to the bottom, so take
- // ascending sort with NaN first and reverse it.
- var anno annotations.Annotations
- vals[0], anno = funcSortDesc(vals, args, enh)
- labels := stringSliceFromArgs(args[1:])
+ lbls := stringSliceFromArgs(args[1:])
slices.SortFunc(vals[0].(Vector), func(a, b Sample) int {
- // Iterate over each given label
- for _, label := range labels {
+ for _, label := range lbls {
lv1 := a.Metric.Get(label)
lv2 := b.Metric.Get(label)
@@ -523,24 +539,29 @@ func funcSortByLabelDesc(vals []parser.Value, args parser.Expressions, enh *Eval
return -1
}
- return 0
+ // If all labels provided as arguments were equal, sort by the full label set. This ensures a consistent ordering.
+ return -labels.Compare(a.Metric, b.Metric)
})
- return vals[0].(Vector), anno
+ return vals[0].(Vector), nil
}
// === clamp(Vector parser.ValueTypeVector, min, max Scalar) (Vector, Annotations) ===
func funcClamp(vals []parser.Value, args parser.Expressions, enh *EvalNodeHelper) (Vector, annotations.Annotations) {
vec := vals[0].(Vector)
- min := vals[1].(Vector)[0].F
- max := vals[2].(Vector)[0].F
- if max < min {
+ minVal := vals[1].(Vector)[0].F
+ maxVal := vals[2].(Vector)[0].F
+ if maxVal < minVal {
return enh.Out, nil
}
for _, el := range vec {
+ if !enh.enableDelayedNameRemoval {
+ el.Metric = el.Metric.DropMetricName()
+ }
enh.Out = append(enh.Out, Sample{
- Metric: el.Metric.DropMetricName(),
- F: math.Max(min, math.Min(max, el.F)),
+ Metric: el.Metric,
+ F: math.Max(minVal, math.Min(maxVal, el.F)),
+ DropName: true,
})
}
return enh.Out, nil
@@ -549,11 +570,15 @@ func funcClamp(vals []parser.Value, args parser.Expressions, enh *EvalNodeHelper
// === clamp_max(Vector parser.ValueTypeVector, max Scalar) (Vector, Annotations) ===
func funcClampMax(vals []parser.Value, args parser.Expressions, enh *EvalNodeHelper) (Vector, annotations.Annotations) {
vec := vals[0].(Vector)
- max := vals[1].(Vector)[0].F
+ maxVal := vals[1].(Vector)[0].F
for _, el := range vec {
+ if !enh.enableDelayedNameRemoval {
+ el.Metric = el.Metric.DropMetricName()
+ }
enh.Out = append(enh.Out, Sample{
- Metric: el.Metric.DropMetricName(),
- F: math.Min(max, el.F),
+ Metric: el.Metric,
+ F: math.Min(maxVal, el.F),
+ DropName: true,
})
}
return enh.Out, nil
@@ -562,11 +587,15 @@ func funcClampMax(vals []parser.Value, args parser.Expressions, enh *EvalNodeHel
// === clamp_min(Vector parser.ValueTypeVector, min Scalar) (Vector, Annotations) ===
func funcClampMin(vals []parser.Value, args parser.Expressions, enh *EvalNodeHelper) (Vector, annotations.Annotations) {
vec := vals[0].(Vector)
- min := vals[1].(Vector)[0].F
+ minVal := vals[1].(Vector)[0].F
for _, el := range vec {
+ if !enh.enableDelayedNameRemoval {
+ el.Metric = el.Metric.DropMetricName()
+ }
enh.Out = append(enh.Out, Sample{
- Metric: el.Metric.DropMetricName(),
- F: math.Max(min, el.F),
+ Metric: el.Metric,
+ F: math.Max(minVal, el.F),
+ DropName: true,
})
}
return enh.Out, nil
@@ -586,9 +615,13 @@ func funcRound(vals []parser.Value, args parser.Expressions, enh *EvalNodeHelper
for _, el := range vec {
f := math.Floor(el.F*toNearestInverse+0.5) / toNearestInverse
+ if !enh.enableDelayedNameRemoval {
+ el.Metric = el.Metric.DropMetricName()
+ }
enh.Out = append(enh.Out, Sample{
- Metric: el.Metric.DropMetricName(),
- F: f,
+ Metric: el.Metric,
+ F: f,
+ DropName: true,
})
}
return enh.Out, nil
@@ -654,9 +687,28 @@ func funcAvgOverTime(vals []parser.Value, args parser.Expressions, enh *EvalNode
return vec, nil
}
return aggrOverTime(vals, enh, func(s Series) float64 {
- var mean, count, c float64
+ var (
+ sum, mean, count, kahanC float64
+ incrementalMean bool
+ )
for _, f := range s.Floats {
count++
+ if !incrementalMean {
+ newSum, newC := kahanSumInc(f.F, sum, kahanC)
+ // Perform regular mean calculation as long as
+ // the sum doesn't overflow and (in any case)
+ // for the first iteration (even if we start
+ // with ±Inf) to not run into division-by-zero
+ // problems below.
+ if count == 1 || !math.IsInf(newSum, 0) {
+ sum, kahanC = newSum, newC
+ continue
+ }
+ // Handle overflow by reverting to incremental calculation of the mean value.
+ incrementalMean = true
+ mean = sum / (count - 1)
+ kahanC /= count - 1
+ }
if math.IsInf(mean, 0) {
if math.IsInf(f.F, 0) && (mean > 0) == (f.F > 0) {
// The `mean` and `f.F` values are `Inf` of the same sign. They
@@ -674,13 +726,13 @@ func funcAvgOverTime(vals []parser.Value, args parser.Expressions, enh *EvalNode
continue
}
}
- mean, c = kahanSumInc(f.F/count-mean/count, mean, c)
+ correctedMean := mean + kahanC
+ mean, kahanC = kahanSumInc(f.F/count-correctedMean/count, mean, kahanC)
}
-
- if math.IsInf(mean, 0) {
- return mean
+ if incrementalMean {
+ return mean + kahanC
}
- return mean + c
+ return (sum + kahanC) / count
}), nil
}
@@ -746,13 +798,13 @@ func funcMaxOverTime(vals []parser.Value, args parser.Expressions, enh *EvalNode
return enh.Out, nil
}
return aggrOverTime(vals, enh, func(s Series) float64 {
- max := s.Floats[0].F
+ maxVal := s.Floats[0].F
for _, f := range s.Floats {
- if f.F > max || math.IsNaN(max) {
- max = f.F
+ if f.F > maxVal || math.IsNaN(maxVal) {
+ maxVal = f.F
}
}
- return max
+ return maxVal
}), nil
}
@@ -766,13 +818,13 @@ func funcMinOverTime(vals []parser.Value, args parser.Expressions, enh *EvalNode
return enh.Out, nil
}
return aggrOverTime(vals, enh, func(s Series) float64 {
- min := s.Floats[0].F
+ minVal := s.Floats[0].F
for _, f := range s.Floats {
- if f.F < min || math.IsNaN(min) {
- min = f.F
+ if f.F < minVal || math.IsNaN(minVal) {
+ minVal = f.F
}
}
- return min
+ return minVal
}), nil
}
@@ -918,9 +970,13 @@ func funcPresentOverTime(vals []parser.Value, args parser.Expressions, enh *Eval
func simpleFunc(vals []parser.Value, enh *EvalNodeHelper, f func(float64) float64) Vector {
for _, el := range vals[0].(Vector) {
if el.H == nil { // Process only float samples.
+ if !enh.enableDelayedNameRemoval {
+ el.Metric = el.Metric.DropMetricName()
+ }
enh.Out = append(enh.Out, Sample{
- Metric: el.Metric.DropMetricName(),
- F: f(el.F),
+ Metric: el.Metric,
+ F: f(el.F),
+ DropName: true,
})
}
}
@@ -1064,9 +1120,13 @@ func funcSgn(vals []parser.Value, args parser.Expressions, enh *EvalNodeHelper)
func funcTimestamp(vals []parser.Value, args parser.Expressions, enh *EvalNodeHelper) (Vector, annotations.Annotations) {
vec := vals[0].(Vector)
for _, el := range vec {
+ if !enh.enableDelayedNameRemoval {
+ el.Metric = el.Metric.DropMetricName()
+ }
enh.Out = append(enh.Out, Sample{
- Metric: el.Metric.DropMetricName(),
- F: float64(el.T) / 1000,
+ Metric: el.Metric,
+ F: float64(el.T) / 1000,
+ DropName: true,
})
}
return enh.Out, nil
@@ -1173,9 +1233,13 @@ func funcHistogramCount(vals []parser.Value, args parser.Expressions, enh *EvalN
if sample.H == nil {
continue
}
+ if !enh.enableDelayedNameRemoval {
+ sample.Metric = sample.Metric.DropMetricName()
+ }
enh.Out = append(enh.Out, Sample{
- Metric: sample.Metric.DropMetricName(),
- F: sample.H.Count,
+ Metric: sample.Metric,
+ F: sample.H.Count,
+ DropName: true,
})
}
return enh.Out, nil
@@ -1190,9 +1254,13 @@ func funcHistogramSum(vals []parser.Value, args parser.Expressions, enh *EvalNod
if sample.H == nil {
continue
}
+ if !enh.enableDelayedNameRemoval {
+ sample.Metric = sample.Metric.DropMetricName()
+ }
enh.Out = append(enh.Out, Sample{
- Metric: sample.Metric.DropMetricName(),
- F: sample.H.Sum,
+ Metric: sample.Metric,
+ F: sample.H.Sum,
+ DropName: true,
})
}
return enh.Out, nil
@@ -1207,9 +1275,13 @@ func funcHistogramAvg(vals []parser.Value, args parser.Expressions, enh *EvalNod
if sample.H == nil {
continue
}
+ if !enh.enableDelayedNameRemoval {
+ sample.Metric = sample.Metric.DropMetricName()
+ }
enh.Out = append(enh.Out, Sample{
- Metric: sample.Metric.DropMetricName(),
- F: sample.H.Sum / sample.H.Count,
+ Metric: sample.Metric,
+ F: sample.H.Sum / sample.H.Count,
+ DropName: true,
})
}
return enh.Out, nil
@@ -1246,9 +1318,13 @@ func funcHistogramStdDev(vals []parser.Value, args parser.Expressions, enh *Eval
}
variance += cVariance
variance /= sample.H.Count
+ if !enh.enableDelayedNameRemoval {
+ sample.Metric = sample.Metric.DropMetricName()
+ }
enh.Out = append(enh.Out, Sample{
- Metric: sample.Metric.DropMetricName(),
- F: math.Sqrt(variance),
+ Metric: sample.Metric,
+ F: math.Sqrt(variance),
+ DropName: true,
})
}
return enh.Out, nil
@@ -1285,9 +1361,13 @@ func funcHistogramStdVar(vals []parser.Value, args parser.Expressions, enh *Eval
}
variance += cVariance
variance /= sample.H.Count
+ if !enh.enableDelayedNameRemoval {
+ sample.Metric = sample.Metric.DropMetricName()
+ }
enh.Out = append(enh.Out, Sample{
- Metric: sample.Metric.DropMetricName(),
- F: variance,
+ Metric: sample.Metric,
+ F: variance,
+ DropName: true,
})
}
return enh.Out, nil
@@ -1304,9 +1384,13 @@ func funcHistogramFraction(vals []parser.Value, args parser.Expressions, enh *Ev
if sample.H == nil {
continue
}
+ if !enh.enableDelayedNameRemoval {
+ sample.Metric = sample.Metric.DropMetricName()
+ }
enh.Out = append(enh.Out, Sample{
- Metric: sample.Metric.DropMetricName(),
- F: histogramFraction(lower, upper, sample.H),
+ Metric: sample.Metric,
+ F: histogramFraction(lower, upper, sample.H),
+ DropName: true,
})
}
return enh.Out, nil
@@ -1374,9 +1458,13 @@ func funcHistogramQuantile(vals []parser.Value, args parser.Expressions, enh *Ev
continue
}
+ if !enh.enableDelayedNameRemoval {
+ sample.Metric = sample.Metric.DropMetricName()
+ }
enh.Out = append(enh.Out, Sample{
- Metric: sample.Metric.DropMetricName(),
- F: histogramQuantile(q, sample.H),
+ Metric: sample.Metric,
+ F: histogramQuantile(q, sample.H),
+ DropName: true,
})
}
@@ -1450,7 +1538,7 @@ func funcChanges(vals []parser.Value, args parser.Expressions, enh *EvalNodeHelp
}
// 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) {
+func (ev *evaluator) evalLabelReplace(ctx context.Context, args parser.Expressions) (parser.Value, annotations.Annotations) {
var (
dst = stringFromArg(args[1])
repl = stringFromArg(args[2])
@@ -1466,7 +1554,7 @@ func (ev *evaluator) evalLabelReplace(args parser.Expressions) (parser.Value, an
panic(fmt.Errorf("invalid destination label name in label_replace(): %s", dst))
}
- val, ws := ev.eval(args[0])
+ val, ws := ev.eval(ctx, args[0])
matrix := val.(Matrix)
lb := labels.NewBuilder(labels.EmptyLabels())
@@ -1478,6 +1566,11 @@ func (ev *evaluator) evalLabelReplace(args parser.Expressions) (parser.Value, an
lb.Reset(el.Metric)
lb.Set(dst, string(res))
matrix[i].Metric = lb.Labels()
+ if dst == model.MetricNameLabel {
+ matrix[i].DropName = false
+ } else {
+ matrix[i].DropName = el.DropName
+ }
}
}
if matrix.ContainsSameLabelset() {
@@ -1502,7 +1595,7 @@ func funcVector(vals []parser.Value, args parser.Expressions, enh *EvalNodeHelpe
}
// 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) {
+func (ev *evaluator) evalLabelJoin(ctx context.Context, args parser.Expressions) (parser.Value, annotations.Annotations) {
var (
dst = stringFromArg(args[1])
sep = stringFromArg(args[2])
@@ -1519,7 +1612,7 @@ func (ev *evaluator) evalLabelJoin(args parser.Expressions) (parser.Value, annot
panic(fmt.Errorf("invalid destination label name in label_join(): %s", dst))
}
- val, ws := ev.eval(args[0])
+ val, ws := ev.eval(ctx, args[0])
matrix := val.(Matrix)
srcVals := make([]string, len(srcLabels))
lb := labels.NewBuilder(labels.EmptyLabels())
@@ -1532,6 +1625,12 @@ func (ev *evaluator) evalLabelJoin(args parser.Expressions) (parser.Value, annot
lb.Reset(el.Metric)
lb.Set(dst, strval)
matrix[i].Metric = lb.Labels()
+
+ if dst == model.MetricNameLabel {
+ matrix[i].DropName = false
+ } else {
+ matrix[i].DropName = el.DropName
+ }
}
return matrix, ws
@@ -1554,9 +1653,13 @@ 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()
+ if !enh.enableDelayedNameRemoval {
+ el.Metric = el.Metric.DropMetricName()
+ }
enh.Out = append(enh.Out, Sample{
- Metric: el.Metric.DropMetricName(),
- F: f(t),
+ Metric: el.Metric,
+ F: f(t),
+ DropName: true,
})
}
return enh.Out
@@ -1658,6 +1761,7 @@ var FunctionCalls = map[string]FunctionCall{
"hour": funcHour,
"idelta": funcIdelta,
"increase": funcIncrease,
+ "info": nil,
"irate": funcIrate,
"label_replace": funcLabelReplace,
"label_join": funcLabelJoin,
diff --git a/promql/functions_test.go b/promql/functions_test.go
index aef59c83795..9ee0ba51dcb 100644
--- a/promql/functions_test.go
+++ b/promql/functions_test.go
@@ -24,6 +24,7 @@ import (
"github.com/prometheus/prometheus/model/timestamp"
"github.com/prometheus/prometheus/promql"
"github.com/prometheus/prometheus/promql/parser"
+ "github.com/prometheus/prometheus/promql/promqltest"
"github.com/prometheus/prometheus/util/teststorage"
)
@@ -39,7 +40,7 @@ func TestDeriv(t *testing.T) {
MaxSamples: 10000,
Timeout: 10 * time.Second,
}
- engine := promql.NewEngine(opts)
+ engine := promqltest.NewTestEngineWithOpts(t, opts)
a := storage.Appender(context.Background())
diff --git a/promql/fuzz.go b/promql/fuzz.go
index 3fd50b94967..5f08e6a72c9 100644
--- a/promql/fuzz.go
+++ b/promql/fuzz.go
@@ -68,6 +68,10 @@ func fuzzParseMetricWithContentType(in []byte, contentType string) int {
panic(warning)
}
+ if contentType == "application/openmetrics-text" {
+ p = textparse.NewOpenMetricsParser(in, symbolTable)
+ }
+
var err error
for {
_, err = p.Next()
diff --git a/promql/histogram_stats_iterator.go b/promql/histogram_stats_iterator.go
index dfafea5f8ca..459d5924aec 100644
--- a/promql/histogram_stats_iterator.go
+++ b/promql/histogram_stats_iterator.go
@@ -48,7 +48,6 @@ func (f *histogramStatsIterator) AtHistogram(h *histogram.Histogram) (int64, *hi
var t int64
t, f.currentH = f.Iterator.AtHistogram(f.currentH)
if value.IsStaleNaN(f.currentH.Sum) {
- f.setLastH(f.currentH)
h = &histogram.Histogram{Sum: f.currentH.Sum}
return t, h
}
@@ -63,9 +62,13 @@ func (f *histogramStatsIterator) AtHistogram(h *histogram.Histogram) (int64, *hi
return t, h
}
- h.CounterResetHint = f.getResetHint(f.currentH)
- h.Count = f.currentH.Count
- h.Sum = f.currentH.Sum
+ returnValue := histogram.Histogram{
+ CounterResetHint: f.getResetHint(f.currentH),
+ Count: f.currentH.Count,
+ Sum: f.currentH.Sum,
+ }
+ returnValue.CopyTo(h)
+
f.setLastH(f.currentH)
return t, h
}
@@ -77,7 +80,6 @@ func (f *histogramStatsIterator) AtFloatHistogram(fh *histogram.FloatHistogram)
var t int64
t, f.currentFH = f.Iterator.AtFloatHistogram(f.currentFH)
if value.IsStaleNaN(f.currentFH.Sum) {
- f.setLastFH(f.currentFH)
return t, &histogram.FloatHistogram{Sum: f.currentFH.Sum}
}
@@ -91,9 +93,13 @@ func (f *histogramStatsIterator) AtFloatHistogram(fh *histogram.FloatHistogram)
return t, fh
}
- fh.CounterResetHint = f.getFloatResetHint(f.currentFH.CounterResetHint)
- fh.Count = f.currentFH.Count
- fh.Sum = f.currentFH.Sum
+ returnValue := histogram.FloatHistogram{
+ CounterResetHint: f.getFloatResetHint(f.currentFH.CounterResetHint),
+ Count: f.currentFH.Count,
+ Sum: f.currentFH.Sum,
+ }
+ returnValue.CopyTo(fh)
+
f.setLastFH(f.currentFH)
return t, fh
}
diff --git a/promql/histogram_stats_iterator_test.go b/promql/histogram_stats_iterator_test.go
index b71a9d60292..7a2953d3e28 100644
--- a/promql/histogram_stats_iterator_test.go
+++ b/promql/histogram_stats_iterator_test.go
@@ -14,62 +14,132 @@
package promql
import (
+ "fmt"
+ "math"
"testing"
"github.com/stretchr/testify/require"
"github.com/prometheus/prometheus/model/histogram"
"github.com/prometheus/prometheus/model/labels"
+ "github.com/prometheus/prometheus/model/value"
"github.com/prometheus/prometheus/tsdb/chunkenc"
"github.com/prometheus/prometheus/tsdb/tsdbutil"
)
func TestHistogramStatsDecoding(t *testing.T) {
- histograms := []*histogram.Histogram{
- tsdbutil.GenerateTestHistogram(0),
- tsdbutil.GenerateTestHistogram(1),
- tsdbutil.GenerateTestHistogram(2),
- tsdbutil.GenerateTestHistogram(2),
- }
- histograms[0].CounterResetHint = histogram.NotCounterReset
- histograms[1].CounterResetHint = histogram.UnknownCounterReset
- histograms[2].CounterResetHint = histogram.CounterReset
- histograms[3].CounterResetHint = histogram.UnknownCounterReset
-
- expectedHints := []histogram.CounterResetHint{
- histogram.NotCounterReset,
- histogram.NotCounterReset,
- histogram.CounterReset,
- histogram.NotCounterReset,
+ cases := []struct {
+ name string
+ histograms []*histogram.Histogram
+ expectedHints []histogram.CounterResetHint
+ }{
+ {
+ name: "unknown counter reset triggers detection",
+ histograms: []*histogram.Histogram{
+ tsdbutil.GenerateTestHistogramWithHint(0, histogram.NotCounterReset),
+ tsdbutil.GenerateTestHistogramWithHint(1, histogram.UnknownCounterReset),
+ tsdbutil.GenerateTestHistogramWithHint(2, histogram.CounterReset),
+ tsdbutil.GenerateTestHistogramWithHint(2, histogram.UnknownCounterReset),
+ },
+ expectedHints: []histogram.CounterResetHint{
+ histogram.NotCounterReset,
+ histogram.NotCounterReset,
+ histogram.CounterReset,
+ histogram.NotCounterReset,
+ },
+ },
+ {
+ name: "stale sample before unknown reset hint",
+ histograms: []*histogram.Histogram{
+ tsdbutil.GenerateTestHistogramWithHint(0, histogram.NotCounterReset),
+ tsdbutil.GenerateTestHistogramWithHint(1, histogram.UnknownCounterReset),
+ {Sum: math.Float64frombits(value.StaleNaN)},
+ tsdbutil.GenerateTestHistogramWithHint(1, histogram.UnknownCounterReset),
+ },
+ expectedHints: []histogram.CounterResetHint{
+ histogram.NotCounterReset,
+ histogram.NotCounterReset,
+ histogram.UnknownCounterReset,
+ histogram.NotCounterReset,
+ },
+ },
+ {
+ name: "unknown counter reset at the beginning",
+ histograms: []*histogram.Histogram{
+ tsdbutil.GenerateTestHistogramWithHint(1, histogram.UnknownCounterReset),
+ },
+ expectedHints: []histogram.CounterResetHint{
+ histogram.NotCounterReset,
+ },
+ },
+ {
+ name: "detect real counter reset",
+ histograms: []*histogram.Histogram{
+ tsdbutil.GenerateTestHistogramWithHint(2, histogram.UnknownCounterReset),
+ tsdbutil.GenerateTestHistogramWithHint(1, histogram.UnknownCounterReset),
+ },
+ expectedHints: []histogram.CounterResetHint{
+ histogram.NotCounterReset,
+ histogram.CounterReset,
+ },
+ },
+ {
+ name: "detect real counter reset after stale NaN",
+ histograms: []*histogram.Histogram{
+ tsdbutil.GenerateTestHistogramWithHint(2, histogram.UnknownCounterReset),
+ {Sum: math.Float64frombits(value.StaleNaN)},
+ tsdbutil.GenerateTestHistogramWithHint(1, histogram.UnknownCounterReset),
+ },
+ expectedHints: []histogram.CounterResetHint{
+ histogram.NotCounterReset,
+ histogram.UnknownCounterReset,
+ histogram.CounterReset,
+ },
+ },
}
- t.Run("histogram_stats", func(t *testing.T) {
- decodedStats := make([]*histogram.Histogram, 0)
- statsIterator := NewHistogramStatsIterator(newHistogramSeries(histograms).Iterator(nil))
- for statsIterator.Next() != chunkenc.ValNone {
- _, h := statsIterator.AtHistogram(nil)
- decodedStats = append(decodedStats, h)
- }
- for i := 0; i < len(histograms); i++ {
- require.Equal(t, expectedHints[i], decodedStats[i].CounterResetHint)
- require.Equal(t, histograms[i].Count, decodedStats[i].Count)
- require.Equal(t, histograms[i].Sum, decodedStats[i].Sum)
- }
- })
- t.Run("float_histogram_stats", func(t *testing.T) {
- decodedStats := make([]*histogram.FloatHistogram, 0)
- statsIterator := NewHistogramStatsIterator(newHistogramSeries(histograms).Iterator(nil))
- for statsIterator.Next() != chunkenc.ValNone {
- _, h := statsIterator.AtFloatHistogram(nil)
- decodedStats = append(decodedStats, h)
- }
- for i := 0; i < len(histograms); i++ {
- fh := histograms[i].ToFloat(nil)
- require.Equal(t, expectedHints[i], decodedStats[i].CounterResetHint)
- require.Equal(t, fh.Count, decodedStats[i].Count)
- require.Equal(t, fh.Sum, decodedStats[i].Sum)
- }
- })
+ for _, tc := range cases {
+ t.Run(tc.name, func(t *testing.T) {
+ t.Run("histogram_stats", func(t *testing.T) {
+ decodedStats := make([]*histogram.Histogram, 0)
+ statsIterator := NewHistogramStatsIterator(newHistogramSeries(tc.histograms).Iterator(nil))
+ for statsIterator.Next() != chunkenc.ValNone {
+ _, h := statsIterator.AtHistogram(nil)
+ decodedStats = append(decodedStats, h)
+ }
+ for i := 0; i < len(tc.histograms); i++ {
+ require.Equal(t, tc.expectedHints[i], decodedStats[i].CounterResetHint, fmt.Sprintf("mismatch in counter reset hint for histogram %d", i))
+ h := tc.histograms[i]
+ if value.IsStaleNaN(h.Sum) {
+ require.True(t, value.IsStaleNaN(decodedStats[i].Sum))
+ require.Equal(t, uint64(0), decodedStats[i].Count)
+ } else {
+ require.Equal(t, tc.histograms[i].Count, decodedStats[i].Count)
+ require.Equal(t, tc.histograms[i].Sum, decodedStats[i].Sum)
+ }
+ }
+ })
+ t.Run("float_histogram_stats", func(t *testing.T) {
+ decodedStats := make([]*histogram.FloatHistogram, 0)
+ statsIterator := NewHistogramStatsIterator(newHistogramSeries(tc.histograms).Iterator(nil))
+ for statsIterator.Next() != chunkenc.ValNone {
+ _, h := statsIterator.AtFloatHistogram(nil)
+ decodedStats = append(decodedStats, h)
+ }
+ for i := 0; i < len(tc.histograms); i++ {
+ require.Equal(t, tc.expectedHints[i], decodedStats[i].CounterResetHint)
+ fh := tc.histograms[i].ToFloat(nil)
+ if value.IsStaleNaN(fh.Sum) {
+ require.True(t, value.IsStaleNaN(decodedStats[i].Sum))
+ require.Equal(t, float64(0), decodedStats[i].Count)
+ } else {
+ require.Equal(t, fh.Count, decodedStats[i].Count)
+ require.Equal(t, fh.Sum, decodedStats[i].Sum)
+ }
+ }
+ })
+ })
+ }
}
type histogramSeries struct {
diff --git a/promql/info.go b/promql/info.go
new file mode 100644
index 00000000000..1a9f7eb18e4
--- /dev/null
+++ b/promql/info.go
@@ -0,0 +1,454 @@
+// Copyright 2024 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 promql
+
+import (
+ "context"
+ "errors"
+ "fmt"
+ "slices"
+ "strings"
+
+ "github.com/grafana/regexp"
+
+ "github.com/prometheus/prometheus/model/labels"
+ "github.com/prometheus/prometheus/promql/parser"
+ "github.com/prometheus/prometheus/storage"
+ "github.com/prometheus/prometheus/util/annotations"
+)
+
+const targetInfo = "target_info"
+
+// identifyingLabels are the labels we consider as identifying for info metrics.
+// Currently hard coded, so we don't need knowledge of individual info metrics.
+var identifyingLabels = []string{"instance", "job"}
+
+// evalInfo implements the info PromQL function.
+func (ev *evaluator) evalInfo(ctx context.Context, args parser.Expressions) (parser.Value, annotations.Annotations) {
+ val, annots := ev.eval(ctx, args[0])
+ mat := val.(Matrix)
+ // Map from data label name to matchers.
+ dataLabelMatchers := map[string][]*labels.Matcher{}
+ var infoNameMatchers []*labels.Matcher
+ if len(args) > 1 {
+ // TODO: Introduce a dedicated LabelSelector type.
+ labelSelector := args[1].(*parser.VectorSelector)
+ for _, m := range labelSelector.LabelMatchers {
+ dataLabelMatchers[m.Name] = append(dataLabelMatchers[m.Name], m)
+ if m.Name == labels.MetricName {
+ infoNameMatchers = append(infoNameMatchers, m)
+ }
+ }
+ } else {
+ infoNameMatchers = []*labels.Matcher{labels.MustNewMatcher(labels.MatchEqual, labels.MetricName, targetInfo)}
+ }
+
+ // Don't try to enrich info series.
+ ignoreSeries := map[int]struct{}{}
+loop:
+ for i, s := range mat {
+ name := s.Metric.Get(labels.MetricName)
+ for _, m := range infoNameMatchers {
+ if m.Matches(name) {
+ ignoreSeries[i] = struct{}{}
+ continue loop
+ }
+ }
+ }
+
+ selectHints := ev.infoSelectHints(args[0])
+ infoSeries, ws, err := ev.fetchInfoSeries(ctx, mat, ignoreSeries, dataLabelMatchers, selectHints)
+ if err != nil {
+ ev.error(err)
+ }
+ annots.Merge(ws)
+
+ res, ws := ev.combineWithInfoSeries(ctx, mat, infoSeries, ignoreSeries, dataLabelMatchers)
+ annots.Merge(ws)
+ return res, annots
+}
+
+// infoSelectHints calculates the storage.SelectHints for selecting info series, given expr (first argument to info call).
+func (ev *evaluator) infoSelectHints(expr parser.Expr) storage.SelectHints {
+ var nodeTimestamp *int64
+ var offset int64
+ parser.Inspect(expr, func(node parser.Node, path []parser.Node) error {
+ switch n := node.(type) {
+ case *parser.VectorSelector:
+ if n.Timestamp != nil {
+ nodeTimestamp = n.Timestamp
+ }
+ offset = durationMilliseconds(n.OriginalOffset)
+ return fmt.Errorf("end traversal")
+ default:
+ return nil
+ }
+ })
+
+ start := ev.startTimestamp
+ end := ev.endTimestamp
+ if nodeTimestamp != nil {
+ // The timestamp on the selector overrides everything.
+ start = *nodeTimestamp
+ end = *nodeTimestamp
+ }
+ // Reduce the start by one fewer ms than the lookback delta
+ // because wo want to exclude samples that are precisely the
+ // lookback delta before the eval time.
+ start -= durationMilliseconds(ev.lookbackDelta) - 1
+ start -= offset
+ end -= offset
+
+ return storage.SelectHints{
+ Start: start,
+ End: end,
+ Step: ev.interval,
+ Func: "info",
+ }
+}
+
+// fetchInfoSeries fetches info series given matching identifying labels in mat.
+// Series in ignoreSeries are not fetched.
+// dataLabelMatchers may be mutated.
+func (ev *evaluator) fetchInfoSeries(ctx context.Context, mat Matrix, ignoreSeries map[int]struct{}, dataLabelMatchers map[string][]*labels.Matcher, selectHints storage.SelectHints) (Matrix, annotations.Annotations, error) {
+ // A map of values for all identifying labels we are interested in.
+ idLblValues := map[string]map[string]struct{}{}
+ for i, s := range mat {
+ if _, exists := ignoreSeries[i]; exists {
+ continue
+ }
+
+ // Register relevant values per identifying label for this series.
+ for _, l := range identifyingLabels {
+ val := s.Metric.Get(l)
+ if val == "" {
+ continue
+ }
+
+ if idLblValues[l] == nil {
+ idLblValues[l] = map[string]struct{}{}
+ }
+ idLblValues[l][val] = struct{}{}
+ }
+ }
+ if len(idLblValues) == 0 {
+ return nil, nil, nil
+ }
+
+ // Generate regexps for every interesting value per identifying label.
+ var sb strings.Builder
+ idLblRegexps := make(map[string]string, len(idLblValues))
+ for name, vals := range idLblValues {
+ sb.Reset()
+ i := 0
+ for v := range vals {
+ if i > 0 {
+ sb.WriteRune('|')
+ }
+ sb.WriteString(regexp.QuoteMeta(v))
+ i++
+ }
+ idLblRegexps[name] = sb.String()
+ }
+
+ var infoLabelMatchers []*labels.Matcher
+ for name, re := range idLblRegexps {
+ infoLabelMatchers = append(infoLabelMatchers, labels.MustNewMatcher(labels.MatchRegexp, name, re))
+ }
+ var nameMatcher *labels.Matcher
+ for name, ms := range dataLabelMatchers {
+ for i, m := range ms {
+ if m.Name == labels.MetricName {
+ nameMatcher = m
+ ms = slices.Delete(ms, i, i+1)
+ }
+ infoLabelMatchers = append(infoLabelMatchers, m)
+ }
+ if len(ms) > 0 {
+ dataLabelMatchers[name] = ms
+ } else {
+ delete(dataLabelMatchers, name)
+ }
+ }
+ if nameMatcher == nil {
+ // Default to using the target_info metric.
+ infoLabelMatchers = append([]*labels.Matcher{labels.MustNewMatcher(labels.MatchEqual, labels.MetricName, targetInfo)}, infoLabelMatchers...)
+ }
+
+ infoIt := ev.querier.Select(ctx, false, &selectHints, infoLabelMatchers...)
+ infoSeries, ws, err := expandSeriesSet(ctx, infoIt)
+ if err != nil {
+ return nil, ws, err
+ }
+
+ infoMat := ev.evalSeries(ctx, infoSeries, 0, true)
+ return infoMat, ws, nil
+}
+
+// combineWithInfoSeries combines mat with select data labels from infoMat.
+func (ev *evaluator) combineWithInfoSeries(ctx context.Context, mat, infoMat Matrix, ignoreSeries map[int]struct{}, dataLabelMatchers map[string][]*labels.Matcher) (Matrix, annotations.Annotations) {
+ buf := make([]byte, 0, 1024)
+ lb := labels.NewScratchBuilder(0)
+ sigFunction := func(name string) func(labels.Labels) string {
+ return func(lset labels.Labels) string {
+ lb.Reset()
+ lb.Add(labels.MetricName, name)
+ lset.MatchLabels(true, identifyingLabels...).Range(func(l labels.Label) {
+ lb.Add(l.Name, l.Value)
+ })
+ lb.Sort()
+ return string(lb.Labels().Bytes(buf))
+ }
+ }
+
+ infoMetrics := map[string]struct{}{}
+ for _, is := range infoMat {
+ lblMap := is.Metric.Map()
+ infoMetrics[lblMap[labels.MetricName]] = struct{}{}
+ }
+ sigfs := make(map[string]func(labels.Labels) string, len(infoMetrics))
+ for name := range infoMetrics {
+ sigfs[name] = sigFunction(name)
+ }
+
+ // Keep a copy of the original point slices so they can be returned to the pool.
+ origMatrices := []Matrix{
+ make(Matrix, len(mat)),
+ make(Matrix, len(infoMat)),
+ }
+ copy(origMatrices[0], mat)
+ copy(origMatrices[1], infoMat)
+
+ numSteps := int((ev.endTimestamp-ev.startTimestamp)/ev.interval) + 1
+ originalNumSamples := ev.currentSamples
+
+ // Create an output vector that is as big as the input matrix with
+ // the most time series.
+ biggestLen := max(len(mat), len(infoMat))
+ baseVector := make(Vector, 0, len(mat))
+ infoVector := make(Vector, 0, len(infoMat))
+ enh := &EvalNodeHelper{
+ Out: make(Vector, 0, biggestLen),
+ }
+ type seriesAndTimestamp struct {
+ Series
+ ts int64
+ }
+ seriess := make(map[uint64]seriesAndTimestamp, biggestLen) // Output series by series hash.
+ tempNumSamples := ev.currentSamples
+
+ // For every base series, compute signature per info metric.
+ baseSigs := make([]map[string]string, 0, len(mat))
+ for _, s := range mat {
+ sigs := make(map[string]string, len(infoMetrics))
+ for infoName := range infoMetrics {
+ sigs[infoName] = sigfs[infoName](s.Metric)
+ }
+ baseSigs = append(baseSigs, sigs)
+ }
+
+ infoSigs := make([]string, 0, len(infoMat))
+ for _, s := range infoMat {
+ name := s.Metric.Map()[labels.MetricName]
+ infoSigs = append(infoSigs, sigfs[name](s.Metric))
+ }
+
+ var warnings annotations.Annotations
+ for ts := ev.startTimestamp; ts <= ev.endTimestamp; ts += ev.interval {
+ if err := contextDone(ctx, "expression evaluation"); err != nil {
+ ev.error(err)
+ }
+
+ // Reset number of samples in memory after each timestamp.
+ ev.currentSamples = tempNumSamples
+ // Gather input vectors for this timestamp.
+ baseVector, _ = ev.gatherVector(ts, mat, baseVector, nil, nil)
+ infoVector, _ = ev.gatherVector(ts, infoMat, infoVector, nil, nil)
+
+ enh.Ts = ts
+ result, err := ev.combineWithInfoVector(baseVector, infoVector, ignoreSeries, baseSigs, infoSigs, enh, dataLabelMatchers)
+ if err != nil {
+ ev.error(err)
+ }
+ enh.Out = result[:0] // Reuse result vector.
+
+ vecNumSamples := result.TotalSamples()
+ ev.currentSamples += vecNumSamples
+ // When we reset currentSamples to tempNumSamples during the next iteration of the loop it also
+ // needs to include the samples from the result here, as they're still in memory.
+ tempNumSamples += vecNumSamples
+ ev.samplesStats.UpdatePeak(ev.currentSamples)
+ if ev.currentSamples > ev.maxSamples {
+ ev.error(ErrTooManySamples(env))
+ }
+
+ // Add samples in result vector to output series.
+ for _, sample := range result {
+ h := sample.Metric.Hash()
+ ss, exists := seriess[h]
+ if exists {
+ if ss.ts == ts { // If we've seen this output series before at this timestamp, it's a duplicate.
+ ev.errorf("vector cannot contain metrics with the same labelset")
+ }
+ ss.ts = ts
+ } else {
+ ss = seriesAndTimestamp{Series{Metric: sample.Metric}, ts}
+ }
+ addToSeries(&ss.Series, enh.Ts, sample.F, sample.H, numSteps)
+ seriess[h] = ss
+ }
+ }
+
+ // Reuse the original point slices.
+ for _, m := range origMatrices {
+ for _, s := range m {
+ putFPointSlice(s.Floats)
+ putHPointSlice(s.Histograms)
+ }
+ }
+ // Assemble the output matrix. By the time we get here we know we don't have too many samples.
+ numSamples := 0
+ output := make(Matrix, 0, len(seriess))
+ for _, ss := range seriess {
+ numSamples += len(ss.Floats) + totalHPointSize(ss.Histograms)
+ output = append(output, ss.Series)
+ }
+ ev.currentSamples = originalNumSamples + numSamples
+ ev.samplesStats.UpdatePeak(ev.currentSamples)
+ return output, warnings
+}
+
+// combineWithInfoVector combines base and info Vectors.
+// Base series in ignoreSeries are not combined.
+func (ev *evaluator) combineWithInfoVector(base, info Vector, ignoreSeries map[int]struct{}, baseSigs []map[string]string, infoSigs []string, enh *EvalNodeHelper, dataLabelMatchers map[string][]*labels.Matcher) (Vector, error) {
+ if len(base) == 0 {
+ return nil, nil // Short-circuit: nothing is going to match.
+ }
+
+ // All samples from the info Vector hashed by the matching label/values.
+ if enh.rightSigs == nil {
+ enh.rightSigs = make(map[string]Sample, len(enh.Out))
+ } else {
+ clear(enh.rightSigs)
+ }
+
+ for i, s := range info {
+ if s.H != nil {
+ ev.error(errors.New("info sample should be float"))
+ }
+ // We encode original info sample timestamps via the float value.
+ origT := int64(s.F)
+
+ sig := infoSigs[i]
+ if existing, exists := enh.rightSigs[sig]; exists {
+ // We encode original info sample timestamps via the float value.
+ existingOrigT := int64(existing.F)
+ switch {
+ case existingOrigT > origT:
+ // Keep the other info sample, since it's newer.
+ case existingOrigT < origT:
+ // Keep this info sample, since it's newer.
+ enh.rightSigs[sig] = s
+ default:
+ // The two info samples have the same timestamp - conflict.
+ name := s.Metric.Map()[labels.MetricName]
+ ev.errorf("found duplicate series for info metric %s", name)
+ }
+ } else {
+ enh.rightSigs[sig] = s
+ }
+ }
+
+ for i, bs := range base {
+ if _, exists := ignoreSeries[i]; exists {
+ // This series should not be enriched with info metric data labels.
+ enh.Out = append(enh.Out, Sample{
+ Metric: bs.Metric,
+ F: bs.F,
+ H: bs.H,
+ })
+ continue
+ }
+
+ baseLabels := bs.Metric.Map()
+ enh.resetBuilder(labels.Labels{})
+
+ // For every info metric name, try to find an info series with the same signature.
+ seenInfoMetrics := map[string]struct{}{}
+ for infoName, sig := range baseSigs[i] {
+ is, exists := enh.rightSigs[sig]
+ if !exists {
+ continue
+ }
+ if _, exists := seenInfoMetrics[infoName]; exists {
+ continue
+ }
+
+ err := is.Metric.Validate(func(l labels.Label) error {
+ if l.Name == labels.MetricName {
+ return nil
+ }
+ if _, exists := dataLabelMatchers[l.Name]; len(dataLabelMatchers) > 0 && !exists {
+ // Not among the specified data label matchers.
+ return nil
+ }
+
+ if v := enh.lb.Get(l.Name); v != "" && v != l.Value {
+ return fmt.Errorf("conflicting label: %s", l.Name)
+ }
+ if _, exists := baseLabels[l.Name]; exists {
+ // Skip labels already on the base metric.
+ return nil
+ }
+
+ enh.lb.Set(l.Name, l.Value)
+ return nil
+ })
+ if err != nil {
+ return nil, err
+ }
+ seenInfoMetrics[infoName] = struct{}{}
+ }
+
+ infoLbls := enh.lb.Labels()
+ if infoLbls.Len() == 0 {
+ // If there's at least one data label matcher not matching the empty string,
+ // we have to ignore this series as there are no matching info series.
+ allMatchersMatchEmpty := true
+ for _, ms := range dataLabelMatchers {
+ for _, m := range ms {
+ if !m.Matches("") {
+ allMatchersMatchEmpty = false
+ break
+ }
+ }
+ }
+ if !allMatchersMatchEmpty {
+ continue
+ }
+ }
+
+ enh.resetBuilder(bs.Metric)
+ infoLbls.Range(func(l labels.Label) {
+ enh.lb.Set(l.Name, l.Value)
+ })
+
+ enh.Out = append(enh.Out, Sample{
+ Metric: enh.lb.Labels(),
+ F: bs.F,
+ H: bs.H,
+ })
+ }
+ return enh.Out, nil
+}
diff --git a/promql/info_test.go b/promql/info_test.go
new file mode 100644
index 00000000000..2e7a67172fb
--- /dev/null
+++ b/promql/info_test.go
@@ -0,0 +1,140 @@
+// Copyright 2024 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 promql_test
+
+import (
+ "testing"
+
+ "github.com/prometheus/prometheus/promql/promqltest"
+)
+
+// The "info" function is experimental. This is why we write those tests here for now instead of promqltest/testdata/info.test.
+func TestInfo(t *testing.T) {
+ engine := promqltest.NewTestEngine(t, false, 0, promqltest.DefaultMaxSamplesPerQuery)
+ promqltest.RunTest(t, `
+load 5m
+ metric{instance="a", job="1", label="value"} 0 1 2
+ metric_not_matching_target_info{instance="a", job="2", label="value"} 0 1 2
+ metric_with_overlapping_label{instance="a", job="1", label="value", data="base"} 0 1 2
+ target_info{instance="a", job="1", data="info", another_data="another info"} 1 1 1
+ build_info{instance="a", job="1", build_data="build"} 1 1 1
+
+# Include one info metric data label.
+eval range from 0m to 10m step 5m info(metric, {data=~".+"})
+ metric{data="info", instance="a", job="1", label="value"} 0 1 2
+
+# Include all info metric data labels.
+eval range from 0m to 10m step 5m info(metric)
+ metric{data="info", instance="a", job="1", label="value", another_data="another info"} 0 1 2
+
+# Try including all info metric data labels, but non-matching identifying labels.
+eval range from 0m to 10m step 5m info(metric_not_matching_target_info)
+ metric_not_matching_target_info{instance="a", job="2", label="value"} 0 1 2
+
+# Try including a certain info metric data label with a non-matching matcher not accepting empty labels.
+# Metric is ignored, due there being a data label matcher not matching empty labels,
+# and there being no info series matches.
+eval range from 0m to 10m step 5m info(metric, {non_existent=~".+"})
+
+# Include a certain info metric data label together with a non-matching matcher accepting empty labels.
+# Since the non_existent matcher matches empty labels, it's simply ignored when there's no match.
+# XXX: This case has to include a matcher not matching empty labels, due the PromQL limitation
+# that vector selectors have to contain at least one matcher not accepting empty labels.
+# We might need another construct than vector selector to get around this limitation.
+eval range from 0m to 10m step 5m info(metric, {data=~".+", non_existent=~".*"})
+ metric{data="info", instance="a", job="1", label="value"} 0 1 2
+
+# Info series data labels overlapping with those of base series are ignored.
+eval range from 0m to 10m step 5m info(metric_with_overlapping_label)
+ metric_with_overlapping_label{data="base", instance="a", job="1", label="value", another_data="another info"} 0 1 2
+
+# Include data labels from target_info specifically.
+eval range from 0m to 10m step 5m info(metric, {__name__="target_info"})
+ metric{data="info", instance="a", job="1", label="value", another_data="another info"} 0 1 2
+
+# Try to include all data labels from a non-existent info metric.
+eval range from 0m to 10m step 5m info(metric, {__name__="non_existent"})
+ metric{instance="a", job="1", label="value"} 0 1 2
+
+# Try to include a certain data label from a non-existent info metric.
+eval range from 0m to 10m step 5m info(metric, {__name__="non_existent", data=~".+"})
+
+# Include data labels from build_info.
+eval range from 0m to 10m step 5m info(metric, {__name__="build_info"})
+ metric{instance="a", job="1", label="value", build_data="build"} 0 1 2
+
+# Include data labels from build_info and target_info.
+eval range from 0m to 10m step 5m info(metric, {__name__=~".+_info"})
+ metric{instance="a", job="1", label="value", build_data="build", data="info", another_data="another info"} 0 1 2
+
+# Info metrics themselves are ignored when it comes to enriching with info metric data labels.
+eval range from 0m to 10m step 5m info(build_info, {__name__=~".+_info", build_data=~".+"})
+ build_info{instance="a", job="1", build_data="build"} 1 1 1
+
+clear
+
+# Overlapping target_info series.
+load 5m
+ metric{instance="a", job="1", label="value"} 0 1 2
+ target_info{instance="a", job="1", data="info", another_data="another info"} 1 1 _
+ target_info{instance="a", job="1", data="updated info", another_data="another info"} _ _ 1
+
+# Conflicting info series are resolved through picking the latest sample.
+eval range from 0m to 10m step 5m info(metric)
+ metric{data="info", instance="a", job="1", label="value", another_data="another info"} 0 1 _
+ metric{data="updated info", instance="a", job="1", label="value", another_data="another info"} _ _ 2
+
+clear
+
+# Non-overlapping target_info series.
+load 5m
+ metric{instance="a", job="1", label="value"} 0 1 2
+ target_info{instance="a", job="1", data="info"} 1 1 stale
+ target_info{instance="a", job="1", data="updated info"} _ _ 1
+
+# Include info metric data labels from a metric which data labels change over time.
+eval range from 0m to 10m step 5m info(metric)
+ metric{data="info", instance="a", job="1", label="value"} 0 1 _
+ metric{data="updated info", instance="a", job="1", label="value"} _ _ 2
+
+clear
+
+# Info series selector matches histogram series, info metrics should be float type.
+load 5m
+ metric{instance="a", job="1", label="value"} 0 1 2
+ histogram{instance="a", job="1"} {{schema:1 sum:3 count:22 buckets:[5 10 7]}}
+
+eval_fail range from 0m to 10m step 5m info(metric, {__name__="histogram"})
+
+clear
+
+# Series with skipped scrape.
+load 1m
+ metric{instance="a", job="1", label="value"} 0 _ 2 3 4
+ target_info{instance="a", job="1", data="info"} 1 _ 1 1 1
+
+# Lookback works also for the info series.
+eval range from 1m to 4m step 1m info(metric)
+ metric{data="info", instance="a", job="1", label="value"} 0 2 3 4
+
+# @ operator works also with info.
+# Note that we pick the timestamp missing a sample, lookback should pick previous sample.
+eval range from 1m to 4m step 1m info(metric @ 60)
+ metric{data="info", instance="a", job="1", label="value"} 0 0 0 0
+
+# offset operator works also with info.
+eval range from 1m to 4m step 1m info(metric offset 1m)
+ metric{data="info", instance="a", job="1", label="value"} 0 0 2 3
+`, engine)
+}
diff --git a/promql/parser/ast.go b/promql/parser/ast.go
index 830e8a2c5e4..132ef3f0d28 100644
--- a/promql/parser/ast.go
+++ b/promql/parser/ast.go
@@ -208,6 +208,10 @@ type VectorSelector struct {
UnexpandedSeriesSet storage.SeriesSet
Series []storage.Series
+ // BypassEmptyMatcherCheck is true when the VectorSelector isn't required to have at least one matcher matching the empty string.
+ // This is the case when VectorSelector is used to represent the info function's second argument.
+ BypassEmptyMatcherCheck bool
+
PosRange posrange.PositionRange
}
@@ -352,8 +356,7 @@ func (f inspector) Visit(node Node, path []Node) (Visitor, error) {
// f(node, path); node must not be nil. If f returns a nil error, Inspect invokes f
// for all the non-nil children of node, recursively.
func Inspect(node Node, f inspector) {
- //nolint: errcheck
- Walk(f, node, nil)
+ Walk(f, node, nil) //nolint:errcheck
}
// Children returns a list of all child nodes of a syntax tree node.
@@ -419,7 +422,7 @@ func mergeRanges(first, last Node) posrange.PositionRange {
}
}
-// Item implements the Node interface.
+// PositionRange implements the Node interface.
// This makes it possible to call mergeRanges on them.
func (i *Item) PositionRange() posrange.PositionRange {
return posrange.PositionRange{
diff --git a/promql/parser/functions.go b/promql/parser/functions.go
index 467ae9b08dc..bf570ed5207 100644
--- a/promql/parser/functions.go
+++ b/promql/parser/functions.go
@@ -224,6 +224,13 @@ var Functions = map[string]*Function{
ArgTypes: []ValueType{ValueTypeMatrix},
ReturnType: ValueTypeVector,
},
+ "info": {
+ Name: "info",
+ ArgTypes: []ValueType{ValueTypeVector, ValueTypeVector},
+ ReturnType: ValueTypeVector,
+ Experimental: true,
+ Variadic: 1,
+ },
"irate": {
Name: "irate",
ArgTypes: []ValueType{ValueTypeMatrix},
diff --git a/promql/parser/generated_parser.y b/promql/parser/generated_parser.y
index d84acc37c5d..befb9bdf3e6 100644
--- a/promql/parser/generated_parser.y
+++ b/promql/parser/generated_parser.y
@@ -23,6 +23,8 @@ import (
"github.com/prometheus/prometheus/model/value"
"github.com/prometheus/prometheus/model/histogram"
"github.com/prometheus/prometheus/promql/parser/posrange"
+
+ "github.com/prometheus/common/model"
)
%}
@@ -43,7 +45,6 @@ import (
int int64
uint uint64
float float64
- duration time.Duration
}
@@ -85,6 +86,7 @@ NEGATIVE_BUCKETS_DESC
ZERO_BUCKET_DESC
ZERO_BUCKET_WIDTH_DESC
CUSTOM_VALUES_DESC
+COUNTER_RESET_HINT_DESC
%token histogramDescEnd
// Operators.
@@ -150,6 +152,14 @@ START
END
%token preprocessorEnd
+// Counter reset hints.
+%token counterResetHintsStart
+%token -
+UNKNOWN_COUNTER_RESET
+COUNTER_RESET
+NOT_COUNTER_RESET
+GAUGE_TYPE
+%token counterResetHintsEnd
// Start symbols for the generated parser.
%token startSymbolsStart
@@ -164,7 +174,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 string_identifier
+%type
- aggregate_op grouping_label match_op maybe_label metric_identifier unary_op at_modifier_preprocessors string_identifier counter_reset_hint
%type
label_set metric
%type label_set_list
%type