Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update module github.com/prometheus/client_golang to v1.20.5 #259

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

renovate[bot]
Copy link
Contributor

@renovate renovate bot commented May 15, 2024

This PR contains the following updates:

Package Change Age Adoption Passing Confidence
github.com/prometheus/client_golang v1.19.0 -> v1.20.5 age adoption passing confidence

Release Notes

prometheus/client_golang (github.com/prometheus/client_golang)

v1.20.5: / 2024-10-15

Compare Source

We decided to revert the testutil change that made our util functions less error-prone, but created a lot of work for our downstream users. Apologies for the pain! This revert should not cause any major breaking change, even if you already did the work--unless you depend on the exact error message.

Going forward, we plan to reinforce our release testing strategy [1],[2] and deliver an enhanced testutil package/module with more flexible and safer APIs.

Thanks to @​dashpole @​dgrisonnet @​kakkoyun @​ArthurSens @​vesari @​logicalhan @​krajorama @​bwplotka who helped in this patch release! 🤗

Changelog

[BUGFIX] testutil: Reverted #​1424; functions using compareMetricFamilies are (again) only failing if filtered metricNames are in the expected input. #​1645

v1.20.4

Compare Source

  • [BUGFIX] histograms: Fix a possible data race when appending exemplars vs metrics gather. #​1623

v1.20.3

Compare Source

  • [BUGFIX] histograms: Fix possible data race when appending exemplars. #​1608

v1.20.2

Compare Source

  • [BUGFIX] promhttp: Unset Content-Encoding header when data is uncompressed. #​1596

v1.20.1

Compare Source

  • [BUGFIX] process-collector: Fixed unregistered descriptor error when using process collector with PedanticRegistry on Linux machines. #​1587

v1.20.0

Compare Source

Thanks everyone for contributions!

⚠️ In this release we remove one (broken anyway, given Go runtime changes) metric and add three new (representing GOGC, GOMEMLIMIT and GOMAXPROCS flags) to the default collectors.NewGoCollector() collector. Given its popular usage, expect your binary to expose two additional metric.

Changes

  • [CHANGE] ⚠️ go-collector: Remove go_memstat_lookups_total metric which was always 0; Go runtime stopped sharing pointer lookup statistics. #​1577
  • [FEATURE] ⚠️ go-collector: Add 3 default metrics: go_gc_gogc_percent, go_gc_gomemlimit_bytes and go_sched_gomaxprocs_threads as those are recommended by the Go team. #​1559
  • [FEATURE] go-collector: Add more information to all metrics' HELP e.g. the exact runtime/metrics sourcing each metric (if relevant). #​1568 #​1578
  • [FEATURE] testutil: Add CollectAndFormat method. #​1503
  • [FEATURE] histograms: Add support for exemplars in native histograms. #​1471
  • [FEATURE] promhttp: Add experimental support for zstd on scrape, controlled by the request Accept-Encoding header. #​1496
  • [FEATURE] api/v1: Add WithLimit parameter to all API methods that supports it. #​1544
  • [FEATURE] prometheus: Add support for created timestamps in constant histograms and constant summaries. #​1537
  • [FEATURE] process-collectors: Add network usage metrics: process_network_receive_bytes_total and process_network_transmit_bytes_total. #​1555
  • [FEATURE] promlint: Add duplicated metric lint rule. #​1472
  • [BUGFIX] promlint: Relax metric type in name linter rule. #​1455
  • [BUGFIX] promhttp: Make sure server
    instrumentation wrapping supports new and future extra responseWriter methods. #​1480
  • [BUGFIX] testutil: Functions using compareMetricFamilies are now failing if filtered metricNames are not in the input. #​1424
All commits

New Contributors

Full Changelog: prometheus/client_golang@v1.19.1...v1.20.0

v1.19.1

Compare Source

What's Changed

  • Security patches for golang.org/x/sys and google.golang.org/protobuf

New Contributors

Full Changelog: prometheus/client_golang@v1.19.0...v1.19.1


Configuration

📅 Schedule: Branch creation - "* 0-4 * * 3" (UTC), Automerge - At any time (no schedule defined).

🚦 Automerge: Disabled by config. Please merge this manually once you are satisfied.

Rebasing: Whenever PR is behind base branch, or you tick the rebase/retry checkbox.

🔕 Ignore: Close this PR and you won't be reminded about this update again.


  • If you want to rebase/retry this PR, check this box

This PR was generated by Mend Renovate. View the repository job log.

@renovate renovate bot force-pushed the renovate/github.com-prometheus-client_golang-1.x branch 5 times, most recently from bcaca70 to 394ff04 Compare May 17, 2024 21:54
@renovate renovate bot force-pushed the renovate/github.com-prometheus-client_golang-1.x branch from 394ff04 to 7e811d6 Compare May 24, 2024 10:10
@renovate renovate bot force-pushed the renovate/github.com-prometheus-client_golang-1.x branch 2 times, most recently from 839500b to 02f9d8e Compare June 14, 2024 08:47
@renovate renovate bot force-pushed the renovate/github.com-prometheus-client_golang-1.x branch 4 times, most recently from 01fd60a to 0ad4b6a Compare July 3, 2024 11:51
@renovate renovate bot force-pushed the renovate/github.com-prometheus-client_golang-1.x branch 3 times, most recently from 78dd2dd to fa1dc8e Compare July 17, 2024 08:03
@renovate renovate bot force-pushed the renovate/github.com-prometheus-client_golang-1.x branch from fa1dc8e to 66d535a Compare August 18, 2024 16:42
@renovate renovate bot changed the title Update module github.com/prometheus/client_golang to v1.19.1 Update module github.com/prometheus/client_golang to v1.20.0 Aug 18, 2024
Copy link
Contributor Author

renovate bot commented Aug 18, 2024

ℹ Artifact update notice

File name: go.mod

In order to perform the update(s) described in the table above, Renovate ran the go get command, which resulted in the following additional change(s):

  • 5 additional dependencies were updated

Details:

Package Change
google.golang.org/protobuf v1.34.1 -> v1.34.2
github.com/cespare/xxhash/v2 v2.2.0 -> v2.3.0
github.com/prometheus/client_model v0.5.0 -> v0.6.1
github.com/prometheus/common v0.48.0 -> v0.55.0
github.com/prometheus/procfs v0.12.0 -> v0.15.1

@renovate renovate bot force-pushed the renovate/github.com-prometheus-client_golang-1.x branch from 66d535a to 43c8c94 Compare August 21, 2024 15:19
Copy link

[puLL-Merge] - prometheus/client_golang@v1.19.0..v1.20.0

Description

This PR updates the client_golang library with various improvements and new features. The main changes include updates to Go collector metrics, enhancements to HTTP compression support, and improvements to testing and documentation.

Changes

Changes

  1. .github/workflows/:

    • Updated various GitHub Actions workflows, including adding new ones for container description and Dagger-based linting.
    • Updated dependencies and Go versions in existing workflows.
  2. dagger/:

    • Added new Dagger-related files for CI/CD improvements.
  3. prometheus/:

    • Updated Go collector metrics and tests for different Go versions.
    • Added support for zstd compression in HTTP responses.
    • Improved histogram implementation with exemplar support.
    • Enhanced testutil package with new functions and improvements.
    • Updated various dependencies.
  4. prometheus/collectors/:

    • Added new metrics and updated existing ones for different Go versions.
    • Improved Go collector implementation.
  5. prometheus/promhttp/:

    • Added support for zstd compression.
    • Improved HTTP handler implementation.
  6. Root directory:

    • Added supported_go_versions.txt and update-go-version.bash for managing supported Go versions.
    • Updated CHANGELOG.md, README.md, and other documentation files.

Possible Issues

  • The changes to the Go collector metrics might require updates in existing monitoring setups.
  • The addition of zstd compression support might need configuration changes in environments using this library.

Security Hotspots

No significant security issues were identified in this change.

@renovate renovate bot force-pushed the renovate/github.com-prometheus-client_golang-1.x branch 4 times, most recently from b47f6cc to d289dd4 Compare August 24, 2024 12:05
@renovate renovate bot changed the title Update module github.com/prometheus/client_golang to v1.20.0 Update module github.com/prometheus/client_golang to v1.20.1 Aug 24, 2024
Copy link

[puLL-Merge] - prometheus/client_golang@v1.19.0..v1.20.1

Description

This PR updates the client_golang repository with several improvements and changes. The main motivations appear to be updating Go versions, improving test coverage, adding new features, and general maintenance.

Changes

Changes

  1. .github/workflows/:

    • Updated various GitHub Actions workflows, including adding new ones for container description pushing and Dagger-based linting.
    • Updated dependencies and Go versions in existing workflows.
  2. dagger/:

    • Added new Dagger configuration and Go files for CI/CD improvements.
  3. prometheus/:

    • Updated Go collector metrics and tests for newer Go versions.
    • Added support for exemplars in native histograms.
    • Improved error handling and logging in various parts of the code.
    • Added new compression options (zstd) for HTTP responses.
    • Updated dependencies and Go versions.
  4. prometheus/testutil/:

    • Improved test utilities and added new functions like CollectAndFormat.
  5. prometheus/promhttp/:

    • Added support for zstd compression in HTTP responses.
    • Improved handling of response writer unwrapping for better compatibility.
  6. Root directory:

    • Added supported_go_versions.txt and update-go-version.bash for managing supported Go versions.
    • Updated CHANGELOG.md, README.md, and other documentation files.
  7. Renamed tutorial directory to tutorials.

Possible Issues

  1. The removal of go_memstat_lookups_total metric might affect existing users who rely on this metric.
  2. Changes to default metrics and compression behavior might require updates to existing monitoring setups.

Security Hotspots

  1. The addition of zstd compression support in promhttp/http.go should be carefully reviewed to ensure it doesn't introduce any security vulnerabilities.
  2. The new Dagger-based CI/CD configurations should be audited to ensure they don't expose any sensitive information or introduce new attack vectors.

@renovate renovate bot force-pushed the renovate/github.com-prometheus-client_golang-1.x branch from d289dd4 to fecb333 Compare August 27, 2024 21:20
@renovate renovate bot changed the title Update module github.com/prometheus/client_golang to v1.20.1 Update module github.com/prometheus/client_golang to v1.20.2 Aug 27, 2024
Copy link

[puLL-Merge] - prometheus/client_golang@v1.19.0..v1.20.2

Description

This PR updates various aspects of the client_golang project, including dependency updates, code improvements, and the addition of new features. The main changes include updating Go versions, improving the Go collector, adding support for zstd compression, and enhancing the test suite.

Changes

Changes

  1. .github/workflows/:

    • Updated various GitHub Actions workflows, including adding new ones for container description pushing and Dagger-based linting.
    • Added concurrency settings to existing workflows.
  2. dagger/:

    • Added new Dagger-related files for CI/CD improvements.
  3. prometheus/:

    • Updated go_collector.go with improvements to metrics collection and descriptions.
    • Added support for zstd compression in promhttp/http.go.
    • Enhanced histogram functionality in histogram.go, including support for exemplars in native histograms.
    • Improved test coverage and added new test cases in various files.
  4. prometheus/testutil/:

    • Added new linting rules and improved existing ones in promlint/.
  5. Root directory:

    • Updated go.mod and go.sum with new dependencies and versions.
    • Added supported_go_versions.txt and update-go-version.bash for managing supported Go versions.
  6. Renamed tutorial/ to tutorials/ and updated related imports.

Possible Issues

  1. The changes to the Go collector and the addition of new metrics might require users to update their monitoring configurations.
  2. The introduction of zstd compression support might need additional testing in various environments to ensure compatibility.

Security Hotspots

No significant security issues were identified in this change. However, the introduction of new dependencies and compression algorithms (zstd) should be thoroughly tested to ensure they don't introduce any vulnerabilities.

@renovate renovate bot force-pushed the renovate/github.com-prometheus-client_golang-1.x branch from fecb333 to 677b0f1 Compare September 9, 2024 12:57
@renovate renovate bot changed the title Update module github.com/prometheus/client_golang to v1.20.2 Update module github.com/prometheus/client_golang to v1.20.3 Sep 9, 2024
Copy link

github-actions bot commented Sep 9, 2024

[puLL-Merge] - prometheus/client_golang@v1.19.0..v1.20.3

Description

This PR makes several significant changes to the client_golang repository, including updating dependencies, adding new features, refactoring existing code, and improving test coverage. The changes span across multiple files and introduce new functionality while also addressing some existing issues.

Changes

Changes

  1. .github/workflows/:

    • Updated various GitHub Actions workflows, including dependency automerge, CodeQL analysis, and Go tests.
    • Added new workflows for container description updates and Dagger-based linting.
  2. dagger/:

    • Introduced Dagger for CI/CD purposes, including new configuration files and Go code.
  3. prometheus/:

    • Updated Go collector metrics generation and testing.
    • Improved histogram implementation, including support for exemplars in native histograms.
    • Enhanced process collector with network usage metrics.
    • Updated and refactored various test files.
    • Improved registry implementation.
  4. prometheus/promhttp/:

    • Added support for zstd compression in HTTP responses.
    • Improved handling of response writer delegation.
  5. prometheus/testutil/:

    • Enhanced test utilities, including new comparison functions and error reporting.
  6. Root directory:

    • Updated dependencies in go.mod and go.sum.
    • Added scripts for updating Go versions and generating metrics.
    • Renamed 'tutorial' directory to 'tutorials'.

Possible Issues

  1. The change from tutorial to tutorials directory may break existing imports or documentation references.
  2. The introduction of Dagger for CI/CD might require additional setup or knowledge for contributors.

Security Hotspots

  1. The addition of zstd compression support in prometheus/promhttp/http.go should be carefully reviewed to ensure it doesn't introduce any security vulnerabilities related to data compression/decompression.

@renovate renovate bot force-pushed the renovate/github.com-prometheus-client_golang-1.x branch from 677b0f1 to fb7031f Compare September 17, 2024 09:11
@renovate renovate bot changed the title Update module github.com/prometheus/client_golang to v1.20.3 Update module github.com/prometheus/client_golang to v1.20.4 Sep 17, 2024
@renovate renovate bot force-pushed the renovate/github.com-prometheus-client_golang-1.x branch from fb7031f to cae9fea Compare October 5, 2024 11:59
@renovate renovate bot force-pushed the renovate/github.com-prometheus-client_golang-1.x branch from cae9fea to 2117bb7 Compare October 13, 2024 13:35
Copy link

[puLL-Merge] - prometheus/client_golang@v1.19.0..v1.20.4

Description

This PR introduces several significant changes to the client_golang repository, including updates to Go versions, improvements to metrics collection, and various code enhancements. The main motivations appear to be keeping the library up-to-date with the latest Go releases, improving performance, and enhancing functionality.

Changes

Changes

  1. Go Version Updates:

    • Added support for Go 1.22
    • Updated minimum Go version to 1.20
    • Introduced a new file supported_go_versions.txt to manage supported Go versions
  2. Metrics and Collectors:

    • Updated Go collector metrics for new Go versions
    • Added new default runtime metrics
    • Improved histogram implementation with support for exemplars in native histograms
  3. Compression Support:

    • Added experimental support for zstd compression in HTTP responses
  4. API Enhancements:

    • Added WithLimit parameter to API methods
    • Improved error handling and reporting
  5. Testing and CI:

    • Updated CI workflows to use Dagger
    • Added new test cases and improved existing ones
  6. Documentation:

    • Updated CHANGELOG.md with new features and bug fixes
    • Improved documentation for various functions and types
  7. Code Refactoring:

    • Reorganized directory structure (e.g., moved tutorial to tutorials)
    • Improved code organization and readability
  8. Dependencies:

    • Updated various dependencies to their latest versions

Possible Issues

  1. The changes to default metrics and collector behavior might affect existing applications that rely on specific metric outputs.
  2. The introduction of zstd compression might require additional configuration for users to take advantage of it.

Security Hotspots

  1. The addition of new compression methods (like zstd) should be carefully reviewed to ensure they don't introduce new attack vectors in HTTP handling.
  2. Changes to the promhttp package, especially in handling of HTTP responses and headers, should be scrutinized for potential security implications.

Overall, this PR represents a significant update to the client_golang library, introducing new features and improvements while maintaining compatibility with recent Go versions. The changes appear well-documented and thoroughly tested, but care should be taken to ensure backward compatibility and security.

@renovate renovate bot force-pushed the renovate/github.com-prometheus-client_golang-1.x branch from 2117bb7 to 4c9abac Compare October 19, 2024 09:50
@renovate renovate bot changed the title Update module github.com/prometheus/client_golang to v1.20.4 Update module github.com/prometheus/client_golang to v1.20.5 Oct 19, 2024
@renovate renovate bot force-pushed the renovate/github.com-prometheus-client_golang-1.x branch from 4c9abac to 7293f3f Compare October 21, 2024 10:14
@renovate renovate bot force-pushed the renovate/github.com-prometheus-client_golang-1.x branch 2 times, most recently from 095d2f7 to 07c3e83 Compare October 30, 2024 12:17
@renovate renovate bot force-pushed the renovate/github.com-prometheus-client_golang-1.x branch 6 times, most recently from 81363d2 to 449fb2b Compare December 12, 2024 20:50
@renovate renovate bot force-pushed the renovate/github.com-prometheus-client_golang-1.x branch from 449fb2b to ef2e099 Compare December 12, 2024 20:53
@renovate renovate bot changed the title Update module github.com/prometheus/client_golang to v1.20.5 Update module github.com/prometheus/client_golang to v1.20.5 - autoclosed Jan 8, 2025
@renovate renovate bot closed this Jan 8, 2025
@renovate renovate bot deleted the renovate/github.com-prometheus-client_golang-1.x branch January 8, 2025 13:24
@renovate renovate bot changed the title Update module github.com/prometheus/client_golang to v1.20.5 - autoclosed Update module github.com/prometheus/client_golang to v1.20.5 Jan 15, 2025
@renovate renovate bot reopened this Jan 15, 2025
Copy link

[puLL-Merge] - prometheus/client_golang@v1.19.0..v1.20.5

Diff
diff --git .github/workflows/automerge-dependabot.yml .github/workflows/automerge-dependabot.yml
index fc664a8a6..7b2687ec3 100644
--- .github/workflows/automerge-dependabot.yml
+++ .github/workflows/automerge-dependabot.yml
@@ -1,6 +1,10 @@
 name: Dependabot auto-merge
 on: pull_request
 
+concurrency:
+  group: ${{ github.workflow }}-${{ (github.event.pull_request && github.event.pull_request.number) || github.ref || github.run_id }}
+  cancel-in-progress: true
+
 permissions:
   contents: write
   pull-requests: write
@@ -12,7 +16,7 @@ jobs:
     steps:
       - name: Dependabot metadata
         id: metadata
-        uses: dependabot/fetch-metadata@c9c4182bf1b97f5224aee3906fd373f6b61b4526 # v1.6.0
+        uses: dependabot/fetch-metadata@dbb049abf0d677abbd7f7eee0375145b417fdd34 # v2.2.0
         with:
           github-token: "${{ secrets.GITHUB_TOKEN }}"
       - name: Enable auto-merge for Dependabot PRs
diff --git .github/workflows/codeql-analysis.yml .github/workflows/codeql-analysis.yml
index 79a71ad3f..4260139cb 100644
--- .github/workflows/codeql-analysis.yml
+++ .github/workflows/codeql-analysis.yml
@@ -20,6 +20,10 @@ on:
   schedule:
     - cron: '31 21 * * 6'
 
+concurrency:
+  group: ${{ github.workflow }}-${{ (github.event.pull_request && github.event.pull_request.number) || github.ref || github.run_id }}
+  cancel-in-progress: true
+
 # Minimal permissions to be inherited by any job that don't declare it's own permissions
 permissions:
   contents: read
@@ -46,7 +50,7 @@ jobs:
 
     # Initializes the CodeQL tools for scanning.
     - name: Initialize CodeQL
-      uses: github/codeql-action/init@b7bf0a3ed3ecfa44160715d7c442788f65f0f923 # v3.23.2
+      uses: github/codeql-action/init@afb54ba388a7dca6ecae48f608c4ff05ff4cc77a # v3.25.15
       with:
         languages: ${{ matrix.language }}
         # If you wish to specify custom queries, you can do so here or in a config file.
@@ -57,7 +61,7 @@ jobs:
     # Autobuild attempts to build any compiled languages  (C/C++, C#, or Java).
     # If this step fails, then you should remove it and run the build manually (see below)
     - name: Autobuild
-      uses: github/codeql-action/autobuild@b7bf0a3ed3ecfa44160715d7c442788f65f0f923 # v3.23.2
+      uses: github/codeql-action/autobuild@afb54ba388a7dca6ecae48f608c4ff05ff4cc77a # v3.25.15
 
     # ℹ️ Command-line programs to run using the OS shell.
     # 📚 https://git.io/JvXDl
@@ -71,4 +75,4 @@ jobs:
     #   make release
 
     - name: Perform CodeQL Analysis
-      uses: github/codeql-action/analyze@b7bf0a3ed3ecfa44160715d7c442788f65f0f923 # v3.23.2
+      uses: github/codeql-action/analyze@afb54ba388a7dca6ecae48f608c4ff05ff4cc77a # v3.25.15
diff --git a/.github/workflows/container_description.yml b/.github/workflows/container_description.yml
new file mode 100644
index 000000000..8ddbc34ae
--- /dev/null
+++ .github/workflows/container_description.yml
@@ -0,0 +1,57 @@
+---
+name: Push README to Docker Hub
+on:
+  push:
+    paths:
+      - "README.md"
+      - "README-containers.md"
+      - ".github/workflows/container_description.yml"
+    branches: [ main, master ]
+
+permissions:
+  contents: read
+
+jobs:
+  PushDockerHubReadme:
+    runs-on: ubuntu-latest
+    name: Push README to Docker Hub
+    if: github.repository_owner == 'prometheus' || github.repository_owner == 'prometheus-community' # Don't run this workflow on forks.
+    steps:
+      - name: git checkout
+        uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
+      - name: Set docker hub repo name
+        run: echo "DOCKER_REPO_NAME=$(make docker-repo-name)" >> $GITHUB_ENV
+      - name: Push README to Dockerhub
+        uses: christian-korneck/update-container-description-action@d36005551adeaba9698d8d67a296bd16fa91f8e8 # v1
+        env:
+          DOCKER_USER: ${{ secrets.DOCKER_HUB_LOGIN }}
+          DOCKER_PASS: ${{ secrets.DOCKER_HUB_PASSWORD }}
+        with:
+          destination_container_repo: ${{ env.DOCKER_REPO_NAME }}
+          provider: dockerhub
+          short_description: ${{ env.DOCKER_REPO_NAME }}
+          # Empty string results in README-containers.md being pushed if it
+          # exists. Otherwise, README.md is pushed.
+          readme_file: ''
+
+  PushQuayIoReadme:
+    runs-on: ubuntu-latest
+    name: Push README to quay.io
+    if: github.repository_owner == 'prometheus' || github.repository_owner == 'prometheus-community' # Don't run this workflow on forks.
+    steps:
+      - name: git checkout
+        uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
+      - name: Set quay.io org name
+        run: echo "DOCKER_REPO=$(echo quay.io/${GITHUB_REPOSITORY_OWNER} | tr -d '-')" >> $GITHUB_ENV
+      - name: Set quay.io repo name
+        run: echo "DOCKER_REPO_NAME=$(make docker-repo-name)" >> $GITHUB_ENV
+      - name: Push README to quay.io
+        uses: christian-korneck/update-container-description-action@d36005551adeaba9698d8d67a296bd16fa91f8e8 # v1
+        env:
+          DOCKER_APIKEY: ${{ secrets.QUAY_IO_API_TOKEN }}
+        with:
+          destination_container_repo: ${{ env.DOCKER_REPO_NAME }}
+          provider: quay
+          # Empty string results in README-containers.md being pushed if it
+          # exists. Otherwise, README.md is pushed.
+          readme_file: ''
diff --git a/.github/workflows/dagger-golangci-lint.yml b/.github/workflows/dagger-golangci-lint.yml
new file mode 100644
index 000000000..0e9fa3cf0
--- /dev/null
+++ .github/workflows/dagger-golangci-lint.yml
@@ -0,0 +1,32 @@
+---
+name: dagger-golangci-lint
+on:
+  push:
+    paths:
+      - "go.sum"
+      - "go.mod"
+      - "**.go"
+      - "scripts/errcheck_excludes.txt"
+      - ".github/workflows/golangci-lint.yml"
+      - ".golangci.yml"
+  pull_request:
+
+permissions:  # added using https://github.com/step-security/secure-repo
+  contents: read
+
+jobs:
+  golangci:
+    permissions:
+      contents: read  # for actions/checkout to fetch code
+      pull-requests: read  # for golangci/golangci-lint-action to fetch pull requests
+    name: lint
+    runs-on: ubuntu-latest
+    steps:
+      - name: Checkout repository
+        uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
+      - name: Lint
+        uses: dagger/dagger-for-github@v5
+        with:
+          version: "latest"
+          verb: call
+          args: -vvv --src . make --args lint
diff --git .github/workflows/go.yml .github/workflows/go.yml
index c72b96cc7..b9be9db3a 100644
--- .github/workflows/go.yml
+++ .github/workflows/go.yml
@@ -1,46 +1,61 @@
----
-name: Go
-on:
-  pull_request:
-  push:
-    branches:
-      - main
-      - "release-*"
-
-# Minimal permissions to be inherited by any job that don't declare it's own permissions
-permissions:
-  contents: read
-
-jobs:
-  test:
-    name: Tests
-    runs-on: ubuntu-latest
-
-    strategy:
-      matrix:
-        go_version: ["1.20", "1.21", "1.22"]
-
-    steps:
-      - name: Checkout code
-        uses: actions/checkout@v4.1.1
-
-      - name: Set up Go ${{ matrix.go_version }}
-        uses: actions/setup-go@v5.0.0
-        with:
-          go-version: ${{ matrix.go_version }}
-
-      - name: Cache Go modules
-        id: cache
-        uses: actions/cache@v4
-        with:
-          path: ~/go/pkg/mod
-          key: v1-go${{ matrix.go_version }}
-
-      - name: Run tests and check license
-        run: make check_license test
-        env:
-          CI: true
-
-      - name: Run style and unused
-        if: ${{ matrix.go_version == '1.20' }}
-        run: make style unused
+---
+name: Go
+on:
+  pull_request:
+  push:
+    branches:
+      - main
+      - "release-*"
+
+concurrency:
+  group: ${{ github.workflow }}-${{ (github.event.pull_request && github.event.pull_request.number) || github.ref || github.run_id }}
+  cancel-in-progress: true
+
+# Minimal permissions to be inherited by any job that don't declare it's own permissions
+permissions:
+  contents: read
+
+jobs:
+
+  supportedVersions:
+    name: Fetch supported Go versions
+    runs-on: ubuntu-latest
+    outputs:
+        supported_versions: ${{ steps.matrix.outputs.supported_versions }}
+    steps:
+      - name: Checkout code
+        uses: actions/checkout@v4
+      - name: Read supported_go_versions.txt
+        id: matrix
+        run: |
+            versions=$(cat supported_go_versions.txt)
+            matrix="[$(echo "$versions" | sed 's/\(.*\)/"\1"/' | paste -s -d,)]"
+            echo "supported_versions=$matrix" >> $GITHUB_OUTPUT
+
+  test:
+    name: Tests
+    runs-on: ubuntu-latest
+    needs: supportedVersions
+
+    strategy:
+      matrix:
+        go_version: ${{ fromJSON(needs.supportedVersions.outputs.supported_versions) }}
+
+    steps:
+      - name: Checkout code
+        uses: actions/checkout@v4.1.1
+
+      - name: Run tests and check license
+        uses: dagger/dagger-for-github@v5
+        with:
+          version: "latest"
+          verb: call
+          args: -vvv --src . make --go-version ${{matrix.go_version}} --args 'check_license test'
+
+      - name: Run style and unused
+        uses: dagger/dagger-for-github@v6
+        if: ${{ matrix.go_version == '1.20' }}
+        with:
+          version: "latest"
+          verb: call
+          args: -vvv --src . make --args 'check_license test'
diff --git .github/workflows/golangci-lint.yml .github/workflows/golangci-lint.yml
index fe63ff3fa..746831a86 100644
--- .github/workflows/golangci-lint.yml
+++ .github/workflows/golangci-lint.yml
@@ -24,15 +24,16 @@ jobs:
     runs-on: ubuntu-latest
     steps:
       - name: Checkout repository
-        uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
-      - name: install Go
-        uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5.0.0
+        uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
+      - name: Install Go
+        uses: actions/setup-go@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32 # v5.0.2
         with:
-          go-version: 1.20.x
+          go-version: 1.22.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@3a919529898de77ec3da873e3063ca4b10e7f5cc # v3.7.0
+        uses: golangci/golangci-lint-action@aaa42aa0628b4ae2578232a66b541047968fac86 # v6.1.0
         with:
-          version: v1.54.2
+          args: --verbose
+          version: v1.59.1
diff --git a/.github/workflows/update-go-versions.yml b/.github/workflows/update-go-versions.yml
new file mode 100644
index 000000000..f560564f2
--- /dev/null
+++ .github/workflows/update-go-versions.yml
@@ -0,0 +1,33 @@
+---
+name: Generate Metric files for new Go version
+
+on:
+  workflow_dispatch:
+  schedule:
+    - cron: '0 0 1 * *'
+
+jobs:
+  update-go-versions:
+    name: Update Go Versions and Generate Tests
+    runs-on: ubuntu-latest
+
+    steps:
+      - name: Checkout repository
+        uses: actions/checkout@v4
+
+      - name: Execute bash script
+        run: bash update-go-version.bash
+
+      # If there are no changes (i.e. no diff exists with the checked-out base branch),
+      # no pull request will be created and the action exits silently.
+      - name: Create a Pull Request
+        if: github.event_name != 'pull_request'
+        uses: peter-evans/create-pull-request@v6
+        with:
+          token: ${{ secrets.GITHUB_TOKEN }}
+          commit-message: "Update Go Collector metrics for new Go version"
+          title: "chore: Update metrics for new Go version"
+          branch: update-metrics-for-new-go-version
+          base: main
+          draft: false
+          delete-branch: true
diff --git .golangci.yml .golangci.yml
index 8043d57b4..d2580cccc 100644
--- .golangci.yml
+++ .golangci.yml
@@ -1,9 +1,13 @@
 ---
 run:
-  deadline: 5m
+  timeout: 5m
   skip-files:
     # Skip autogenerated files.
     - ^.*\.(pb|y)\.go$
+    - dagger/dagger.gen.go
+  skip-dirs:
+    - dagger/internal
+
 
 output:
   sort-results: true
diff --git CHANGELOG.md CHANGELOG.md
index a4574779c..654a8d039 100644
--- CHANGELOG.md
+++ CHANGELOG.md
@@ -1,8 +1,44 @@
 ## Unreleased
 
-## 1.19.0 / 2023-02-27
+## 1.20.5 / 2024-10-15
 
-The module `prometheus/common v0.48.0` introduced a bug when used together with client_golang. If your project uses client_golang and you want to use `prometheus/common v0.48.0` or higher, please update client_golang to v1.19.0.
+* [BUGFIX] testutil: Reverted #1424; functions using compareMetricFamilies are (again) only failing if filtered metricNames are in the expected input.
+
+## 1.20.4 / 2024-09-07
+
+* [BUGFIX] histograms: Fix possible data race when appending exemplars vs metrics gather. #1623
+
+## 1.20.3 / 2024-09-05
+
+* [BUGFIX] histograms: Fix possible data race when appending exemplars. #1608
+
+## 1.20.2 / 2024-08-23
+
+* [BUGFIX] promhttp: Unset Content-Encoding header when data is uncompressed. #1596
+
+## 1.20.1 / 2024-08-20
+
+* [BUGFIX] process-collector: Fixed unregistered descriptor error when using process collector with `PedanticRegistry` on linux machines. #1587
+
+## 1.20.0 / 2024-08-14
+
+* [CHANGE] :warning: go-collector: Remove `go_memstat_lookups_total` metric which was always 0; Go runtime stopped sharing pointer lookup statistics. #1577
+* [FEATURE] :warning: go-collector: Add 3 default metrics: `go_gc_gogc_percent`, `go_gc_gomemlimit_bytes` and `go_sched_gomaxprocs_threads` as those are recommended by the Go team. #1559
+* [FEATURE] go-collector: Add more information to all metrics' HELP e.g. the exact `runtime/metrics` sourcing each metric (if relevant). #1568 #1578
+* [FEATURE] testutil: Add CollectAndFormat method. #1503
+* [FEATURE] histograms: Add support for exemplars in native histograms. #1471
+* [FEATURE] promhttp: Add experimental support for `zstd` on scrape, controlled by the request `Accept-Encoding` header. #1496
+* [FEATURE] api/v1: Add `WithLimit` parameter to all API methods that supports it. #1544
+* [FEATURE] prometheus: Add support for created timestamps in constant histograms and constant summaries. #1537
+* [FEATURE] process-collector: Add network usage metrics: `process_network_receive_bytes_total` and `process_network_transmit_bytes_total`. #1555
+* [FEATURE] promlint: Add duplicated metric lint rule. #1472
+* [BUGFIX] promlint: Relax metric type in name linter rule. #1455
+* [BUGFIX] promhttp: Make sure server instrumentation wrapping supports new and future extra responseWriter methods. #1480
+* [BUGFIX] **breaking** testutil: Functions using compareMetricFamilies are now failing if filtered metricNames are not in the input. #1424 (reverted in 1.20.5)
+
+## 1.19.0 / 2024-02-27
+
+The module `prometheus/common v0.48.0` introduced an incompatibility when used together with client_golang (See https://github.com/prometheus/client_golang/pull/1448 for more details). If your project uses client_golang and you want to use `prometheus/common v0.48.0` or higher, please update client_golang to v1.19.0.
 
 * [CHANGE] Minimum required go version is now 1.20 (we also test client_golang against new 1.22 version). #1445 #1449
 * [FEATURE] collectors: Add version collector. #1422 #1427
diff --git CONTRIBUTING.md CONTRIBUTING.md
index e015a85cb..abc101b18 100644
--- CONTRIBUTING.md
+++ CONTRIBUTING.md
@@ -1,5 +1,7 @@
 # Contributing
 
+Thank you for contributing to our project! Here are the steps and guidelines to follow when creating a pull request (PR).
+
 Prometheus uses GitHub to manage reviews of pull requests.
 
 * If you have a trivial fix or improvement, go ahead and create a pull request,
diff --git Makefile Makefile
index 1c5eb709d..2af8c5270 100644
--- Makefile
+++ Makefile
@@ -20,8 +20,22 @@ test: deps common-test
 .PHONY: test-short
 test-short: deps common-test-short
 
+# Overriding Makefile.common check_license target to add
+# dagger paths
+.PHONY: common-check_license
+common-check_license:
+	@echo ">> checking license header"
+	@licRes=$$(for file in $$(find . -type f -iname '*.go' ! -path './vendor/*' ! -path './dagger/internal/*') ; do \
+               awk 'NR<=3' $$file | grep -Eq "(Copyright|generated|GENERATED)" || echo $$file; \
+       done); \
+       if [ -n "$${licRes}" ]; then \
+               echo "license header checking failed:"; echo "$${licRes}"; \
+               exit 1; \
+       fi
+
 .PHONY: generate-go-collector-test-files
-VERSIONS := 1.20 1.21 1.22
+file := supported_go_versions.txt
+VERSIONS := $(shell cat ${file})
 generate-go-collector-test-files:
 	for GO_VERSION in $(VERSIONS); do \
 		docker run \
diff --git Makefile.common Makefile.common
index 062a28185..e3da72ab4 100644
--- Makefile.common
+++ Makefile.common
@@ -49,23 +49,23 @@ endif
 GOTEST := $(GO) test
 GOTEST_DIR :=
 ifneq ($(CIRCLE_JOB),)
-ifneq ($(shell command -v gotestsum > /dev/null),)
+ifneq ($(shell command -v gotestsum 2> /dev/null),)
 	GOTEST_DIR := test-results
 	GOTEST := gotestsum --junitfile $(GOTEST_DIR)/unit-tests.xml --
 endif
 endif
 
-PROMU_VERSION ?= 0.15.0
+PROMU_VERSION ?= 0.17.0
 PROMU_URL     := https://github.com/prometheus/promu/releases/download/v$(PROMU_VERSION)/promu-$(PROMU_VERSION).$(GO_BUILD_PLATFORM).tar.gz
 
 SKIP_GOLANGCI_LINT :=
 GOLANGCI_LINT :=
 GOLANGCI_LINT_OPTS ?=
-GOLANGCI_LINT_VERSION ?= v1.54.2
-# golangci-lint only supports linux, darwin and windows platforms on i386/amd64.
+GOLANGCI_LINT_VERSION ?= v1.59.1
+# golangci-lint only supports linux, darwin and windows platforms on i386/amd64/arm64.
 # windows isn't included here because of the path separator being different.
 ifeq ($(GOHOSTOS),$(filter $(GOHOSTOS),linux darwin))
-	ifeq ($(GOHOSTARCH),$(filter $(GOHOSTARCH),amd64 i386))
+	ifeq ($(GOHOSTARCH),$(filter $(GOHOSTARCH),amd64 i386 arm64))
 		# If we're in CI and there is an Actions file, that means the linter
 		# is being run in Actions, so we don't need to run it here.
 		ifneq (,$(SKIP_GOLANGCI_LINT))
@@ -169,16 +169,20 @@ common-vet:
 common-lint: $(GOLANGCI_LINT)
 ifdef GOLANGCI_LINT
 	@echo ">> running golangci-lint"
-# 'go list' needs to be executed before staticcheck to prepopulate the modules cache.
-# Otherwise staticcheck might fail randomly for some reason not yet explained.
-	$(GO) list -e -compiled -test=true -export=false -deps=true -find=false -tags= -- ./... > /dev/null
 	$(GOLANGCI_LINT) run $(GOLANGCI_LINT_OPTS) $(pkgs)
 endif
 
+.PHONY: common-lint-fix
+common-lint-fix: $(GOLANGCI_LINT)
+ifdef GOLANGCI_LINT
+	@echo ">> running golangci-lint fix"
+	$(GOLANGCI_LINT) run --fix $(GOLANGCI_LINT_OPTS) $(pkgs)
+endif
+
 .PHONY: common-yamllint
 common-yamllint:
 	@echo ">> running yamllint on all YAML files in the repository"
-ifeq (, $(shell command -v yamllint > /dev/null))
+ifeq (, $(shell command -v yamllint 2> /dev/null))
 	@echo "yamllint not installed so skipping"
 else
 	yamllint .
@@ -204,6 +208,10 @@ common-tarball: promu
 	@echo ">> building release tarball"
 	$(PROMU) tarball --prefix $(PREFIX) $(BIN_DIR)
 
+.PHONY: common-docker-repo-name
+common-docker-repo-name:
+	@echo "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)"
+
 .PHONY: common-docker $(BUILD_DOCKER_ARCHS)
 common-docker: $(BUILD_DOCKER_ARCHS)
 $(BUILD_DOCKER_ARCHS): common-docker-%:
diff --git NOTICE NOTICE
index dd878a30e..b9cc55abb 100644
--- NOTICE
+++ NOTICE
@@ -16,8 +16,3 @@ Go support for Protocol Buffers - Google's data interchange format
 http://github.com/golang/protobuf/
 Copyright 2010 The Go Authors
 See source code for license details.
-
-Support for streaming Protocol Buffer messages for the Go language (golang).
-https://github.com/matttproud/golang_protobuf_extensions
-Copyright 2013 Matt T. Proud
-Licensed under the Apache License, Version 2.0
diff --git README.md README.md
index f1019d0e2..d83a30cf6 100644
--- README.md
+++ README.md
@@ -24,12 +24,10 @@ CHANGELOG.md.
 
 Features that require breaking changes in the stable parts of the repository
 are being batched up and tracked in the [v2
-milestone](https://github.com/prometheus/client_golang/milestone/2). The v2
-development happens in a [separate
-branch](https://github.com/prometheus/client_golang/tree/dev-v2) for the time
-being. v2 releases off that branch will happen once sufficient stability is
-reached. In view of the widespread use of this repository, v1 and v2 will
-coexist for a while to enable a convenient transition.
+milestone](https://github.com/prometheus/client_golang/milestone/2), but plans for further development of v2 at the moment.
+
+> NOTE: The initial v2 attempt is in a [separate branch](https://github.com/prometheus/client_golang/tree/dev-v2). We also started
+experimenting on a new `prometheus.V2.*` APIs in [the 1.x's V2 struct](https://github.com/prometheus/client_golang/blob/main/prometheus/vnext.go#L23). Help wanted!
 
 ## Instrumenting applications
 
diff --git VERSION VERSION
index 815d5ca06..7bf9455f0 100644
--- VERSION
+++ VERSION
@@ -1 +1 @@
-1.19.0
+1.20.5
diff --git api/prometheus/v1/api.go api/prometheus/v1/api.go
index 1cfe8d863..cddf027fd 100644
--- api/prometheus/v1/api.go
+++ api/prometheus/v1/api.go
@@ -475,9 +475,9 @@ type API interface {
 	// Flags returns the flag values that Prometheus was launched with.
 	Flags(ctx context.Context) (FlagsResult, error)
 	// LabelNames returns the unique label names present in the block in sorted order by given time range and matchers.
-	LabelNames(ctx context.Context, matches []string, startTime, endTime time.Time) ([]string, Warnings, error)
+	LabelNames(ctx context.Context, matches []string, startTime, endTime time.Time, opts ...Option) ([]string, Warnings, error)
 	// LabelValues performs a query for the values of the given label, time range and matchers.
-	LabelValues(ctx context.Context, label string, matches []string, startTime, endTime time.Time) (model.LabelValues, Warnings, error)
+	LabelValues(ctx context.Context, label string, matches []string, startTime, endTime time.Time, opts ...Option) (model.LabelValues, Warnings, error)
 	// Query performs a query for the given time.
 	Query(ctx context.Context, query string, ts time.Time, opts ...Option) (model.Value, Warnings, error)
 	// QueryRange performs a query for the given range.
@@ -489,7 +489,7 @@ type API interface {
 	// Runtimeinfo returns the various runtime information properties about the Prometheus server.
 	Runtimeinfo(ctx context.Context) (RuntimeinfoResult, error)
 	// Series finds series by label matchers.
-	Series(ctx context.Context, matches []string, startTime, endTime time.Time) ([]model.LabelSet, Warnings, error)
+	Series(ctx context.Context, matches []string, startTime, endTime time.Time, opts ...Option) ([]model.LabelSet, Warnings, error)
 	// Snapshot creates a snapshot of all current data into snapshots/<datetime>-<rand>
 	// under the TSDB's data directory and returns the directory as response.
 	Snapshot(ctx context.Context, skipHead bool) (SnapshotResult, error)
@@ -502,7 +502,7 @@ type API interface {
 	// Metadata returns metadata about metrics currently scraped by the metric name.
 	Metadata(ctx context.Context, metric, limit string) (map[string][]Metadata, error)
 	// TSDB returns the cardinality statistics.
-	TSDB(ctx context.Context) (TSDBResult, error)
+	TSDB(ctx context.Context, opts ...Option) (TSDBResult, error)
 	// WalReplay returns the current replay status of the wal.
 	WalReplay(ctx context.Context) (WalReplayStatus, error)
 }
@@ -1024,9 +1024,10 @@ func (h *httpAPI) Runtimeinfo(ctx context.Context) (RuntimeinfoResult, error) {
 	return res, err
 }
 
-func (h *httpAPI) LabelNames(ctx context.Context, matches []string, startTime, endTime time.Time) ([]string, Warnings, error) {
+func (h *httpAPI) LabelNames(ctx context.Context, matches []string, startTime, endTime time.Time, opts ...Option) ([]string, Warnings, error) {
 	u := h.client.URL(epLabels, nil)
-	q := u.Query()
+	q := addOptionalURLParams(u.Query(), opts)
+
 	if !startTime.IsZero() {
 		q.Set("start", formatTime(startTime))
 	}
@@ -1046,9 +1047,10 @@ func (h *httpAPI) LabelNames(ctx context.Context, matches []string, startTime, e
 	return labelNames, w, err
 }
 
-func (h *httpAPI) LabelValues(ctx context.Context, label string, matches []string, startTime, endTime time.Time) (model.LabelValues, Warnings, error) {
+func (h *httpAPI) LabelValues(ctx context.Context, label string, matches []string, startTime, endTime time.Time, opts ...Option) (model.LabelValues, Warnings, error) {
 	u := h.client.URL(epLabelValues, map[string]string{"name": label})
-	q := u.Query()
+	q := addOptionalURLParams(u.Query(), opts)
+
 	if !startTime.IsZero() {
 		q.Set("start", formatTime(startTime))
 	}
@@ -1076,6 +1078,7 @@ func (h *httpAPI) LabelValues(ctx context.Context, label string, matches []strin
 
 type apiOptions struct {
 	timeout time.Duration
+	limit   uint64
 }
 
 type Option func(c *apiOptions)
@@ -1088,20 +1091,35 @@ func WithTimeout(timeout time.Duration) Option {
 	}
 }
 
-func (h *httpAPI) Query(ctx context.Context, query string, ts time.Time, opts ...Option) (model.Value, Warnings, error) {
-	u := h.client.URL(epQuery, nil)
-	q := u.Query()
+// WithLimit provides an optional maximum number of returned entries for APIs that support limit parameter
+// e.g. https://prometheus.io/docs/prometheus/latest/querying/api/#instant-querie:~:text=%3A%20End%20timestamp.-,limit%3D%3Cnumber%3E,-%3A%20Maximum%20number%20of
+func WithLimit(limit uint64) Option {
+	return func(o *apiOptions) {
+		o.limit = limit
+	}
+}
 
+func addOptionalURLParams(q url.Values, opts []Option) url.Values {
 	opt := &apiOptions{}
 	for _, o := range opts {
 		o(opt)
 	}
 
-	d := opt.timeout
-	if d > 0 {
-		q.Set("timeout", d.String())
+	if opt.timeout > 0 {
+		q.Set("timeout", opt.timeout.String())
 	}
 
+	if opt.limit > 0 {
+		q.Set("limit", strconv.FormatUint(opt.limit, 10))
+	}
+
+	return q
+}
+
+func (h *httpAPI) Query(ctx context.Context, query string, ts time.Time, opts ...Option) (model.Value, Warnings, error) {
+	u := h.client.URL(epQuery, nil)
+	q := addOptionalURLParams(u.Query(), opts)
+
 	q.Set("query", query)
 	if !ts.IsZero() {
 		q.Set("time", formatTime(ts))
@@ -1118,36 +1136,25 @@ func (h *httpAPI) Query(ctx context.Context, query string, ts time.Time, opts ..
 
 func (h *httpAPI) QueryRange(ctx context.Context, query string, r Range, opts ...Option) (model.Value, Warnings, error) {
 	u := h.client.URL(epQueryRange, nil)
-	q := u.Query()
+	q := addOptionalURLParams(u.Query(), opts)
 
 	q.Set("query", query)
 	q.Set("start", formatTime(r.Start))
 	q.Set("end", formatTime(r.End))
 	q.Set("step", strconv.FormatFloat(r.Step.Seconds(), 'f', -1, 64))
 
-	opt := &apiOptions{}
-	for _, o := range opts {
-		o(opt)
-	}
-
-	d := opt.timeout
-	if d > 0 {
-		q.Set("timeout", d.String())
-	}
-
 	_, body, warnings, err := h.client.DoGetFallback(ctx, u, q)
 	if err != nil {
 		return nil, warnings, err
 	}
 
 	var qres queryResult
-
 	return qres.v, warnings, json.Unmarshal(body, &qres)
 }
 
-func (h *httpAPI) Series(ctx context.Context, matches []string, startTime, endTime time.Time) ([]model.LabelSet, Warnings, error) {
+func (h *httpAPI) Series(ctx context.Context, matches []string, startTime, endTime time.Time, opts ...Option) ([]model.LabelSet, Warnings, error) {
 	u := h.client.URL(epSeries, nil)
-	q := u.Query()
+	q := addOptionalURLParams(u.Query(), opts)
 
 	for _, m := range matches {
 		q.Add("match[]", m)
@@ -1166,8 +1173,7 @@ func (h *httpAPI) Series(ctx context.Context, matches []string, startTime, endTi
 	}
 
 	var mset []model.LabelSet
-	err = json.Unmarshal(body, &mset)
-	return mset, warnings, err
+	return mset, warnings, json.Unmarshal(body, &mset)
 }
 
 func (h *httpAPI) Snapshot(ctx context.Context, skipHead bool) (SnapshotResult, error) {
@@ -1278,8 +1284,10 @@ func (h *httpAPI) Metadata(ctx context.Context, metric, limit string) (map[strin
 	return res, err
 }
 
-func (h *httpAPI) TSDB(ctx context.Context) (TSDBResult, error) {
+func (h *httpAPI) TSDB(ctx context.Context, opts ...Option) (TSDBResult, error) {
 	u := h.client.URL(epTSDB, nil)
+	q := addOptionalURLParams(u.Query(), opts)
+	u.RawQuery = q.Encode()
 
 	req, err := http.NewRequest(http.MethodGet, u.String(), nil)
 	if err != nil {
diff --git api/prometheus/v1/api_test.go api/prometheus/v1/api_test.go
index e87e48311..01ddf45ca 100644
--- api/prometheus/v1/api_test.go
+++ api/prometheus/v1/api_test.go
@@ -154,15 +154,15 @@ func TestAPIs(t *testing.T) {
 		}
 	}
 
-	doLabelNames := func(matches []string, startTime, endTime time.Time) func() (interface{}, Warnings, error) {
+	doLabelNames := func(matches []string, startTime, endTime time.Time, opts ...Option) func() (interface{}, Warnings, error) {
 		return func() (interface{}, Warnings, error) {
-			return promAPI.LabelNames(context.Background(), matches, startTime, endTime)
+			return promAPI.LabelNames(context.Background(), matches, startTime, endTime, opts...)
 		}
 	}
 
-	doLabelValues := func(matches []string, label string, startTime, endTime time.Time) func() (interface{}, Warnings, error) {
+	doLabelValues := func(matches []string, label string, startTime, endTime time.Time, opts ...Option) func() (interface{}, Warnings, error) {
 		return func() (interface{}, Warnings, error) {
-			return promAPI.LabelValues(context.Background(), label, matches, startTime, endTime)
+			return promAPI.LabelValues(context.Background(), label, matches, startTime, endTime, opts...)
 		}
 	}
 
@@ -178,9 +178,9 @@ func TestAPIs(t *testing.T) {
 		}
 	}
 
-	doSeries := func(matcher string, startTime, endTime time.Time) func() (interface{}, Warnings, error) {
+	doSeries := func(matcher string, startTime, endTime time.Time, opts ...Option) func() (interface{}, Warnings, error) {
 		return func() (interface{}, Warnings, error) {
-			return promAPI.Series(context.Background(), []string{matcher}, startTime, endTime)
+			return promAPI.Series(context.Background(), []string{matcher}, startTime, endTime, opts...)
 		}
 	}
 
@@ -219,9 +219,9 @@ func TestAPIs(t *testing.T) {
 		}
 	}
 
-	doTSDB := func() func() (interface{}, Warnings, error) {
+	doTSDB := func(opts ...Option) func() (interface{}, Warnings, error) {
 		return func() (interface{}, Warnings, error) {
-			v, err := promAPI.TSDB(context.Background())
+			v, err := promAPI.TSDB(context.Background(), opts...)
 			return v, nil, err
 		}
 	}
diff --git api/prometheus/v1/example_test.go api/prometheus/v1/example_test.go
index 04cfac690..1247587cb 100644
--- api/prometheus/v1/example_test.go
+++ api/prometheus/v1/example_test.go
@@ -135,7 +135,11 @@ func ExampleAPI_queryRangeWithBasicAuth() {
 	client, err := api.NewClient(api.Config{
 		Address: "http://demo.robustperception.io:9090",
 		// We can use amazing github.com/prometheus/common/config helper!
-		RoundTripper: config.NewBasicAuthRoundTripper("me", "definitely_me", "", "", api.DefaultRoundTripper),
+		RoundTripper: config.NewBasicAuthRoundTripper(
+			config.NewInlineSecret("me"),
+			config.NewInlineSecret("definitely_me"),
+			api.DefaultRoundTripper,
+		),
 	})
 	if err != nil {
 		fmt.Printf("Error creating client: %v\n", err)
@@ -165,7 +169,11 @@ func ExampleAPI_queryRangeWithAuthBearerToken() {
 	client, err := api.NewClient(api.Config{
 		Address: "http://demo.robustperception.io:9090",
 		// We can use amazing github.com/prometheus/common/config helper!
-		RoundTripper: config.NewAuthorizationCredentialsRoundTripper("Bearer", "secret_token", api.DefaultRoundTripper),
+		RoundTripper: config.NewAuthorizationCredentialsRoundTripper(
+			"Bearer",
+			config.NewInlineSecret("secret_token"),
+			api.DefaultRoundTripper,
+		),
 	})
 	if err != nil {
 		fmt.Printf("Error creating client: %v\n", err)
diff --git a/dagger.json b/dagger.json
new file mode 100644
index 000000000..e1ed52ff0
--- /dev/null
+++ dagger.json
@@ -0,0 +1,12 @@
+{
+  "name": "client_golang",
+  "sdk": "go",
+  "dependencies": [
+    {
+      "name": "golang",
+      "source": "github.com/kpenfound/dagger-modules/golang@fd1a6e75721454d8ee3bcb0bd1a94d6bb4be1737"
+    }
+  ],
+  "source": "dagger",
+  "engineVersion": "v0.12.0"
+}
diff --git a/dagger/.gitattributes b/dagger/.gitattributes
new file mode 100644
index 000000000..3a454933c
--- /dev/null
+++ dagger/.gitattributes
@@ -0,0 +1,4 @@
+/dagger.gen.go linguist-generated
+/internal/dagger/** linguist-generated
+/internal/querybuilder/** linguist-generated
+/internal/telemetry/** linguist-generated
diff --git a/dagger/.gitignore b/dagger/.gitignore
new file mode 100644
index 000000000..7ebabcc14
--- /dev/null
+++ dagger/.gitignore
@@ -0,0 +1,4 @@
+/dagger.gen.go
+/internal/dagger
+/internal/querybuilder
+/internal/telemetry
diff --git a/dagger/go.mod b/dagger/go.mod
new file mode 100644
index 000000000..be916ea5d
--- /dev/null
+++ dagger/go.mod
@@ -0,0 +1,40 @@
+module dagger
+
+go 1.21.7
+
+require (
+	github.com/99designs/gqlgen v0.17.49
+	github.com/Khan/genqlient v0.7.0
+	github.com/vektah/gqlparser/v2 v2.5.16
+	go.opentelemetry.io/otel 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
+	golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa
+	golang.org/x/sync v0.7.0
+	google.golang.org/grpc v1.64.0
+)
+
+require (
+	github.com/cenkalti/backoff/v4 v4.3.0 // indirect
+	github.com/go-logr/logr v1.4.1 // indirect
+	github.com/go-logr/stdr v1.2.2 // indirect
+	github.com/google/uuid v1.6.0 // indirect
+	github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 // indirect
+	github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect
+	github.com/sosodev/duration v1.3.1 // indirect
+	go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.0.0-20240518090000-14441aefdf88
+	go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.3.0
+	go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.27.0 // indirect
+	go.opentelemetry.io/otel/log v0.3.0
+	go.opentelemetry.io/otel/metric v1.27.0 // indirect
+	go.opentelemetry.io/otel/sdk/log v0.3.0
+	go.opentelemetry.io/proto/otlp v1.3.1
+	golang.org/x/net v0.26.0 // indirect
+	golang.org/x/sys v0.21.0 // indirect
+	golang.org/x/text v0.16.0 // indirect
+	google.golang.org/genproto/googleapis/api v0.0.0-20240520151616-dc85e6b867a5 // indirect
+	google.golang.org/genproto/googleapis/rpc v0.0.0-20240515191416-fc5f0ca64291 // indirect
+	google.golang.org/protobuf v1.34.1 // indirect
+)
diff --git a/dagger/go.sum b/dagger/go.sum
new file mode 100644
index 000000000..6fea81b9c
--- /dev/null
+++ dagger/go.sum
@@ -0,0 +1,87 @@
+github.com/99designs/gqlgen v0.17.49 h1:b3hNGexHd33fBSAd4NDT/c3NCcQzcAVkknhN9ym36YQ=
+github.com/99designs/gqlgen v0.17.49/go.mod h1:tC8YFVZMed81x7UJ7ORUwXF4Kn6SXuucFqQBhN8+BU0=
+github.com/Khan/genqlient v0.7.0 h1:GZ1meyRnzcDTK48EjqB8t3bcfYvHArCUUvgOwpz1D4w=
+github.com/Khan/genqlient v0.7.0/go.mod h1:HNyy3wZvuYwmW3Y7mkoQLZsa/R5n5yIRajS1kPBvSFM=
+github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883 h1:bvNMNQO63//z+xNgfBlViaCIJKLlCJ6/fmUseuG0wVQ=
+github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8=
+github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
+github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+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/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
+github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
+github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
+github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
+github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
+github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+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/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
+github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
+github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8=
+github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=
+github.com/sosodev/duration v1.3.1 h1:qtHBDMQ6lvMQsL15g4aopM4HEfOaYuhWBw3NPTtlqq4=
+github.com/sosodev/duration v1.3.1/go.mod h1:RQIBBX0+fMLc/D9+Jb/fwvVmo0eZvDDEERAikUR6SDg=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
+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/vektah/gqlparser/v2 v2.5.16 h1:1gcmLTvs3JLKXckwCwlUagVn/IlV2bwqle0vJ0vy5p8=
+github.com/vektah/gqlparser/v2 v2.5.16/go.mod h1:1lz1OeCqgQbQepsGxPVywrjdBHW2T08PUS3pJqepRww=
+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/otlplog/otlploggrpc v0.0.0-20240518090000-14441aefdf88 h1:oM0GTNKGlc5qHctWeIGTVyda4iFFalOzMZ3Ehj5rwB4=
+go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.0.0-20240518090000-14441aefdf88/go.mod h1:JGG8ebaMO5nXOPnvKEl+DiA4MGwFjCbjsxT1WHIEBPY=
+go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.3.0 h1:ccBrA8nCY5mM0y5uO7FT0ze4S0TuFcWdDB2FxGMTjkI=
+go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.3.0/go.mod h1:/9pb6634zi2Lk8LYg9Q0X8Ar6jka4dkFOylBLbVQPCE=
+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/log v0.3.0 h1:kJRFkpUFYtny37NQzL386WbznUByZx186DpEMKhEGZs=
+go.opentelemetry.io/otel/log v0.3.0/go.mod h1:ziCwqZr9soYDwGNbIL+6kAvQC+ANvjgG367HVcyR/ys=
+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/sdk/log v0.3.0 h1:GEjJ8iftz2l+XO1GF2856r7yYVh74URiF9JMcAacr5U=
+go.opentelemetry.io/otel/sdk/log v0.3.0/go.mod h1:BwCxtmux6ACLuys1wlbc0+vGBd+xytjmjajwqqIul2g=
+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.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0=
+go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8=
+go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
+go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
+golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa h1:FRnLl4eNAQl8hwxVVC17teOw8kdjVDVAiFMtgUdTSRQ=
+golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa/go.mod h1:zk2irFbV9DP96SEBUUAy67IdHUaZuSnrz1n472HUCLE=
+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/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
+golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
+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/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
+golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
+google.golang.org/genproto/googleapis/api v0.0.0-20240520151616-dc85e6b867a5 h1:P8OJ/WCl/Xo4E4zoe4/bifHpSmmKwARqyqE4nW6J2GQ=
+google.golang.org/genproto/googleapis/api v0.0.0-20240520151616-dc85e6b867a5/go.mod h1:RGnPtTG7r4i8sPlNyDeikXF99hMM+hN6QMm4ooG9g2g=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20240515191416-fc5f0ca64291 h1:AgADTJarZTBqgjiUzRgfaBchgYB3/WFTC80GPwsMcRI=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20240515191416-fc5f0ca64291/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/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg=
+google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/dagger/main.go b/dagger/main.go
new file mode 100644
index 000000000..7a608af90
--- /dev/null
+++ dagger/main.go
@@ -0,0 +1,84 @@
+// 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.
+
+// A minimal example of how to include Prometheus instrumentation.
+
+package main
+
+import (
+	"context"
+	"dagger/internal/dagger"
+	"strings"
+
+	"golang.org/x/sync/errgroup"
+)
+
+type ClientGolang struct {
+	Source *dagger.Directory // +private
+}
+
+func New(src *dagger.Directory) *ClientGolang {
+	return &ClientGolang{Source: src}
+}
+
+// runs `make` with the given arguments
+func (m *ClientGolang) Make(
+	// +optional
+	args string,
+	// +default="1.20"
+	goVersion string,
+	// +optional
+	env []string,
+) (string, error) {
+	return dag.Golang().
+		Base(goVersion).
+		Container().
+		WithMountedDirectory("/src", m.Source).
+		WithWorkdir("/src").
+		WithMountedCache("/go/bin", dag.CacheVolume("gobincache")).
+		WithExec([]string{"sh", "-c", "make " + args}).
+		Stdout(context.Background())
+}
+
+// runs `make` with the given arguments for all supported go versions
+func (m *ClientGolang) MakeRun(
+	ctx context.Context,
+	// +optional,
+	args string,
+) error {
+	c, err := m.Source.File("supported_go_versions.txt").Contents(ctx)
+	if err != nil {
+		return err
+	}
+	goVersions := strings.Split(c, "\n")
+
+	eg := new(errgroup.Group)
+
+	for _, version := range goVersions {
+		version := version
+		if len(version) > 0 {
+			eg.Go(func() error {
+				_, err := dag.Golang().
+					Base(version).
+					Container().
+					WithMountedDirectory("/src", m.Source).
+					WithWorkdir("/src").
+					WithMountedCache("/go/bin", dag.CacheVolume("gobincache")).
+					WithExec([]string{"sh", "-c", "make " + args}).Sync(ctx)
+				return err
+			})
+		}
+	}
+
+	return eg.Wait()
+}
diff --git examples/middleware/go.mod examples/middleware/go.mod
deleted file mode 100644
index 0e6b185d9..000000000
--- examples/middleware/go.mod
+++ /dev/null
@@ -1,16 +0,0 @@
-module github.com/jessicalins/instrumentation-practices-examples/middleware
-
-go 1.19
-
-require github.com/prometheus/client_golang v1.18.0
-
-require (
-	github.com/beorn7/perks v1.0.1 // indirect
-	github.com/cespare/xxhash/v2 v2.2.0 // indirect
-	github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 // indirect
-	github.com/prometheus/client_model v0.5.0 // indirect
-	github.com/prometheus/common v0.45.0 // indirect
-	github.com/prometheus/procfs v0.12.0 // indirect
-	golang.org/x/sys v0.15.0 // indirect
-	google.golang.org/protobuf v1.31.0 // indirect
-)
diff --git examples/middleware/go.sum examples/middleware/go.sum
deleted file mode 100644
index 361dab08b..000000000
--- examples/middleware/go.sum
+++ /dev/null
@@ -1,24 +0,0 @@
-github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
-github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
-github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
-github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
-github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
-github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
-github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
-github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
-github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 h1:jWpvCLoY8Z/e3VKvlsiIGKtc+UG6U5vzxaoagmhXfyg=
-github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0/go.mod h1:QUyp042oQthUoa9bqDv0ER0wrtXnBruoNd7aNjkbP+k=
-github.com/prometheus/client_golang v1.18.0 h1:HzFfmkOzH5Q8L8G+kSJKUx5dtG87sewO+FoDDqP5Tbk=
-github.com/prometheus/client_golang v1.18.0/go.mod h1:T+GXkCk5wSJyOqMIzVgvvjFDlkOQntgjkJWKrN5txjA=
-github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw=
-github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI=
-github.com/prometheus/common v0.45.0 h1:2BGz0eBc2hdMDLnO/8n0jeB3oPrt2D08CekT0lneoxM=
-github.com/prometheus/common v0.45.0/go.mod h1:YJmSTw9BoKxJplESWWxlbyttQR4uaEcGyv9MZjVOJsY=
-github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo=
-github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo=
-golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
-golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
-golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
-google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
-google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
-google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
diff --git examples/middleware/main.go examples/middleware/main.go
index 227ed7ce6..67136cf42 100644
--- examples/middleware/main.go
+++ examples/middleware/main.go
@@ -19,11 +19,10 @@ import (
 	"log"
 	"net/http"
 
+	"github.com/prometheus/client_golang/examples/middleware/httpmiddleware"
 	"github.com/prometheus/client_golang/prometheus"
 	"github.com/prometheus/client_golang/prometheus/collectors"
 	"github.com/prometheus/client_golang/prometheus/promhttp"
-
-	"github.com/jessicalins/instrumentation-practices-examples/middleware/httpmiddleware"
 )
 
 func main() {
diff --git generate-go-collector.bash generate-go-collector.bash
index 3fa822aab..a9e2fa55f 100644
--- generate-go-collector.bash
+++ generate-go-collector.bash
@@ -5,3 +5,5 @@ set -e
 go get github.com/hashicorp/go-version@v1.6.0
 go run prometheus/gen_go_collector_metrics_set.go
 mv -f go_collector_metrics_* prometheus
+go run prometheus/collectors/gen_go_collector_set.go
+mv -f go_collector_* prometheus/collectors
diff --git go.mod go.mod
index 662608543..918344c7f 100644
--- go.mod
+++ go.mod
@@ -4,27 +4,28 @@ go 1.20
 
 require (
 	github.com/beorn7/perks v1.0.1
-	github.com/cespare/xxhash/v2 v2.2.0
-	github.com/davecgh/go-spew v1.1.1
+	github.com/cespare/xxhash/v2 v2.3.0
+	github.com/google/go-cmp v0.6.0
 	github.com/json-iterator/go v1.1.12
-	github.com/prometheus/client_model v0.5.0
-	github.com/prometheus/common v0.48.0
-	github.com/prometheus/procfs v0.12.0
-	golang.org/x/sys v0.16.0
-	google.golang.org/protobuf v1.32.0
+	github.com/klauspost/compress v1.17.9
+	github.com/kylelemons/godebug v1.1.0
+	github.com/prometheus/client_model v0.6.1
+	github.com/prometheus/common v0.55.0
+	github.com/prometheus/procfs v0.15.1
+	golang.org/x/sys v0.22.0
+	google.golang.org/protobuf v1.34.2
 )
 
 require (
-	github.com/golang/protobuf v1.5.3 // indirect
 	github.com/jpillora/backoff v1.0.0 // indirect
 	github.com/kr/pretty v0.3.1 // indirect
 	github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
 	github.com/modern-go/reflect2 v1.0.2 // indirect
+	github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
 	github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f // indirect
-	golang.org/x/net v0.20.0 // indirect
-	golang.org/x/oauth2 v0.16.0 // indirect
-	golang.org/x/text v0.14.0 // indirect
-	google.golang.org/appengine v1.6.7 // indirect
+	golang.org/x/net v0.26.0 // indirect
+	golang.org/x/oauth2 v0.21.0 // indirect
+	golang.org/x/text v0.16.0 // indirect
 	gopkg.in/yaml.v2 v2.4.0 // indirect
 )
 
diff --git go.sum go.sum
index 450a27317..6102926ed 100644
--- go.sum
+++ go.sum
@@ -1,69 +1,61 @@
 github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
 github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
-github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
-github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
+github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
+github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
 github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
-github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
-github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
-github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
-github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
-github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
+github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
 github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
 github.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA=
 github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
 github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
 github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
+github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA=
+github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
 github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
 github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
 github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
 github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
+github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
+github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
 github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
 github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
 github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
 github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
 github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
+github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
+github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
 github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f h1:KUppIJq7/+SVif2QVs3tOP0zanoHgBEVAwHxUSIzRqU=
 github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
 github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
-github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw=
-github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI=
-github.com/prometheus/common v0.48.0 h1:QO8U2CdOzSn1BBsmXJXduaaW+dY/5QLjfB8svtSzKKE=
-github.com/prometheus/common v0.48.0/go.mod h1:0/KsvlIEfPQCQ5I2iNSAWKPZziNCvRs5EC6ILDTlAPc=
-github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo=
-github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo=
+github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E=
+github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY=
+github.com/prometheus/common v0.55.0 h1:KEi6DK7lXW/m7Ig5i47x0vRzuBsHuvJdi5ee6Y3G1dc=
+github.com/prometheus/common v0.55.0/go.mod h1:2SECS4xJG1kd8XF9IcM1gMX6510RAEL65zxzNImwdc8=
+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/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
 github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
-github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
 github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
-golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
-golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
-golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo=
-golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
-golang.org/x/oauth2 v0.16.0 h1:aDkGMBSYxElaoP81NpoUoz2oo2R2wHdZpGToUxfyQrQ=
-golang.org/x/oauth2 v0.16.0/go.mod h1:hqZ+0LWXsiVoZpeld6jVt06P3adbS2Uu911W1SsJv2o=
-golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU=
-golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
-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.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
-golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
-golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
-golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
-google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
-google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
-google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
-google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
-google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I=
-google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
+github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
+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/oauth2 v0.21.0 h1:tsimM75w1tF/uws5rbeHzIWxEqElMehnc+iW793zsZs=
+golang.org/x/oauth2 v0.21.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
+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/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
+golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
+google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
+google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
 gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
 gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
diff --git a/internal/github.com/golang/gddo/LICENSE b/internal/github.com/golang/gddo/LICENSE
new file mode 100644
index 000000000..65d761bc9
--- /dev/null
+++ internal/github.com/golang/gddo/LICENSE
@@ -0,0 +1,27 @@
+Copyright (c) 2013 The Go Authors. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+   * Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+   * Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the following disclaimer
+in the documentation and/or other materials provided with the
+distribution.
+   * Neither the name of Google Inc. nor the names of its
+contributors may be used to endorse or promote products derived from
+this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/internal/github.com/golang/gddo/README.md b/internal/github.com/golang/gddo/README.md
new file mode 100644
index 000000000..69af39a33
--- /dev/null
+++ internal/github.com/golang/gddo/README.md
@@ -0,0 +1 @@
+This source code is a stripped down version from the archived repository https://github.com/golang/gddo and licensed under BSD.
diff --git a/internal/github.com/golang/gddo/httputil/header/header.go b/internal/github.com/golang/gddo/httputil/header/header.go
new file mode 100644
index 000000000..8547c8dfd
--- /dev/null
+++ internal/github.com/golang/gddo/httputil/header/header.go
@@ -0,0 +1,145 @@
+// Copyright 2013 The Go Authors. All rights reserved.
+//
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file or at
+// https://developers.google.com/open-source/licenses/bsd.
+
+// Package header provides functions for parsing HTTP headers.
+package header
+
+import (
+	"net/http"
+	"strings"
+)
+
+// Octet types from RFC 2616.
+var octetTypes [256]octetType
+
+type octetType byte
+
+const (
+	isToken octetType = 1 << iota
+	isSpace
+)
+
+func init() {
+	// OCTET      = <any 8-bit sequence of data>
+	// CHAR       = <any US-ASCII character (octets 0 - 127)>
+	// CTL        = <any US-ASCII control character (octets 0 - 31) and DEL (127)>
+	// CR         = <US-ASCII CR, carriage return (13)>
+	// LF         = <US-ASCII LF, linefeed (10)>
+	// SP         = <US-ASCII SP, space (32)>
+	// HT         = <US-ASCII HT, horizontal-tab (9)>
+	// <">        = <US-ASCII double-quote mark (34)>
+	// CRLF       = CR LF
+	// LWS        = [CRLF] 1*( SP | HT )
+	// TEXT       = <any OCTET except CTLs, but including LWS>
+	// separators = "(" | ")" | "<" | ">" | "@" | "," | ";" | ":" | "\" | <">
+	//              | "/" | "[" | "]" | "?" | "=" | "{" | "}" | SP | HT
+	// token      = 1*<any CHAR except CTLs or separators>
+	// qdtext     = <any TEXT except <">>
+
+	for c := 0; c < 256; c++ {
+		var t octetType
+		isCtl := c <= 31 || c == 127
+		isChar := 0 <= c && c <= 127
+		isSeparator := strings.ContainsRune(" \t\"(),/:;<=>?@[]\\{}", rune(c))
+		if strings.ContainsRune(" \t\r\n", rune(c)) {
+			t |= isSpace
+		}
+		if isChar && !isCtl && !isSeparator {
+			t |= isToken
+		}
+		octetTypes[c] = t
+	}
+}
+
+// AcceptSpec describes an Accept* header.
+type AcceptSpec struct {
+	Value string
+	Q     float64
+}
+
+// ParseAccept parses Accept* headers.
+func ParseAccept(header http.Header, key string) (specs []AcceptSpec) {
+loop:
+	for _, s := range header[key] {
+		for {
+			var spec AcceptSpec
+			spec.Value, s = expectTokenSlash(s)
+			if spec.Value == "" {
+				continue loop
+			}
+			spec.Q = 1.0
+			s = skipSpace(s)
+			if strings.HasPrefix(s, ";") {
+				s = skipSpace(s[1:])
+				if !strings.HasPrefix(s, "q=") {
+					continue loop
+				}
+				spec.Q, s = expectQuality(s[2:])
+				if spec.Q < 0.0 {
+					continue loop
+				}
+			}
+			specs = append(specs, spec)
+			s = skipSpace(s)
+			if !strings.HasPrefix(s, ",") {
+				continue loop
+			}
+			s = skipSpace(s[1:])
+		}
+	}
+	return
+}
+
+func skipSpace(s string) (rest string) {
+	i := 0
+	for ; i < len(s); i++ {
+		if octetTypes[s[i]]&isSpace == 0 {
+			break
+		}
+	}
+	return s[i:]
+}
+
+func expectTokenSlash(s string) (token, rest string) {
+	i := 0
+	for ; i < len(s); i++ {
+		b := s[i]
+		if (octetTypes[b]&isToken == 0) && b != '/' {
+			break
+		}
+	}
+	return s[:i], s[i:]
+}
+
+func expectQuality(s string) (q float64, rest string) {
+	switch {
+	case len(s) == 0:
+		return -1, ""
+	case s[0] == '0':
+		q = 0
+	case s[0] == '1':
+		q = 1
+	default:
+		return -1, ""
+	}
+	s = s[1:]
+	if !strings.HasPrefix(s, ".") {
+		return q, s
+	}
+	s = s[1:]
+	i := 0
+	n := 0
+	d := 1
+	for ; i < len(s); i++ {
+		b := s[i]
+		if b < '0' || b > '9' {
+			break
+		}
+		n = n*10 + int(b) - '0'
+		d *= 10
+	}
+	return q + float64(n)/float64(d), s[i:]
+}
diff --git a/internal/github.com/golang/gddo/httputil/header/,header_test.go b/internal/github.com/golang/gddo/httputil/header/header_test.go
new file mode 100644
index 000000000..e26eb6c30
--- /dev/null
+++ internal/github.com/golang/gddo/httputil/header/header_test.go
@@ -0,0 +1,49 @@
+// Copyright 2013 The Go Authors. All rights reserved.
+//
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file or at
+// https://developers.google.com/open-source/licenses/bsd.
+
+package header
+
+import (
+	"net/http"
+	"testing"
+
+	"github.com/google/go-cmp/cmp"
+)
+
+var parseAcceptTests = []struct {
+	s        string
+	expected []AcceptSpec
+}{
+	{"text/html", []AcceptSpec{{"text/html", 1}}},
+	{"text/html; q=0", []AcceptSpec{{"text/html", 0}}},
+	{"text/html; q=0.0", []AcceptSpec{{"text/html", 0}}},
+	{"text/html; q=1", []AcceptSpec{{"text/html", 1}}},
+	{"text/html; q=1.0", []AcceptSpec{{"text/html", 1}}},
+	{"text/html; q=0.1", []AcceptSpec{{"text/html", 0.1}}},
+	{"text/html;q=0.1", []AcceptSpec{{"text/html", 0.1}}},
+	{"text/html, text/plain", []AcceptSpec{{"text/html", 1}, {"text/plain", 1}}},
+	{"text/html; q=0.1, text/plain", []AcceptSpec{{"text/html", 0.1}, {"text/plain", 1}}},
+	{"iso-8859-5, unicode-1-1;q=0.8,iso-8859-1", []AcceptSpec{{"iso-8859-5", 1}, {"unicode-1-1", 0.8}, {"iso-8859-1", 1}}},
+	{"iso-8859-1", []AcceptSpec{{"iso-8859-1", 1}}},
+	{"*", []AcceptSpec{{"*", 1}}},
+	{"da, en-gb;q=0.8, en;q=0.7", []AcceptSpec{{"da", 1}, {"en-gb", 0.8}, {"en", 0.7}}},
+	{"da, q, en-gb;q=0.8", []AcceptSpec{{"da", 1}, {"q", 1}, {"en-gb", 0.8}}},
+	{"image/png, image/*;q=0.5", []AcceptSpec{{"image/png", 1}, {"image/*", 0.5}}},
+
+	// bad cases
+	{"value1; q=0.1.2", []AcceptSpec{{"value1", 0.1}}},
+	{"da, en-gb;q=foo", []AcceptSpec{{"da", 1}}},
+}
+
+func TestParseAccept(t *testing.T) {
+	for _, tt := range parseAcceptTests {
+		header := http.Header{"Accept": {tt.s}}
+		actual := ParseAccept(header, "Accept")
+		if !cmp.Equal(actual, tt.expected) {
+			t.Errorf("ParseAccept(h, %q)=%v, want %v", tt.s, actual, tt.expected)
+		}
+	}
+}
diff --git a/internal/github.com/golang/gddo/httputil/negotiate.go b/internal/github.com/golang/gddo/httputil/negotiate.go
new file mode 100644
index 000000000..2e45780b7
--- /dev/null
+++ internal/github.com/golang/gddo/httputil/negotiate.go
@@ -0,0 +1,36 @@
+// Copyright 2013 The Go Authors. All rights reserved.
+//
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file or at
+// https://developers.google.com/open-source/licenses/bsd.
+
+package httputil
+
+import (
+	"net/http"
+
+	"github.com/prometheus/client_golang/internal/github.com/golang/gddo/httputil/header"
+)
+
+// NegotiateContentEncoding returns the best offered content encoding for the
+// request's Accept-Encoding header. If two offers match with equal weight and
+// then the offer earlier in the list is preferred. If no offers are
+// acceptable, then "" is returned.
+func NegotiateContentEncoding(r *http.Request, offers []string) string {
+	bestOffer := "identity"
+	bestQ := -1.0
+	specs := header.ParseAccept(r.Header, "Accept-Encoding")
+	for _, offer := range offers {
+		for _, spec := range specs {
+			if spec.Q > bestQ &&
+				(spec.Value == "*" || spec.Value == offer) {
+				bestQ = spec.Q
+				bestOffer = offer
+			}
+		}
+	}
+	if bestQ == 0 {
+		bestOffer = ""
+	}
+	return bestOffer
+}
diff --git a/internal/github.com/golang/gddo/httputil/negotiate_test.go b/internal/github.com/golang/gddo/httputil/negotiate_test.go
new file mode 100644
index 000000000..cdd5807ca
--- /dev/null
+++ internal/github.com/golang/gddo/httputil/negotiate_test.go
@@ -0,0 +1,40 @@
+// Copyright 2013 The Go Authors. All rights reserved.
+//
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file or at
+// https://developers.google.com/open-source/licenses/bsd.
+
+package httputil
+
+import (
+	"net/http"
+	"testing"
+)
+
+var negotiateContentEncodingTests = []struct {
+	s      string
+	offers []string
+	expect string
+}{
+	{"", []string{"identity", "gzip"}, "identity"},
+	{"*;q=0", []string{"identity", "gzip"}, ""},
+	{"gzip", []string{"identity", "gzip"}, "gzip"},
+	{"gzip,zstd", []string{"identity", "zstd"}, "zstd"},
+	{"zstd,gzip", []string{"gzip", "zstd"}, "gzip"},
+	{"gzip,zstd", []string{"gzip", "zstd"}, "gzip"},
+	{"gzip,zstd", []string{"zstd", "gzip"}, "zstd"},
+	{"gzip;q=0.1,zstd;q=0.5", []string{"gzip", "zstd"}, "zstd"},
+	{"gzip;q=1.0, identity; q=0.5, *;q=0", []string{"identity", "gzip"}, "gzip"},
+	{"gzip;q=1.0, identity; q=0.5, *;q=0", []string{"identity", "zstd"}, "identity"},
+	{"zstd", []string{"identity", "gzip"}, "identity"},
+}
+
+func TestNegotiateContentEncoding(t *testing.T) {
+	for _, tt := range negotiateContentEncodingTests {
+		r := &http.Request{Header: http.Header{"Accept-Encoding": {tt.s}}}
+		actual := NegotiateContentEncoding(r, tt.offers)
+		if actual != tt.expect {
+			t.Errorf("NegotiateContentEncoding(%q, %#v)=%q, want %q", tt.s, tt.offers, actual, tt.expect)
+		}
+	}
+}
diff --git prometheus/collectors/dbstats_collector_test.go prometheus/collectors/dbstats_collector_test.go
index 0698bb289..057e24384 100644
--- prometheus/collectors/dbstats_collector_test.go
+++ prometheus/collectors/dbstats_collector_test.go
@@ -21,7 +21,7 @@ import (
 )
 
 func TestDBStatsCollector(t *testing.T) {
-	reg := prometheus.NewRegistry()
+	reg := prometheus.NewPedanticRegistry()
 	{
 		db := new(sql.DB)
 		if err := reg.Register(NewDBStatsCollector(db, "db_A")); err != nil {
diff --git a/prometheus/collectors/gen_go_collector_set.go b/prometheus/collectors/gen_go_collector_set.go
new file mode 100644
index 000000000..6d1ff7e71
--- /dev/null
+++ prometheus/collectors/gen_go_collector_set.go
@@ -0,0 +1,189 @@
+// Copyright 2021 The Prometheus Authors
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+//go:build ignore
+// +build ignore
+
+package main
+
+import (
+	"bytes"
+	"fmt"
+	"go/format"
+	"log"
+	"os"
+	"regexp"
+	"runtime"
+	"runtime/metrics"
+	"sort"
+	"strings"
+	"text/template"
+
+	"github.com/prometheus/client_golang/prometheus"
+	"github.com/prometheus/client_golang/prometheus/internal"
+
+	version "github.com/hashicorp/go-version"
+)
+
+type metricGroup struct {
+	Name    string
+	Regex   *regexp.Regexp
+	Metrics []string
+}
+
+var metricGroups = []metricGroup{
+	{"withAllMetrics", nil, nil},
+	{"withGCMetrics", regexp.MustCompile("^go_gc_.*"), nil},
+	{"withMemoryMetrics", regexp.MustCompile("^go_memory_classes_.*"), nil},
+	{"withSchedulerMetrics", regexp.MustCompile("^go_sched_.*"), nil},
+	{"withDebugMetrics", regexp.MustCompile("^go_godebug_non_default_behavior_.*"), nil},
+}
+
+func main() {
+	var givenVersion string
+	toolVersion := runtime.Version()
+	if len(os.Args) != 2 {
+		log.Printf("requires Go version (e.g. go1.17) as an argument. Since it is not specified, assuming %s.", toolVersion)
+		givenVersion = toolVersion
+	} else {
+		givenVersion = os.Args[1]
+	}
+	log.Printf("given version for Go: %s", givenVersion)
+	log.Printf("tool version for Go: %s", toolVersion)
+
+	tv, err := version.NewVersion(strings.TrimPrefix(givenVersion, "go"))
+	if err != nil {
+		log.Fatal(err)
+	}
+
+	toolVersion = strings.Split(strings.TrimPrefix(toolVersion, "go"), " ")[0]
+	gv, err := version.NewVersion(toolVersion)
+	if err != nil {
+		log.Fatal(err)
+	}
+	if !gv.Equal(tv) {
+		log.Fatalf("using Go version %q but expected Go version %q", tv, gv)
+	}
+
+	v := goVersion(gv.Segments()[1])
+	log.Printf("generating metrics for Go version %q", v)
+
+	descriptions := computeMetricsList()
+	groupedMetrics := groupMetrics(descriptions)
+
+	// Generate code.
+	var buf bytes.Buffer
+	err = testFile.Execute(&buf, struct {
+		GoVersion goVersion
+		Groups    []metricGroup
+	}{
+		GoVersion: v,
+		Groups:    groupedMetrics,
+	})
+	if err != nil {
+		log.Fatalf("executing template: %v", err)
+	}
+
+	// Format it.
+	result, err := format.Source(buf.Bytes())
+	if err != nil {
+		log.Fatalf("formatting code: %v", err)
+	}
+
+	// Write it to a file.
+	fname := fmt.Sprintf("go_collector_%s_test.go", v.Abbr())
+	if err := os.WriteFile(fname, result, 0o644); err != nil {
+		log.Fatalf("writing file: %v", err)
+	}
+}
+
+func computeMetricsList() []string {
+	var metricsList []string
+	for _, d := range metrics.All() {
+		if trans := rm2prom(d); trans != "" {
+			metricsList = append(metricsList, trans)
+		}
+	}
+	return metricsList
+}
+
+func rm2prom(d metrics.Description) string {
+	ns, ss, n, ok := internal.RuntimeMetricsToProm(&d)
+	if !ok {
+		return ""
+	}
+	return prometheus.BuildFQName(ns, ss, n)
+}
+
+func groupMetrics(metricsList []string) []metricGroup {
+	var groupedMetrics []metricGroup
+	for _, group := range metricGroups {
+		matchedMetrics := make([]string, 0)
+		for _, metric := range metricsList {
+			if group.Regex == nil || group.Regex.MatchString(metric) {
+				matchedMetrics = append(matchedMetrics, metric)
+			}
+		}
+
+		sort.Strings(matchedMetrics)
+		groupedMetrics = append(groupedMetrics, metricGroup{
+			Name:    group.Name,
+			Regex:   group.Regex,
+			Metrics: matchedMetrics,
+		})
+	}
+	return groupedMetrics
+}
+
+type goVersion int
+
+func (g goVersion) String() string {
+	return fmt.Sprintf("go1.%d", g)
+}
+
+func (g goVersion) Abbr() string {
+	return fmt.Sprintf("go1%d", g)
+}
+
+var testFile = template.Must(template.New("testFile").Funcs(map[string]interface{}{
+	"nextVersion": func(version goVersion) string {
+		return (version + goVersion(1)).String()
+	},
+}).Parse(`// Copyright 2022 The Prometheus Authors
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+//go:build {{.GoVersion}} && !{{nextVersion .GoVersion}}
+// +build {{.GoVersion}},!{{nextVersion .GoVersion}}
+
+package collectors
+
+{{- range .Groups }}
+func {{ .Name }}() []string {
+	return withBaseMetrics([]string{
+		{{- range $metric := .Metrics }}
+			{{ $metric | printf "%q" }},
+		{{- end }}
+	})
+}
+{{ end }}
+`))
diff --git prometheus/collectors/go_collector_go117_test.go prometheus/collectors/go_collector_go117_test.go
index 370758da7..f378d4097 100644
--- prometheus/collectors/go_collector_go117_test.go
+++ prometheus/collectors/go_collector_go117_test.go
@@ -98,3 +98,13 @@ func withSchedulerMetrics() []string {
 		"go_threads",
 	}
 }
+
+func withDebugMetrics() []string {
+	return withBaseMetrics([]string{})
+}
+
+var defaultRuntimeMetrics = []string{}
+
+func withDefaultRuntimeMetrics(metricNames []string, withoutGC, withoutSched bool) []string {
+	return metricNames
+}
diff --git prometheus/collectors/go_collector_go119_test.go prometheus/collectors/go_collector_go119_test.go
index 4577a219c..4febad2ce 100644
--- prometheus/collectors/go_collector_go119_test.go
+++ prometheus/collectors/go_collector_go119_test.go
@@ -16,6 +16,8 @@
 
 package collectors
 
+import "sort"
+
 func withAllMetrics() []string {
 	return withBaseMetrics([]string{
 		"go_cgo_go_to_c_calls_calls_total",
@@ -105,3 +107,22 @@ func withSchedulerMetrics() []string {
 		"go_threads",
 	}
 }
+
+func withDebugMetrics() []string {
+	return withBaseMetrics([]string{})
+}
+
+var defaultRuntimeMetrics = []string{
+	"go_sched_gomaxprocs_threads",
+}
+
+func withDefaultRuntimeMetrics(metricNames []string, withoutGC, withoutSched bool) []string {
+	// If withoutSched is true, exclude "go_sched_gomaxprocs_threads".
+	if withoutSched {
+		return metricNames
+	}
+	metricNames = append(metricNames, defaultRuntimeMetrics...)
+	// sorting is required
+	sort.Strings(metricNames)
+	return metricNames
+}
diff --git prometheus/collectors/go_collector_go120_test.go prometheus/collectors/go_collector_go120_test.go
index 3bdb4d576..968a016ab 100644
--- prometheus/collectors/go_collector_go120_test.go
+++ prometheus/collectors/go_collector_go120_test.go
@@ -16,12 +16,11 @@
 
 package collectors
 
+import "sort"
+
 func withAllMetrics() []string {
 	return withBaseMetrics([]string{
 		"go_cgo_go_to_c_calls_calls_total",
-		"go_gc_cycles_automatic_gc_cycles_total",
-		"go_gc_cycles_forced_gc_cycles_total",
-		"go_gc_cycles_total_gc_cycles_total",
 		"go_cpu_classes_gc_mark_assist_cpu_seconds_total",
 		"go_cpu_classes_gc_mark_dedicated_cpu_seconds_total",
 		"go_cpu_classes_gc_mark_idle_cpu_seconds_total",
@@ -33,6 +32,9 @@ func withAllMetrics() []string {
 		"go_cpu_classes_scavenge_total_cpu_seconds_total",
 		"go_cpu_classes_total_cpu_seconds_total",
 		"go_cpu_classes_user_cpu_seconds_total",
+		"go_gc_cycles_automatic_gc_cycles_total",
+		"go_gc_cycles_forced_gc_cycles_total",
+		"go_gc_cycles_total_gc_cycles_total",
 		"go_gc_heap_allocs_by_size_bytes",
 		"go_gc_heap_allocs_bytes_total",
 		"go_gc_heap_allocs_objects_total",
@@ -106,14 +108,28 @@ func withMemoryMetrics() []string {
 }
 
 func withSchedulerMetrics() []string {
-	return []string{
-		"go_gc_duration_seconds",
-		"go_goroutines",
-		"go_info",
-		"go_memstats_last_gc_time_seconds",
+	return withBaseMetrics([]string{
 		"go_sched_gomaxprocs_threads",
 		"go_sched_goroutines_goroutines",
 		"go_sched_latencies_seconds",
-		"go_threads",
+	})
+}
+
+func withDebugMetrics() []string {
+	return withBaseMetrics([]string{})
+}
+
+var defaultRuntimeMetrics = []string{
+	"go_sched_gomaxprocs_threads",
+}
+
+func withDefaultRuntimeMetrics(metricNames []string, withoutGC, withoutSched bool) []string {
+	// If withoutSched is true, exclude "go_sched_gomaxprocs_threads".
+	if withoutSched {
+		return metricNames
 	}
+	metricNames = append(metricNames, defaultRuntimeMetrics...)
+	// sorting is required
+	sort.Strings(metricNames)
+	return metricNames
 }
diff --git prometheus/collectors/go_collector_go121_test.go prometheus/collectors/go_collector_go121_test.go
index 6facf6798..f9cc318cc 100644
--- prometheus/collectors/go_collector_go121_test.go
+++ prometheus/collectors/go_collector_go121_test.go
@@ -16,6 +16,8 @@
 
 package collectors
 
+import "sort"
+
 func withAllMetrics() []string {
 	return withBaseMetrics([]string{
 		"go_cgo_go_to_c_calls_calls_total",
@@ -63,6 +65,7 @@ func withAllMetrics() []string {
 		"go_godebug_non_default_behavior_multipartmaxheaders_events_total",
 		"go_godebug_non_default_behavior_multipartmaxparts_events_total",
 		"go_godebug_non_default_behavior_multipathtcp_events_total",
+		"go_godebug_non_default_behavior_netedns0_events_total",
 		"go_godebug_non_default_behavior_panicnil_events_total",
 		"go_godebug_non_default_behavior_randautoseed_events_total",
 		"go_godebug_non_default_behavior_tarinsecurepath_events_total",
@@ -138,20 +141,15 @@ func withMemoryMetrics() []string {
 }
 
 func withSchedulerMetrics() []string {
-	return []string{
-		"go_gc_duration_seconds",
-		"go_goroutines",
-		"go_info",
-		"go_memstats_last_gc_time_seconds",
+	return withBaseMetrics([]string{
 		"go_sched_gomaxprocs_threads",
 		"go_sched_goroutines_goroutines",
 		"go_sched_latencies_seconds",
-		"go_threads",
-	}
+	})
 }
 
 func withDebugMetrics() []string {
-	return []string{
+	return withBaseMetrics([]string{
 		"go_godebug_non_default_behavior_execerrdot_events_total",
 		"go_godebug_non_default_behavior_gocachehash_events_total",
 		"go_godebug_non_default_behavior_gocachetest_events_total",
@@ -163,6 +161,7 @@ func withDebugMetrics() []string {
 		"go_godebug_non_default_behavior_multipartmaxheaders_events_total",
 		"go_godebug_non_default_behavior_multipartmaxparts_events_total",
 		"go_godebug_non_default_behavior_multipathtcp_events_total",
+		"go_godebug_non_default_behavior_netedns0_events_total",
 		"go_godebug_non_default_behavior_panicnil_events_total",
 		"go_godebug_non_default_behavior_randautoseed_events_total",
 		"go_godebug_non_default_behavior_tarinsecurepath_events_total",
@@ -170,5 +169,30 @@ func withDebugMetrics() []string {
 		"go_godebug_non_default_behavior_x509sha1_events_total",
 		"go_godebug_non_default_behavior_x509usefallbackroots_events_total",
 		"go_godebug_non_default_behavior_zipinsecurepath_events_total",
+	})
+}
+
+var defaultRuntimeMetrics = []string{
+	"go_gc_gogc_percent",
+	"go_gc_gomemlimit_bytes",
+	"go_sched_gomaxprocs_threads",
+}
+
+func withDefaultRuntimeMetrics(metricNames []string, withoutGC, withoutSched bool) []string {
+	if withoutGC && withoutSched {
+		// If both flags are true, return the metricNames as is.
+		return metricNames
+	} else if withoutGC && !withoutSched {
+		// If only withoutGC is true, include "go_sched_gomaxprocs_threads" only.
+		metricNames = append(metricNames, []string{"go_sched_gomaxprocs_threads"}...)
+	} else if withoutSched && !withoutGC {
+		// If only withoutSched is true, exclude "go_sched_gomaxprocs_threads".
+		metricNames = append(metricNames, []string{"go_gc_gogc_percent", "go_gc_gomemlimit_bytes"}...)
+	} else {
+		// If neither flag is true, use the default metrics.
+		metricNames = append(metricNames, defaultRuntimeMetrics...)
 	}
+	// sorting is required
+	sort.Strings(metricNames)
+	return metricNames
 }
diff --git prometheus/collectors/go_collector_go122_test.go prometheus/collectors/go_collector_go122_test.go
index 0b3c37adf..2413c2750 100644
--- prometheus/collectors/go_collector_go122_test.go
+++ prometheus/collectors/go_collector_go122_test.go
@@ -16,6 +16,8 @@
 
 package collectors
 
+import "sort"
+
 func withAllMetrics() []string {
 	return withBaseMetrics([]string{
 		"go_cgo_go_to_c_calls_calls_total",
@@ -66,6 +68,7 @@ func withAllMetrics() []string {
 		"go_godebug_non_default_behavior_multipartmaxheaders_events_total",
 		"go_godebug_non_default_behavior_multipartmaxparts_events_total",
 		"go_godebug_non_default_behavior_multipathtcp_events_total",
+		"go_godebug_non_default_behavior_netedns0_events_total",
 		"go_godebug_non_default_behavior_panicnil_events_total",
 		"go_godebug_non_default_behavior_randautoseed_events_total",
 		"go_godebug_non_default_behavior_tarinsecurepath_events_total",
@@ -149,11 +152,7 @@ func withMemoryMetrics() []string {
 }
 
 func withSchedulerMetrics() []string {
-	return []string{
-		"go_gc_duration_seconds",
-		"go_goroutines",
-		"go_info",
-		"go_memstats_last_gc_time_seconds",
+	return withBaseMetrics([]string{
 		"go_sched_gomaxprocs_threads",
 		"go_sched_goroutines_goroutines",
 		"go_sched_latencies_seconds",
@@ -161,12 +160,11 @@ func withSchedulerMetrics() []string {
 		"go_sched_pauses_stopping_other_seconds",
 		"go_sched_pauses_total_gc_seconds",
 		"go_sched_pauses_total_other_seconds",
-		"go_threads",
-	}
+	})
 }
 
 func withDebugMetrics() []string {
-	return []string{
+	return withBaseMetrics([]string{
 		"go_godebug_non_default_behavior_execerrdot_events_total",
 		"go_godebug_non_default_behavior_gocachehash_events_total",
 		"go_godebug_non_default_behavior_gocachetest_events_total",
@@ -181,6 +179,7 @@ func withDebugMetrics() []string {
 		"go_godebug_non_default_behavior_multipartmaxheaders_events_total",
 		"go_godebug_non_default_behavior_multipartmaxparts_events_total",
 		"go_godebug_non_default_behavior_multipathtcp_events_total",
+		"go_godebug_non_default_behavior_netedns0_events_total",
 		"go_godebug_non_default_behavior_panicnil_events_total",
 		"go_godebug_non_default_behavior_randautoseed_events_total",
 		"go_godebug_non_default_behavior_tarinsecurepath_events_total",
@@ -192,5 +191,30 @@ func withDebugMetrics() []string {
 		"go_godebug_non_default_behavior_x509usefallbackroots_events_total",
 		"go_godebug_non_default_behavior_x509usepolicies_events_total",
 		"go_godebug_non_default_behavior_zipinsecurepath_events_total",
+	})
+}
+
+var defaultRuntimeMetrics = []string{
+	"go_gc_gogc_percent",
+	"go_gc_gomemlimit_bytes",
+	"go_sched_gomaxprocs_threads",
+}
+
+func withDefaultRuntimeMetrics(metricNames []string, withoutGC, withoutSched bool) []string {
+	if withoutGC && withoutSched {
+		// If both flags are true, return the metricNames as is.
+		return metricNames
+	} else if withoutGC && !withoutSched {
+		// If only withoutGC is true, include "go_sched_gomaxprocs_threads" only.
+		metricNames = append(metricNames, []string{"go_sched_gomaxprocs_threads"}...)
+	} else if withoutSched && !withoutGC {
+		// If only withoutSched is true, exclude "go_sched_gomaxprocs_threads".
+		metricNames = append(metricNames, []string{"go_gc_gogc_percent", "go_gc_gomemlimit_bytes"}...)
+	} else {
+		// If neither flag is true, use the default metrics.
+		metricNames = append(metricNames, defaultRuntimeMetrics...)
 	}
+	// sorting is required
+	sort.Strings(metricNames)
+	return metricNames
 }
diff --git prometheus/collectors/go_collector_latest.go prometheus/collectors/go_collector_latest.go
index bcfa4fa10..cc4ef1077 100644
--- prometheus/collectors/go_collector_latest.go
+++ prometheus/collectors/go_collector_latest.go
@@ -37,6 +37,9 @@ var (
 	// MetricsScheduler allows only scheduler metrics to be collected from Go runtime.
 	// e.g. go_sched_goroutines_goroutines
 	MetricsScheduler = GoRuntimeMetricsRule{regexp.MustCompile(`^/sched/.*`)}
+	// MetricsDebug allows only debug metrics to be collected from Go runtime.
+	// e.g. go_godebug_non_default_behavior_gocachetest_events_total
+	MetricsDebug = GoRuntimeMetricsRule{regexp.MustCompile(`^/godebug/.*`)}
 )
 
 // WithGoCollectorMemStatsMetricsDisabled disables metrics that is gathered in runtime.MemStats structure such as:
@@ -44,7 +47,6 @@ var (
 // go_memstats_alloc_bytes
 // go_memstats_alloc_bytes_total
 // go_memstats_sys_bytes
-// go_memstats_lookups_total
 // go_memstats_mallocs_total
 // go_memstats_frees_total
 // go_memstats_heap_alloc_bytes
diff --git prometheus/collectors/go_collector_latest_test.go prometheus/collectors/go_collector_latest_test.go
index ebde89118..2f5440abb 100644
--- prometheus/collectors/go_collector_latest_test.go
+++ prometheus/collectors/go_collector_latest_test.go
@@ -20,11 +20,12 @@ import (
 	"encoding/json"
 	"log"
 	"net/http"
-	"reflect"
 	"regexp"
 	"sort"
 	"testing"
 
+	"github.com/google/go-cmp/cmp"
+
 	"github.com/prometheus/client_golang/prometheus"
 	"github.com/prometheus/client_golang/prometheus/promhttp"
 )
@@ -37,8 +38,32 @@ var baseMetrics = []string{
 	"go_threads",
 }
 
+var memstatMetrics = []string{
+	"go_memstats_alloc_bytes",
+	"go_memstats_alloc_bytes_total",
+	"go_memstats_buck_hash_sys_bytes",
+	"go_memstats_frees_total",
+	"go_memstats_gc_sys_bytes",
+	"go_memstats_heap_alloc_bytes",
+	"go_memstats_heap_idle_bytes",
+	"go_memstats_heap_inuse_bytes",
+	"go_memstats_heap_objects",
+	"go_memstats_heap_released_bytes",
+	"go_memstats_heap_sys_bytes",
+	"go_memstats_mallocs_total",
+	"go_memstats_mcache_inuse_bytes",
+	"go_memstats_mcache_sys_bytes",
+	"go_memstats_mspan_inuse_bytes",
+	"go_memstats_mspan_sys_bytes",
+	"go_memstats_next_gc_bytes",
+	"go_memstats_other_sys_bytes",
+	"go_memstats_stack_inuse_bytes",
+	"go_memstats_stack_sys_bytes",
+	"go_memstats_sys_bytes",
+}
+
 func TestGoCollectorMarshalling(t *testing.T) {
-	reg := prometheus.NewRegistry()
+	reg := prometheus.NewPedanticRegistry()
 	reg.MustRegister(NewGoCollector(
 		WithGoCollectorRuntimeMetrics(GoRuntimeMetricsRule{
 			Matcher: regexp.MustCompile("/.*"),
@@ -54,8 +79,28 @@ func TestGoCollectorMarshalling(t *testing.T) {
 	}
 }
 
+func TestWithGoCollectorDefault(t *testing.T) {
+	reg := prometheus.NewPedanticRegistry()
+	reg.MustRegister(NewGoCollector())
+	result, err := reg.Gather()
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	got := []string{}
+	for _, r := range result {
+		got = append(got, r.GetName())
+	}
+
+	expected := append(withBaseMetrics(memstatMetrics), defaultRuntimeMetrics...)
+	sort.Strings(expected)
+	if diff := cmp.Diff(got, expected); diff != "" {
+		t.Errorf("[IMPORTANT, those are default metrics, can't change in 1.x] missmatch (-want +got):\n%s", diff)
+	}
+}
+
 func TestWithGoCollectorMemStatsMetricsDisabled(t *testing.T) {
-	reg := prometheus.NewRegistry()
+	reg := prometheus.NewPedanticRegistry()
 	reg.MustRegister(NewGoCollector(
 		WithGoCollectorMemStatsMetricsDisabled(),
 	))
@@ -69,8 +114,8 @@ func TestWithGoCollectorMemStatsMetricsDisabled(t *testing.T) {
 		got = append(got, r.GetName())
 	}
 
-	if !reflect.DeepEqual(got, baseMetrics) {
-		t.Errorf("got %v, want %v", got, baseMetrics)
+	if diff := cmp.Diff(got, withBaseMetrics(defaultRuntimeMetrics)); diff != "" {
+		t.Errorf("missmatch (-want +got):\n%s", diff)
 	}
 }
 
@@ -83,7 +128,7 @@ func TestGoCollectorAllowList(t *testing.T) {
 		{
 			name:     "Without any rules",
 			rules:    nil,
-			expected: baseMetrics,
+			expected: withBaseMetrics(defaultRuntimeMetrics),
 		},
 		{
 			name:     "allow all",
@@ -93,21 +138,26 @@ func TestGoCollectorAllowList(t *testing.T) {
 		{
 			name:     "allow GC",
 			rules:    []GoRuntimeMetricsRule{MetricsGC},
-			expected: withGCMetrics(),
+			expected: withDefaultRuntimeMetrics(withGCMetrics(), true, false),
 		},
 		{
 			name:     "allow Memory",
 			rules:    []GoRuntimeMetricsRule{MetricsMemory},
-			expected: withMemoryMetrics(),
+			expected: withDefaultRuntimeMetrics(withMemoryMetrics(), false, false),
 		},
 		{
 			name:     "allow Scheduler",
 			rules:    []GoRuntimeMetricsRule{MetricsScheduler},
-			expected: withSchedulerMetrics(),
+			expected: withDefaultRuntimeMetrics(withSchedulerMetrics(), false, true),
+		},
+		{
+			name:     "allow debug",
+			rules:    []GoRuntimeMetricsRule{MetricsDebug},
+			expected: withDefaultRuntimeMetrics(withDebugMetrics(), false, false),
 		},
 	} {
 		t.Run(test.name, func(t *testing.T) {
-			reg := prometheus.NewRegistry()
+			reg := prometheus.NewPedanticRegistry()
 			reg.MustRegister(NewGoCollector(
 				WithGoCollectorMemStatsMetricsDisabled(),
 				WithGoCollectorRuntimeMetrics(test.rules...),
@@ -122,8 +172,8 @@ func TestGoCollectorAllowList(t *testing.T) {
 				got = append(got, r.GetName())
 			}
 
-			if !reflect.DeepEqual(got, test.expected) {
-				t.Errorf("got %v, want %v", got, test.expected)
+			if diff := cmp.Diff(got, test.expected); diff != "" {
+				t.Errorf("missmatch (-want +got):\n%s", diff)
 			}
 		})
 	}
@@ -144,7 +194,7 @@ func TestGoCollectorDenyList(t *testing.T) {
 		{
 			name:     "Without any matchers",
 			matchers: nil,
-			expected: baseMetrics,
+			expected: withBaseMetrics(defaultRuntimeMetrics),
 		},
 		{
 			name:     "deny all",
@@ -157,11 +207,19 @@ func TestGoCollectorDenyList(t *testing.T) {
 				regexp.MustCompile("^/gc/.*"),
 				regexp.MustCompile("^/sched/latencies:.*"),
 			},
+			expected: withDefaultRuntimeMetrics(baseMetrics, true, false),
+		},
+		{
+			name: "deny gc and scheduler",
+			matchers: []*regexp.Regexp{
+				regexp.MustCompile("^/gc/.*"),
+				regexp.MustCompile("^/sched/.*"),
+			},
 			expected: baseMetrics,
 		},
 	} {
 		t.Run(test.name, func(t *testing.T) {
-			reg := prometheus.NewRegistry()
+			reg := prometheus.NewPedanticRegistry()
 			reg.MustRegister(NewGoCollector(
 				WithGoCollectorMemStatsMetricsDisabled(),
 				WithoutGoCollectorRuntimeMetrics(test.matchers...),
@@ -176,17 +234,17 @@ func TestGoCollectorDenyList(t *testing.T) {
 				got = append(got, r.GetName())
 			}
 
-			if !reflect.DeepEqual(got, test.expected) {
-				t.Errorf("got %v, want %v", got, test.expected)
+			if diff := cmp.Diff(got, test.expected); diff != "" {
+				t.Errorf("missmatch (-want +got):\n%s", diff)
 			}
 		})
 	}
 }
 
 func ExampleGoCollector() {
-	reg := prometheus.NewRegistry()
+	reg := prometheus.NewPedanticRegistry()
 
-	// Register the GoCollector with the default options. Only the base metrics will be enabled.
+	// Register the GoCollector with the default options. Only the base metrics, default runtime metrics and memstats are enabled.
 	reg.MustRegister(NewGoCollector())
 
 	http.Handle("/metrics", promhttp.HandlerFor(reg, promhttp.HandlerOpts{}))
@@ -194,7 +252,7 @@ func ExampleGoCollector() {
 }
 
 func ExampleGoCollector_WithAdvancedGoMetrics() {
-	reg := prometheus.NewRegistry()
+	reg := prometheus.NewPedanticRegistry()
 
 	// Enable Go metrics with pre-defined rules. Or your custom rules.
 	reg.MustRegister(
diff --git prometheus/examples_test.go prometheus/examples_test.go
index e4fed3e95..fdbb6f747 100644
--- prometheus/examples_test.go
+++ prometheus/examples_test.go
@@ -405,6 +405,35 @@ func ExampleNewConstSummary() {
 	// {"label":[{"name":"code","value":"200"},{"name":"method","value":"get"},{"name":"owner","value":"example"}],"summary":{"sampleCount":"4711","sampleSum":403.34,"quantile":[{"quantile":0.5,"value":42.3},{"quantile":0.9,"value":323.3}]}}
 }
 
+func ExampleNewConstSummaryWithCreatedTimestamp() {
+	desc := prometheus.NewDesc(
+		"http_request_duration_seconds",
+		"A summary of the HTTP request durations.",
+		[]string{"code", "method"},
+		prometheus.Labels{"owner": "example"},
+	)
+
+	// Create a constant summary with created timestamp set
+	createdTs := time.Unix(1719670764, 123)
+	s := prometheus.MustNewConstSummaryWithCreatedTimestamp(
+		desc,
+		4711, 403.34,
+		map[float64]float64{0.5: 42.3, 0.9: 323.3},
+		createdTs,
+		"200", "get",
+	)
+
+	// Just for demonstration, let's check the state of the summary by
+	// (ab)using its Write method (which is usually only used by Prometheus
+	// internally).
+	metric := &dto.Metric{}
+	s.Write(metric)
+	fmt.Println(toNormalizedJSON(metric))
+
+	// Output:
+	// {"label":[{"name":"code","value":"200"},{"name":"method","value":"get"},{"name":"owner","value":"example"}],"summary":{"sampleCount":"4711","sampleSum":403.34,"quantile":[{"quantile":0.5,"value":42.3},{"quantile":0.9,"value":323.3}],"createdTimestamp":"2024-06-29T14:19:24.000000123Z"}}
+}
+
 func ExampleHistogram() {
 	temps := prometheus.NewHistogram(prometheus.HistogramOpts{
 		Name:    "pond_temperature_celsius",
@@ -456,6 +485,34 @@ func ExampleNewConstHistogram() {
 	// {"label":[{"name":"code","value":"200"},{"name":"method","value":"get"},{"name":"owner","value":"example"}],"histogram":{"sampleCount":"4711","sampleSum":403.34,"bucket":[{"cumulativeCount":"121","upperBound":25},{"cumulativeCount":"2403","upperBound":50},{"cumulativeCount":"3221","upperBound":100},{"cumulativeCount":"4233","upperBound":200}]}}
 }
 
+func ExampleNewConstHistogramWithCreatedTimestamp() {
+	desc := prometheus.NewDesc(
+		"http_request_duration_seconds",
+		"A histogram of the HTTP request durations.",
+		[]string{"code", "method"},
+		prometheus.Labels{"owner": "example"},
+	)
+
+	createdTs := time.Unix(1719670764, 123)
+	h := prometheus.MustNewConstHistogramWithCreatedTimestamp(
+		desc,
+		4711, 403.34,
+		map[float64]uint64{25: 121, 50: 2403, 100: 3221, 200: 4233},
+		createdTs,
+		"200", "get",
+	)
+
+	// Just for demonstration, let's check the state of the histogram by
+	// (ab)using its Write method (which is usually only used by Prometheus
+	// internally).
+	metric := &dto.Metric{}
+	h.Write(metric)
+	fmt.Println(toNormalizedJSON(metric))
+
+	// Output:
+	// {"label":[{"name":"code","value":"200"},{"name":"method","value":"get"},{"name":"owner","value":"example"}],"histogram":{"sampleCount":"4711","sampleSum":403.34,"bucket":[{"cumulativeCount":"121","upperBound":25},{"cumulativeCount":"2403","upperBound":50},{"cumulativeCount":"3221","upperBound":100},{"cumulativeCount":"4233","upperBound":200}],"createdTimestamp":"2024-06-29T14:19:24.000000123Z"}}
+}
+
 func ExampleNewConstHistogram_WithExemplar() {
 	desc := prometheus.NewDesc(
 		"http_request_duration_seconds",
diff --git prometheus/gen_go_collector_metrics_set.go prometheus/gen_go_collector_metrics_set.go
index 2622b71e4..f1db3d8d1 100644
--- prometheus/gen_go_collector_metrics_set.go
+++ prometheus/gen_go_collector_metrics_set.go
@@ -63,16 +63,29 @@ func main() {
 	v := goVersion(gv.Segments()[1])
 	log.Printf("generating metrics for Go version %q", v)
 
+	allDesc := metrics.All()
+
+	// Find default metrics.
+	var defaultDesc []metrics.Description
+	for _, d := range allDesc {
+		if !internal.GoCollectorDefaultRuntimeMetrics.MatchString(d.Name) {
+			continue
+		}
+		defaultDesc = append(defaultDesc, d)
+	}
+
 	// Generate code.
 	var buf bytes.Buffer
 	err = testFile.Execute(&buf, struct {
-		Descriptions []metrics.Description
-		GoVersion    goVersion
-		Cardinality  int
+		AllDescriptions     []metrics.Description
+		DefaultDescriptions []metrics.Description
+		GoVersion           goVersion
+		Cardinality         int
 	}{
-		Descriptions: metrics.All(),
-		GoVersion:    v,
-		Cardinality:  rmCardinality(),
+		AllDescriptions:     allDesc,
+		DefaultDescriptions: defaultDesc,
+		GoVersion:           v,
+		Cardinality:         rmCardinality(),
 	})
 	if err != nil {
 		log.Fatalf("executing template: %v", err)
@@ -167,14 +180,25 @@ var testFile = template.Must(template.New("testFile").Funcs(map[string]interface
 
 package prometheus
 
-var expectedRuntimeMetrics = map[string]string{
-{{- range .Descriptions -}}
+var (
+	expectedRuntimeMetrics = map[string]string{
+{{- range .AllDescriptions -}}
 	{{- $trans := rm2prom . -}}
 	{{- if ne $trans "" }}
 	{{.Name | printf "%q"}}: {{$trans | printf "%q"}},
 	{{- end -}}
 {{end}}
-}
+	}
+
+	expMetrics = map[string]string{
+{{- range .DefaultDescriptions -}}
+	{{- $trans := rm2prom . -}}
+	{{- if ne $trans "" }}
+	{{.Name | printf "%q"}}: {{$trans | printf "%q"}},
+	{{- end -}}
+{{end}}
+	}
+)
 
 const expectedRuntimeMetricsCardinality = {{.Cardinality}}
 `))
diff --git prometheus/go_collector.go prometheus/go_collector.go
index ad9a71a5e..520cbd7d4 100644
--- prometheus/go_collector.go
+++ prometheus/go_collector.go
@@ -22,13 +22,13 @@ import (
 // goRuntimeMemStats provides the metrics initially provided by runtime.ReadMemStats.
 // From Go 1.17 those similar (and better) statistics are provided by runtime/metrics, so
 // while eval closure works on runtime.MemStats, the struct from Go 1.17+ is
-// populated using runtime/metrics.
+// populated using runtime/metrics. Those are the defaults we can't alter.
 func goRuntimeMemStats() memStatsMetrics {
 	return memStatsMetrics{
 		{
 			desc: NewDesc(
 				memstatNamespace("alloc_bytes"),
-				"Number of bytes allocated and still in use.",
+				"Number of bytes allocated in heap and currently in use. Equals to /memory/classes/heap/objects:bytes.",
 				nil, nil,
 			),
 			eval:    func(ms *runtime.MemStats) float64 { return float64(ms.Alloc) },
@@ -36,7 +36,7 @@ func goRuntimeMemStats() memStatsMetrics {
 		}, {
 			desc: NewDesc(
 				memstatNamespace("alloc_bytes_total"),
-				"Total number of bytes allocated, even if freed.",
+				"Total number of bytes allocated in heap until now, even if released already. Equals to /gc/heap/allocs:bytes.",
 				nil, nil,
 			),
 			eval:    func(ms *runtime.MemStats) float64 { return float64(ms.TotalAlloc) },
@@ -44,23 +44,16 @@ func goRuntimeMemStats() memStatsMetrics {
 		}, {
 			desc: NewDesc(
 				memstatNamespace("sys_bytes"),
-				"Number of bytes obtained from system.",
+				"Number of bytes obtained from system. Equals to /memory/classes/total:byte.",
 				nil, nil,
 			),
 			eval:    func(ms *runtime.MemStats) float64 { return float64(ms.Sys) },
 			valType: GaugeValue,
-		}, {
-			desc: NewDesc(
-				memstatNamespace("lookups_total"),
-				"Total number of pointer lookups.",
-				nil, nil,
-			),
-			eval:    func(ms *runtime.MemStats) float64 { return float64(ms.Lookups) },
-			valType: CounterValue,
 		}, {
 			desc: NewDesc(
 				memstatNamespace("mallocs_total"),
-				"Total number of mallocs.",
+				// TODO(bwplotka): We could add go_memstats_heap_objects, probably useful for discovery. Let's gather more feedback, kind of a waste of bytes for everybody for compatibility reasons to keep both, and we can't really rename/remove useful metric.
+				"Total number of heap objects allocated, both live and gc-ed. Semantically a counter version for go_memstats_heap_objects gauge. Equals to /gc/heap/allocs:objects + /gc/heap/tiny/allocs:objects.",
 				nil, nil,
 			),
 			eval:    func(ms *runtime.MemStats) float64 { return float64(ms.Mallocs) },
@@ -68,7 +61,7 @@ func goRuntimeMemStats() memStatsMetrics {
 		}, {
 			desc: NewDesc(
 				memstatNamespace("frees_total"),
-				"Total number of frees.",
+				"Total number of heap objects frees. Equals to /gc/heap/frees:objects + /gc/heap/tiny/allocs:objects.",
 				nil, nil,
 			),
 			eval:    func(ms *runtime.MemStats) float64 { return float64(ms.Frees) },
@@ -76,7 +69,7 @@ func goRuntimeMemStats() memStatsMetrics {
 		}, {
 			desc: NewDesc(
 				memstatNamespace("heap_alloc_bytes"),
-				"Number of heap bytes allocated and still in use.",
+				"Number of heap bytes allocated and currently in use, same as go_memstats_alloc_bytes. Equals to /memory/classes/heap/objects:bytes.",
 				nil, nil,
 			),
 			eval:    func(ms *runtime.MemStats) float64 { return float64(ms.HeapAlloc) },
@@ -84,7 +77,7 @@ func goRuntimeMemStats() memStatsMetrics {
 		}, {
 			desc: NewDesc(
 				memstatNamespace("heap_sys_bytes"),
-				"Number of heap bytes obtained from system.",
+				"Number of heap bytes obtained from system. Equals to /memory/classes/heap/objects:bytes + /memory/classes/heap/unused:bytes + /memory/classes/heap/released:bytes + /memory/classes/heap/free:bytes.",
 				nil, nil,
 			),
 			eval:    func(ms *runtime.MemStats) float64 { return float64(ms.HeapSys) },
@@ -92,7 +85,7 @@ func goRuntimeMemStats() memStatsMetrics {
 		}, {
 			desc: NewDesc(
 				memstatNamespace("heap_idle_bytes"),
-				"Number of heap bytes waiting to be used.",
+				"Number of heap bytes waiting to be used. Equals to /memory/classes/heap/released:bytes + /memory/classes/heap/free:bytes.",
 				nil, nil,
 			),
 			eval:    func(ms *runtime.MemStats) float64 { return float64(ms.HeapIdle) },
@@ -100,7 +93,7 @@ func goRuntimeMemStats() memStatsMetrics {
 		}, {
 			desc: NewDesc(
 				memstatNamespace("heap_inuse_bytes"),
-				"Number of heap bytes that are in use.",
+				"Number of heap bytes that are in use. Equals to /memory/classes/heap/objects:bytes + /memory/classes/heap/unused:bytes",
 				nil, nil,
 			),
 			eval:    func(ms *runtime.MemStats) float64 { return float64(ms.HeapInuse) },
@@ -108,7 +101,7 @@ func goRuntimeMemStats() memStatsMetrics {
 		}, {
 			desc: NewDesc(
 				memstatNamespace("heap_released_bytes"),
-				"Number of heap bytes released to OS.",
+				"Number of heap bytes released to OS. Equals to /memory/classes/heap/released:bytes.",
 				nil, nil,
 			),
 			eval:    func(ms *runtime.MemStats) float64 { return float64(ms.HeapReleased) },
@@ -116,7 +109,7 @@ func goRuntimeMemStats() memStatsMetrics {
 		}, {
 			desc: NewDesc(
 				memstatNamespace("heap_objects"),
-				"Number of allocated objects.",
+				"Number of currently allocated objects. Equals to /gc/heap/objects:objects.",
 				nil, nil,
 			),
 			eval:    func(ms *runtime.MemStats) float64 { return float64(ms.HeapObjects) },
@@ -124,7 +117,7 @@ func goRuntimeMemStats() memStatsMetrics {
 		}, {
 			desc: NewDesc(
 				memstatNamespace("stack_inuse_bytes"),
-				"Number of bytes in use by the stack allocator.",
+				"Number of bytes obtained from system for stack allocator in non-CGO environments. Equals to /memory/classes/heap/stacks:bytes.",
 				nil, nil,
 			),
 			eval:    func(ms *runtime.MemStats) float64 { return float64(ms.StackInuse) },
@@ -132,7 +125,7 @@ func goRuntimeMemStats() memStatsMetrics {
 		}, {
 			desc: NewDesc(
 				memstatNamespace("stack_sys_bytes"),
-				"Number of bytes obtained from system for stack allocator.",
+				"Number of bytes obtained from system for stack allocator. Equals to /memory/classes/heap/stacks:bytes + /memory/classes/os-stacks:bytes.",
 				nil, nil,
 			),
 			eval:    func(ms *runtime.MemStats) float64 { return float64(ms.StackSys) },
@@ -140,7 +133,7 @@ func goRuntimeMemStats() memStatsMetrics {
 		}, {
 			desc: NewDesc(
 				memstatNamespace("mspan_inuse_bytes"),
-				"Number of bytes in use by mspan structures.",
+				"Number of bytes in use by mspan structures. Equals to /memory/classes/metadata/mspan/inuse:bytes.",
 				nil, nil,
 			),
 			eval:    func(ms *runtime.MemStats) float64 { return float64(ms.MSpanInuse) },
@@ -148,7 +141,7 @@ func goRuntimeMemStats() memStatsMetrics {
 		}, {
 			desc: NewDesc(
 				memstatNamespace("mspan_sys_bytes"),
-				"Number of bytes used for mspan structures obtained from system.",
+				"Number of bytes used for mspan structures obtained from system. Equals to /memory/classes/metadata/mspan/inuse:bytes + /memory/classes/metadata/mspan/free:bytes.",
 				nil, nil,
 			),
 			eval:    func(ms *runtime.MemStats) float64 { return float64(ms.MSpanSys) },
@@ -156,7 +149,7 @@ func goRuntimeMemStats() memStatsMetrics {
 		}, {
 			desc: NewDesc(
 				memstatNamespace("mcache_inuse_bytes"),
-				"Number of bytes in use by mcache structures.",
+				"Number of bytes in use by mcache structures. Equals to /memory/classes/metadata/mcache/inuse:bytes.",
 				nil, nil,
 			),
 			eval:    func(ms *runtime.MemStats) float64 { return float64(ms.MCacheInuse) },
@@ -164,7 +157,7 @@ func goRuntimeMemStats() memStatsMetrics {
 		}, {
 			desc: NewDesc(
 				memstatNamespace("mcache_sys_bytes"),
-				"Number of bytes used for mcache structures obtained from system.",
+				"Number of bytes used for mcache structures obtained from system. Equals to /memory/classes/metadata/mcache/inuse:bytes + /memory/classes/metadata/mcache/free:bytes.",
 				nil, nil,
 			),
 			eval:    func(ms *runtime.MemStats) float64 { return float64(ms.MCacheSys) },
@@ -172,7 +165,7 @@ func goRuntimeMemStats() memStatsMetrics {
 		}, {
 			desc: NewDesc(
 				memstatNamespace("buck_hash_sys_bytes"),
-				"Number of bytes used by the profiling bucket hash table.",
+				"Number of bytes used by the profiling bucket hash table. Equals to /memory/classes/profiling/buckets:bytes.",
 				nil, nil,
 			),
 			eval:    func(ms *runtime.MemStats) float64 { return float64(ms.BuckHashSys) },
@@ -180,7 +173,7 @@ func goRuntimeMemStats() memStatsMetrics {
 		}, {
 			desc: NewDesc(
 				memstatNamespace("gc_sys_bytes"),
-				"Number of bytes used for garbage collection system metadata.",
+				"Number of bytes used for garbage collection system metadata. Equals to /memory/classes/metadata/other:bytes.",
 				nil, nil,
 			),
 			eval:    func(ms *runtime.MemStats) float64 { return float64(ms.GCSys) },
@@ -188,7 +181,7 @@ func goRuntimeMemStats() memStatsMetrics {
 		}, {
 			desc: NewDesc(
 				memstatNamespace("other_sys_bytes"),
-				"Number of bytes used for other system allocations.",
+				"Number of bytes used for other system allocations. Equals to /memory/classes/other:bytes.",
 				nil, nil,
 			),
 			eval:    func(ms *runtime.MemStats) float64 { return float64(ms.OtherSys) },
@@ -196,7 +189,7 @@ func goRuntimeMemStats() memStatsMetrics {
 		}, {
 			desc: NewDesc(
 				memstatNamespace("next_gc_bytes"),
-				"Number of heap bytes when next garbage collection will take place.",
+				"Number of heap bytes when next garbage collection will take place. Equals to /gc/heap/goal:bytes.",
 				nil, nil,
 			),
 			eval:    func(ms *runtime.MemStats) float64 { return float64(ms.NextGC) },
@@ -225,7 +218,7 @@ func newBaseGoCollector() baseGoCollector {
 			nil, nil),
 		gcDesc: NewDesc(
 			"go_gc_duration_seconds",
-			"A summary of the pause duration of garbage collection cycles.",
+			"A summary of the wall-time pause (stop-the-world) duration in garbage collection cycles.",
 			nil, nil),
 		gcLastTimeDesc: NewDesc(
 			"go_memstats_last_gc_time_seconds",
diff --git prometheus/go_collector_latest.go prometheus/go_collector_latest.go
index 2d8d9f64f..511746417 100644
--- prometheus/go_collector_latest.go
+++ prometheus/go_collector_latest.go
@@ -17,6 +17,7 @@
 package prometheus
 
 import (
+	"fmt"
 	"math"
 	"runtime"
 	"runtime/metrics"
@@ -153,7 +154,8 @@ func defaultGoCollectorOptions() internal.GoCollectorOptions {
 			"/gc/heap/frees-by-size:bytes":  goGCHeapFreesBytes,
 		},
 		RuntimeMetricRules: []internal.GoCollectorRule{
-			//{Matcher: regexp.MustCompile("")},
+			// Recommended metrics we want by default from runtime/metrics.
+			{Matcher: internal.GoCollectorDefaultRuntimeMetrics},
 		},
 	}
 }
@@ -203,6 +205,7 @@ func NewGoCollector(opts ...func(o *internal.GoCollectorOptions)) Collector {
 			// to fail here. This condition is tested in TestExpectedRuntimeMetrics.
 			continue
 		}
+		help := attachOriginalName(d.Description.Description, d.Name)
 
 		sampleBuf = append(sampleBuf, metrics.Sample{Name: d.Name})
 		sampleMap[d.Name] = &sampleBuf[len(sampleBuf)-1]
@@ -214,7 +217,7 @@ func NewGoCollector(opts ...func(o *internal.GoCollectorOptions)) Collector {
 			m = newBatchHistogram(
 				NewDesc(
 					BuildFQName(namespace, subsystem, name),
-					d.Description.Description,
+					help,
 					nil,
 					nil,
 				),
@@ -226,7 +229,7 @@ func NewGoCollector(opts ...func(o *internal.GoCollectorOptions)) Collector {
 				Namespace: namespace,
 				Subsystem: subsystem,
 				Name:      name,
-				Help:      d.Description.Description,
+				Help:      help,
 			},
 			)
 		} else {
@@ -234,7 +237,7 @@ func NewGoCollector(opts ...func(o *internal.GoCollectorOptions)) Collector {
 				Namespace: namespace,
 				Subsystem: subsystem,
 				Name:      name,
-				Help:      d.Description.Description,
+				Help:      help,
 			})
 		}
 		metricSet = append(metricSet, m)
@@ -284,6 +287,10 @@ func NewGoCollector(opts ...func(o *internal.GoCollectorOptions)) Collector {
 	}
 }
 
+func attachOriginalName(desc, origName string) string {
+	return fmt.Sprintf("%s Sourced from %s", desc, origName)
+}
+
 // Describe returns all descriptions of the collector.
 func (c *goCollector) Describe(ch chan<- *Desc) {
 	c.base.Describe(ch)
@@ -376,13 +383,13 @@ func unwrapScalarRMValue(v metrics.Value) float64 {
 		//
 		// This should never happen because we always populate our metric
 		// set from the runtime/metrics package.
-		panic("unexpected unsupported metric")
+		panic("unexpected bad kind metric")
 	default:
 		// Unsupported metric kind.
 		//
 		// This should never happen because we check for this during initialization
 		// and flag and filter metrics whose kinds we don't understand.
-		panic("unexpected unsupported metric kind")
+		panic(fmt.Sprintf("unexpected unsupported metric: %v", v.Kind()))
 	}
 }
 
diff --git prometheus/go_collector_latest_test.go prometheus/go_collector_latest_test.go
index 551f49797..2fbe01bae 100644
--- prometheus/go_collector_latest_test.go
+++ prometheus/go_collector_latest_test.go
@@ -74,6 +74,13 @@ func addExpectedRuntimeMetrics(metrics map[string]struct{}) map[string]struct{}
 	return metrics
 }
 
+func addExpectedDefaultRuntimeMetrics(metrics map[string]struct{}) map[string]struct{} {
+	for _, e := range expMetrics {
+		metrics[e] = struct{}{}
+	}
+	return metrics
+}
+
 func TestGoCollector_ExposedMetrics(t *testing.T) {
 	for _, tcase := range []struct {
 		opts              internal.GoCollectorOptions
@@ -86,8 +93,9 @@ func TestGoCollector_ExposedMetrics(t *testing.T) {
 			expectedFQNameSet: expectedBaseMetrics(),
 		},
 		{
-			// Default, only MemStats.
-			expectedFQNameSet: addExpectedRuntimeMemStats(expectedBaseMetrics()),
+			// Default, only MemStats and default Runtime metrics.
+			opts:              defaultGoCollectorOptions(),
+			expectedFQNameSet: addExpectedDefaultRuntimeMetrics(addExpectedRuntimeMemStats(expectedBaseMetrics())),
 		},
 		{
 			// Get all runtime/metrics without MemStats.
diff --git prometheus/go_collector_metrics_go120_test.go prometheus/go_collector_metrics_go120_test.go
index 60517c4c2..4c1ca38d3 100644
--- prometheus/go_collector_metrics_go120_test.go
+++ prometheus/go_collector_metrics_go120_test.go
@@ -6,52 +6,58 @@
 
 package prometheus
 
-var expectedRuntimeMetrics = map[string]string{
-	"/cgo/go-to-c-calls:calls":                     "go_cgo_go_to_c_calls_calls_total",
-	"/cpu/classes/gc/mark/assist:cpu-seconds":      "go_cpu_classes_gc_mark_assist_cpu_seconds_total",
-	"/cpu/classes/gc/mark/dedicated:cpu-seconds":   "go_cpu_classes_gc_mark_dedicated_cpu_seconds_total",
-	"/cpu/classes/gc/mark/idle:cpu-seconds":        "go_cpu_classes_gc_mark_idle_cpu_seconds_total",
-	"/cpu/classes/gc/pause:cpu-seconds":            "go_cpu_classes_gc_pause_cpu_seconds_total",
-	"/cpu/classes/gc/total:cpu-seconds":            "go_cpu_classes_gc_total_cpu_seconds_total",
-	"/cpu/classes/idle:cpu-seconds":                "go_cpu_classes_idle_cpu_seconds_total",
-	"/cpu/classes/scavenge/assist:cpu-seconds":     "go_cpu_classes_scavenge_assist_cpu_seconds_total",
-	"/cpu/classes/scavenge/background:cpu-seconds": "go_cpu_classes_scavenge_background_cpu_seconds_total",
-	"/cpu/classes/scavenge/total:cpu-seconds":      "go_cpu_classes_scavenge_total_cpu_seconds_total",
-	"/cpu/classes/total:cpu-seconds":               "go_cpu_classes_total_cpu_seconds_total",
-	"/cpu/classes/user:cpu-seconds":                "go_cpu_classes_user_cpu_seconds_total",
-	"/gc/cycles/automatic:gc-cycles":               "go_gc_cycles_automatic_gc_cycles_total",
-	"/gc/cycles/forced:gc-cycles":                  "go_gc_cycles_forced_gc_cycles_total",
-	"/gc/cycles/total:gc-cycles":                   "go_gc_cycles_total_gc_cycles_total",
-	"/gc/heap/allocs-by-size:bytes":                "go_gc_heap_allocs_by_size_bytes",
-	"/gc/heap/allocs:bytes":                        "go_gc_heap_allocs_bytes_total",
-	"/gc/heap/allocs:objects":                      "go_gc_heap_allocs_objects_total",
-	"/gc/heap/frees-by-size:bytes":                 "go_gc_heap_frees_by_size_bytes",
-	"/gc/heap/frees:bytes":                         "go_gc_heap_frees_bytes_total",
-	"/gc/heap/frees:objects":                       "go_gc_heap_frees_objects_total",
-	"/gc/heap/goal:bytes":                          "go_gc_heap_goal_bytes",
-	"/gc/heap/objects:objects":                     "go_gc_heap_objects_objects",
-	"/gc/heap/tiny/allocs:objects":                 "go_gc_heap_tiny_allocs_objects_total",
-	"/gc/limiter/last-enabled:gc-cycle":            "go_gc_limiter_last_enabled_gc_cycle",
-	"/gc/pauses:seconds":                           "go_gc_pauses_seconds",
-	"/gc/stack/starting-size:bytes":                "go_gc_stack_starting_size_bytes",
-	"/memory/classes/heap/free:bytes":              "go_memory_classes_heap_free_bytes",
-	"/memory/classes/heap/objects:bytes":           "go_memory_classes_heap_objects_bytes",
-	"/memory/classes/heap/released:bytes":          "go_memory_classes_heap_released_bytes",
-	"/memory/classes/heap/stacks:bytes":            "go_memory_classes_heap_stacks_bytes",
-	"/memory/classes/heap/unused:bytes":            "go_memory_classes_heap_unused_bytes",
-	"/memory/classes/metadata/mcache/free:bytes":   "go_memory_classes_metadata_mcache_free_bytes",
-	"/memory/classes/metadata/mcache/inuse:bytes":  "go_memory_classes_metadata_mcache_inuse_bytes",
-	"/memory/classes/metadata/mspan/free:bytes":    "go_memory_classes_metadata_mspan_free_bytes",
-	"/memory/classes/metadata/mspan/inuse:bytes":   "go_memory_classes_metadata_mspan_inuse_bytes",
-	"/memory/classes/metadata/other:bytes":         "go_memory_classes_metadata_other_bytes",
-	"/memory/classes/os-stacks:bytes":              "go_memory_classes_os_stacks_bytes",
-	"/memory/classes/other:bytes":                  "go_memory_classes_other_bytes",
-	"/memory/classes/profiling/buckets:bytes":      "go_memory_classes_profiling_buckets_bytes",
-	"/memory/classes/total:bytes":                  "go_memory_classes_total_bytes",
-	"/sched/gomaxprocs:threads":                    "go_sched_gomaxprocs_threads",
-	"/sched/goroutines:goroutines":                 "go_sched_goroutines_goroutines",
-	"/sched/latencies:seconds":                     "go_sched_latencies_seconds",
-	"/sync/mutex/wait/total:seconds":               "go_sync_mutex_wait_total_seconds_total",
-}
+var (
+	expectedRuntimeMetrics = map[string]string{
+		"/cgo/go-to-c-calls:calls":                     "go_cgo_go_to_c_calls_calls_total",
+		"/cpu/classes/gc/mark/assist:cpu-seconds":      "go_cpu_classes_gc_mark_assist_cpu_seconds_total",
+		"/cpu/classes/gc/mark/dedicated:cpu-seconds":   "go_cpu_classes_gc_mark_dedicated_cpu_seconds_total",
+		"/cpu/classes/gc/mark/idle:cpu-seconds":        "go_cpu_classes_gc_mark_idle_cpu_seconds_total",
+		"/cpu/classes/gc/pause:cpu-seconds":            "go_cpu_classes_gc_pause_cpu_seconds_total",
+		"/cpu/classes/gc/total:cpu-seconds":            "go_cpu_classes_gc_total_cpu_seconds_total",
+		"/cpu/classes/idle:cpu-seconds":                "go_cpu_classes_idle_cpu_seconds_total",
+		"/cpu/classes/scavenge/assist:cpu-seconds":     "go_cpu_classes_scavenge_assist_cpu_seconds_total",
+		"/cpu/classes/scavenge/background:cpu-seconds": "go_cpu_classes_scavenge_background_cpu_seconds_total",
+		"/cpu/classes/scavenge/total:cpu-seconds":      "go_cpu_classes_scavenge_total_cpu_seconds_total",
+		"/cpu/classes/total:cpu-seconds":               "go_cpu_classes_total_cpu_seconds_total",
+		"/cpu/classes/user:cpu-seconds":                "go_cpu_classes_user_cpu_seconds_total",
+		"/gc/cycles/automatic:gc-cycles":               "go_gc_cycles_automatic_gc_cycles_total",
+		"/gc/cycles/forced:gc-cycles":                  "go_gc_cycles_forced_gc_cycles_total",
+		"/gc/cycles/total:gc-cycles":                   "go_gc_cycles_total_gc_cycles_total",
+		"/gc/heap/allocs-by-size:bytes":                "go_gc_heap_allocs_by_size_bytes",
+		"/gc/heap/allocs:bytes":                        "go_gc_heap_allocs_bytes_total",
+		"/gc/heap/allocs:objects":                      "go_gc_heap_allocs_objects_total",
+		"/gc/heap/frees-by-size:bytes":                 "go_gc_heap_frees_by_size_bytes",
+		"/gc/heap/frees:bytes":                         "go_gc_heap_frees_bytes_total",
+		"/gc/heap/frees:objects":                       "go_gc_heap_frees_objects_total",
+		"/gc/heap/goal:bytes":                          "go_gc_heap_goal_bytes",
+		"/gc/heap/objects:objects":                     "go_gc_heap_objects_objects",
+		"/gc/heap/tiny/allocs:objects":                 "go_gc_heap_tiny_allocs_objects_total",
+		"/gc/limiter/last-enabled:gc-cycle":            "go_gc_limiter_last_enabled_gc_cycle",
+		"/gc/pauses:seconds":                           "go_gc_pauses_seconds",
+		"/gc/stack/starting-size:bytes":                "go_gc_stack_starting_size_bytes",
+		"/memory/classes/heap/free:bytes":              "go_memory_classes_heap_free_bytes",
+		"/memory/classes/heap/objects:bytes":           "go_memory_classes_heap_objects_bytes",
+		"/memory/classes/heap/released:bytes":          "go_memory_classes_heap_released_bytes",
+		"/memory/classes/heap/stacks:bytes":            "go_memory_classes_heap_stacks_bytes",
+		"/memory/classes/heap/unused:bytes":            "go_memory_classes_heap_unused_bytes",
+		"/memory/classes/metadata/mcache/free:bytes":   "go_memory_classes_metadata_mcache_free_bytes",
+		"/memory/classes/metadata/mcache/inuse:bytes":  "go_memory_classes_metadata_mcache_inuse_bytes",
+		"/memory/classes/metadata/mspan/free:bytes":    "go_memory_classes_metadata_mspan_free_bytes",
+		"/memory/classes/metadata/mspan/inuse:bytes":   "go_memory_classes_metadata_mspan_inuse_bytes",
+		"/memory/classes/metadata/other:bytes":         "go_memory_classes_metadata_other_bytes",
+		"/memory/classes/os-stacks:bytes":              "go_memory_classes_os_stacks_bytes",
+		"/memory/classes/other:bytes":                  "go_memory_classes_other_bytes",
+		"/memory/classes/profiling/buckets:bytes":      "go_memory_classes_profiling_buckets_bytes",
+		"/memory/classes/total:bytes":                  "go_memory_classes_total_bytes",
+		"/sched/gomaxprocs:threads":                    "go_sched_gomaxprocs_threads",
+		"/sched/goroutines:goroutines":                 "go_sched_goroutines_goroutines",
+		"/sched/latencies:seconds":                     "go_sched_latencies_seconds",
+		"/sync/mutex/wait/total:seconds":               "go_sync_mutex_wait_total_seconds_total",
+	}
+
+	expMetrics = map[string]string{
+		"/sched/gomaxprocs:threads": "go_sched_gomaxprocs_threads",
+	}
+)
 
 const expectedRuntimeMetricsCardinality = 89
diff --git prometheus/go_collector_metrics_go121_test.go prometheus/go_collector_metrics_go121_test.go
index 90d05bd0e..217b04fc7 100644
--- prometheus/go_collector_metrics_go121_test.go
+++ prometheus/go_collector_metrics_go121_test.go
@@ -6,77 +6,86 @@
 
 package prometheus
 
-var expectedRuntimeMetrics = map[string]string{
-	"/cgo/go-to-c-calls:calls":                                  "go_cgo_go_to_c_calls_calls_total",
-	"/cpu/classes/gc/mark/assist:cpu-seconds":                   "go_cpu_classes_gc_mark_assist_cpu_seconds_total",
-	"/cpu/classes/gc/mark/dedicated:cpu-seconds":                "go_cpu_classes_gc_mark_dedicated_cpu_seconds_total",
-	"/cpu/classes/gc/mark/idle:cpu-seconds":                     "go_cpu_classes_gc_mark_idle_cpu_seconds_total",
-	"/cpu/classes/gc/pause:cpu-seconds":                         "go_cpu_classes_gc_pause_cpu_seconds_total",
-	"/cpu/classes/gc/total:cpu-seconds":                         "go_cpu_classes_gc_total_cpu_seconds_total",
-	"/cpu/classes/idle:cpu-seconds":                             "go_cpu_classes_idle_cpu_seconds_total",
-	"/cpu/classes/scavenge/assist:cpu-seconds":                  "go_cpu_classes_scavenge_assist_cpu_seconds_total",
-	"/cpu/classes/scavenge/background:cpu-seconds":              "go_cpu_classes_scavenge_background_cpu_seconds_total",
-	"/cpu/classes/scavenge/total:cpu-seconds":                   "go_cpu_classes_scavenge_total_cpu_seconds_total",
-	"/cpu/classes/total:cpu-seconds":                            "go_cpu_classes_total_cpu_seconds_total",
-	"/cpu/classes/user:cpu-seconds":                             "go_cpu_classes_user_cpu_seconds_total",
-	"/gc/cycles/automatic:gc-cycles":                            "go_gc_cycles_automatic_gc_cycles_total",
-	"/gc/cycles/forced:gc-cycles":                               "go_gc_cycles_forced_gc_cycles_total",
-	"/gc/cycles/total:gc-cycles":                                "go_gc_cycles_total_gc_cycles_total",
-	"/gc/gogc:percent":                                          "go_gc_gogc_percent",
-	"/gc/gomemlimit:bytes":                                      "go_gc_gomemlimit_bytes",
-	"/gc/heap/allocs-by-size:bytes":                             "go_gc_heap_allocs_by_size_bytes",
-	"/gc/heap/allocs:bytes":                                     "go_gc_heap_allocs_bytes_total",
-	"/gc/heap/allocs:objects":                                   "go_gc_heap_allocs_objects_total",
-	"/gc/heap/frees-by-size:bytes":                              "go_gc_heap_frees_by_size_bytes",
-	"/gc/heap/frees:bytes":                                      "go_gc_heap_frees_bytes_total",
-	"/gc/heap/frees:objects":                                    "go_gc_heap_frees_objects_total",
-	"/gc/heap/goal:bytes":                                       "go_gc_heap_goal_bytes",
-	"/gc/heap/live:bytes":                                       "go_gc_heap_live_bytes",
-	"/gc/heap/objects:objects":                                  "go_gc_heap_objects_objects",
-	"/gc/heap/tiny/allocs:objects":                              "go_gc_heap_tiny_allocs_objects_total",
-	"/gc/limiter/last-enabled:gc-cycle":                         "go_gc_limiter_last_enabled_gc_cycle",
-	"/gc/pauses:seconds":                                        "go_gc_pauses_seconds",
-	"/gc/scan/globals:bytes":                                    "go_gc_scan_globals_bytes",
-	"/gc/scan/heap:bytes":                                       "go_gc_scan_heap_bytes",
-	"/gc/scan/stack:bytes":                                      "go_gc_scan_stack_bytes",
-	"/gc/scan/total:bytes":                                      "go_gc_scan_total_bytes",
-	"/gc/stack/starting-size:bytes":                             "go_gc_stack_starting_size_bytes",
-	"/godebug/non-default-behavior/execerrdot:events":           "go_godebug_non_default_behavior_execerrdot_events_total",
-	"/godebug/non-default-behavior/gocachehash:events":          "go_godebug_non_default_behavior_gocachehash_events_total",
-	"/godebug/non-default-behavior/gocachetest:events":          "go_godebug_non_default_behavior_gocachetest_events_total",
-	"/godebug/non-default-behavior/gocacheverify:events":        "go_godebug_non_default_behavior_gocacheverify_events_total",
-	"/godebug/non-default-behavior/http2client:events":          "go_godebug_non_default_behavior_http2client_events_total",
-	"/godebug/non-default-behavior/http2server:events":          "go_godebug_non_default_behavior_http2server_events_total",
-	"/godebug/non-default-behavior/installgoroot:events":        "go_godebug_non_default_behavior_installgoroot_events_total",
-	"/godebug/non-default-behavior/jstmpllitinterp:events":      "go_godebug_non_default_behavior_jstmpllitinterp_events_total",
-	"/godebug/non-default-behavior/multipartmaxheaders:events":  "go_godebug_non_default_behavior_multipartmaxheaders_events_total",
-	"/godebug/non-default-behavior/multipartmaxparts:events":    "go_godebug_non_default_behavior_multipartmaxparts_events_total",
-	"/godebug/non-default-behavior/multipathtcp:events":         "go_godebug_non_default_behavior_multipathtcp_events_total",
-	"/godebug/non-default-behavior/panicnil:events":             "go_godebug_non_default_behavior_panicnil_events_total",
-	"/godebug/non-default-behavior/randautoseed:events":         "go_godebug_non_default_behavior_randautoseed_events_total",
-	"/godebug/non-default-behavior/tarinsecurepath:events":      "go_godebug_non_default_behavior_tarinsecurepath_events_total",
-	"/godebug/non-default-behavior/tlsmaxrsasize:events":        "go_godebug_non_default_behavior_tlsmaxrsasize_events_total",
-	"/godebug/non-default-behavior/x509sha1:events":             "go_godebug_non_default_behavior_x509sha1_events_total",
-	"/godebug/non-default-behavior/x509usefallbackroots:events": "go_godebug_non_default_behavior_x509usefallbackroots_events_total",
-	"/godebug/non-default-behavior/zipinsecurepath:events":      "go_godebug_non_default_behavior_zipinsecurepath_events_total",
-	"/memory/classes/heap/free:bytes":                           "go_memory_classes_heap_free_bytes",
-	"/memory/classes/heap/objects:bytes":                        "go_memory_classes_heap_objects_bytes",
-	"/memory/classes/heap/released:bytes":                       "go_memory_classes_heap_released_bytes",
-	"/memory/classes/heap/stacks:bytes":                         "go_memory_classes_heap_stacks_bytes",
-	"/memory/classes/heap/unused:bytes":                         "go_memory_classes_heap_unused_bytes",
-	"/memory/classes/metadata/mcache/free:bytes":                "go_memory_classes_metadata_mcache_free_bytes",
-	"/memory/classes/metadata/mcache/inuse:bytes":               "go_memory_classes_metadata_mcache_inuse_bytes",
-	"/memory/classes/metadata/mspan/free:bytes":                 "go_memory_classes_metadata_mspan_free_bytes",
-	"/memory/classes/metadata/mspan/inuse:bytes":                "go_memory_classes_metadata_mspan_inuse_bytes",
-	"/memory/classes/metadata/other:bytes":                      "go_memory_classes_metadata_other_bytes",
-	"/memory/classes/os-stacks:bytes":                           "go_memory_classes_os_stacks_bytes",
-	"/memory/classes/other:bytes":                               "go_memory_classes_other_bytes",
-	"/memory/classes/profiling/buckets:bytes":                   "go_memory_classes_profiling_buckets_bytes",
-	"/memory/classes/total:bytes":                               "go_memory_classes_total_bytes",
-	"/sched/gomaxprocs:threads":                                 "go_sched_gomaxprocs_threads",
-	"/sched/goroutines:goroutines":                              "go_sched_goroutines_goroutines",
-	"/sched/latencies:seconds":                                  "go_sched_latencies_seconds",
-	"/sync/mutex/wait/total:seconds":                            "go_sync_mutex_wait_total_seconds_total",
-}
+var (
+	expectedRuntimeMetrics = map[string]string{
+		"/cgo/go-to-c-calls:calls":                                  "go_cgo_go_to_c_calls_calls_total",
+		"/cpu/classes/gc/mark/assist:cpu-seconds":                   "go_cpu_classes_gc_mark_assist_cpu_seconds_total",
+		"/cpu/classes/gc/mark/dedicated:cpu-seconds":                "go_cpu_classes_gc_mark_dedicated_cpu_seconds_total",
+		"/cpu/classes/gc/mark/idle:cpu-seconds":                     "go_cpu_classes_gc_mark_idle_cpu_seconds_total",
+		"/cpu/classes/gc/pause:cpu-seconds":                         "go_cpu_classes_gc_pause_cpu_seconds_total",
+		"/cpu/classes/gc/total:cpu-seconds":                         "go_cpu_classes_gc_total_cpu_seconds_total",
+		"/c,pu/classes/idle:cpu-seconds":                             "go_cpu_classes_idle_cpu_seconds_total",
+		"/cpu/classes/scavenge/assist:cpu-seconds":                  "go_cpu_classes_scavenge_assist_cpu_seconds_total",
+		"/cpu/classes/scavenge/background:cpu-seconds":              "go_cpu_classes_scavenge_background_cpu_seconds_total",
+		"/cpu/classes/scavenge/total:cpu-seconds":                   "go_cpu_classes_scavenge_total_cpu_seconds_total",
+		"/cpu/classes/total:cpu-seconds":                            "go_cpu_classes_total_cpu_seconds_total",
+		"/cpu/classes/user:cpu-seconds":                             "go_cpu_classes_user_cpu_seconds_total",
+		"/gc/cycles/automatic:gc-cycles":                            "go_gc_cycles_automatic_gc_cycles_total",
+		"/gc/cycles/forced:gc-cycles":                               "go_gc_cycles_forced_gc_cycles_total",
+		"/gc/cycles/total:gc-cycles":                                "go_gc_cycles_total_gc_cycles_total",
+		"/gc/gogc:percent":                                          "go_gc_gogc_percent",
+		"/gc/gomemlimit:bytes":                                      "go_gc_gomemlimit_bytes",
+		"/gc/heap/allocs-by-size:bytes":                             "go_gc_heap_allocs_by_size_bytes",
+		"/gc/heap/allocs:bytes":                                     "go_gc_heap_allocs_bytes_total",
+		"/gc/heap/allocs:objects":                                   "go_gc_heap_allocs_objects_total",
+		"/gc/heap/frees-by-size:bytes":                              "go_gc_heap_frees_by_size_bytes",
+		"/gc/heap/frees:bytes":                                      "go_gc_heap_frees_bytes_total",
+		"/gc/heap/frees:objects":                                    "go_gc_heap_frees_objects_total",
+		"/gc/heap/goal:bytes":                                       "go_gc_heap_goal_bytes",
+		"/gc/heap/live:bytes":                                       "go_gc_heap_live_bytes",
+		"/gc/heap/objects:objects":                                  "go_gc_heap_objects_objects",
+		"/gc/heap/tiny/allocs:objects":                              "go_gc_heap_tiny_allocs_objects_total",
+		"/gc/limiter/last-enabled:gc-cycle":                         "go_gc_limiter_last_enabled_gc_cycle",
+		"/gc/pauses:seconds":                                        "go_gc_pauses_seconds",
+		"/gc/scan/globals:bytes":                                    "go_gc_scan_globals_bytes",
+		"/gc/scan/heap:bytes":                                       "go_gc_scan_heap_bytes",
+		"/gc/scan/stack:bytes":                                      "go_gc_scan_stack_bytes",
+		"/gc/scan/total:bytes":                                      "go_gc_scan_total_bytes",
+		"/gc/stack/starting-size:bytes":                             "go_gc_stack_starting_size_bytes",
+		"/godebug/non-default-behavior/execerrdot:events":           "go_godebug_non_default_behavior_execerrdot_events_total",
+		"/godebug/non-default-behavior/gocachehash:events":          "go_godebug_non_default_behavior_gocachehash_events_total",
+		"/godebug/non-default-behavior/gocachetest:events":          "go_godebug_non_default_behavior_gocachetest_events_total",
+		"/godebug/non-default-behavior/gocacheverify:events":        "go_godebug_non_default_behavior_gocacheverify_events_total",
+		"/godebug/non-default-behavior/http2client:events":          "go_godebug_non_default_behavior_http2client_events_total",
+		"/godebug/non-default-behavior/http2server:events":          "go_godebug_non_default_behavior_http2server_events_total",
+		"/godebug/non-default-behavior/installgoroot:events":        "go_godebug_non_default_behavior_installgoroot_events_total",
+		"/godebug/non-default-behavior/jstmpllitinterp:events":      "go_godebug_non_default_behavior_jstmpllitinterp_events_total",
+		"/godebug/non-default-behavior/multipartmaxheaders:events":  "go_godebug_non_default_behavior_multipartmaxheaders_events_total",
+		"/godebug/non-default-behavior/multipartmaxparts:events":    "go_godebug_non_default_behavior_multipartmaxparts_events_total",
+		"/godebug/non-default-behavior/multipathtcp:events":         "go_godebug_non_default_behavior_multipathtcp_events_total",
+		"/godebug/non-default-behavior/netedns0:events":             "go_godebug_non_default_behavior_netedns0_events_total",
+		"/godebug/non-default-behavior/panicnil:events":             "go_godebug_non_default_behavior_panicnil_events_total",
+		"/godebug/non-default-behavior/randautoseed:events":         "go_godebug_non_default_behavior_randautoseed_events_total",
+		"/godebug/non-default-behavior/tarinsecurepath:events":      "go_godebug_non_default_behavior_tarinsecurepath_events_total",
+		"/godebug/non-default-behavior/tlsmaxrsasize:events":        "go_godebug_non_default_behavior_tlsmaxrsasize_events_total",
+		"/godebug/non-default-behavior/x509sha1:events":             "go_godebug_non_default_behavior_x509sha1_events_total",
+		"/godebug/non-default-behavior/x509usefallbackroots:events": "go_godebug_non_default_behavior_x509usefallbackroots_events_total",
+		"/godebug/non-default-behavior/zipinsecurepath:events":      "go_godebug_non_default_behavior_zipinsecurepath_events_total",
+		"/memory/classes/heap/free:bytes":                           "go_memory_classes_heap_free_bytes",
+		"/memory/classes/heap/objects:bytes":                        "go_memory_classes_heap_objects_bytes",
+		"/memory/classes/heap/released:bytes":                       "go_memory_classes_heap_released_bytes",
+		"/memory/classes/heap/stacks:bytes":                         "go_memory_classes_heap_stacks_bytes",
+		"/memory/classes/heap/unused:bytes":                         "go_memory_classes_heap_unused_bytes",
+		"/memory/classes/metadata/mcache/free:bytes":                "go_memory_classes_metadata_mcache_free_bytes",
+		"/memory/classes/metadata/mcache/inuse:bytes":               "go_memory_classes_metadata_mcache_inuse_bytes",
+		"/memory/classes/metadata/mspan/free:bytes":                 "go_memory_classes_metadata_mspan_free_bytes",
+		"/memory/classes/metadata/mspan/inuse:bytes":                "go_memory_classes_metadata_mspan_inuse_bytes",
+		"/memory/classes/metadata/other:bytes":                      "go_memory_classes_metadata_other_bytes",
+		"/memory/classes/os-stacks:bytes":                           "go_memory_classes_os_stacks_bytes",
+		"/memory/classes/other:bytes":                               "go_memory_classes_other_bytes",
+		"/memory/classes/profiling/buckets:bytes":                   "go_memory_classes_profiling_buckets_bytes",
+		"/memory/classes/total:bytes":                               "go_memory_classes_total_bytes",
+		"/sched/gomaxprocs:threads":                                 "go_sched_gomaxprocs_threads",
+		"/sched/goroutines:goroutines":                              "go_sched_goroutines_goroutines",
+		"/sched/latencies:seconds":                                  "go_sched_latencies_seconds",
+		"/sync/mutex/wait/total:seconds":                            "go_sync_mutex_wait_total_seconds_total",
+	}
 
-const expectedRuntimeMetricsCardinality = 114
+	expMetrics = map[string]string{
+		"/gc/gogc:percent":          "go_gc_gogc_percent",
+		"/gc/gomemlimit:bytes":      "go_gc_gomemlimit_bytes",
+		"/sched/gomaxprocs:threads": "go_sched_gomaxprocs_threads",
+	}
+)
+
+const expectedRuntimeMetricsCardinality = 115
diff --git prometheus/go_collector_metrics_go122_test.go prometheus/go_collector_metrics_go122_test.go
index 86998b8a6..6aa3a9f7e 100644
--- prometheus/go_collector_metrics_go122_test.go
+++ prometheus/go_collector_metrics_go122_test.go
@@ -6,88 +6,97 @@
 
 package prometheus
 
-var expectedRuntimeMetrics = map[string]string{
-	"/cgo/go-to-c-calls:calls":                                  "go_cgo_go_to_c_calls_calls_total",
-	"/cpu/classes/gc/mark/assist:cpu-seconds":                   "go_cpu_classes_gc_mark_assist_cpu_seconds_total",
-	"/cpu/classes/gc/mark/dedicated:cpu-seconds":                "go_cpu_classes_gc_mark_dedicated_cpu_seconds_total",
-	"/cpu/classes/gc/mark/idle:cpu-seconds":                     "go_cpu_classes_gc_mark_idle_cpu_seconds_total",
-	"/cpu/classes/gc/pause:cpu-seconds":                         "go_cpu_classes_gc_pause_cpu_seconds_total",
-	"/cpu/classes/gc/total:cpu-seconds":                         "go_cpu_classes_gc_total_cpu_seconds_total",
-	"/cpu/classes/idle:cpu-seconds":                             "go_cpu_classes_idle_cpu_seconds_total",
-	"/cpu/classes/scavenge/assist:cpu-seconds":                  "go_cpu_classes_scavenge_assist_cpu_seconds_total",
-	"/cpu/classes/scavenge/background:cpu-seconds":              "go_cpu_classes_scavenge_background_cpu_seconds_total",
-	"/cpu/classes/scavenge/total:cpu-seconds":                   "go_cpu_classes_scavenge_total_cpu_seconds_total",
-	"/cpu/classes/total:cpu-seconds":                            "go_cpu_classes_total_cpu_seconds_total",
-	"/cpu/classes/user:cpu-seconds":                             "go_cpu_classes_user_cpu_seconds_total",
-	"/gc/cycles/automatic:gc-cycles":                            "go_gc_cycles_automatic_gc_cycles_total",
-	"/gc/cycles/forced:gc-cycles":                               "go_gc_cycles_forced_gc_cycles_total",
-	"/gc/cycles/total:gc-cycles":                                "go_gc_cycles_total_gc_cycles_total",
-	"/gc/gogc:percent":                                          "go_gc_gogc_percent",
-	"/gc/gomemlimit:bytes":                                      "go_gc_gomemlimit_bytes",
-	"/gc/heap/allocs-by-size:bytes":                             "go_gc_heap_allocs_by_size_bytes",
-	"/gc/heap/allocs:bytes":                                     "go_gc_heap_allocs_bytes_total",
-	"/gc/heap/allocs:objects":                                   "go_gc_heap_allocs_objects_total",
-	"/gc/heap/frees-by-size:bytes":                              "go_gc_heap_frees_by_size_bytes",
-	"/gc/heap/frees:bytes":                                      "go_gc_heap_frees_bytes_total",
-	"/gc/heap/frees:objects":                                    "go_gc_heap_frees_objects_total",
-	"/gc/heap/goal:bytes":                                       "go_gc_heap_goal_bytes",
-	"/gc/heap/live:bytes":                                       "go_gc_heap_live_bytes",
-	"/gc/heap/objects:objects":                                  "go_gc_heap_objects_objects",
-	"/gc/heap/tiny/allocs:objects":                              "go_gc_heap_tiny_allocs_objects_total",
-	"/gc/limiter/last-enabled:gc-cycle":                         "go_gc_limiter_last_enabled_gc_cycle",
-	"/gc/pauses:seconds":                                        "go_gc_pauses_seconds",
-	"/gc/scan/globals:bytes":                                    "go_gc_scan_globals_bytes",
-	"/gc/scan/heap:bytes":                                       "go_gc_scan_heap_bytes",
-	"/gc/scan/stack:bytes":                                      "go_gc_scan_stack_bytes",
-	"/gc/scan/total:bytes":                                      "go_gc_scan_total_bytes",
-	"/gc/stack/starting-size:bytes":                             "go_gc_stack_starting_size_bytes",
-	"/godebug/non-default-behavior/execerrdot:events":           "go_godebug_non_default_behavior_execerrdot_events_total",
-	"/godebug/non-default-behavior/gocachehash:events":          "go_godebug_non_default_behavior_gocachehash_events_total",
-	"/godebug/non-default-behavior/gocachetest:events":          "go_godebug_non_default_behavior_gocachetest_events_total",
-	"/godebug/non-default-behavior/gocacheverify:events":        "go_godebug_non_default_behavior_gocacheverify_events_total",
-	"/godebug/non-default-behavior/gotypesalias:events":         "go_godebug_non_default_behavior_gotypesalias_events_total",
-	"/godebug/non-default-behavior/http2client:events":          "go_godebug_non_default_behavior_http2client_events_total",
-	"/godebug/non-default-behavior/http2server:events":          "go_godebug_non_default_behavior_http2server_events_total",
-	"/godebug/non-default-behavior/httplaxcontentlength:events": "go_godebug_non_default_behavior_httplaxcontentlength_events_total",
-	"/godebug/non-default-behavior/httpmuxgo121:events":         "go_godebug_non_default_behavior_httpmuxgo121_events_total",
-	"/godebug/non-default-behavior/installgoroot:events":        "go_godebug_non_default_behavior_installgoroot_events_total",
-	"/godebug/non-default-behavior/jstmpllitinterp:events":      "go_godebug_non_default_behavior_jstmpllitinterp_events_total",
-	"/godebug/non-default-behavior/multipartmaxheaders:events":  "go_godebug_non_default_behavior_multipartmaxheaders_events_total",
-	"/godebug/non-default-behavior/multipartmaxparts:events":    "go_godebug_non_default_behavior_multipartmaxparts_events_total",
-	"/godebug/non-default-behavior/multipathtcp:events":         "go_godebug_non_default_behavior_multipathtcp_events_total",
-	"/godebug/non-default-behavior/panicnil:events":             "go_godebug_non_default_behavior_panicnil_events_total",
-	"/godebug/non-default-behavior/randautoseed:events":         "go_godebug_non_default_behavior_randautoseed_events_total",
-	"/godebug/non-default-behavior/tarinsecurepath:events":      "go_godebug_non_default_behavior_tarinsecurepath_events_total",
-	"/godebug/non-default-behavior/tls10server:events":          "go_godebug_non_default_behavior_tls10server_events_total",
-	"/godebug/non-default-behavior/tlsmaxrsasize:events":        "go_godebug_non_default_behavior_tlsmaxrsasize_events_total",
-	"/godebug/non-default-behavior/tlsrsakex:events":            "go_godebug_non_default_behavior_tlsrsakex_events_total",
-	"/godebug/non-default-behavior/tlsunsafeekm:events":         "go_godebug_non_default_behavior_tlsunsafeekm_events_total",
-	"/godebug/non-default-behavior/x509sha1:events":             "go_godebug_non_default_behavior_x509sha1_events_total",
-	"/godebug/non-default-behavior/x509usefallbackroots:events": "go_godebug_non_default_behavior_x509usefallbackroots_events_total",
-	"/godebug/non-default-behavior/x509usepolicies:events":      "go_godebug_non_default_behavior_x509usepolicies_events_total",
-	"/godebug/non-default-behavior/zipinsecurepath:events":      "go_godebug_non_default_behavior_zipinsecurepath_events_total",
-	"/memory/classes/heap/free:bytes":                           "go_memory_classes_heap_free_bytes",
-	"/memory/classes/heap/objects:bytes":                        "go_memory_classes_heap_objects_bytes",
-	"/memory/classes/heap/released:bytes":                       "go_memory_classes_heap_released_bytes",
-	"/memory/classes/heap/stacks:bytes":                         "go_memory_classes_heap_stacks_bytes",
-	"/memory/classes/heap/unused:bytes":                         "go_memory_classes_heap_unused_bytes",
-	"/memory/classes/metadata/mcache/free:bytes":                "go_memory_classes_metadata_mcache_free_bytes",
-	"/memory/classes/metadata/mcache/inuse:bytes":               "go_memory_classes_metadata_mcache_inuse_bytes",
-	"/memory/classes/metadata/mspan/free:bytes":                 "go_memory_classes_metadata_mspan_free_bytes",
-	"/memory/classes/metadata/mspan/inuse:bytes":                "go_memory_classes_metadata_mspan_inuse_bytes",
-	"/memory/classes/metadata/other:bytes":                      "go_memory_classes_metadata_other_bytes",
-	"/memory/classes/os-stacks:bytes":                           "go_memory_classes_os_stacks_bytes",
-	"/memory/classes/other:bytes":                               "go_memory_classes_other_bytes",
-	"/memory/classes/profiling/buckets:bytes":                   "go_memory_classes_profiling_buckets_bytes",
-	"/memory/classes/total:bytes":                               "go_memory_classes_total_bytes",
-	"/sched/gomaxprocs:threads":                                 "go_sched_gomaxprocs_threads",
-	"/sched/goroutines:goroutines":                              "go_sched_goroutines_goroutines",
-	"/sched/latencies:seconds":                                  "go_sched_latencies_seconds",
-	"/sched/pauses/stopping/gc:seconds":                         "go_sched_pauses_stopping_gc_seconds",
-	"/sched/pauses/stopping/other:seconds":                      "go_sched_pauses_stopping_other_seconds",
-	"/sched/pauses/total/gc:seconds":                            "go_sched_pauses_total_gc_seconds",
-	"/sched/pauses/total/other:seconds":                         "go_sched_pauses_total_other_seconds",
-	"/sync/mutex/wait/total:seconds":                            "go_sync_mutex_wait_total_seconds_total",
-}
+var (
+	expectedRuntimeMetrics = map[string]string{
+		"/cgo/go-to-c-calls:calls":                                  "go_cgo_go_to_c_calls_calls_total",
+		"/cpu/classes/gc/mark/assist:cpu-seconds":                   "go_cpu_classes_gc_mark_assist_cpu_seconds_total",
+		"/cpu/classes/gc/mark/dedicated:cpu-seconds":                "go_cpu_classes_gc_mark_dedicated_cpu_seconds_total",
+		"/cpu/classes/gc/mark/idle:cpu-seconds":                     "go_cpu_classes_gc_mark_idle_cpu_seconds_total",
+		"/cpu/classes/gc/pause:cpu-seconds":                         "go_cpu_classes_gc_pause_cpu_seconds_total",
+		"/cpu/classes/gc/total:cpu-seconds":                         "go_cpu_classes_gc_total_cpu_seconds_total",
+		"/cpu/classes/idle:cpu-seconds":                             "go_cpu_classes_idle_cpu_seconds_total",
+		"/cpu/classes/scavenge/assist:cpu-seconds":                  "go_cpu_classes_scavenge_assist_cpu_seconds_total",
+		"/cpu/classes/scavenge/background:cpu-seconds":              "go_cpu_classes_scavenge_background_cpu_seconds_total",
+		"/cpu/classes/scavenge/total:cpu-seconds":                   "go_cpu_classes_scavenge_total_cpu_seconds_total",
+		"/cpu/classes/total:cpu-seconds":                            "go_cpu_classes_total_cpu_seconds_total",
+		"/cpu/classes/user:cpu-seconds":                             "go_cpu_classes_user_cpu_seconds_total",
+		"/gc/cycles/automatic:gc-cycles":                            "go_gc_cycles_automatic_gc_cycles_total",
+		"/gc/cycles/forced:gc-cycles":                               "go_gc_cycles_forced_gc_cycles_total",
+		"/gc/cycles/total:gc-cycles":                                "go_gc_cycles_total_gc_cycles_total",
+		"/gc/gogc:percent":                                          "go_gc_gogc_percent",
+		"/gc/gomemlimit:bytes":                                      "go_gc_gomemlimit_bytes",
+		"/gc/heap/allocs-by-size:bytes":                             "go_gc_heap_allocs_by_size_bytes",
+		"/gc/heap/allocs:bytes":                                     "go_gc_heap_allocs_bytes_total",
+		"/gc/heap/allocs:objects":                                   "go_gc_heap_allocs_objects_total",
+		"/gc/heap/frees-by-size:bytes":                              "go_gc_heap_frees_by_size_bytes",
+		"/gc/heap/frees:bytes":                                      "go_gc_heap_frees_bytes_total",
+		"/gc/heap/frees:objects":                                    "go_gc_heap_frees_objects_total",
+		"/gc/heap/goal:bytes":                                       "go_gc_heap_goal_bytes",
+		"/gc/heap/live:bytes":                                       "go_gc_heap_live_bytes",
+		"/gc/heap/objects:objects":                                  "go_gc_heap_objects_objects",
+		"/gc/heap/tiny/allocs:objects":                              "go_gc_heap_tiny_allocs_objects_total",
+		"/gc/limiter/last-enabled:gc-cycle":                         "go_gc_limiter_last_enabled_gc_cycle",
+		"/gc/pauses:seconds":                                        "go_gc_pauses_seconds",
+		"/gc/scan/globals:bytes":                                    "go_gc_scan_globals_bytes",
+		"/gc/scan/heap:bytes":                                       "go_gc_scan_heap_bytes",
+		"/gc/scan/stack:bytes":                                      "go_gc_scan_stack_bytes",
+		"/gc/scan/total:bytes":                                      "go_gc_scan_total_bytes",
+		"/gc/stack/starting-size:bytes":                             "go_gc_stack_starting_size_bytes",
+		"/godebug/non-default-behavior/execerrdot:events":           "go_godebug_non_default_behavior_execerrdot_events_total",
+		"/godebug/non-default-behavior/gocachehash:events":          "go_godebug_non_default_behavior_gocachehash_events_total",
+		"/godebug/non-default-behavior/gocachetest:events":          "go_godebug_non_default_behavior_gocachetest_events_total",
+		"/godebug/non-default-behavior/gocacheverify:events":        "go_godebug_non_default_behavior_gocacheverify_events_total",
+		"/godebug/non-default-behavior/gotypesalias:events":         "go_godebug_non_default_behavior_gotypesalias_events_total",
+		"/godebug/non-default-behavior/http2client:events":          "go_godebug_non_default_behavior_http2client_events_total",
+		"/godebug/non-default-behavior/http2server:events":          "go_godebug_non_default_behavior_http2server_events_total",
+		"/godebug/non-default-behavior/httplaxcontentlength:events": "go_godebug_non_default_behavior_httplaxcontentlength_events_total",
+		"/godebug/non-default-behavior/httpmuxgo121:events":         "go_godebug_non_default_behavior_httpmuxgo121_events_total",
+		"/godebug/non-default-behavior/installgoroot:events":        "go_godebug_non_default_behavior_installgoroot_events_total",
+		"/godebug/non-default-behavior/jstmpllitinterp:events":      "go_godebug_non_default_behavior_jstmpllitinterp_events_total",
+		"/godebug/non-default-behavior/multipartmaxheaders:events":  "go_godebug_non_default_behavior_multipartmaxheaders_events_total",
+		"/godebug/non-default-behavior/multipartmaxparts:events":    "go_godebug_non_default_behavior_multipartmaxparts_events_total",
+		"/godebug/non-default-behavior/multipathtcp:events":         "go_godebug_non_default_behavior_multipathtcp_events_total",
+		"/godebug/non-default-behavior/netedns0:events":             "go_godebug_non_default_behavior_netedns0_events_total",
+		"/godebug/non-default-behavior/panicnil:events":             "go_godebug_non_default_behavior_panicnil_events_total",
+		"/godebug/non-default-behavior/randautoseed:events":         "go_godebug_non_default_behavior_randautoseed_events_total",
+		"/godebug/non-default-behavior/tarinsecurepath:events":      "go_godebug_non_default_behavior_tarinsecurepath_events_total",
+		"/godebug/non-default-behavior/tls10server:events":          "go_godebug_non_default_behavior_tls10server_events_total",
+		"/godebug/non-default-behavior/tlsmaxrsasize:events":        "go_godebug_non_default_behavior_tlsmaxrsasize_events_total",
+		"/godebug/non-default-behavior/tlsrsakex:events":            "go_godebug_non_default_behavior_tlsrsakex_events_total",
+		"/godebug/non-default-behavior/tlsunsafeekm:events":         "go_godebug_non_default_behavior_tlsunsafeekm_events_total",
+		"/godebug/non-default-behavior/x509sha1:events":             "go_godebug_non_default_behavior_x509sha1_events_total",
+		"/godebug/non-default-behavior/x509usefallbackroots:events": "go_godebug_non_default_behavior_x509usefallbackroots_events_total",
+		"/godebug/non-default-behavior/x509usepolicies:events":      "go_godebug_non_default_behavior_x509usepolicies_events_total",
+		"/godebug/non-default-behavior/zipinsecurepath:events":      "go_godebug_non_default_behavior_zipinsecurepath_events_total",
+		"/memory/classes/heap/free:bytes":                           "go_memory_classes_heap_free_bytes",
+		"/memory/classes/heap/objects:bytes":                        "go_memory_classes_heap_objects_bytes",
+		"/memory/classes/heap/released:bytes":                       "go_memory_classes_heap_released_bytes",
+		"/memory/classes/heap/stacks:bytes":                         "go_memory_classes_heap_stacks_bytes",
+		"/memory/classes/heap/unused:bytes":                         "go_memory_classes_heap_unused_bytes",
+		"/memory/classes/metadata/mcache/free:bytes":                "go_memory_classes_metadata_mcache_free_bytes",
+		"/memory/classes/metadata/mcache/inuse:bytes":               "go_memory_classes_metadata_mcache_inuse_bytes",
+		"/memory/classes/metadata/mspan/free:bytes":                 "go_memory_classes_metadata_mspan_free_bytes",
+		"/memory/classes/metadata/mspan/inuse:bytes":                "go_memory_classes_metadata_mspan_inuse_bytes",
+		"/memory/classes/metadata/other:bytes":                      "go_memory_classes_metadata_other_bytes",
+		"/memory/classes/os-stacks:bytes":                           "go_memory_classes_os_stacks_bytes",
+		"/memory/classes/other:bytes":                               "go_memory_classes_other_bytes",
+		"/memory/classes/profiling/buckets:bytes":                   "go_memory_classes_profiling_buckets_bytes",
+		"/memory/classes/total:bytes":                               "go_memory_classes_total_bytes",
+		"/sched/gomaxprocs:threads":                                 "go_sched_gomaxprocs_threads",
+		"/sched/goroutines:goroutines":                              "go_sched_goroutines_goroutines",
+		"/sched/latencies:seconds":                                  "go_sched_latencies_seconds",
+		"/sched/pauses/stopping/gc:seconds":                         "go_sched_pauses_stopping_gc_seconds",
+		"/sched/pauses/stopping/other:seconds":                      "go_sched_pauses_stopping_other_seconds",
+		"/sched/pauses/total/gc:seconds":                            "go_sched_pauses_total_gc_seconds",
+		"/sched/pauses/total/other:seconds":                         "go_sched_pauses_total_other_seconds",
+		"/sync/mutex/wait/total:seconds":                            "go_sync_mutex_wait_total_seconds_total",
+	}
 
-const expectedRuntimeMetricsCardinality = 161
+	expMetrics = map[string]string{
+		"/gc/gogc:percent":          "go_gc_gogc_percent",
+		"/gc/gomemlimit:bytes":      "go_gc_gomemlimit_bytes",
+		"/sched/gomaxprocs:threads": "go_sched_gomaxprocs_threads",
+	}
+)
+
+const expectedRuntimeMetricsCardinality = 162
diff --git prometheus/histogram.go prometheus/histogram.go
index b5c8bcb39..519db348a 100644
--- prometheus/histogram.go
+++ prometheus/histogram.go
@@ -440,7 +440,7 @@ type HistogramOpts struct {
 	// constant (or any negative float value).
 	NativeHistogramZeroThreshold float64
 
-	// The remaining fields define a strategy to limit the number of
+	// The next three fields define a strategy to limit the number of
 	// populated sparse buckets. If NativeHistogramMaxBucketNumber is left
 	// at zero, the number of buckets is not limited. (Note that this might
 	// lead to unbounded memory consumption if the values observed by the
@@ -473,6 +473,22 @@ type HistogramOpts struct {
 	NativeHistogramMinResetDuration time.Duration
 	NativeHistogramMaxZeroThreshold float64
 
+	// NativeHistogramMaxExemplars limits the number of exemplars
+	// that are kept in memory for each native histogram. If you leave it at
+	// zero, a default value of 10 is used. If no exemplars should be kept specifically
+	// for native histograms, set it to a negative value. (Scrapers can
+	// still use the exemplars exposed for classic buckets, which are managed
+	// independently.)
+	NativeHistogramMaxExemplars int
+	// NativeHistogramExemplarTTL is only checked once
+	// NativeHistogramMaxExemplars is exceeded. In that case, the
+	// oldest exemplar is removed if it is older than NativeHistogramExemplarTTL.
+	// Otherwise, the older exemplar in the pair of exemplars that are closest
+	// together (on an exponential scale) is removed.
+	// If NativeHistogramExemplarTTL is left at its zero value, a default value of
+	// 5m is used. To always delete the oldest exemplar, set it to a negative value.
+	NativeHistogramExemplarTTL time.Duration
+
 	// now is for testing purposes, by default it's time.Now.
 	now func() time.Time
 
@@ -532,6 +548,7 @@ func newHistogram(desc *Desc, opts HistogramOpts, labelValues ...string) Histogr
 	if opts.afterFunc == nil {
 		opts.afterFunc = time.AfterFunc
 	}
+
 	h := &histogram{
 		desc:                            desc,
 		upperBounds:                     opts.Buckets,
@@ -556,6 +573,7 @@ func newHistogram(desc *Desc, opts HistogramOpts, labelValues ...string) Histogr
 			h.nativeHistogramZeroThreshold = DefNativeHistogramZeroThreshold
 		} // Leave h.nativeHistogramZeroThreshold at 0 otherwise.
 		h.nativeHistogramSchema = pickSchema(opts.NativeHistogramBucketFactor)
+		h.nativeExemplars = makeNativeExemplars(opts.NativeHistogramExemplarTTL, opts.NativeHistogramMaxExemplars)
 	}
 	for i, upperBound := range h.upperBounds {
 		if i < len(h.upperBounds)-1 {
@@ -725,7 +743,8 @@ type histogram struct {
 	// resetScheduled is protected by mtx. It is true if a reset is
 	// scheduled for a later time (when nativeHistogramMinResetDuration has
 	// passed).
-	resetScheduled bool
+	resetScheduled  bool
+	nativeExemplars nativeExemplars
 
 	// now is for testing purposes, by default it's time.Now.
 	now func() time.Time
@@ -742,6 +761,9 @@ func (h *histogram) Observe(v float64) {
 	h.observe(v, h.findBucket(v))
 }
 
+// ObserveWithExemplar should not be called in a high-frequency setting
+// for a native histogram with configured exemplars. For this case,
+// the implementation isn't lock-free and might suffer from lock contention.
 func (h *histogram) ObserveWithExemplar(v float64, e Labels) {
 	i := h.findBucket(v)
 	h.observe(v, i)
@@ -821,6 +843,13 @@ func (h *histogram) Write(out *dto.Metric) error {
 				Length: proto.Uint32(0),
 			}}
 		}
+
+		if h.nativeExemplars.isEnabled() {
+			h.nativeExemplars.Lock()
+			his.Exemplars = append(his.Exemplars, h.nativeExemplars.exemplars...)
+			h.nativeExemplars.Unlock()
+		}
+
 	}
 	addAndResetCounts(hotCounts, coldCounts)
 	return nil
@@ -1091,8 +1120,10 @@ func (h *histogram) resetCounts(counts *histogramCounts) {
 	deleteSyncMap(&counts.nativeHistogramBucketsPositive)
 }
 
-// updateExemplar replaces the exemplar for the provided bucket. With empty
-// labels, it's a no-op. It panics if any of the labels is invalid.
+// updateExemplar replaces the exemplar for the provided classic bucket.
+// With empty labels, it's a no-op. It panics if any of the labels is invalid.
+// If histogram is native, the exemplar will be cached into nativeExemplars,
+// which has a limit, and will remove one exemplar when limit is reached.
 func (h *histogram) updateExemplar(v float64, bucket int, l Labels) {
 	if l == nil {
 		return
@@ -1102,6 +1133,10 @@ func (h *histogram) updateExemplar(v float64, bucket int, l Labels) {
 		panic(err)
 	}
 	h.exemplars[bucket].Store(e)
+	doSparse := h.nativeHistogramSchema > math.MinInt32 && !math.IsNaN(v)
+	if doSparse {
+		h.nativeExemplars.addExemplar(e)
+	}
 }
 
 // HistogramVec is a Collector that bundles a set of Histograms that all share the
@@ -1336,6 +1371,48 @@ func MustNewConstHistogram(
 	return m
 }
 
+// NewConstHistogramWithCreatedTimestamp does the same thing as NewConstHistogram but sets the created timestamp.
+func NewConstHistogramWithCreatedTimestamp(
+	desc *Desc,
+	count uint64,
+	sum float64,
+	buckets map[float64]uint64,
+	ct time.Time,
+	labelValues ...string,
+) (Metric, error) {
+	if desc.err != nil {
+		return nil, desc.err
+	}
+	if err := validateLabelValues(labelValues, len(desc.variableLabels.names)); err != nil {
+		return nil, err
+	}
+	return &constHistogram{
+		desc:       desc,
+		count:      count,
+		sum:        sum,
+		buckets:    buckets,
+		labelPairs: MakeLabelPairs(desc, labelValues),
+		createdTs:  timestamppb.New(ct),
+	}, nil
+}
+
+// MustNewConstHistogramWithCreatedTimestamp is a version of NewConstHistogramWithCreatedTimestamp that panics where
+// NewConstHistogramWithCreatedTimestamp would have returned an error.
+func MustNewConstHistogramWithCreatedTimestamp(
+	desc *Desc,
+	count uint64,
+	sum float64,
+	buckets map[float64]uint64,
+	ct time.Time,
+	labelValues ...string,
+) Metric {
+	m, err := NewConstHistogramWithCreatedTimestamp(desc, count, sum, buckets, ct, labelValues...)
+	if err != nil {
+		panic(err)
+	}
+	return m
+}
+
 type buckSort []*dto.Bucket
 
 func (s buckSort) Len() int {
@@ -1575,3 +1652,186 @@ func addAndResetCounts(hot, cold *histogramCounts) {
 	atomic.AddUint64(&hot.nativeHistogramZeroBucket, atomic.LoadUint64(&cold.nativeHistogramZeroBucket))
 	atomic.StoreUint64(&cold.nativeHistogramZeroBucket, 0)
 }
+
+type nativeExemplars struct {
+	sync.Mutex
+
+	// Time-to-live for exemplars, it is set to -1 if exemplars are disabled, that is NativeHistogramMaxExemplars is below 0.
+	// The ttl is used on insertion to remove an exemplar that is older than ttl, if present.
+	ttl time.Duration
+
+	exemplars []*dto.Exemplar
+}
+
+func (n *nativeExemplars) isEnabled() bool {
+	return n.ttl != -1
+}
+
+func makeNativeExemplars(ttl time.Duration, maxCount int) nativeExemplars {
+	if ttl == 0 {
+		ttl = 5 * time.Minute
+	}
+
+	if maxCount == 0 {
+		maxCount = 10
+	}
+
+	if maxCount < 0 {
+		maxCount = 0
+		ttl = -1
+	}
+
+	return nativeExemplars{
+		ttl:       ttl,
+		exemplars: make([]*dto.Exemplar, 0, maxCount),
+	}
+}
+
+func (n *nativeExemplars) addExemplar(e *dto.Exemplar) {
+	if !n.isEnabled() {
+		return
+	}
+
+	n.Lock()
+	defer n.Unlock()
+
+	// When the number of exemplars has not yet exceeded or
+	// is equal to cap(n.exemplars), then
+	// insert the new exemplar directly.
+	if len(n.exemplars) < cap(n.exemplars) {
+		var nIdx int
+		for nIdx = 0; nIdx < len(n.exemplars); nIdx++ {
+			if *e.Value < *n.exemplars[nIdx].Value {
+				break
+			}
+		}
+		n.exemplars = append(n.exemplars[:nIdx], append([]*dto.Exemplar{e}, n.exemplars[nIdx:]...)...)
+		return
+	}
+
+	if len(n.exemplars) == 1 {
+		// When the number of exemplars is 1, then
+		// replace the existing exemplar with the new exemplar.
+		n.exemplars[0] = e
+		return
+	}
+	// From this point on, the number of exemplars is greater than 1.
+
+	// When the number of exemplars exceeds the limit, remove one exemplar.
+	var (
+		ot    = time.Time{} // Oldest timestamp seen. Initial value doesn't matter as we replace it due to otIdx == -1 in the loop.
+		otIdx = -1          // Index of the exemplar with the oldest timestamp.
+
+		md = -1.0 // Logarithm of the delta of the closest pair of exemplars.
+
+		// The insertion point of the new exemplar in the exemplars slice after insertion.
+		// This is calculated purely based on the order of the exemplars by value.
+		// nIdx == len(n.exemplars) means the new exemplar is to be inserted after the end.
+		nIdx = -1
+
+		// rIdx is ultimately the index for the exemplar that we are replacing with the new exemplar.
+		// The aim is to keep a good spread of exemplars by value and not let them bunch up too much.
+		// It is calculated in 3 steps:
+		//   1. First we set rIdx to the index of the older exemplar within the closest pair by value.
+		//      That is the following will be true (on log scale):
+		//      either the exemplar pair on index (rIdx-1, rIdx) or (rIdx, rIdx+1) will have
+		//      the closest values to each other from all pairs.
+		//      For example, suppose the values are distributed like this:
+		//        |-----------x-------------x----------------x----x-----|
+		//                                                   ^--rIdx as this is older.
+		//      Or like this:
+		//        |-----------x-------------x----------------x----x-----|
+		//                                                        ^--rIdx as this is older.
+		//   2. If there is an exemplar that expired, then we simple reset rIdx to that index.
+		//   3. We check if by inserting the new exemplar we would create a closer pair at
+		//      (nIdx-1, nIdx) or (nIdx, nIdx+1) and set rIdx to nIdx-1 or nIdx accordingly to
+		//      keep the spread of exemplars by value; otherwise we keep rIdx as it is.
+		rIdx = -1
+		cLog float64 // Logarithm of the current exemplar.
+		pLog float64 // Logarithm of the previous exemplar.
+	)
+
+	for i, exemplar := range n.exemplars {
+		// Find the exemplar with the oldest timestamp.
+		if otIdx == -1 || exemplar.Timestamp.AsTime().Before(ot) {
+			ot = exemplar.Timestamp.AsTime()
+			otIdx = i
+		}
+
+		// Find the index at which to insert new the exemplar.
+		if nIdx == -1 && *e.Value <= *exemplar.Value {
+			nIdx = i
+		}
+
+		// Find the two closest exemplars and pick the one the with older timestamp.
+		pLog = cLog
+		cLog = math.Log(exemplar.GetValue())
+		if i == 0 {
+			continue
+		}
+		diff := math.Abs(cLog - pLog)
+		if md == -1 || diff < md {
+			// The closest exemplar pair is at index: i-1, i.
+			// Choose the exemplar with the older timestamp for replacement.
+			md = diff
+			if n.exemplars[i].Timestamp.AsTime().Before(n.exemplars[i-1].Timestamp.AsTime()) {
+				rIdx = i
+			} else {
+				rIdx = i - 1
+			}
+		}
+
+	}
+
+	// If all existing exemplar are smaller than new exemplar,
+	// then the exemplar should be inserted at the end.
+	if nIdx == -1 {
+		nIdx = len(n.exemplars)
+	}
+	// Here, we have the following relationships:
+	// n.exemplars[nIdx-1].Value < e.Value (if nIdx > 0)
+	// e.Value <= n.exemplars[nIdx].Value (if nIdx < len(n.exemplars))
+
+	if otIdx != -1 && e.Timestamp.AsTime().Sub(ot) > n.ttl {
+		// If the oldest exemplar has expired, then replace it with the new exemplar.
+		rIdx = otIdx
+	} else {
+		// In the previous for loop, when calculating the closest pair of exemplars,
+		// we did not take into account the newly inserted exemplar.
+		// So we need to calculate with the newly inserted exemplar again.
+		elog := math.Log(e.GetValue())
+		if nIdx > 0 {
+			diff := math.Abs(elog - math.Log(n.exemplars[nIdx-1].GetValue()))
+			if diff < md {
+				// The value we are about to insert is closer to the previous exemplar at the insertion point than what we calculated before in rIdx.
+				//                                            v--rIdx
+				// |-----------x-n-----------x----------------x----x-----|
+				//     nIdx-1--^ ^--new exemplar value
+				// Do not make the spread worse, replace nIdx-1 and not rIdx.
+				md = diff
+				rIdx = nIdx - 1
+			}
+		}
+		if nIdx < len(n.exemplars) {
+			diff := math.Abs(math.Log(n.exemplars[nIdx].GetValue()) - elog)
+			if diff < md {
+				// The value we are about to insert is closer to the next exemplar at the insertion point than what we calculated before in rIdx.
+				//                                            v--rIdx
+				// |-----------x-----------n-x----------------x----x-----|
+				//     new exemplar value--^ ^--nIdx
+				// Do not make the spread worse, replace nIdx-1 and not rIdx.
+				rIdx = nIdx
+			}
+		}
+	}
+
+	// Adjust the slice according to rIdx and nIdx.
+	switch {
+	case rIdx == nIdx:
+		n.exemplars[nIdx] = e
+	case rIdx < nIdx:
+		n.exemplars = append(n.exemplars[:rIdx], append(n.exemplars[rIdx+1:nIdx], append([]*dto.Exemplar{e}, n.exemplars[nIdx:]...)...)...)
+	case rIdx > nIdx:
+		n.exemplars = append(n.exemplars[:nIdx], append([]*dto.Exemplar{e}, append(n.exemplars[nIdx:rIdx], n.exemplars[rIdx+1:]...)...)...)
+	}
+}
diff --git prometheus/histogram_test.go prometheus/histogram_test.go
index 39bb0dcd4..c2a14ae72 100644
--- prometheus/histogram_test.go
+++ prometheus/histogram_test.go
@@ -1049,10 +1049,14 @@ func TestNativeHistogramConcurrency(t *testing.T) {
 
 			go func(vals []float64) {
 				start.Wait()
-				for _, v := range vals {
+				for i, v := range vals {
 					// An observation every 1 to 10 seconds.
 					atomic.AddInt64(&ts, rand.Int63n(10)+1)
-					his.Observe(v)
+					if i%2 == 0 {
+						his.Observe(v)
+					} else {
+						his.(ExemplarObserver).ObserveWithExemplar(v, Labels{"foo": "bar"})
+					}
 				}
 				end.Done()
 			}(vals)
@@ -1271,3 +1275,183 @@ func TestHistogramVecCreatedTimestampWithDeletes(t *testing.T) {
 	now = now.Add(1 * time.Hour)
 	expectCTsForMetricVecValues(t, histogramVec.MetricVec, dto.MetricType_HISTOGRAM, expected)
 }
+
+func TestNewConstHistogramWithCreatedTimestamp(t *testing.T) {
+	metricDesc := NewDesc(
+		"sample_value",
+		"sample value",
+		nil,
+		nil,
+	)
+	buckets := map[float64]uint64{25: 100, 50: 200}
+	createdTs := time.Unix(1719670764, 123)
+
+	h, err := NewConstHistogramWithCreatedTimestamp(metricDesc, 100, 200, buckets, createdTs)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	var metric dto.Metric
+	if err := h.Write(&metric); err != nil {
+		t.Fatal(err)
+	}
+
+	if metric.Histogram.CreatedTimestamp.AsTime().UnixMicro() != createdTs.UnixMicro() {
+		t.Errorf("Expected created timestamp %v, got %v", createdTs, &metric.Histogram.CreatedTimestamp)
+	}
+}
+
+func TestNativeHistogramExemplar(t *testing.T) {
+	// Test the histogram with positive NativeHistogramExemplarTTL and NativeHistogramMaxExemplars
+	h := NewHistogram(HistogramOpts{
+		Name:                        "test",
+		Help:                        "test help",
+		Buckets:                     []float64{1, 2, 3, 4},
+		NativeHistogramBucketFactor: 1.1,
+		NativeHistogramMaxExemplars: 3,
+		NativeHistogramExemplarTTL:  10 * time.Second,
+	}).(*histogram)
+
+	tcs := []struct {
+		name           string
+		addFunc        func(*histogram)
+		expectedValues []float64
+	}{
+		{
+			name: "add exemplars to the limit",
+			addFunc: func(h *histogram) {
+				h.ObserveWithExemplar(1, Labels{"id": "1"})
+				h.ObserveWithExemplar(3, Labels{"id": "1"})
+				h.ObserveWithExemplar(5, Labels{"id": "1"})
+			},
+			expectedValues: []float64{1, 3, 5},
+		},
+		{
+			name: "remove exemplar in closest pair, the removed index equals to inserted index",
+			addFunc: func(h *histogram) {
+				h.ObserveWithExemplar(4, Labels{"id": "1"})
+			},
+			expectedValues: []float64{1, 3, 4},
+		},
+		{
+			name: "remove exemplar in closest pair, the removed index is bigger than inserted index",
+			addFunc: func(h *histogram) {
+				h.ObserveWithExemplar(0, Labels{"id": "1"})
+			},
+			expectedValues: []float64{0, 1, 4},
+		},
+		{
+			name: "remove exemplar with oldest timestamp, the removed index is smaller than inserted index",
+			addFunc: func(h *histogram) {
+				h.now = func() time.Time { return time.Now().Add(time.Second * 11) }
+				h.ObserveWithExemplar(6, Labels{"id": "1"})
+			},
+			expectedValues: []float64{0, 4, 6},
+		},
+	}
+
+	for _, tc := range tcs {
+		t.Run(tc.name, func(t *testing.T) {
+			tc.addFunc(h)
+			compareNativeExemplarValues(t, h.nativeExemplars.exemplars, tc.expectedValues)
+		})
+	}
+
+	// Test the histogram with negative NativeHistogramExemplarTTL
+	h = NewHistogram(HistogramOpts{
+		Name:                        "test",
+		Help:                        "test help",
+		Buckets:                     []float64{1, 2, 3, 4},
+		NativeHistogramBucketFactor: 1.1,
+		NativeHistogramMaxExemplars: 3,
+		NativeHistogramExemplarTTL:  -1 * time.Second,
+	}).(*histogram)
+
+	tcs = []struct {
+		name           string
+		addFunc        func(*histogram)
+		expectedValues []float64
+	}{
+		{
+			name: "add exemplars to the limit",
+			addFunc: func(h *histogram) {
+				h.ObserveWithExemplar(1, Labels{"id": "1"})
+				h.ObserveWithExemplar(3, Labels{"id": "1"})
+				h.ObserveWithExemplar(5, Labels{"id": "1"})
+			},
+			expectedValues: []float64{1, 3, 5},
+		},
+		{
+			name: "remove exemplar with oldest timestamp, the removed index is smaller than inserted index",
+			addFunc: func(h *histogram) {
+				h.ObserveWithExemplar(4, Labels{"id": "1"})
+			},
+			expectedValues: []float64{3, 4, 5},
+		},
+		{
+			name: "remove exemplar with oldest timestamp, the removed index equals to inserted index",
+			addFunc: func(h *histogram) {
+				h.ObserveWithExemplar(0, Labels{"id": "1"})
+			},
+			expectedValues: []float64{0, 4, 5},
+		},
+		{
+			name: "remove exemplar with oldest timestamp, the removed index is bigger than inserted index",
+			addFunc: func(h *histogram) {
+				h.ObserveWithExemplar(3, Labels{"id": "1"})
+			},
+			expectedValues: []float64{0, 3, 4},
+		},
+	}
+
+	for _, tc := range tcs {
+		t.Run(tc.name, func(t *testing.T) {
+			tc.addFunc(h)
+			compareNativeExemplarValues(t, h.nativeExemplars.exemplars, tc.expectedValues)
+		})
+	}
+
+	// Test the histogram with negative NativeHistogramMaxExemplars
+	h = NewHistogram(HistogramOpts{
+		Name:                        "test",
+		Help:                        "test help",
+		Buckets:                     []float64{1, 2, 3, 4},
+		NativeHistogramBucketFactor: 1.1,
+		NativeHistogramMaxExemplars: -1,
+		NativeHistogramExemplarTTL:  -1 * time.Second,
+	}).(*histogram)
+
+	tcs = []struct {
+		name           string
+		addFunc        func(*histogram)
+		expectedValues []float64
+	}{
+		{
+			name: "add exemplars to the limit, but no effect",
+			addFunc: func(h *histogram) {
+				h.ObserveWithExemplar(1, Labels{"id": "1"})
+				h.ObserveWithExemplar(3, Labels{"id": "1"})
+				h.ObserveWithExemplar(5, Labels{"id": "1"})
+			},
+			expectedValues: []float64{},
+		},
+	}
+
+	for _, tc := range tcs {
+		t.Run(tc.name, func(t *testing.T) {
+			tc.addFunc(h)
+			compareNativeExemplarValues(t, h.nativeExemplars.exemplars, tc.expectedValues)
+		})
+	}
+}
+
+func compareNativeExemplarValues(t *testing.T, exps []*dto.Exemplar, values []float64) {
+	if len(exps) != len(values) {
+		t.Errorf("the count of exemplars is not %d", len(values))
+	}
+	for i, e := range exps {
+		if e.GetValue() != values[i] {
+			t.Errorf("the %dth exemplar value %v is not as expected: %v", i, e.GetValue(), values[i])
+		}
+	}
+}
diff --git prometheus/internal/go_collector_options.go prometheus/internal/go_collector_options.go
index 723b45d64..a4fa6eabd 100644
--- prometheus/internal/go_collector_options.go
+++ prometheus/internal/go_collector_options.go
@@ -30,3 +30,5 @@ type GoCollectorOptions struct {
 	RuntimeMetricSumForHist    map[string]string
 	RuntimeMetricRules         []GoCollectorRule
 }
+
+var GoCollectorDefaultRuntimeMetrics = regexp.MustCompile(`/gc/gogc:percent|/gc/gomemlimit:bytes|/sched/gomaxprocs:threads`)
diff --git prometheus/metric.go prometheus/metric.go
index f018e5723..9d9b81ab4 100644
--- prometheus/metric.go
+++ prometheus/metric.go
@@ -234,7 +234,7 @@ func NewMetricWithExemplars(m Metric, exemplars ...Exemplar) (Metric, error) {
 	)
 	for i, e := range exemplars {
 		ts := e.Timestamp
-		if ts == (time.Time{}) {
+		if ts.IsZero() {
 			ts = now
 		}
 		exs[i], err = newExemplar(e.Value, ts, e.Labels)
diff --git prometheus/process_collector.go prometheus/process_collector.go
index 8548dd18e..62a4e7ad9 100644
--- prometheus/process_collector.go
+++ prometheus/process_collector.go
@@ -22,14 +22,15 @@ import (
 )
 
 type processCollector struct {
-	collectFn       func(chan<- Metric)
-	pidFn           func() (int, error)
-	reportErrors    bool
-	cpuTotal        *Desc
-	openFDs, maxFDs *Desc
-	vsize, maxVsize *Desc
-	rss             *Desc
-	startTime       *Desc
+	collectFn         func(chan<- Metric)
+	pidFn             func() (int, error)
+	reportErrors      bool
+	cpuTotal          *Desc
+	openFDs, maxFDs   *Desc
+	vsize, maxVsize   *Desc
+	rss               *Desc
+	startTime         *Desc
+	inBytes, outBytes *Desc
 }
 
 // ProcessCollectorOpts defines the behavior of a process metrics collector
@@ -100,6 +101,16 @@ func NewProcessCollector(opts ProcessCollectorOpts) Collector {
 			"Start time of the process since unix epoch in seconds.",
 			nil, nil,
 		),
+		inBytes: NewDesc(
+			ns+"process_network_receive_bytes_total",
+			"Number of bytes received by the process over the network.",
+			nil, nil,
+		),
+		outBytes: NewDesc(
+			ns+"process_network_transmit_bytes_total",
+			"Number of bytes sent by the process over the network.",
+			nil, nil,
+		),
 	}
 
 	if opts.PidFn == nil {
@@ -129,6 +140,8 @@ func (c *processCollector) Describe(ch chan<- *Desc) {
 	ch <- c.maxVsize
 	ch <- c.rss
 	ch <- c.startTime
+	ch <- c.inBytes
+	ch <- c.outBytes
 }
 
 // Collect returns the current state of all metrics of the collector.
diff --git prometheus/process_collector_other.go prometheus/process_collector_other.go
index 8c1136cee..14d56d2d0 100644
--- prometheus/process_collector_other.go
+++ prometheus/process_collector_other.go
@@ -63,4 +63,18 @@ func (c *processCollector) processCollect(ch chan<- Metric) {
 	} else {
 		c.reportError(ch, nil, err)
 	}
+
+	if netstat, err := p.Netstat(); err == nil {
+		var inOctets, outOctets float64
+		if netstat.IpExt.InOctets != nil {
+			inOctets = *netstat.IpExt.InOctets
+		}
+		if netstat.IpExt.OutOctets != nil {
+			outOctets = *netstat.IpExt.OutOctets
+		}
+		ch <- MustNewConstMetric(c.inBytes, CounterValue, inOctets)
+		ch <- MustNewConstMetric(c.outBytes, CounterValue, outOctets)
+	} else {
+		c.reportError(ch, nil, err)
+	}
 }
diff --git prometheus/process_collector_test.go prometheus/process_collector_test.go
index 3a604aba9..0d62d41e0 100644
--- prometheus/process_collector_test.go
+++ prometheus/process_collector_test.go
@@ -37,7 +37,7 @@ func TestProcessCollector(t *testing.T) {
 		t.Skipf("skipping TestProcessCollector, procfs not available: %s", err)
 	}
 
-	registry := NewRegistry()
+	registry := NewPedanticRegistry()
 	if err := registry.Register(NewProcessCollector(ProcessCollectorOpts{})); err != nil {
 		t.Fatal(err)
 	}
@@ -69,6 +69,8 @@ func TestProcessCollector(t *testing.T) {
 		regexp.MustCompile("\nprocess_virtual_memory_bytes [1-9]"),
 		regexp.MustCompile("\nprocess_resident_memory_bytes [1-9]"),
 		regexp.MustCompile("\nprocess_start_time_seconds [0-9.]{10,}"),
+		regexp.MustCompile("\nprocess_network_receive_bytes_total [0-9]+"),
+		regexp.MustCompile("\nprocess_network_transmit_bytes_total [0-9]+"),
 		regexp.MustCompile("\nfoobar_process_cpu_seconds_total [0-9]"),
 		regexp.MustCompile("\nfoobar_process_max_fds [1-9]"),
 		regexp.MustCompile("\nfoobar_process_open_fds [1-9]"),
@@ -76,6 +78,8 @@ func TestProcessCollector(t *testing.T) {
 		regexp.MustCompile("\nfoobar_process_virtual_memory_bytes [1-9]"),
 		regexp.MustCompile("\nfoobar_process_resident_memory_bytes [1-9]"),
 		regexp.MustCompile("\nfoobar_process_start_time_seconds [0-9.]{10,}"),
+		regexp.MustCompile("\nfoobar_process_network_receive_bytes_total [0-9]+"),
+		regexp.MustCompile("\nfoobar_process_network_transmit_bytes_total [0-9]+"),
 	} {
 		if !re.Match(buf.Bytes()) {
 			t.Errorf("want body to match %s\n%s", re, buf.String())
diff --git prometheus/promhttp/delegator.go prometheus/promhttp/delegator.go
index 9819917b8..315eab5f1 100644
--- prometheus/promhttp/delegator.go
+++ prometheus/promhttp/delegator.go
@@ -76,6 +76,12 @@ func (r *responseWriterDelegator) Write(b []byte) (int, error) {
 	return n, err
 }
 
+// Unwrap lets http.ResponseController get the underlying http.ResponseWriter,
+// by implementing the [rwUnwrapper](https://cs.opensource.google/go/go/+/refs/tags/go1.21.4:src/net/http/responsecontroller.go;l=42-44) interface.
+func (r *responseWriterDelegator) Unwrap() http.ResponseWriter {
+	return r.ResponseWriter
+}
+
 type (
 	closeNotifierDelegator struct{ *responseWriterDelegator }
 	flusherDelegator       struct{ *responseWriterDelegator }
diff --git a/prometheus/promhttp/delegator_test.go b/prometheus/promhttp/delegator_test.go
new file mode 100644
index 000000000..4576ae7c0
--- /dev/null
+++ prometheus/promhttp/delegator_test.go
@@ -0,0 +1,78 @@
+// 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 promhttp
+
+import (
+	"net/http"
+	"testing"
+	"time"
+)
+
+type responseWriter struct {
+	flushErrorCalled       bool
+	setWriteDeadlineCalled time.Time
+	setReadDeadlineCalled  time.Time
+}
+
+func (rw *responseWriter) Header() http.Header {
+	return nil
+}
+
+func (rw *responseWriter) Write(p []byte) (int, error) {
+	return 0, nil
+}
+
+func (rw *responseWriter) WriteHeader(statusCode int) {
+}
+
+func (rw *responseWriter) FlushError() error {
+	rw.flushErrorCalled = true
+
+	return nil
+}
+
+func (rw *responseWriter) SetWriteDeadline(deadline time.Time) error {
+	rw.setWriteDeadlineCalled = deadline
+
+	return nil
+}
+
+func (rw *responseWriter) SetReadDeadline(deadline time.Time) error {
+	rw.setReadDeadlineCalled = deadline
+
+	return nil
+}
+
+func TestResponseWriterDelegatorUnwrap(t *testing.T) {
+	w := &responseWriter{}
+	rwd := &responseWriterDelegator{ResponseWriter: w}
+
+	if rwd.Unwrap() != w {
+		t.Error("unwrapped responsewriter must equal to the original responsewriter")
+	}
+
+	controller := http.NewResponseController(rwd)
+	if err := controller.Flush(); err != nil || !w.flushErrorCalled {
+		t.Error("FlushError must be propagated to the original responsewriter")
+	}
+
+	timeNow := time.Now()
+	if err := controller.SetWriteDeadline(timeNow); err != nil || w.setWriteDeadlineCalled != timeNow {
+		t.Error("SetWriteDeadline must be propagated to the original responsewriter")
+	}
+
+	if err := controller.SetReadDeadline(timeNow); err != nil || w.setReadDeadlineCalled != timeNow {
+		t.Error("SetReadDeadline must be propagated to the original responsewriter")
+	}
+}
diff --git prometheus/promhttp/http.go prometheus/promhttp/http.go
index 09b8d2fbe..e598e66e6 100644
--- prometheus/promhttp/http.go
+++ prometheus/promhttp/http.go
@@ -38,12 +38,13 @@ import (
 	"io"
 	"net/http"
 	"strconv"
-	"strings"
 	"sync"
 	"time"
 
+	"github.com/klauspost/compress/zstd"
 	"github.com/prometheus/common/expfmt"
 
+	"github.com/prometheus/client_golang/internal/github.com/golang/gddo/httputil"
 	"github.com/prometheus/client_golang/prometheus"
 )
 
@@ -54,6 +55,18 @@ const (
 	processStartTimeHeader = "Process-Start-Time-Unix"
 )
 
+// Compression represents the content encodings handlers support for the HTTP
+// responses.
+type Compression string
+
+const (
+	Identity Compression = "identity"
+	Gzip     Compression = "gzip"
+	Zstd     Compression = "zstd"
+)
+
+var defaultCompressionFormats = []Compression{Identity, Gzip, Zstd}
+
 var gzipPool = sync.Pool{
 	New: func() interface{} {
 		return gzip.NewWriter(nil)
@@ -122,6 +135,18 @@ func HandlerForTransactional(reg prometheus.TransactionalGatherer, opts HandlerO
 		}
 	}
 
+	// Select compression formats to offer based on default or user choice.
+	var compressions []string
+	if !opts.DisableCompression {
+		offers := defaultCompressionFormats
+		if len(opts.OfferedCompressions) > 0 {
+			offers = opts.OfferedCompressions
+		}
+		for _, comp := range offers {
+			compressions = append(compressions, string(comp))
+		}
+	}
+
 	h := http.HandlerFunc(func(rsp http.ResponseWriter, req *http.Request) {
 		if !opts.ProcessStartTime.IsZero() {
 			rsp.Header().Set(processStartTimeHeader, strconv.FormatInt(opts.ProcessStartTime.Unix(), 10))
@@ -165,21 +190,23 @@ func HandlerForTransactional(reg prometheus.TransactionalGatherer, opts HandlerO
 		} else {
 			contentType = expfmt.Negotiate(req.Header)
 		}
-		header := rsp.Header()
-		header.Set(contentTypeHeader, string(contentType))
+		rsp.Header().Set(contentTypeHeader, string(contentType))
 
-		w := io.Writer(rsp)
-		if !opts.DisableCompression && gzipAccepted(req.Header) {
-			header.Set(contentEncodingHeader, "gzip")
-			gz := gzipPool.Get().(*gzip.Writer)
-			defer gzipPool.Put(gz)
+		w, encodingHeader, closeWriter, err := negotiateEncodingWriter(req, rsp, compressions)
+		if err != nil {
+			if opts.ErrorLog != nil {
+				opts.ErrorLog.Println("error getting writer", err)
+			}
+			w = io.Writer(rsp)
+			encodingHeader = string(Identity)
+		}
 
-			gz.Reset(w)
-			defer gz.Close()
+		defer closeWriter()
 
-			w = gz
+		// Set Content-Encoding only when data is compressed
+		if encodingHeader != string(Identity) {
+			rsp.Header().Set(contentEncodingHeader, encodingHeader)
 		}
-
 		enc := expfmt.NewEncoder(w, contentType)
 
 		// handleError handles the error according to opts.ErrorHandling
@@ -343,9 +370,19 @@ type HandlerOpts struct {
 	// no effect on the HTTP status code because ErrorHandling is set to
 	// ContinueOnError.
 	Registry prometheus.Registerer
-	// If DisableCompression is true, the handler will never compress the
-	// response, even if requested by the client.
+	// DisableCompression disables the response encoding (compression) and
+	// encoding negotiation. If true, the handler will
+	// never compress the response, even if requested
+	// by the client and the OfferedCompressions field is set.
 	DisableCompression bool
+	// OfferedCompressions is a set of encodings (compressions) handler will
+	// try to offer when negotiating with the client. This defaults to identity, gzip
+	// and zstd.
+	// NOTE: If handler can't agree with the client on the encodings or
+	// unsupported or empty encodings are set in OfferedCompressions,
+	// handler always fallbacks to no compression (identity), for
+	// compatibility reasons. In such cases ErrorLog will be used if set.
+	OfferedCompressions []Compression
 	// The number of concurrent HTTP requests is limited to
 	// MaxRequestsInFlight. Additional requests are responded to with 503
 	// Service Unavailable and a suitable message in the body. If
@@ -381,19 +418,6 @@ type HandlerOpts struct {
 	ProcessStartTime time.Time
 }
 
-// gzipAccepted returns whether the client will accept gzip-encoded content.
-func gzipAccepted(header http.Header) bool {
-	a := header.Get(acceptEncodingHeader)
-	parts := strings.Split(a, ",")
-	for _, part := range parts {
-		part = strings.TrimSpace(part)
-		if part == "gzip" || strings.HasPrefix(part, "gzip;") {
-			return true
-		}
-	}
-	return false
-}
-
 // httpError removes any content-encoding header and then calls http.Error with
 // the provided error and http.StatusInternalServerError. Error contents is
 // supposed to be uncompressed plain text. Same as with a plain http.Error, this
@@ -406,3 +430,38 @@ func httpError(rsp http.ResponseWriter, err error) {
 		http.StatusInternalServerError,
 	)
 }
+
+// negotiateEncodingWriter reads the Accept-Encoding header from a request and
+// selects the right compression based on an allow-list of supported
+// compressions. It returns a writer implementing the compression and an the
+// correct value that the caller can set in the response header.
+func negotiateEncodingWriter(r *http.Request, rw io.Writer, compressions []string) (_ io.Writer, encodingHeaderValue string, closeWriter func(), _ error) {
+	if len(compressions) == 0 {
+		return rw, string(Identity), func() {}, nil
+	}
+
+	// TODO(mrueg): Replace internal/github.com/gddo once https://github.com/golang/go/issues/19307 is implemented.
+	selected := httputil.NegotiateContentEncoding(r, compressions)
+
+	switch selected {
+	case "zstd":
+		// TODO(mrueg): Replace klauspost/compress with stdlib implementation once https://github.com/golang/go/issues/62513 is implemented.
+		z, err := zstd.NewWriter(rw, zstd.WithEncoderLevel(zstd.SpeedFastest))
+		if err != nil {
+			return nil, "", func() {}, err
+		}
+
+		z.Reset(rw)
+		return z, selected, func() { _ = z.Close() }, nil
+	case "gzip":
+		gz := gzipPool.Get().(*gzip.Writer)
+		gz.Reset(rw)
+		return gz, selected, func() { _ = gz.Close(); gzipPool.Put(gz) }, nil
+	case "identity":
+		// This means the content is not compressed.
+		return rw, selected, func() {}, nil
+	default:
+		// The content encoding was not implemented yet.
+		return nil, "", func() {}, fmt.Errorf("content compression format not recognized: %s. Valid formats are: %s", selected, defaultCompressionFormats)
+	}
+}
diff --git prometheus/promhttp/http_test.go prometheus/promhttp/http_test.go
index 8ca192748..3ad2d1da8 100644
--- prometheus/promhttp/http_test.go
+++ prometheus/promhttp/http_test.go
@@ -15,8 +15,10 @@ package promhttp
 
 import (
 	"bytes"
+	"compress/gzip"
 	"errors"
 	"fmt"
+	"io"
 	"log"
 	"net/http"
 	"net/http/httptest"
@@ -24,6 +26,7 @@ import (
 	"testing"
 	"time"
 
+	"github.com/klauspost/compress/zstd"
 	dto "github.com/prometheus/client_model/go"
 
 	"github.com/prometheus/client_golang/prometheus"
@@ -31,6 +34,11 @@ import (
 
 type errorCollector struct{}
 
+const (
+	acceptHeader    = "Accept"
+	acceptTextPlain = "text/plain"
+)
+
 func (e errorCollector) Describe(ch chan<- *prometheus.Desc) {
 	ch <- prometheus.NewDesc("invalid_metric", "not helpful", nil, nil)
 }
@@ -71,6 +79,28 @@ func (g *mockTransactionGatherer) Gather() (_ []*dto.MetricFamily, done func(),
 	return mfs, func() { g.doneInvoked++ }, err
 }
 
+func readCompressedBody(r io.Reader, comp Compression) (string, error) {
+	switch comp {
+	case Gzip:
+		reader, err := gzip.NewReader(r)
+		if err != nil {
+			return "", err
+		}
+		defer reader.Close()
+		got, err := io.ReadAll(reader)
+		return string(got), err
+	case Zstd:
+		reader, err := zstd.NewReader(r)
+		if err != nil {
+			return "", err
+		}
+		defer reader.Close()
+		got, err := io.ReadAll(reader)
+		return string(got), err
+	}
+	return "", fmt.Errorf("Unsupported compression")
+}
+
 func TestHandlerErrorHandling(t *testing.T) {
 	// Create a registry that collects a MetricFamily with two elements,
 	// another with one, and reports an error. Further down, we'll use the
@@ -223,7 +253,7 @@ func TestInstrumentMetricHandler(t *testing.T) {
 	InstrumentMetricHandler(reg, HandlerForTransactional(mReg, HandlerOpts{}))
 	writer := httptest.NewRecorder()
 	request, _ := http.NewRequest("GET", "/", nil)
-	request.Header.Add("Accept", "test/plain")
+	request.Header.Add(acceptHeader, acceptTextPlain)
 
 	handler.ServeHTTP(writer, request)
 	if got := mReg.gatherInvoked; got != 1 {
@@ -237,6 +267,10 @@ func TestInstrumentMetricHandler(t *testing.T) {
 		t.Errorf("got HTTP status code %d, want %d", got, want)
 	}
 
+	if got, want := writer.Header().Get(contentEncodingHeader), ""; got != want {
+		t.Errorf("got HTTP content encoding header %s, want %s", got, want)
+	}
+
 	want := "promhttp_metric_handler_requests_in_flight 1\n"
 	if got := writer.Body.String(); !strings.Contains(got, want) {
 		t.Errorf("got body %q, does not contain %q", got, want)
@@ -278,7 +312,7 @@ func TestHandlerMaxRequestsInFlight(t *testing.T) {
 	w2 := httptest.NewRecorder()
 	w3 := httptest.NewRecorder()
 	request, _ := http.NewRequest("GET", "/", nil)
-	request.Header.Add("Accept", "test/plain")
+	request.Header.Add(acceptHeader, acceptTextPlain)
 
 	c := blockingCollector{Block: make(chan struct{}), CollectStarted: make(chan struct{}, 1)}
 	reg.MustRegister(c)
@@ -331,3 +365,277 @@ func TestHandlerTimeout(t *testing.T) {
 
 	close(c.Block) // To not leak a goroutine.
 }
+
+func TestInstrumentMetricHandlerWithCompression(t *testing.T) {
+	reg := prometheus.NewRegistry()
+	mReg := &mockTransactionGatherer{g: reg}
+	handler := InstrumentMetricHandler(reg, HandlerForTransactional(mReg, HandlerOpts{DisableCompression: false}))
+	compression := Zstd
+	writer := httptest.NewRecorder()
+	request, _ := http.NewRequest("GET", "/", nil)
+	request.Header.Add(acceptHeader, acceptTextPlain)
+	request.Header.Add(acceptEncodingHeader, string(compression))
+
+	handler.ServeHTTP(writer, request)
+	if got := mReg.gatherInvoked; got != 1 {
+		t.Fatalf("unexpected number of gather invokes, want 1, got %d", got)
+	}
+	if got := mReg.doneInvoked; got != 1 {
+		t.Fatalf("unexpected number of done invokes, want 1, got %d", got)
+	}
+
+	if got, want := writer.Code, http.StatusOK; got != want {
+		t.Errorf("got HTTP status code %d, want %d", got, want)
+	}
+
+	if got, want := writer.Header().Get(contentEncodingHeader), string(compression); got != want {
+		t.Errorf("got HTTP content encoding header %s, want %s", got, want)
+	}
+
+	body, err := readCompressedBody(writer.Body, compression)
+	want := "promhttp_metric_handler_requests_in_flight 1\n"
+	if got := body; !strings.Contains(got, want) {
+		t.Errorf("got body %q, does not contain %q, err: %v", got, want, err)
+	}
+
+	want = "promhttp_metric_handler_requests_total{code=\"200\"} 0\n"
+	if got := body; !strings.Contains(got, want) {
+		t.Errorf("got body %q, does not contain %q, err: %v", got, want, err)
+	}
+
+	for i := 0; i < 100; i++ {
+		writer.Body.Reset()
+		handler.ServeHTTP(writer, request)
+
+		if got, want := mReg.gatherInvoked, i+2; got != want {
+			t.Fatalf("unexpected number of gather invokes, want %d, got %d", want, got)
+		}
+		if got, want := mReg.doneInvoked, i+2; got != want {
+			t.Fatalf("unexpected number of done invokes, want %d, got %d", want, got)
+		}
+		if got, want := writer.Code, http.StatusOK; got != want {
+			t.Errorf("got HTTP status code %d, want %d", got, want)
+		}
+		body, err := readCompressedBody(writer.Body, compression)
+
+		want := "promhttp_metric_handler_requests_in_flight 1\n"
+		if got := body; !strings.Contains(got, want) {
+			t.Errorf("got body %q, does not contain %q, err: %v", got, want, err)
+		}
+
+		want = fmt.Sprintf("promhttp_metric_handler_requests_total{code=\"200\"} %d\n", i+1)
+		if got := body; !strings.Contains(got, want) {
+			t.Errorf("got body %q, does not contain %q, err: %v", got, want, err)
+		}
+	}
+
+	// Test with Zstd
+	compression = Zstd
+	request.Header.Set(acceptEncodingHeader, string(compression))
+
+	handler.ServeHTTP(writer, request)
+
+	if got, want := writer.Code, http.StatusOK; got != want {
+		t.Errorf("got HTTP status code %d, want %d", got, want)
+	}
+
+	if got, want := writer.Header().Get(contentEncodingHeader), string(compression); got != want {
+		t.Errorf("got HTTP content encoding header %s, want %s", got, want)
+	}
+
+	body, err = readCompressedBody(writer.Body, compression)
+	want, = "promhttp_metric_handler_requests_in_flight 1\n"
+	if got := body; !strings.Contains(got, want) {
+		t.Errorf("got body %q, does not contain %q, err: %v", got, want, err)
+	}
+
+	want = "promhttp_metric_handler_requests_total{code=\"200\"} 101\n"
+	if got := body; !strings.Contains(got, want) {
+		t.Errorf("got body %q, does not contain %q, err: %v", got, want, err)
+	}
+
+	for i := 101; i < 201; i++ {
+		writer.Body.Reset()
+		handler.ServeHTTP(writer, request)
+
+		if got, want := mReg.gatherInvoked, i+2; got != want {
+			t.Fatalf("unexpected number of gather invokes, want %d, got %d", want, got)
+		}
+		if got, want := mReg.doneInvoked, i+2; got != want {
+			t.Fatalf("unexpected number of done invokes, want %d, got %d", want, got)
+		}
+		if got, want := writer.Code, http.StatusOK; got != want {
+			t.Errorf("got HTTP status code %d, want %d", got, want)
+		}
+		body, err := readCompressedBody(writer.Body, compression)
+
+		want := "promhttp_metric_handler_requests_in_flight 1\n"
+		if got := body; !strings.Contains(got, want) {
+			t.Errorf("got body %q, does not contain %q, err: %v", got, want, err)
+		}
+
+		want = fmt.Sprintf("promhttp_metric_handler_requests_total{code=\"200\"} %d\n", i+1)
+		if got := body; !strings.Contains(got, want) {
+			t.Errorf("got body %q, does not contain %q, err: %v", got, want, err)
+		}
+	}
+}
+
+func TestNegotiateEncodingWriter(t *testing.T) {
+	var defaultCompressions []string
+
+	for _, comp := range defaultCompressionFormats {
+		defaultCompressions = append(defaultCompressions, string(comp))
+	}
+
+	testCases := []struct {
+		name                string
+		offeredCompressions []string
+		acceptEncoding      string
+		expectedCompression string
+		err                 error
+	}{
+		{
+			name:                "test without compression enabled",
+			offeredCompressions: []string{},
+			acceptEncoding:      "",
+			expectedCompression: "identity",
+			err:                 nil,
+		},
+		{
+			name:                "test with compression enabled with empty accept-encoding header",
+			offeredCompressions: defaultCompressions,
+			acceptEncoding:      "",
+			expectedCompression: "identity",
+			err:                 nil,
+		},
+		{
+			name:                "test with gzip compression requested",
+			offeredCompressions: defaultCompressions,
+			acceptEncoding:      "gzip",
+			expectedCompression: "gzip",
+			err:                 nil,
+		},
+		{
+			name:                "test with gzip, zstd compression requested",
+			offeredCompressions: defaultCompressions,
+			acceptEncoding:      "gzip,zstd",
+			expectedCompression: "gzip",
+			err:                 nil,
+		},
+		{
+			name:                "test with zstd, gzip compression requested",
+			offeredCompressions: defaultCompressions,
+			acceptEncoding:      "zstd,gzip",
+			expectedCompression: "gzip",
+			err:                 nil,
+		},
+	}
+
+	for _, test := range testCases {
+		request, _ := http.NewRequest("GET", "/", nil)
+		request.Header.Add(acceptEncodingHeader, test.acceptEncoding)
+		rr := httptest.NewRecorder()
+		_, encodingHeader, _, err := negotiateEncodingWriter(request, rr, test.offeredCompressions)
+
+		if !errors.Is(err, test.err) {
+			t.Errorf("got error: %v, expected: %v", err, test.err)
+		}
+
+		if encodingHeader != test.expectedCompression {
+			t.Errorf("got different compression type: %v, expected: %v", encodingHeader, test.expectedCompression)
+		}
+	}
+}
+
+func BenchmarkCompression(b *testing.B) {
+	benchmarks := []struct {
+		name            string
+		compressionType string
+	}{
+		{
+			name:            "test with gzip compression",
+			compressionType: "gzip",
+		},
+		{
+			name:            "test with zstd compression",
+			compressionType: "zstd",
+		},
+		{
+			name:            "test with no compression",
+			compressionType: "identity",
+		},
+	}
+	sizes := []struct {
+		name         string
+		metricCount  int
+		labelCount   int
+		labelLength  int
+		metricLength int
+	}{
+		{
+			name:         "small",
+			metricCount:  50,
+			labelCount:   5,
+			labelLength:  5,
+			metricLength: 5,
+		},
+		{
+			name:         "medium",
+			metricCount:  500,
+			labelCount:   10,
+			labelLength:  5,
+			metricLength: 10,
+		},
+		{
+			name:         "large",
+			metricCount:  5000,
+			labelCount:   10,
+			labelLength:  5,
+			metricLength: 10,
+		},
+		{
+			name:         "extra-large",
+			metricCount:  50000,
+			labelCount:   20,
+			labelLength:  5,
+			metricLength: 10,
+		},
+	}
+
+	for _, size := range sizes {
+		reg := prometheus.NewRegistry()
+		handler := HandlerFor(reg, HandlerOpts{})
+
+		// Generate Metrics
+		// Original source: https://github.com/prometheus-community/avalanche/blob/main/metrics/serve.go
+		labelKeys := make([]string, size.labelCount)
+		for idx := 0; idx < size.labelCount; idx++ {
+			labelKeys[idx] = fmt.Sprintf("label_key_%s_%v", strings.Repeat("k", size.labelLength), idx)
+		}
+		labelValues := make([]string, size.labelCount)
+		for idx := 0; idx < size.labelCount; idx++ {
+			labelValues[idx] = fmt.Sprintf("label_val_%s_%v", strings.Repeat("v", size.labelLength), idx)
+		}
+		metrics := make([]*prometheus.GaugeVec, size.metricCount)
+		for idx := 0; idx < size.metricCount; idx++ {
+			gauge := prometheus.NewGaugeVec(prometheus.GaugeOpts{
+				Name: fmt.Sprintf("avalanche_metric_%s_%v_%v", strings.Repeat("m", size.metricLength), 0, idx),
+				Help: "A tasty metric morsel",
+			}, append([]string{"series_id", "cycle_id"}, labelKeys...))
+			reg.MustRegister(gauge)
+			metrics[idx] = gauge
+		}
+
+		for _, benchmark := range benchmarks {
+			b.Run(benchmark.name+"_"+size.name, func(b *testing.B) {
+				for i := 0; i < b.N; i++ {
+					writer := httptest.NewRecorder()
+					request, _ := http.NewRequest("GET", "/", nil)
+					request.Header.Add(acceptEncodingHeader, benchmark.compressionType)
+					handler.ServeHTTP(writer, request)
+				}
+			})
+		}
+	}
+}
diff --git prometheus/registry.go prometheus/registry.go
index 5e2ced25a..c6fd2f58b 100644
--- prometheus/registry.go
+++ prometheus/registry.go
@@ -314,16 +314,17 @@ func (r *Registry) Register(c Collector) error {
 			if dimHash != desc.dimHash {
 				return fmt.Errorf("a previously registered descriptor with the same fully-qualified name as %s has different label names or a different help string", desc)
 			}
-		} else {
-			// ...then check the new descriptors already seen.
-			if dimHash, exists := newDimHashesByName[desc.fqName]; exists {
-				if dimHash != desc.dimHash {
-					return fmt.Errorf("descriptors reported by collector have inconsistent label names or help strings for the same fully-qualified name, offender is %s", desc)
-				}
-			} else {
-				newDimHashesByName[desc.fqName] = desc.dimHash
+			continue
+		}
+
+		// ...then check the new descriptors already seen.
+		if dimHash, exists := newDimHashesByName[desc.fqName]; exists {
+			if dimHash != desc.dimHash {
+				return fmt.Errorf("descriptors reported by collector have inconsistent label names or help strings for the same fully-qualified name, offender is %s", desc)
 			}
+			continue
 		}
+		newDimHashesByName[desc.fqName] = desc.dimHash
 	}
 	// A Collector yielding no Desc at all is considered unchecked.
 	if len(newDescIDs) == 0 {
diff --git prometheus/summary.go prometheus/summary.go
index 146270444..1ab0e4796 100644
--- prometheus/summary.go
+++ prometheus/summary.go
@@ -783,3 +783,45 @@ func MustNewConstSummary(
 	}
 	return m
 }
+
+// NewConstSummaryWithCreatedTimestamp does the same thing as NewConstSummary but sets the created timestamp.
+func NewConstSummaryWithCreatedTimestamp(
+	desc *Desc,
+	count uint64,
+	sum float64,
+	quantiles map[float64]float64,
+	ct time.Time,
+	labelValues ...string,
+) (Metric, error) {
+	if desc.err != nil {
+		return nil, desc.err
+	}
+	if err := validateLabelValues(labelValues, len(desc.variableLabels.names)); err != nil {
+		return nil, err
+	}
+	return &constSummary{
+		desc:       desc,
+		count:      count,
+		sum:        sum,
+		quantiles:  quantiles,
+		labelPairs: MakeLabelPairs(desc, labelValues),
+		createdTs:  timestamppb.New(ct),
+	}, nil
+}
+
+// MustNewConstSummaryWithCreatedTimestamp is a version of NewConstSummaryWithCreatedTimestamp that panics where
+// NewConstSummaryWithCreatedTimestamp would have returned an error.
+func MustNewConstSummaryWithCreatedTimestamp(
+	desc *Desc,
+	count uint64,
+	sum float64,
+	quantiles map[float64]float64,
+	ct time.Time,
+	labelValues ...string,
+) Metric {
+	m, err := NewConstSummaryWithCreatedTimestamp(desc, count, sum, quantiles, ct, labelValues...)
+	if err != nil {
+		panic(err)
+	}
+	return m
+}
diff --git prometheus/summary_test.go prometheus/summary_test.go
index d1ea07257..9c961e9b5 100644
--- prometheus/summary_test.go
+++ prometheus/summary_test.go
@@ -474,3 +474,28 @@ func TestSummaryVecCreatedTimestampWithDeletes(t *testing.T) {
 		})
 	}
 }
+
+func TestNewConstSummaryWithCreatedTimestamp(t *testing.T) {
+	metricDesc := NewDesc(
+		"sample_value",
+		"sample value",
+		nil,
+		nil,
+	)
+	quantiles := map[float64]float64{50: 200.12, 99: 500.342}
+	createdTs := time.Unix(1719670764, 123)
+
+	s, err := NewConstSummaryWithCreatedTimestamp(metricDesc, 100, 200, quantiles, createdTs)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	var metric dto.Metric
+	if err := s.Write(&metric); err != nil {
+		t.Fatal(err)
+	}
+
+	if metric.Summary.CreatedTimestamp.AsTime().UnixMicro() != createdTs.UnixMicro() {
+		t.Errorf("Expected created timestamp %v, got %v", createdTs, &metric.Summary.CreatedTimestamp)
+	}
+}
diff --git prometheus/testutil/promlint/promlint_test.go prometheus/testutil/promlint/promlint_test.go
index 3617ed84c..c60507c19 100644
--- prometheus/testutil/promlint/promlint_test.go
+++ prometheus/testutil/promlint/promlint_test.go
@@ -668,7 +668,6 @@ func TestLintMetricTypeInName(t *testing.T) {
 		twoProbTest,
 		genTest("instance_memory_limit_bytes_gauge", "gauge", "gauge"),
 		genTest("request_duration_seconds_summary", "summary", "summary"),
-		genTest("request_duration_seconds_summary", "histogram", "summary"),
 		genTest("request_duration_seconds_histogram", "histogram", "histogram"),
 		genTest("request_duration_seconds_HISTOGRAM", "histogram", "histogram"),
 
@@ -840,3 +839,27 @@ mc_something_total 10
 		lintAndVerify(l2, cv)
 	})
 }
+
+func TestLintDuplicateMetric(t *testing.T) {
+	const msg = "metric not unique"
+
+	tests := []test{
+		{
+			name: "metric not unique",
+			in: `
+# HELP not_unique_total the helptext
+# TYPE not_unique_total counter
+not_unique_total{bar="abc", spam="xyz"} 1
+not_unique_total{bar="abc", spam="xyz"} 2
+`,
+			problems: []promlint.Problem{
+				{
+					Metric: "not_unique_total",
+					Text:   msg,
+				},
+			},
+		},
+	}
+
+	runTests(t, tests)
+}
diff --git prometheus/testutil/promlint/validation.go prometheus/testutil/promlint/validation.go
index f52ad9eab..e1441598d 100644
--- prometheus/testutil/promlint/validation.go
+++ prometheus/testutil/promlint/validation.go
@@ -30,4 +30,5 @@ var defaultValidations = []Validation{
 	validations.LintReservedChars,
 	validations.LintCamelCase,
 	validations.LintUnitAbbreviations,
+	validations.LintDuplicateMetric,
 }
diff --git a/prometheus/testutil/promlint/validations/duplicate_validations.go b/prometheus/testutil/promlint/validations/duplicate_validations.go
new file mode 100644
index 000000000..fdc1e6239
--- /dev/null
+++ prometheus/testutil/promlint/validations/duplicate_validations.go
@@ -0,0 +1,37 @@
+// 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 validations
+
+import (
+	"fmt"
+	"reflect"
+
+	dto "github.com/prometheus/client_model/go"
+)
+
+// LintDuplicateMetric detects duplicate metric.
+func LintDuplicateMetric(mf *dto.MetricFamily) []error {
+	var problems []error
+
+	for i, m := range mf.Metric {
+		for _, k := range mf.Metric[i+1:] {
+			if reflect.DeepEqual(m.Label, k.Label) {
+				problems = append(problems, fmt.Errorf("metric not unique"))
+				break
+			}
+		}
+	}
+
+	return problems
+}
diff --git prometheus/testutil/promlint/validations/generic_name_validations.go prometheus/testutil/promlint/validations/generic_name_validations.go
index bc8dbd1e1..de52cfee4 100644
--- prometheus/testutil/promlint/validations/generic_name_validations.go
+++ prometheus/testutil/promlint/validations/generic_name_validations.go
@@ -44,21 +44,21 @@ func LintMetricUnits(mf *dto.MetricFamily) []error {
 	return problems
 }
 
-// LintMetricTypeInName detects when metric types are included in the metric name.
+// LintMetricTypeInName detects when the metric type is included in the metric name.
 func LintMetricTypeInName(mf *dto.MetricFamily) []error {
+	if mf.GetType() == dto.MetricType_UNTYPED {
+		return nil
+	}
+
 	var problems []error
-	n := strings.ToLower(mf.GetName())
 
-	for i, t := range dto.MetricType_name {
-		if i == int32(dto.MetricType_UNTYPED) {
-			continue
-		}
+	n := strings.ToLower(mf.GetName())
+	typename := strings.ToLower(mf.GetType().String())
 
-		typename := strings.ToLower(t)
-		if strings.Contains(n, "_"+typename+"_") || strings.HasSuffix(n, "_"+typename) {
-			problems = append(problems, fmt.Errorf(`metric name should not include type '%s'`, typename))
-		}
+	if strings.Contains(n, "_"+typename+"_") || strings.HasSuffix(n, "_"+typename) {
+		problems = append(problems, fmt.Errorf(`metric name should not include type '%s'`, typename))
 	}
+
 	return problems
 }
 
diff --git prometheus/testutil/testutil.go prometheus/testutil/testutil.go
index 9dce15eaf..6f1200180 100644
--- prometheus/testutil/testutil.go
+++ prometheus/testutil/testutil.go
@@ -42,9 +42,8 @@ import (
 	"fmt"
 	"io"
 	"net/http"
-	"reflect"
 
-	"github.com/davecgh/go-spew/spew"
+	"github.com/kylelemons/godebug/diff"
 	dto "github.com/prometheus/client_model/go"
 	"github.com/prometheus/common/expfmt"
 	"google.golang.org/protobuf/proto"
@@ -159,6 +158,9 @@ func GatherAndCount(g prometheus.Gatherer, metricNames ...string) (int, error) {
 // ScrapeAndCompare calls a remote exporter's endpoint which is expected to return some metrics in
 // plain text format. Then it compares it with the results that the `expected` would return.
 // If the `metricNames` is not empty it would filter the comparison only to the given metric names.
+//
+// NOTE: Be mindful of accidental discrepancies between expected and metricNames; metricNames filter
+// both expected and scraped metrics. See https://github.com/prometheus/client_golang/issues/1351.
 func ScrapeAndCompare(url string, expected io.Reader, metricNames ...string) error {
 	resp, err := http.Get(url)
 	if err != nil {
@@ -184,9 +186,11 @@ func ScrapeAndCompare(url string, expected io.Reader, metricNames ...string) err
 	return compareMetricFamilies(scraped, wanted, metricNames...)
 }
 
-// CollectAndCompare registers the provided Collector with a newly created
-// pedantic Registry. It then calls GatherAndCompare with that Registry and with
-// the provided metricNames.
+// CollectAndCompare collects the metrics identified by `metricNames` and compares them in the Prometheus text
+// exposition format to the data read from expected.
+//
+// NOTE: Be mindful of accidental discrepancies between expected and metricNames; metricNames filter
+// both expected and collected metrics. See https://github.com/prometheus/client_golang/issues/1351.
 func CollectAndCompare(c prometheus.Collector, expected io.Reader, metricNames ...string) error {
 	reg := prometheus.NewPedanticRegistry()
 	if err := reg.Register(c); err != nil {
@@ -199,6 +203,9 @@ func CollectAndCompare(c prometheus.Collector, expected io.Reader, metricNames .
 // it to an expected output read from the provided Reader in the Prometheus text
 // exposition format. If any metricNames are provided, only metrics with those
 // names are compared.
+//
+// NOTE: Be mindful of accidental discrepancies between expected and metricNames; metricNames filter
+// both expected and gathered metrics. See https://github.com/prometheus/client_golang/issues/1351.
 func GatherAndCompare(g prometheus.Gatherer, expected io.Reader, metricNames ...string) error {
 	return TransactionalGatherAndCompare(prometheus.ToTransactionalGatherer(g), expected, metricNames...)
 }
@@ -207,6 +214,9 @@ func GatherAndCompare(g prometheus.Gatherer, expected io.Reader, metricNames ...
 // it to an expected output read from the provided Reader in the Prometheus text
 // exposition format. If any metricNames are provided, only metrics with those
 // names are compared.
+//
+// NOTE: Be mindful of accidental discrepancies between expected and metricNames; metricNames filter
+// both expected and gathered metrics. See https://github.com/prometheus/client_golang/issues/1351.
 func TransactionalGatherAndCompare(g prometheus.TransactionalGatherer, expected io.Reader, metricNames ...string) error {
 	got, done, err := g.Gather()
 	defer done()
@@ -222,6 +232,31 @@ func TransactionalGatherAndCompare(g prometheus.TransactionalGatherer, expected
 	return compareMetricFamilies(got, wanted, metricNames...)
 }
 
+// CollectAndFormat collects the metrics identified by `metricNames` and returns them in the given format.
+func CollectAndFormat(c prometheus.Collector, format expfmt.FormatType, metricNames ...string) ([]byte, error) {
+	reg := prometheus.NewPedanticRegistry()
+	if err := reg.Register(c); err != nil {
+		return nil, fmt.Errorf("registering collector failed: %w", err)
+	}
+
+	gotFiltered, err := reg.Gather()
+	if err != nil {
+		return nil, fmt.Errorf("gathering metrics failed: %w", err)
+	}
+
+	gotFiltered = filterMetrics(gotFiltered, metricNames)
+
+	var gotFormatted bytes.Buffer
+	enc := expfmt.NewEncoder(&gotFormatted, expfmt.NewFormat(format))
+	for _, mf := range gotFiltered {
+		if err := enc.Encode(mf); err != nil {
+			return nil, fmt.Errorf("encoding gathered metrics failed: %w", err)
+		}
+	}
+
+	return gotFormatted.Bytes(), nil
+}
+
 // convertReaderToMetricFamily would read from a io.Reader object and convert it to a slice of
 // dto.MetricFamily.
 func convertReaderToMetricFamily(reader io.Reader) ([]*dto.MetricFamily, error) {
@@ -277,73 +312,12 @@ func compare(got, want []*dto.MetricFamily) error {
 			return fmt.Errorf("encoding expected metrics failed: %w", err)
 		}
 	}
-	if diffErr := diff(wantBuf, gotBuf); diffErr != "" {
+	if diffErr := diff.Diff(gotBuf.String(), wantBuf.String()); diffErr != "" {
 		return fmt.Errorf(diffErr)
 	}
 	return nil
 }
 
-// diff returns a diff of both values as long as both are of the same type and
-// are a struct, map, slice, array or string. Otherwise it returns an empty string.
-func diff(expected, actual interface{}) string {
-	if expected == nil || actual == nil {
-		return ""
-	}
-
-	et, ek := typeAndKind(expected)
-	at, _ := typeAndKind(actual)
-	if et != at {
-		return ""
-	}
-
-	if ek != reflect.Struct && ek != reflect.Map && ek != reflect.Slice && ek != reflect.Array && ek != reflect.String {
-		return ""
-	}
-
-	var e, a string
-	c := spew.ConfigState{
-		Indent:                  " ",
-		DisablePointerAddresses: true,
-		DisableCapacities:       true,
-		SortKeys:                true,
-	}
-	if et != reflect.TypeOf("") {
-		e = c.Sdump(expected)
-		a = c.Sdump(actual)
-	} else {
-		e = reflect.ValueOf(expected).String()
-		a = reflect.ValueOf(actual).String()
-	}
-
-	diff, _ := internal.GetUnifiedDiffString(internal.UnifiedDiff{
-		A:        internal.SplitLines(e),
-		B:        internal.SplitLines(a),
-		FromFile: "metric output does not match expectation; want",
-		FromDate: "",
-		ToFile:   "got:",
-		ToDate:   "",
-		Context:  1,
-	})
-
-	if diff == "" {
-		return ""
-	}
-
-	return "\n\nDiff:\n" + diff
-}
-
-// typeAndKind returns the type and kind of the given interface{}
-func typeAndKind(v interface{}) (reflect.Type, reflect.Kind) {
-	t := reflect.TypeOf(v)
-	k := t.Kind()
-
-	if k == reflect.Ptr {
-		t = t.Elem()
-		k = t.Kind()
-	}
-	return t, k
-}
-
 func fil,terMetrics(metrics []*dto.MetricFamily, names []string) []*dto.MetricFamily {
 	var filtered []*dto.MetricFamily
 	for _, m := range metrics {
diff --git prometheus/testutil/testutil_test.go prometheus/testutil/testutil_test.go
index f2e1cbaff..06e367744 100644
--- prometheus/testutil/testutil_test.go
+++ prometheus/testutil/testutil_test.go
@@ -20,6 +20,8 @@ import (
 	"strings"
 	"testing"
 
+	"github.com/prometheus/common/expfmt"
+
 	"github.com/prometheus/client_golang/prometheus"
 )
 
@@ -300,26 +302,20 @@ func TestMetricNotFound(t *testing.T) {
 			"label1": "value1",
 		},
 	})
+
 	c.Inc()
 
 	expected := `
 		some_other_metric{label1="value1"} 1
 	`
 
-	expectedError := `
-
-Diff:
---- metric output does not match expectation; want
-+++ got:
-@@ -1,4 +1,4 @@
--(bytes.Buffer) # HELP some_other_metric A value that represents a counter.
--# TYPE some_other_metric counter
--some_other_metric{label1="value1"} 1
-+(bytes.Buffer) # HELP some_total A value that represents a counter.
-+# TYPE some_total counter
-+some_total{label1="value1"} 1
- 
-`
+	expectedError := `-# HELP some_total A value that represents a counter.
+-# TYPE some_total counter
+-some_total{label1="value1"} 1
++# HELP some_other_metric A value that represents a counter.
++# TYPE some_other_metric counter
++some_other_metric{label1="value1"} 1
+ `
 
 	err := CollectAndCompare(c, strings.NewReader(metadata+expected))
 	if err == nil {
@@ -437,3 +433,32 @@ func TestCollectAndCount(t *testing.T) {
 		t.Errorf("unexpected metric count, got %d, want %d", got, want)
 	}
 }
+
+func TestCollectAndFormat(t *testing.T) {
+	const expected = `# HELP foo_bar A value that represents the number of bars in foo.
+# TYPE foo_bar counter
+foo_bar{fizz="bang"} 1
+`
+	c := prometheus.NewCounterVec(
+		prometheus.CounterOpts{
+			Name: "foo_bar",
+			Help: "A value that represents the number of bars in foo.",
+		},
+		[]string{"fizz"},
+	)
+	c.WithLabelValues("bang").Inc()
+
+	got, err := CollectAndFormat(c, expfmt.TypeTextPlain, "foo_bar")
+	if err != nil {
+		t.Errorf("unexpected error: %s", err.Error())
+	}
+
+	gotS := string(got)
+	if err != nil {
+		t.Errorf("unexpected error: %s", err.Error())
+	}
+
+	if gotS != expected {
+		t.Errorf("unexpected metric output, got %q, expected %q", gotS, expected)
+	}
+}
diff --git prometheus/vec.go prometheus/vec.go
index 955cfd59f..2c808eece 100644
--- prometheus/vec.go
+++ prometheus/vec.go
@@ -507,7 +507,7 @@ func (m *metricMap) getOrCreateMetricWithLabelValues(
 	return metric
 }
 
-// getOrCreateMetricWithLabelValues retrieves the metric by hash and label value
+// getOrCreateMetricWithLabels retrieves the metric by hash and label value
 // or creates it and returns the new one.
 //
 // This function holds the mutex.
diff --git a/supported_go_versions.txt b/supported_go_versions.txt
new file mode 100644
index 000000000..321c2b6b7
--- /dev/null
+++ supported_go_versions.txt
@@ -0,0 +1,3 @@
+1.22
+1.21
+1.20
\ No newline at end of file
diff --git a/tutorial/whatsup/.gitignore b/tutorials/whatsup/.gitignore
similarity index 100%
rename from tutorial/whatsup/.gitignore
rename to tutorials/whatsup/.gitignore
diff --git a/tutorial/whatsup/ContribFest.pdf b/tutorials/whatsup/ContribFest.pdf
diff --git a/tutorial/whatsup/ContribFest.pdf b/tutorials/whatsup/ContribFest.pdf
similarity index 100%
rename from tutorial/whatsup/ContribFest.pdf
rename to tutorials/whatsup/ContribFest.pdf
diff --git a/tutorial/whatsup/Makefile b/tutorials/whatsup/Makefile
diff --git a/tutorial/whatsup/Makefile b/tutorials/whatsup/Makefile
similarity index 100%
rename from tutorial/whatsup/Makefile
rename to tutorials/whatsup/Makefile
diff --git a/tutorial/whatsup/README.md b/tutorials/whatsup/README.md
diff --git a/tutorial/whatsup/README.md b/tutorials/whatsup/README.md
similarity index 100%
rename from tutorial/whatsup/README.md
rename to tutorials/whatsup/README.md
diff --git a/tutorial/whatsup/go.mod b/tutorials/whatsup/go.mod
diff --git tutorial/whatsup/go.mod tutorials/whatsup/go.mod
similarity index 76%
rename from tutorial/whatsup/go.mod
rename to tutorials/whatsup/go.mod
index 93878d708..a3311154d 100644
--- tutorial/whatsup/go.mod
+++ tutorials/whatsup/go.mod
@@ -1,4 +1,4 @@
-module github.com/prometheus/client_golang/tutorial
+module github.com/prometheus/client_golang/tutorials/whatsup
 
 go 1.20
 
@@ -7,8 +7,8 @@ require (
 	github.com/efficientgo/core v1.0.0-rc.2
 	github.com/efficientgo/e2e v0.14.1-0.20230421070206-d72d43f3b937
 	github.com/oklog/run v1.1.0
-	github.com/prometheus/client_golang v1.18.0
-	github.com/prometheus/common v0.46.0
+	github.com/prometheus/client_golang v1.19.1
+	github.com/prometheus/common v0.55.0
 	gopkg.in/yaml.v2 v2.4.0
 )
 
@@ -22,16 +22,17 @@ require (
 	github.com/go-logr/logr v1.2.3 // indirect
 	github.com/go-logr/stdr v1.2.2 // indirect
 	github.com/golang/protobuf v1.5.3 // indirect
-	github.com/google/go-cmp v0.5.9 // indirect
+	github.com/google/go-cmp v0.6.0 // indirect
 	github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0 // indirect
 	github.com/jpillora/backoff v1.0.0 // indirect
 	github.com/json-iterator/go v1.1.12 // indirect
 	github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
 	github.com/modern-go/reflect2 v1.0.2 // indirect
+	github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
 	github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f // indirect
 	github.com/pkg/errors v0.9.1 // indirect
-	github.com/prometheus/client_model v0.5.0 // indirect
-	github.com/prometheus/procfs v0.12.0 // indirect
+	github.com/prometheus/client_model v0.6.1 // indirect
+	github.com/prometheus/procfs v0.15.1 // indirect
 	go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.32.0 // indirect
 	go.opentelemetry.io/otel v1.7.0 // indirect
 	go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.6.3 // indirect
@@ -42,12 +43,11 @@ require (
 	go.opentelemetry.io/otel/sdk v1.6.3 // indirect
 	go.opentelemetry.io/otel/trace v1.7.0 // indirect
 	go.opentelemetry.io/proto/otlp v0.15.0 // indirect
-	golang.org/x/net v0.20.0 // indirect
-	golang.org/x/oauth2 v0.16.0 // indirect
-	golang.org/x/sys v0.16.0 // indirect
-	golang.org/x/text v0.14.0 // indirect
-	google.golang.org/appengine v1.6.7 // indirect
+	golang.org/x/net v0.26.0 // indirect
+	golang.org/x/oauth2 v0.21.0 // indirect
+	golang.org/x/sys v0.21.0 // indirect
+	golang.org/x/text v0.16.0 // indirect
 	google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 // indirect
 	google.golang.org/grpc v1.56.3 // indirect
-	google.golang.org/protobuf v1.32.0 // indirect
+	google.golang.org/protobuf v1.34.2 // indirect
 )
diff --git tutorial/whatsup/go.sum tutorials/whatsup/go.sum
similarity index 95%
rename from tutorial/whatsup/go.sum
rename to tutorials/whatsup/go.sum
index 63dc245be..7a05b05e3 100644
--- tutorial/whatsup/go.sum
+++ tutorials/whatsup/go.sum
@@ -128,8 +128,8 @@ github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
 github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
-github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
-github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
+github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
+github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
 github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
 github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
 github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
@@ -173,6 +173,8 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w
 github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
 github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
 github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
+github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
+github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
 github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f h1:KUppIJq7/+SVif2QVs3tOP0zanoHgBEVAwHxUSIzRqU=
 github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
 github.com/oklog/run v1.1.0 h1:GEenZ1cK0+q0+wsJew9qUg/DyD8k3JzYsZAi5gYi2mA=
@@ -181,15 +183,15 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
 github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
-github.com/prometheus/client_golang v1.18.0 h1:HzFfmkOzH5Q8L8G+kSJKUx5dtG87sewO+FoDDqP5Tbk=
-github.com/prometheus/client_golang v1.18.0/go.mod h1:T+GXkCk5wSJyOqMIzVgvvjFDlkOQntgjkJWKrN5txjA=
+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_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
-github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw=
-github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI=
-github.com/prometheus/common v0.46.0 h1:doXzt5ybi1HBKpsZOL0sSkaNHJJqkyfEWZGGqqScV0Y=
-github.com/prometheus/common v0.46.0/go.mod h1:Tp0qkxpb9Jsg54QMe+EAmqXkSV7Evdy1BTn+g2pa/hQ=
-github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo=
-github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo=
+github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E=
+github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY=
+github.com/prometheus/common v0.55.0 h1:KEi6DK7lXW/m7Ig5i47x0vRzuBsHuvJdi5ee6Y3G1dc=
+github.com/prometheus/common v0.55.0/go.mod h1:2SECS4xJG1kd8XF9IcM1gMX6510RAEL65zxzNImwdc8=
+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/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
 github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
 github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
@@ -201,8 +203,8 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV
 github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
 github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
 github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
-github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY=
 github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
 github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
 github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
 github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
@@ -244,7 +246,7 @@ golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8U
 golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 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.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc=
+golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI=
 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=
@@ -303,16 +305,16 @@ golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81R
 golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
 golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
 golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
-golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo=
-golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
+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/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-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
-golang.org/x/oauth2 v0.16.0 h1:aDkGMBSYxElaoP81NpoUoz2oo2R2wHdZpGToUxfyQrQ=
-golang.org/x/oauth2 v0.16.0/go.mod h1:hqZ+0LWXsiVoZpeld6jVt06P3adbS2Uu911W1SsJv2o=
+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/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=
@@ -351,8 +353,8 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w
 golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU=
-golang.org/x/sys v0.16.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/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 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=
@@ -360,8 +362,8 @@ golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3
 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=
 golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
-golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
-golang.org/x/text v0.14.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/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=
@@ -433,8 +435,6 @@ google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7
 google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
 google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
 google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
-google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
-google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
 google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
 google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
 google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
@@ -501,8 +501,8 @@ google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp0
 google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
 google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
 google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
-google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I=
-google.golang.org/protobuf v1.32.0/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/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=
 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
@@ -512,8 +512,8 @@ gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
 gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
-gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
 honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
 honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
 honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
diff --git a/tutorial/whatsup/internal/acceptance_test.go b/tutorials/whatsup/internal/acceptance_test.go
similarity index 100%
rename from tutorial/whatsup/internal/acceptance_test.go
rename to tutorials/whatsup/internal/acceptance_test.go
diff --git a/tutorial/whatsup/internal/common.go b/tutorials/whatsup/internal/common.go
diff --git a/tutorial/whatsup/internal/common.go b/tutorials/whatsup/internal/common.go
similarity index 100%
rename from tutorial/whatsup/internal/common.go
rename to tutorials/whatsup/internal/common.go
diff --git a/tutorial/whatsup/internal/playground_test.go b/tutorials/whatsup/internal/playground_test.go
diff --git a/tutorial/whatsup/internal/playground_test.go b/tutorials/whatsup/internal/playground_test.go
similarity index 100%
rename from tutorial/whatsup/internal/playground_test.go
rename to tutorials/whatsup/internal/playground_test.go
diff --git a/tutorial/whatsup/main.go b/tutorials/whatsup/main.go
diff --git tutorial/whatsup/main.go tutorials/whatsup/main.go
similarity index 98%
rename from tutorial/whatsup/main.go
rename to tutorials/whatsup/main.go
index b4252f3ba..5943eefb0 100644
--- tutorial/whatsup/main.go
+++ tutorials/whatsup/main.go
@@ -36,7 +36,7 @@ import (
 	"github.com/prometheus/client_golang/api"
 	v1 "github.com/prometheus/client_golang/api/prometheus/v1"
 	"github.com/prometheus/client_golang/prometheus"
-	"github.com/prometheus/client_golang/tutorial/internal"
+	"github.com/prometheus/client_golang/tutorials/whatsup/internal"
 )
 
 func main() {
diff --git tutorial/whatsup/reference/main.go tutorials/whatsup/reference/main.go
similarity index 99%
rename from tutorial/whatsup/reference/main.go
rename to tutorials/whatsup/reference/main.go
index 3c4115149..d7ac1c7b7 100644
--- tutorial/whatsup/reference/main.go
+++ tutorials/whatsup/reference/main.go
@@ -39,7 +39,7 @@ import (
 	"github.com/prometheus/client_golang/prometheus/collectors"
 	"github.com/prometheus/client_golang/prometheus/promauto"
 	"github.com/prometheus/client_golang/prometheus/promhttp"
-	"github.com/prometheus/client_golang/tutorial/internal"
+	"github.com/prometheus/client_golang/tutorials/whatsup/internal"
 )
 
 func main() {
diff --git a/update-go-version.bash b/update-go-version.bash
new file mode 100644
index 000000000..02e6aa6c4
--- /dev/null
+++ update-go-version.bash
@@ -0,0 +1,23 @@
+#!/bin/env bash
+
+set -e
+
+get_latest_versions() {
+  curl -s https://go.dev/VERSION?m=text | sed -E -n 's/go([0-9]+\.[0-9]+|\.[0-9]+).*/\1/p'
+}
+
+current_version=$(cat supported_go_versions.txt | head -n 1)
+latest_version=$(get_latest_versions)
+
+# Check for new version of Go, and generate go collector test files
+# Add new Go version to supported_go_versions.txt, and remove the oldest version
+if [[ ! $current_version =~ $latest_version ]]; then
+  echo "New Go version available: $latest_version"
+  echo "Updating supported_go_versions.txt and generating Go Collector test files"
+  sed -i "1i $latest_version" supported_go_versions.txt
+  sed -i '$d' supported_go_versions.txt
+  make generate-go-collector-test-files
+else
+  echo "No new Go version detected. Current Go version is: $current_version"
+fi
+

Description

This PR updates the client_golang library with several improvements and new features. The main changes include adding support for new Go versions, updating dependencies, improving metrics collection and handling, enhancing compression support, and various bug fixes and performance improvements.

Changes

Changes

  1. .github/workflows/:

    • Updated several GitHub Actions workflows, including adding new ones for Docker image description updates and Go version updates.
    • Added concurrency settings to some workflows to prevent redundant runs.
  2. dagger/:

    • Added new Dagger-related files for improved build and CI processes.
  3. prometheus/:

    • Updated Go collector metrics and tests for newer Go versions.
    • Added support for exemplars in native histograms.
    • Improved handling of runtime metrics and added new default metrics.
    • Enhanced compression support in HTTP responses, including zstd compression.
    • Added new methods for creating constant metrics with timestamps.
    • Improved error handling and performance in various parts of the library.
  4. prometheus/testutil/:

    • Added new testing utilities and improved existing ones.
  5. tutorials/:

    • Renamed tutorial directory to tutorials and updated related imports.
  6. Root directory:

    • Added supported_go_versions.txt and update-go-version.bash for managing supported Go versions.
    • Updated go.mod and go.sum with new dependencies and versions.
sequenceDiagram
    participant Client
    participant PrometheusClient
    participant Collector
    participant Registry
    participant HTTPHandler

    Client->>PrometheusClient: Initialize
    PrometheusClient->>Collector: Create Collectors
    Collector->>Registry: Register
    Client->>HTTPHandler: Request Metrics
    HTTPHandler->>Registry: Gather Metrics
    Registry->>Collector: Collect Metrics
    Collector-->>Registry: Return Metrics
    Registry-->>HTTPHandler: Return Gathered Metrics
    HTTPHandler->>HTTPHandler: Compress Response (if supported)
    HTTPHandler-->>Client: Return Metrics (compressed if applicable)
Loading

Possible Issues

  • The changes to the default metrics and runtime metrics collection might affect existing users who rely on specific metric names or behaviors.
  • The addition of zstd compression support might require updates to client applications to take advantage of the new feature.

Security Hotspots

No significant security issues were identified in this change. However, as with any major update, it's important to thoroughly test the library in your specific use case to ensure no unexpected behaviors arise.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

0 participants