forked from mirror/client_golang
Compare commits
155 Commits
release-1.
...
main
Author | SHA1 | Date |
---|---|---|
re | 1919ef1b31 | |
Jéssica Lins | 07b1397ded | |
Bartlomiej Plotka | 8b6e68085b | |
beorn7 | 043372ee04 | |
beorn7 | efef9034c5 | |
Björn Rabenstein | 449b464350 | |
beorn7 | b804be1e63 | |
Kemal Akkoyun | db41dad599 | |
Bartlomiej Plotka | 254e546841 | |
Kemal Akkoyun | c8a3d321a0 | |
Bartlomiej Plotka | 07d3a81494 | |
Kemal Akkoyun | 870469ecf9 | |
copy rogers | b785d0c828 | |
Seth Bunce | 4d54769c6b | |
Kemal Akkoyun | 53e51c4f53 | |
bwplotka | 79ca0eb2ba | |
bwplotka | 078f11f85b | |
Fabian Stäber | ddd7f0edcd | |
Balint Zsilavecz | 1f93f64580 | |
Dave Henderson | 8cc2b6c472 | |
Björn Rabenstein | 5f202eefdb | |
beorn7 | fffb76cafe | |
beorn7 | e92a8c7f48 | |
Kemal Akkoyun | 0859bb8f37 | |
Jéssica Lins | a340ca4ba6 | |
Jéssica Lins | 6056615b26 | |
Jéssica Lins | 0b7f4888b0 | |
Jéssica Lins | 9b5c5b8a47 | |
beorn7 | d31f13b599 | |
beorn7 | 58a8ca4588 | |
beorn7 | 4e71e6ff20 | |
beorn7 | 111fae11e1 | |
Fabian Stäber | 10b0550932 | |
Balint Zsilavecz | dcea97eee2 | |
Björn Rabenstein | 25bc1886c0 | |
beorn7 | 6942f9e454 | |
rogerogers | 9801a4e3ce | |
Rafael Franco | 7c46c150bd | |
beorn7 | 95cf173f19 | |
Robert Fratto | 83d56b1144 | |
Dave Henderson | 4c41dfbcd5 | |
Dave Henderson | f73e3cc0e2 | |
Kemal Akkoyun | c7aa2a5b84 | |
PrometheusBot | 1e61b8ea3c | |
Bartlomiej Plotka | 64435fc00a | |
Bartlomiej Plotka | 5b7e8b2e67 | |
Christian Stewart | d44fbbefdd | |
Soroosh Azary Marhabi | 1638da9ae4 | |
Kemal Akkoyun | c576b951ad | |
Christoph Mewes | 618194de6a | |
Bartlomiej Plotka | c7488be2e4 | |
Bartlomiej Plotka | 3faf3bae70 | |
Joseph Woodward | 44ce5e1ee5 | |
Arun Mahendra | 807b1ee73c | |
dependabot[bot] | c6d4e40244 | |
inosato | 44c2c4de85 | |
dependabot[bot] | 76cdae298e | |
dependabot[bot] | 9154d30db7 | |
Fredrik Enestad | a528affed9 | |
Björn Rabenstein | ec86ef1833 | |
beorn7 | 8cbcd4076a | |
Bryan Boreham | ba4a543ab4 | |
beorn7 | 6141a0784e | |
Bartlomiej Plotka | 3d482bb7cf | |
beorn7 | 5a321c7b05 | |
beorn7 | c1f2d13898 | |
Björn Rabenstein | c6a634fa36 | |
beorn7 | e93e38410f | |
beorn7 | a516626ff3 | |
beorn7 | 525d042127 | |
dependabot[bot] | e8f91604d8 | |
Curith | 810fcb46ab | |
PrometheusBot | 4ad265f1b4 | |
Ganesh Vernekar | 6ba7871ebb | |
Soroosh Azary Marhabi | 2cfd1eb960 | |
PrometheusBot | ebd77f0360 | |
Ben Kochie | 2c3d072cdd | |
Ben Kochie | e38d614cd6 | |
PrometheusBot | 0dd939295e | |
Björn Rabenstein | eb59a7b3d7 | |
beorn7 | b2372302ca | |
Kemal Akkoyun | 5d584e2717 | |
dependabot[bot] | fab6748c34 | |
Ben Kochie | edecbb214b | |
Kemal Akkoyun | 35c82f2c7e | |
Kemal Akkoyun | f25114699a | |
PrometheusBot | 589b2ea560 | |
PrometheusBot | 0222f88f4a | |
Christoph Mewes | 24605c59ac | |
S Santhosh Nagaraj | 404809144b | |
Joseph Woodward | efe8e6fac8 | |
Zach Stone | 4dcf02ec7b | |
Joseph Woodward | 48a686a603 | |
Bartlomiej Plotka | 11ee9add27 | |
Sourik Ghosh | cd90f33be8 | |
Manuel Rüger | 0c691ed35f | |
Manuel Rüger | 46d3dd4e6c | |
Bartlomiej Plotka | 24172847e3 | |
Tomas Dohnalek | cc7991d977 | |
Tatsuhiro Tsujikawa | 0bab4fda94 | |
Bartlomiej Plotka | 06b641214c | |
Kemal Akkoyun | 130da3b8ec | |
Kemal Akkoyun | 3e9269d7d3 | |
Tomáš Dohnálek | 40e54a75a6 | |
prombot | e2504f86bb | |
Manuel Rüger | 29e8191aff | |
Mitsuo Heijo | 8dfa334295 | |
PrometheusBot | 3bc8f2c651 | |
Kemal Akkoyun | 6559749c87 | |
zhijian | 36b47eb0ab | |
Kemal Akkoyun | 5d78aaad41 | |
Kemal Akkoyun | 157170dd1a | |
Bartlomiej Plotka | 9894406186 | |
William Perron | 66837e3298 | |
Kemal Akkoyun | fe8d1e13cd | |
Kemal Akkoyun | 6c18569eab | |
prombot | 0291563b9f | |
alissa-tung | ffd6362a06 | |
alissa-tung | b05177a553 | |
PrometheusBot | 868ec2137f | |
Bartlomiej Plotka | 1f81b3e913 | |
Kemal Akkoyun | f3021b0b81 | |
Kemal Akkoyun | 5ac1e9208b | |
beorn7 | 294cca4252 | |
Manuel Rüger | 5678ca5ed1 | |
Kemal Akkoyun | 2e1c4818cc | |
Michael Knyszek | 77626d64fa | |
Bryan Boreham | 4dd3cbb4ab | |
Michael Knyszek | 85206714ae | |
Michael Knyszek | f63e219e6b | |
Kemal Akkoyun | 01087964d0 | |
beorn7 | 70253f4dd0 | |
beorn7 | 5b19c553c3 | |
Björn Rabenstein | dfbcc28fff | |
beorn7 | 263be8dab7 | |
beorn7 | 24099603bc | |
beorn7 | 84fcafffb1 | |
beorn7 | 9ef5f90a76 | |
beorn7 | aa6f67a9e6 | |
Björn Rabenstein | 43f31c25a8 | |
beorn7 | 514234486b | |
beorn7 | 6c4e0ef740 | |
beorn7 | 31318b7523 | |
beorn7 | 5aa8534cd0 | |
beorn7 | 97eb0411ac | |
beorn7 | 553ed73917 | |
beorn7 | b7a540a1b2 | |
beorn7 | a9df0bac89 | |
beorn7 | ce36ee3182 | |
beorn7 | d6983369d2 | |
beorn7 | 08104a0ef9 | |
beorn7 | a9d0066408 | |
beorn7 | d1f5366b52 | |
beorn7 | abe540f8c0 | |
beorn7 | c98db4eccd |
|
@ -1,7 +1,7 @@
|
||||||
version: 2.1
|
version: 2.1
|
||||||
orbs:
|
orbs:
|
||||||
go: circleci/go@0.2.0
|
go: circleci/go@1.7.1
|
||||||
prometheus: prometheus/prometheus@0.15.0
|
prometheus: prometheus/prometheus@0.16.0
|
||||||
jobs:
|
jobs:
|
||||||
test:
|
test:
|
||||||
parameters:
|
parameters:
|
||||||
|
@ -17,8 +17,7 @@ jobs:
|
||||||
type: boolean
|
type: boolean
|
||||||
default: true
|
default: true
|
||||||
docker:
|
docker:
|
||||||
- image: circleci/golang:<< parameters.go_version >>
|
- image: cimg/go:<< parameters.go_version >>
|
||||||
working_directory: /go/src/github.com/prometheus/client_golang
|
|
||||||
steps:
|
steps:
|
||||||
- checkout
|
- checkout
|
||||||
- when:
|
- when:
|
||||||
|
@ -47,24 +46,18 @@ workflows:
|
||||||
client_golang:
|
client_golang:
|
||||||
jobs:
|
jobs:
|
||||||
# Refer to README.md for the currently supported versions.
|
# Refer to README.md for the currently supported versions.
|
||||||
- test:
|
|
||||||
name: go-1-13
|
|
||||||
go_version: "1.13"
|
|
||||||
run_lint: true
|
|
||||||
- test:
|
|
||||||
name: go-1-14
|
|
||||||
go_version: "1.14"
|
|
||||||
run_lint: true
|
|
||||||
- test:
|
|
||||||
name: go-1-15
|
|
||||||
go_version: "1.15"
|
|
||||||
run_lint: true
|
|
||||||
- test:
|
|
||||||
name: go-1-16
|
|
||||||
go_version: "1.16"
|
|
||||||
run_lint: true
|
|
||||||
- test:
|
- test:
|
||||||
name: go-1-17
|
name: go-1-17
|
||||||
go_version: "1.17"
|
go_version: "1.17"
|
||||||
run_lint: true
|
run_lint: true
|
||||||
|
- test:
|
||||||
|
name: go-1-18
|
||||||
|
go_version: "1.18"
|
||||||
|
run_lint: true
|
||||||
|
- test:
|
||||||
|
name: go-1-19
|
||||||
|
go_version: "1.19"
|
||||||
|
run_lint: true
|
||||||
|
# Style and unused/missing packages are only checked against
|
||||||
|
# the latest supported Go version.
|
||||||
run_style_and_unused: true
|
run_style_and_unused: true
|
||||||
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
version: 2
|
||||||
|
updates:
|
||||||
|
- package-ecosystem: "gomod"
|
||||||
|
directory: "/"
|
||||||
|
schedule:
|
||||||
|
interval: "monthly"
|
|
@ -0,0 +1,34 @@
|
||||||
|
---
|
||||||
|
branches:
|
||||||
|
- name: main
|
||||||
|
protection:
|
||||||
|
# Required. Require at least one approving review on a pull request, before merging. Set to null to disable.
|
||||||
|
required_pull_request_reviews:
|
||||||
|
# The number of approvals required. (1-6)
|
||||||
|
required_approving_review_count: 1
|
||||||
|
# Dismiss approved reviews automatically when a new commit is pushed.
|
||||||
|
dismiss_stale_reviews: false
|
||||||
|
# Blocks merge until code owners have reviewed.
|
||||||
|
require_code_owner_reviews: false
|
||||||
|
# Specify which users and teams can dismiss pull request reviews. Pass an empty dismissal_restrictions object to disable. User and team dismissal_restrictions are only available for organization-owned repositories. Omit this parameter for personal repositories.
|
||||||
|
dismissal_restrictions:
|
||||||
|
users: []
|
||||||
|
teams: []
|
||||||
|
# Required. Require status checks to pass before merging. Set to null to disable
|
||||||
|
required_status_checks:
|
||||||
|
# Required. Require branches to be up to date before merging.
|
||||||
|
strict: false
|
||||||
|
# Required. The list of status checks to require in order to merge into this branch
|
||||||
|
contexts:
|
||||||
|
- DCO
|
||||||
|
- "ci/circleci: go-1-17"
|
||||||
|
- "ci/circleci: go-1-18"
|
||||||
|
# Required. Enforce all configured restrictions for administrators. Set to true to enforce required status checks for repository administrators. Set to null to disable.
|
||||||
|
enforce_admins: false
|
||||||
|
# Prevent merge commits from being pushed to matching branches
|
||||||
|
required_linear_history: false
|
||||||
|
# Required. Restrict who can push to this branch. Team and user restrictions are only available for organization-owned repositories. Set to null to disable.
|
||||||
|
restrictions:
|
||||||
|
apps: []
|
||||||
|
users: []
|
||||||
|
teams: []
|
|
@ -0,0 +1,88 @@
|
||||||
|
# Configuration for probot-stale - https://github.com/probot/stale
|
||||||
|
|
||||||
|
# Number of days of inactivity before an Issue or Pull Request becomes stale
|
||||||
|
# daysUntilStale: 30
|
||||||
|
|
||||||
|
# Number of days of inactivity before an Issue or Pull Request with the stale label is closed.
|
||||||
|
# Set to false to disable. If disabled, issues still need to be closed manually, but will remain marked as stale.
|
||||||
|
# daysUntilClose: 7
|
||||||
|
|
||||||
|
# NOTICE: Check below for the individual settings for each type of event.
|
||||||
|
|
||||||
|
# Only issues or pull requests with all of these labels are check if stale. Defaults to `[]` (disabled)
|
||||||
|
onlyLabels: []
|
||||||
|
|
||||||
|
# Issues or Pull Requests with these labels will never be considered stale. Set to `[]` to disable
|
||||||
|
exemptLabels:
|
||||||
|
- pinned
|
||||||
|
- security
|
||||||
|
- keep-open
|
||||||
|
- wip
|
||||||
|
- "[Status] Maybe Later"
|
||||||
|
|
||||||
|
# Set to true to ignore issues in a project (defaults to false)
|
||||||
|
exemptProjects: true # default: false
|
||||||
|
|
||||||
|
# Set to true to ignore issues in a milestone (defaults to false)
|
||||||
|
exemptMilestones: true # default: false
|
||||||
|
|
||||||
|
# Set to true to ignore issues with an assignee (defaults to false)
|
||||||
|
exemptAssignees: false
|
||||||
|
|
||||||
|
# Label to use when marking as stale
|
||||||
|
staleLabel: stale
|
||||||
|
|
||||||
|
# Comment to post when marking as stale. Set to `false` to disable
|
||||||
|
# markComment: >
|
||||||
|
# This issue has been automatically marked as stale because it has not had
|
||||||
|
# recent activity. It will be closed if no further activity occurs. Thank you
|
||||||
|
# for your contributions.
|
||||||
|
|
||||||
|
# Comment to post when removing the stale label.
|
||||||
|
# unmarkComment: >
|
||||||
|
# Your comment here.
|
||||||
|
|
||||||
|
# Comment to post when closing a stale Issue or Pull Request.
|
||||||
|
# closeComment: >
|
||||||
|
# Your comment here.
|
||||||
|
|
||||||
|
# Limit the number of actions per hour, from 1-30. Default is 30
|
||||||
|
limitPerRun: 30
|
||||||
|
|
||||||
|
# Limit to only `issues` or `pulls`
|
||||||
|
# only: issues
|
||||||
|
|
||||||
|
# Optionally, specify configuration settings that are specific to just 'issues' or 'pulls':
|
||||||
|
# pulls:
|
||||||
|
# daysUntilStale: 30
|
||||||
|
# markComment: >
|
||||||
|
# This pull request has been automatically marked as stale because it has not had
|
||||||
|
# recent activity. It will be closed if no further activity occurs. Thank you
|
||||||
|
# for your contributions.
|
||||||
|
|
||||||
|
# issues:
|
||||||
|
# exemptLabels:
|
||||||
|
# - confirmed
|
||||||
|
|
||||||
|
pull:
|
||||||
|
daysUntilClose: 14
|
||||||
|
daysUntilStale: 60
|
||||||
|
markComment: >
|
||||||
|
Hello 👋 Looks like there was no activity on this amazing PR for the last 60 days.
|
||||||
|
**Do you mind updating us on the status?** Is there anything we can help with? If you plan to still work on it, just comment on this PR or push a commit. Thanks! 🤗
|
||||||
|
|
||||||
|
If there will be no activity in the next 2 weeks, this issue will be closed (we can always reopen a PR if you get back to this!).
|
||||||
|
# unmarkComment: No need for unmark comment.
|
||||||
|
closeComment: >
|
||||||
|
Closing for now as promised, let us know if you need this to be reopened! 🤗
|
||||||
|
issues:
|
||||||
|
daysUntilClose: 90
|
||||||
|
daysUntilStale: 180
|
||||||
|
markComment: >
|
||||||
|
Hello 👋 Looks like there was no activity on this issue for the last 3 months.
|
||||||
|
**Do you mind updating us on the status?** Is this still reproducible or needed? If yes, just comment on this PR or push a commit. Thanks! 🤗
|
||||||
|
|
||||||
|
If there will be no activity in the next 4 weeks, this issue will be closed (we can always reopen an issue if we need!).
|
||||||
|
# unmarkComment: No need for unmark comment.
|
||||||
|
closeComment: >
|
||||||
|
Closing for now as promised, let us know if you need this to be reopened! 🤗
|
|
@ -0,0 +1,70 @@
|
||||||
|
# For most projects, this workflow file will not need changing; you simply need
|
||||||
|
# to commit it to your repository.
|
||||||
|
#
|
||||||
|
# You may wish to alter this file to override the set of languages analyzed,
|
||||||
|
# or to provide custom queries or build logic.
|
||||||
|
#
|
||||||
|
# ******** NOTE ********
|
||||||
|
# We have attempted to detect the languages in your repository. Please check
|
||||||
|
# the `language` matrix defined below to confirm you have the correct set of
|
||||||
|
# supported CodeQL languages.
|
||||||
|
#
|
||||||
|
name: "CodeQL"
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ main ]
|
||||||
|
pull_request:
|
||||||
|
# The branches below must be a subset of the branches above
|
||||||
|
branches: [ main ]
|
||||||
|
schedule:
|
||||||
|
- cron: '31 21 * * 6'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
analyze:
|
||||||
|
name: Analyze
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
actions: read
|
||||||
|
contents: read
|
||||||
|
security-events: write
|
||||||
|
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
language: [ 'go' ]
|
||||||
|
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
|
||||||
|
# Learn more about CodeQL language support at https://git.io/codeql-language-support
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
|
||||||
|
# Initializes the CodeQL tools for scanning.
|
||||||
|
- name: Initialize CodeQL
|
||||||
|
uses: github/codeql-action/init@v1
|
||||||
|
with:
|
||||||
|
languages: ${{ matrix.language }}
|
||||||
|
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||||
|
# By default, queries listed here will override any specified in a config file.
|
||||||
|
# Prefix the list here with "+" to use these queries and those in the config file.
|
||||||
|
# queries: ./path/to/local/query, your-org/your-repo/queries@main
|
||||||
|
|
||||||
|
# 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@v1
|
||||||
|
|
||||||
|
# ℹ️ Command-line programs to run using the OS shell.
|
||||||
|
# 📚 https://git.io/JvXDl
|
||||||
|
|
||||||
|
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
|
||||||
|
# and modify them (or add more) to build your code if your project
|
||||||
|
# uses a compiled language
|
||||||
|
|
||||||
|
#- run: |
|
||||||
|
# make bootstrap
|
||||||
|
# make release
|
||||||
|
|
||||||
|
- name: Perform CodeQL Analysis
|
||||||
|
uses: github/codeql-action/analyze@v1
|
|
@ -9,13 +9,6 @@ on:
|
||||||
- ".github/workflows/golangci-lint.yml"
|
- ".github/workflows/golangci-lint.yml"
|
||||||
- ".golangci.yml"
|
- ".golangci.yml"
|
||||||
pull_request:
|
pull_request:
|
||||||
paths:
|
|
||||||
- "go.sum"
|
|
||||||
- "go.mod"
|
|
||||||
- "**.go"
|
|
||||||
- "scripts/errcheck_excludes.txt"
|
|
||||||
- ".github/workflows/golangci-lint.yml"
|
|
||||||
- ".golangci.yml"
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
golangci:
|
golangci:
|
||||||
|
@ -23,9 +16,15 @@ jobs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v3
|
||||||
|
- name: install Go
|
||||||
- name: Lint
|
uses: actions/setup-go@v2
|
||||||
uses: golangci/golangci-lint-action@v2
|
|
||||||
with:
|
with:
|
||||||
version: v1.42.0
|
go-version: 1.18.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@v3.2.0
|
||||||
|
with:
|
||||||
|
version: v1.45.2
|
||||||
|
|
|
@ -12,7 +12,7 @@ examples/gocollector/gocollector
|
||||||
vendor/
|
vendor/
|
||||||
|
|
||||||
# The remainder of this file is taken from
|
# The remainder of this file is taken from
|
||||||
# https://github.com/github/gitignore/blob/master/Go.gitignore
|
# https://github.com/github/gitignore/blob/main/Go.gitignore
|
||||||
|
|
||||||
# Binaries for programs and plugins
|
# Binaries for programs and plugins
|
||||||
*.exe
|
*.exe
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
1.18
|
|
@ -1,5 +1,47 @@
|
||||||
# Run only staticcheck for now. Additional linters will be enabled one-by-one.
|
---
|
||||||
|
|
||||||
|
run:
|
||||||
|
deadline: 5m
|
||||||
|
|
||||||
|
output:
|
||||||
|
sort-results: true
|
||||||
|
|
||||||
linters:
|
linters:
|
||||||
enable:
|
enable:
|
||||||
- staticcheck
|
- deadcode
|
||||||
disable-all: true
|
- depguard
|
||||||
|
- durationcheck
|
||||||
|
- errorlint
|
||||||
|
- exportloopref
|
||||||
|
- gofmt
|
||||||
|
- gofumpt
|
||||||
|
- goimports
|
||||||
|
- gosimple
|
||||||
|
- ineffassign
|
||||||
|
- misspell
|
||||||
|
- nolintlint
|
||||||
|
- predeclared
|
||||||
|
- revive
|
||||||
|
- staticcheck
|
||||||
|
- structcheck
|
||||||
|
- unconvert
|
||||||
|
- unused
|
||||||
|
- varcheck
|
||||||
|
- wastedassign
|
||||||
|
|
||||||
|
issues:
|
||||||
|
max-same-issues: 0
|
||||||
|
exclude-rules:
|
||||||
|
- path: _test.go
|
||||||
|
linters:
|
||||||
|
- errcheck
|
||||||
|
- govet
|
||||||
|
- structcheck
|
||||||
|
|
||||||
|
linters-settings:
|
||||||
|
errcheck:
|
||||||
|
exclude: scripts/errcheck_excludes.txt
|
||||||
|
goimports:
|
||||||
|
local-prefixes: git.internal/re/client_golang
|
||||||
|
gofumpt:
|
||||||
|
extra-rules: true
|
||||||
|
|
33
CHANGELOG.md
33
CHANGELOG.md
|
@ -1,10 +1,37 @@
|
||||||
## Unreleased
|
## Unreleased
|
||||||
|
|
||||||
* [CHANGE] Minimum required Go version is now 1.16.
|
## 1.14.0 / 2022-11-08
|
||||||
|
|
||||||
## 1.12.2 / 2022-01-29
|
* [FEATURE] Add Support for Native Histograms. #1150
|
||||||
|
* [CHANGE] Extend `prometheus.Registry` to implement `prometheus.Collector` interface. #1103
|
||||||
|
|
||||||
* [CHANGE] Added `collectors.WithGoCollections` that allows to choose what collection of Go runtime metrics user wants: Equivalent of [`MemStats` structure](https://pkg.go.dev/runtime#MemStats) configured using `GoRuntimeMemStatsCollection`, new based on dedicated [runtime/metrics](https://pkg.go.dev/runtime/metrics) metrics represented by `GoRuntimeMetricsCollection` option, or both by specifying `GoRuntimeMemStatsCollection | GoRuntimeMetricsCollection` flag.
|
## 1.13.1 / 2022-11-01
|
||||||
|
|
||||||
|
* [BUGFIX] Fix race condition with Exemplar in Counter. #1146
|
||||||
|
* [BUGFIX] Fix `CumulativeCount` value of `+Inf` bucket created from exemplar. #1148
|
||||||
|
* [BUGFIX] Fix double-counting bug in `promhttp.InstrumentRoundTripperCounter`. #1118
|
||||||
|
|
||||||
|
## 1.13.0 / 2022-08-05
|
||||||
|
|
||||||
|
* [CHANGE] Minimum required Go version is now 1.17 (we also test client_golang against new 1.19 version).
|
||||||
|
* [ENHANCEMENT] Added `prometheus.TransactionalGatherer` interface for `promhttp.Handler` use which allows using low allocation update techniques for custom collectors. #989
|
||||||
|
* [ENHANCEMENT] Added exemplar support to `prometheus.NewConstHistogram`. See [`ExampleNewConstHistogram_WithExemplar`](prometheus/examples_test.go#L602) example on how to use it. #986
|
||||||
|
* [ENHANCEMENT] `prometheus/push.Pusher` has now context aware methods that pass context to HTTP request. #1028
|
||||||
|
* [ENHANCEMENT] `prometheus/push.Pusher` has now `Error` method that retrieve last error. #1075
|
||||||
|
* [ENHANCEMENT] `testutil.GatherAndCompare` provides now readable diff on failed comparisons. #998
|
||||||
|
* [ENHANCEMENT] Query API now supports timeouts. #1014
|
||||||
|
* [ENHANCEMENT] New `MetricVec` method `DeletePartialMatch(labels Labels)` for deleting all metrics that match provided labels. #1013
|
||||||
|
* [ENHANCEMENT] `api.Config` now accepts passing custom `*http.Client`. #1025
|
||||||
|
* [BUGFIX] Raise exemplar labels limit from 64 to 128 bytes as specified in OpenMetrics spec. #1091
|
||||||
|
* [BUGFIX] Allow adding exemplar to +Inf bucket to const histograms. #1094
|
||||||
|
* [ENHANCEMENT] Most `promhttp.Instrument*` middlewares now supports adding exemplars to metrics. This allows hooking those to your tracing middleware that retrieves trace ID and put it in exemplar if present. #1055
|
||||||
|
* [ENHANCEMENT] Added `testutil.ScrapeAndCompare` method. #1043
|
||||||
|
* [BUGFIX] Fixed `GopherJS` build support. #897
|
||||||
|
* [ENHANCEMENT] :warning: Added way to specify what `runtime/metrics` `collectors.NewGoCollector` should use. See [`ExampleGoCollector_WithAdvancedGoMetrics`](prometheus/collectors/go_collector_latest_test.go#L263). #1102
|
||||||
|
|
||||||
|
## 1.12.2 / 2022-05-13
|
||||||
|
|
||||||
|
* [CHANGE] Added `collectors.WithGoCollections` that allows to choose what collection of Go runtime metrics user wants: Equivalent of [`MemStats` structure](https://pkg.go.dev/runtime#MemStats) configured using `GoRuntimeMemStatsCollection`, new based on dedicated [runtime/metrics](https://pkg.go.dev/runtime/metrics) metrics represented by `GoRuntimeMetricsCollection` option, or both by specifying `GoRuntimeMemStatsCollection | GoRuntimeMetricsCollection` flag. #1031
|
||||||
* [CHANGE] :warning: Change in `collectors.NewGoCollector` metrics: Reverting addition of new ~80 runtime metrics by default. You can enable this back with `GoRuntimeMetricsCollection` option or `GoRuntimeMemStatsCollection | GoRuntimeMetricsCollection` for smooth transition.
|
* [CHANGE] :warning: Change in `collectors.NewGoCollector` metrics: Reverting addition of new ~80 runtime metrics by default. You can enable this back with `GoRuntimeMetricsCollection` option or `GoRuntimeMemStatsCollection | GoRuntimeMetricsCollection` for smooth transition.
|
||||||
* [BUGFIX] Fixed the bug that causes generated histogram metric names to end with `_total`. ⚠️ This changes 3 metric names in the new Go collector that was reverted from default in this release.
|
* [BUGFIX] Fixed the bug that causes generated histogram metric names to end with `_total`. ⚠️ This changes 3 metric names in the new Go collector that was reverted from default in this release.
|
||||||
* `go_gc_heap_allocs_by_size_bytes_total` -> `go_gc_heap_allocs_by_size_bytes`,
|
* `go_gc_heap_allocs_by_size_bytes_total` -> `go_gc_heap_allocs_by_size_bytes`,
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
## Prometheus Community Code of Conduct
|
# Prometheus Community Code of Conduct
|
||||||
|
|
||||||
Prometheus follows the [CNCF Code of Conduct](https://github.com/cncf/foundation/blob/master/code-of-conduct.md).
|
Prometheus follows the [CNCF Code of Conduct](https://github.com/cncf/foundation/blob/main/code-of-conduct.md).
|
||||||
|
|
26
Dockerfile
26
Dockerfile
|
@ -1,26 +1,34 @@
|
||||||
# This Dockerfile builds an image for a client_golang example.
|
# This Dockerfile builds an image for a client_golang example.
|
||||||
#
|
#
|
||||||
# Use as (from the root for the client_golang repository):
|
# Use as (from the root for the client_golang repository):
|
||||||
# docker build -f examples/$name/Dockerfile -t prometheus/golang-example-$name .
|
# docker build -f Dockerfile -t prometheus/golang-example .
|
||||||
|
|
||||||
|
# Run as
|
||||||
|
# docker run -P prometheus/golang-example /random
|
||||||
|
# or
|
||||||
|
# docker run -P prometheus/golang-example /simple
|
||||||
|
|
||||||
|
# Test as
|
||||||
|
# curl $ip:$port/metrics
|
||||||
|
|
||||||
# Builder image, where we build the example.
|
# Builder image, where we build the example.
|
||||||
FROM golang:1 AS builder
|
FROM golang:1 AS builder
|
||||||
WORKDIR /go/src/github.com/prometheus/client_golang
|
WORKDIR /go/src/git.internal/re/client_golang
|
||||||
COPY . .
|
COPY . .
|
||||||
WORKDIR /go/src/github.com/prometheus/client_golang/prometheus
|
WORKDIR /go/src/git.internal/re/client_golang/prometheus
|
||||||
RUN go get -d
|
RUN go get -d
|
||||||
WORKDIR /go/src/github.com/prometheus/client_golang/examples/random
|
WORKDIR /go/src/git.internal/re/client_golang/examples/random
|
||||||
RUN CGO_ENABLED=0 GOOS=linux go build -a -tags netgo -ldflags '-w'
|
RUN CGO_ENABLED=0 GOOS=linux go build -a -tags netgo -ldflags '-w'
|
||||||
WORKDIR /go/src/github.com/prometheus/client_golang/examples/simple
|
WORKDIR /go/src/git.internal/re/client_golang/examples/simple
|
||||||
RUN CGO_ENABLED=0 GOOS=linux go build -a -tags netgo -ldflags '-w'
|
RUN CGO_ENABLED=0 GOOS=linux go build -a -tags netgo -ldflags '-w'
|
||||||
WORKDIR /go/src/github.com/prometheus/client_golang/examples/gocollector
|
WORKDIR /go/src/git.internal/re/client_golang/examples/gocollector
|
||||||
RUN CGO_ENABLED=0 GOOS=linux go build -a -tags netgo -ldflags '-w'
|
RUN CGO_ENABLED=0 GOOS=linux go build -a -tags netgo -ldflags '-w'
|
||||||
|
|
||||||
# Final image.
|
# Final image.
|
||||||
FROM quay.io/prometheus/busybox:latest
|
FROM quay.io/prometheus/busybox:latest
|
||||||
LABEL maintainer="The Prometheus Authors <prometheus-developers@googlegroups.com>"
|
LABEL maintainer="The Prometheus Authors <prometheus-developers@googlegroups.com>"
|
||||||
COPY --from=builder /go/src/github.com/prometheus/client_golang/examples/random \
|
COPY --from=builder /go/src/git.internal/re/client_golang/examples/random \
|
||||||
/go/src/github.com/prometheus/client_golang/examples/simple \
|
/go/src/git.internal/re/client_golang/examples/simple \
|
||||||
/go/src/github.com/prometheus/client_golang/examples/gocollector ./
|
/go/src/git.internal/re/client_golang/examples/gocollector ./
|
||||||
EXPOSE 8080
|
EXPOSE 8080
|
||||||
CMD ["echo", "Please run an example. Either /random, /simple or /gocollector"]
|
CMD ["echo", "Please run an example. Either /random, /simple or /gocollector"]
|
||||||
|
|
8
Makefile
8
Makefile
|
@ -18,3 +18,11 @@ test: deps common-test
|
||||||
|
|
||||||
.PHONY: test-short
|
.PHONY: test-short
|
||||||
test-short: deps common-test-short
|
test-short: deps common-test-short
|
||||||
|
|
||||||
|
.PHONY: generate-go-collector-test-files
|
||||||
|
VERSIONS := 1.17 1.18 1.19
|
||||||
|
generate-go-collector-test-files:
|
||||||
|
for GO_VERSION in $(VERSIONS); do \
|
||||||
|
docker run --rm -v $(PWD):/workspace -w /workspace golang:$$GO_VERSION go run prometheus/gen_go_collector_metrics_set.go; \
|
||||||
|
mv -f go_collector_metrics* prometheus; \
|
||||||
|
done
|
||||||
|
|
|
@ -36,29 +36,6 @@ GO_VERSION ?= $(shell $(GO) version)
|
||||||
GO_VERSION_NUMBER ?= $(word 3, $(GO_VERSION))
|
GO_VERSION_NUMBER ?= $(word 3, $(GO_VERSION))
|
||||||
PRE_GO_111 ?= $(shell echo $(GO_VERSION_NUMBER) | grep -E 'go1\.(10|[0-9])\.')
|
PRE_GO_111 ?= $(shell echo $(GO_VERSION_NUMBER) | grep -E 'go1\.(10|[0-9])\.')
|
||||||
|
|
||||||
GOVENDOR :=
|
|
||||||
GO111MODULE :=
|
|
||||||
ifeq (, $(PRE_GO_111))
|
|
||||||
ifneq (,$(wildcard go.mod))
|
|
||||||
# Enforce Go modules support just in case the directory is inside GOPATH (and for Travis CI).
|
|
||||||
GO111MODULE := on
|
|
||||||
|
|
||||||
ifneq (,$(wildcard vendor))
|
|
||||||
# Always use the local vendor/ directory to satisfy the dependencies.
|
|
||||||
GOOPTS := $(GOOPTS) -mod=vendor
|
|
||||||
endif
|
|
||||||
endif
|
|
||||||
else
|
|
||||||
ifneq (,$(wildcard go.mod))
|
|
||||||
ifneq (,$(wildcard vendor))
|
|
||||||
$(warning This repository requires Go >= 1.11 because of Go modules)
|
|
||||||
$(warning Some recipes may not work as expected as the current Go runtime is '$(GO_VERSION_NUMBER)')
|
|
||||||
endif
|
|
||||||
else
|
|
||||||
# This repository isn't using Go modules (yet).
|
|
||||||
GOVENDOR := $(FIRST_GOPATH)/bin/govendor
|
|
||||||
endif
|
|
||||||
endif
|
|
||||||
PROMU := $(FIRST_GOPATH)/bin/promu
|
PROMU := $(FIRST_GOPATH)/bin/promu
|
||||||
pkgs = ./...
|
pkgs = ./...
|
||||||
|
|
||||||
|
@ -83,7 +60,7 @@ PROMU_URL := https://github.com/prometheus/promu/releases/download/v$(PROMU_
|
||||||
|
|
||||||
GOLANGCI_LINT :=
|
GOLANGCI_LINT :=
|
||||||
GOLANGCI_LINT_OPTS ?=
|
GOLANGCI_LINT_OPTS ?=
|
||||||
GOLANGCI_LINT_VERSION ?= v1.42.0
|
GOLANGCI_LINT_VERSION ?= v1.45.2
|
||||||
# golangci-lint only supports linux, darwin and windows platforms on i386/amd64.
|
# golangci-lint only supports linux, darwin and windows platforms on i386/amd64.
|
||||||
# windows isn't included here because of the path separator being different.
|
# windows isn't included here because of the path separator being different.
|
||||||
ifeq ($(GOHOSTOS),$(filter $(GOHOSTOS),linux darwin))
|
ifeq ($(GOHOSTOS),$(filter $(GOHOSTOS),linux darwin))
|
||||||
|
@ -150,11 +127,7 @@ common-check_license:
|
||||||
.PHONY: common-deps
|
.PHONY: common-deps
|
||||||
common-deps:
|
common-deps:
|
||||||
@echo ">> getting dependencies"
|
@echo ">> getting dependencies"
|
||||||
ifdef GO111MODULE
|
$(GO) mod download
|
||||||
GO111MODULE=$(GO111MODULE) $(GO) mod download
|
|
||||||
else
|
|
||||||
$(GO) get $(GOOPTS) -t ./...
|
|
||||||
endif
|
|
||||||
|
|
||||||
.PHONY: update-go-deps
|
.PHONY: update-go-deps
|
||||||
update-go-deps:
|
update-go-deps:
|
||||||
|
@ -162,20 +135,17 @@ update-go-deps:
|
||||||
@for m in $$($(GO) list -mod=readonly -m -f '{{ if and (not .Indirect) (not .Main)}}{{.Path}}{{end}}' all); do \
|
@for m in $$($(GO) list -mod=readonly -m -f '{{ if and (not .Indirect) (not .Main)}}{{.Path}}{{end}}' all); do \
|
||||||
$(GO) get -d $$m; \
|
$(GO) get -d $$m; \
|
||||||
done
|
done
|
||||||
GO111MODULE=$(GO111MODULE) $(GO) mod tidy
|
$(GO) mod tidy
|
||||||
ifneq (,$(wildcard vendor))
|
|
||||||
GO111MODULE=$(GO111MODULE) $(GO) mod vendor
|
|
||||||
endif
|
|
||||||
|
|
||||||
.PHONY: common-test-short
|
.PHONY: common-test-short
|
||||||
common-test-short: $(GOTEST_DIR)
|
common-test-short: $(GOTEST_DIR)
|
||||||
@echo ">> running short tests"
|
@echo ">> running short tests"
|
||||||
GO111MODULE=$(GO111MODULE) $(GOTEST) -short $(GOOPTS) $(pkgs)
|
$(GOTEST) -short $(GOOPTS) $(pkgs)
|
||||||
|
|
||||||
.PHONY: common-test
|
.PHONY: common-test
|
||||||
common-test: $(GOTEST_DIR)
|
common-test: $(GOTEST_DIR)
|
||||||
@echo ">> running all tests"
|
@echo ">> running all tests"
|
||||||
GO111MODULE=$(GO111MODULE) $(GOTEST) $(test-flags) $(GOOPTS) $(pkgs)
|
$(GOTEST) $(test-flags) $(GOOPTS) $(pkgs)
|
||||||
|
|
||||||
$(GOTEST_DIR):
|
$(GOTEST_DIR):
|
||||||
@mkdir -p $@
|
@mkdir -p $@
|
||||||
|
@ -183,25 +153,21 @@ $(GOTEST_DIR):
|
||||||
.PHONY: common-format
|
.PHONY: common-format
|
||||||
common-format:
|
common-format:
|
||||||
@echo ">> formatting code"
|
@echo ">> formatting code"
|
||||||
GO111MODULE=$(GO111MODULE) $(GO) fmt $(pkgs)
|
$(GO) fmt $(pkgs)
|
||||||
|
|
||||||
.PHONY: common-vet
|
.PHONY: common-vet
|
||||||
common-vet:
|
common-vet:
|
||||||
@echo ">> vetting code"
|
@echo ">> vetting code"
|
||||||
GO111MODULE=$(GO111MODULE) $(GO) vet $(GOOPTS) $(pkgs)
|
$(GO) vet $(GOOPTS) $(pkgs)
|
||||||
|
|
||||||
.PHONY: common-lint
|
.PHONY: common-lint
|
||||||
common-lint: $(GOLANGCI_LINT)
|
common-lint: $(GOLANGCI_LINT)
|
||||||
ifdef GOLANGCI_LINT
|
ifdef GOLANGCI_LINT
|
||||||
@echo ">> running golangci-lint"
|
@echo ">> running golangci-lint"
|
||||||
ifdef GO111MODULE
|
|
||||||
# 'go list' needs to be executed before staticcheck to prepopulate the modules cache.
|
# 'go list' needs to be executed before staticcheck to prepopulate the modules cache.
|
||||||
# Otherwise staticcheck might fail randomly for some reason not yet explained.
|
# Otherwise staticcheck might fail randomly for some reason not yet explained.
|
||||||
GO111MODULE=$(GO111MODULE) $(GO) list -e -compiled -test=true -export=false -deps=true -find=false -tags= -- ./... > /dev/null
|
$(GO) list -e -compiled -test=true -export=false -deps=true -find=false -tags= -- ./... > /dev/null
|
||||||
GO111MODULE=$(GO111MODULE) $(GOLANGCI_LINT) run $(GOLANGCI_LINT_OPTS) $(pkgs)
|
$(GOLANGCI_LINT) run $(GOLANGCI_LINT_OPTS) $(pkgs)
|
||||||
else
|
|
||||||
$(GOLANGCI_LINT) run $(pkgs)
|
|
||||||
endif
|
|
||||||
endif
|
endif
|
||||||
|
|
||||||
.PHONY: common-yamllint
|
.PHONY: common-yamllint
|
||||||
|
@ -218,28 +184,15 @@ endif
|
||||||
common-staticcheck: lint
|
common-staticcheck: lint
|
||||||
|
|
||||||
.PHONY: common-unused
|
.PHONY: common-unused
|
||||||
common-unused: $(GOVENDOR)
|
common-unused:
|
||||||
ifdef GOVENDOR
|
|
||||||
@echo ">> running check for unused packages"
|
|
||||||
@$(GOVENDOR) list +unused | grep . && exit 1 || echo 'No unused packages'
|
|
||||||
else
|
|
||||||
ifdef GO111MODULE
|
|
||||||
@echo ">> running check for unused/missing packages in go.mod"
|
@echo ">> running check for unused/missing packages in go.mod"
|
||||||
GO111MODULE=$(GO111MODULE) $(GO) mod tidy
|
$(GO) mod tidy
|
||||||
ifeq (,$(wildcard vendor))
|
|
||||||
@git diff --exit-code -- go.sum go.mod
|
@git diff --exit-code -- go.sum go.mod
|
||||||
else
|
|
||||||
@echo ">> running check for unused packages in vendor/"
|
|
||||||
GO111MODULE=$(GO111MODULE) $(GO) mod vendor
|
|
||||||
@git diff --exit-code -- go.sum go.mod vendor/
|
|
||||||
endif
|
|
||||||
endif
|
|
||||||
endif
|
|
||||||
|
|
||||||
.PHONY: common-build
|
.PHONY: common-build
|
||||||
common-build: promu
|
common-build: promu
|
||||||
@echo ">> building binaries"
|
@echo ">> building binaries"
|
||||||
GO111MODULE=$(GO111MODULE) $(PROMU) build --prefix $(PREFIX) $(PROMU_BINARIES)
|
$(PROMU) build --prefix $(PREFIX) $(PROMU_BINARIES)
|
||||||
|
|
||||||
.PHONY: common-tarball
|
.PHONY: common-tarball
|
||||||
common-tarball: promu
|
common-tarball: promu
|
||||||
|
@ -295,12 +248,6 @@ $(GOLANGCI_LINT):
|
||||||
| sh -s -- -b $(FIRST_GOPATH)/bin $(GOLANGCI_LINT_VERSION)
|
| sh -s -- -b $(FIRST_GOPATH)/bin $(GOLANGCI_LINT_VERSION)
|
||||||
endif
|
endif
|
||||||
|
|
||||||
ifdef GOVENDOR
|
|
||||||
.PHONY: $(GOVENDOR)
|
|
||||||
$(GOVENDOR):
|
|
||||||
GOOS= GOARCH= $(GO) get -u github.com/kardianos/govendor
|
|
||||||
endif
|
|
||||||
|
|
||||||
.PHONY: precheck
|
.PHONY: precheck
|
||||||
precheck::
|
precheck::
|
||||||
|
|
||||||
|
|
50
README.md
50
README.md
|
@ -1,15 +1,16 @@
|
||||||
# Prometheus Go client library
|
# Prometheus Go client library
|
||||||
|
|
||||||
[![CircleCI](https://circleci.com/gh/prometheus/client_golang/tree/master.svg?style=svg)](https://circleci.com/gh/prometheus/client_golang/tree/master)
|
[![CircleCI](https://circleci.com/gh/prometheus/client_golang/tree/main.svg?style=svg)](https://circleci.com/gh/prometheus/client_golang/tree/main)
|
||||||
[![Go Report Card](https://goreportcard.com/badge/github.com/prometheus/client_golang)](https://goreportcard.com/report/github.com/prometheus/client_golang)
|
[![Go Report Card](https://goreportcard.com/badge/git.internal/re/client_golang)](https://goreportcard.com/report/git.internal/re/client_golang)
|
||||||
[![Go Reference](https://pkg.go.dev/badge/github.com/prometheus/client_golang.svg)](https://pkg.go.dev/github.com/prometheus/client_golang)
|
[![Go Reference](https://pkg.go.dev/badge/git.internal/re/client_golang.svg)](https://pkg.go.dev/git.internal/re/client_golang)
|
||||||
|
[![Slack](https://img.shields.io/badge/join%20slack-%23prometheus--client_golang-brightgreen.svg)](https://slack.cncf.io/)
|
||||||
|
|
||||||
This is the [Go](http://golang.org) client library for
|
This is the [Go](http://golang.org) client library for
|
||||||
[Prometheus](http://prometheus.io). It has two separate parts, one for
|
[Prometheus](http://prometheus.io). It has two separate parts, one for
|
||||||
instrumenting application code, and one for creating clients that talk to the
|
instrumenting application code, and one for creating clients that talk to the
|
||||||
Prometheus HTTP API.
|
Prometheus HTTP API.
|
||||||
|
|
||||||
__This library requires Go1.13 or later.__
|
__This library requires Go1.17 or later.__
|
||||||
|
|
||||||
## Important note about releases and stability
|
## Important note about releases and stability
|
||||||
|
|
||||||
|
@ -22,33 +23,33 @@ CHANGELOG.md.
|
||||||
|
|
||||||
Features that require breaking changes in the stable parts of the repository
|
Features that require breaking changes in the stable parts of the repository
|
||||||
are being batched up and tracked in the [v2
|
are being batched up and tracked in the [v2
|
||||||
milestone](https://github.com/prometheus/client_golang/milestone/2). The v2
|
milestone](https://git.internal/re/client_golang/milestone/2). The v2
|
||||||
development happens in a [separate
|
development happens in a [separate
|
||||||
branch](https://github.com/prometheus/client_golang/tree/dev-v2) for the time
|
branch](https://git.internal/re/client_golang/tree/dev-v2) for the time
|
||||||
being. v2 releases off that branch will happen once sufficient stability is
|
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
|
reached. In view of the widespread use of this repository, v1 and v2 will
|
||||||
coexist for a while to enable a convenient transition.
|
coexist for a while to enable a convenient transition.
|
||||||
|
|
||||||
## Instrumenting applications
|
## Instrumenting applications
|
||||||
|
|
||||||
[![code-coverage](http://gocover.io/_badge/github.com/prometheus/client_golang/prometheus)](http://gocover.io/github.com/prometheus/client_golang/prometheus) [![Go Reference](https://pkg.go.dev/badge/github.com/prometheus/client_golang/prometheus.svg)](https://pkg.go.dev/github.com/prometheus/client_golang/prometheus)
|
[![code-coverage](http://gocover.io/_badge/git.internal/re/client_golang/prometheus)](http://gocover.io/git.internal/re/client_golang/prometheus) [![Go Reference](https://pkg.go.dev/badge/git.internal/re/client_golang/prometheus.svg)](https://pkg.go.dev/git.internal/re/client_golang/prometheus)
|
||||||
|
|
||||||
The
|
The
|
||||||
[`prometheus` directory](https://github.com/prometheus/client_golang/tree/master/prometheus)
|
[`prometheus` directory](https://git.internal/re/client_golang/tree/main/prometheus)
|
||||||
contains the instrumentation library. See the
|
contains the instrumentation library. See the
|
||||||
[guide](https://prometheus.io/docs/guides/go-application/) on the Prometheus
|
[guide](https://prometheus.io/docs/guides/go-application/) on the Prometheus
|
||||||
website to learn more about instrumenting applications.
|
website to learn more about instrumenting applications.
|
||||||
|
|
||||||
The
|
The
|
||||||
[`examples` directory](https://github.com/prometheus/client_golang/tree/master/examples)
|
[`examples` directory](https://git.internal/re/client_golang/tree/main/examples)
|
||||||
contains simple examples of instrumented code.
|
contains simple examples of instrumented code.
|
||||||
|
|
||||||
## Client for the Prometheus HTTP API
|
## Client for the Prometheus HTTP API
|
||||||
|
|
||||||
[![code-coverage](http://gocover.io/_badge/github.com/prometheus/client_golang/api/prometheus/v1)](http://gocover.io/github.com/prometheus/client_golang/api/prometheus/v1) [![Go Reference](https://pkg.go.dev/badge/github.com/prometheus/client_golang/api.svg)](https://pkg.go.dev/github.com/prometheus/client_golang/api)
|
[![code-coverage](http://gocover.io/_badge/git.internal/re/client_golang/api/prometheus/v1)](http://gocover.io/git.internal/re/client_golang/api/prometheus/v1) [![Go Reference](https://pkg.go.dev/badge/git.internal/re/client_golang/api.svg)](https://pkg.go.dev/git.internal/re/client_golang/api)
|
||||||
|
|
||||||
The
|
The
|
||||||
[`api/prometheus` directory](https://github.com/prometheus/client_golang/tree/master/api/prometheus)
|
[`api/prometheus` directory](https://git.internal/re/client_golang/tree/main/api/prometheus)
|
||||||
contains the client for the
|
contains the client for the
|
||||||
[Prometheus HTTP API](http://prometheus.io/docs/querying/api/). It allows you
|
[Prometheus HTTP API](http://prometheus.io/docs/querying/api/). It allows you
|
||||||
to write Go applications that query time series data from a Prometheus
|
to write Go applications that query time series data from a Prometheus
|
||||||
|
@ -57,12 +58,35 @@ server. It is still in alpha stage.
|
||||||
## Where is `model`, `extraction`, and `text`?
|
## Where is `model`, `extraction`, and `text`?
|
||||||
|
|
||||||
The `model` packages has been moved to
|
The `model` packages has been moved to
|
||||||
[`prometheus/common/model`](https://github.com/prometheus/common/tree/master/model).
|
[`prometheus/common/model`](https://github.com/prometheus/common/tree/main/model).
|
||||||
|
|
||||||
The `extraction` and `text` packages are now contained in
|
The `extraction` and `text` packages are now contained in
|
||||||
[`prometheus/common/expfmt`](https://github.com/prometheus/common/tree/master/expfmt).
|
[`prometheus/common/expfmt`](https://github.com/prometheus/common/tree/main/expfmt).
|
||||||
|
|
||||||
## Contributing and community
|
## Contributing and community
|
||||||
|
|
||||||
See the [contributing guidelines](CONTRIBUTING.md) and the
|
See the [contributing guidelines](CONTRIBUTING.md) and the
|
||||||
[Community section](http://prometheus.io/community/) of the homepage.
|
[Community section](http://prometheus.io/community/) of the homepage.
|
||||||
|
|
||||||
|
`clint_golang` community is also present on the CNCF Slack `#prometheus-client_golang`.
|
||||||
|
|
||||||
|
### For Maintainers: Release Process
|
||||||
|
|
||||||
|
To cut a minor version:
|
||||||
|
|
||||||
|
1. Create a new branch `release-<major>.<minor>` on top of the `main` commit you want to cut the version from and push it.
|
||||||
|
2. Create a new branch on top of the release branch, e.g. `<yourname>/cut-<major>.<minor>.<patch>`,
|
||||||
|
3. Change the `VERSION` file.
|
||||||
|
4. Update `CHANGELOG` (only user-impacting changes to mention).
|
||||||
|
5. Create PR, and get it reviewed.
|
||||||
|
6. Once merged, create a release with the `release-<major>.<minor>` tag on GitHub with the `<version> / <date>` title.
|
||||||
|
7. Announce on the prometheus-announce mailing list, slack and Twitter.
|
||||||
|
8. Merge the release branch back to the `main` using the "merge without squashing" approach (!).
|
||||||
|
|
||||||
|
> NOTE: In case of merge conflicts, you can checkout the release branch in a new branch, e.g. <yourname>/resolve-conflicts`, fix the merge problems there, and then do a PR into main from the new branch. In that way, you still get all the commits in the release branch back into `main`, but leave the release branch alone.
|
||||||
|
|
||||||
|
To cut the patch version:
|
||||||
|
|
||||||
|
1. Create a branch on top of the release branch you want to use.
|
||||||
|
2. Cherry-pick the fixes from the `main` branch (or add new commits) to fix critical bugs for that patch release.
|
||||||
|
3. Follow steps 3-8 as above.
|
||||||
|
|
|
@ -3,4 +3,4 @@
|
||||||
The Prometheus security policy, including how to report vulnerabilities, can be
|
The Prometheus security policy, including how to report vulnerabilities, can be
|
||||||
found here:
|
found here:
|
||||||
|
|
||||||
https://prometheus.io/docs/operating/security/
|
<https://prometheus.io/docs/operating/security/>
|
||||||
|
|
|
@ -17,6 +17,7 @@ package api
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
@ -40,6 +41,10 @@ type Config struct {
|
||||||
// The address of the Prometheus to connect to.
|
// The address of the Prometheus to connect to.
|
||||||
Address string
|
Address string
|
||||||
|
|
||||||
|
// Client is used by the Client to drive HTTP requests. If not provided,
|
||||||
|
// a new one based on the provided RoundTripper (or DefaultRoundTripper) will be used.
|
||||||
|
Client *http.Client
|
||||||
|
|
||||||
// RoundTripper is used by the Client to drive HTTP requests. If not
|
// RoundTripper is used by the Client to drive HTTP requests. If not
|
||||||
// provided, DefaultRoundTripper will be used.
|
// provided, DefaultRoundTripper will be used.
|
||||||
RoundTripper http.RoundTripper
|
RoundTripper http.RoundTripper
|
||||||
|
@ -52,6 +57,22 @@ func (cfg *Config) roundTripper() http.RoundTripper {
|
||||||
return cfg.RoundTripper
|
return cfg.RoundTripper
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (cfg *Config) client() http.Client {
|
||||||
|
if cfg.Client == nil {
|
||||||
|
return http.Client{
|
||||||
|
Transport: cfg.roundTripper(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return *cfg.Client
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg *Config) validate() error {
|
||||||
|
if cfg.Client != nil && cfg.RoundTripper != nil {
|
||||||
|
return errors.New("api.Config.RoundTripper and api.Config.Client are mutually exclusive")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// Client is the interface for an API client.
|
// Client is the interface for an API client.
|
||||||
type Client interface {
|
type Client interface {
|
||||||
URL(ep string, args map[string]string) *url.URL
|
URL(ep string, args map[string]string) *url.URL
|
||||||
|
@ -68,9 +89,13 @@ func NewClient(cfg Config) (Client, error) {
|
||||||
}
|
}
|
||||||
u.Path = strings.TrimRight(u.Path, "/")
|
u.Path = strings.TrimRight(u.Path, "/")
|
||||||
|
|
||||||
|
if err := cfg.validate(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
return &httpClient{
|
return &httpClient{
|
||||||
endpoint: u,
|
endpoint: u,
|
||||||
client: http.Client{Transport: cfg.roundTripper()},
|
client: cfg.client(),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -84,7 +109,7 @@ func (c *httpClient) URL(ep string, args map[string]string) *url.URL {
|
||||||
|
|
||||||
for arg, val := range args {
|
for arg, val := range args {
|
||||||
arg = ":" + arg
|
arg = ":" + arg
|
||||||
p = strings.Replace(p, arg, val, -1)
|
p = strings.ReplaceAll(p, arg, val)
|
||||||
}
|
}
|
||||||
|
|
||||||
u := *c.endpoint
|
u := *c.endpoint
|
||||||
|
|
|
@ -134,7 +134,6 @@ func BenchmarkClient(b *testing.B) {
|
||||||
|
|
||||||
for _, sizeKB := range []int{4, 50, 1000, 2000} {
|
for _, sizeKB := range []int{4, 50, 1000, 2000} {
|
||||||
b.Run(fmt.Sprintf("%dKB", sizeKB), func(b *testing.B) {
|
b.Run(fmt.Sprintf("%dKB", sizeKB), func(b *testing.B) {
|
||||||
|
|
||||||
testServer := httptest.NewServer(serveSpaces{sizeKB})
|
testServer := httptest.NewServer(serveSpaces{sizeKB})
|
||||||
defer testServer.Close()
|
defer testServer.Close()
|
||||||
|
|
||||||
|
|
|
@ -31,7 +31,7 @@ import (
|
||||||
|
|
||||||
"github.com/prometheus/common/model"
|
"github.com/prometheus/common/model"
|
||||||
|
|
||||||
"github.com/prometheus/client_golang/api"
|
"git.internal/re/client_golang/api"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
@ -109,7 +109,6 @@ func marshalPointJSON(ptr unsafe.Pointer, stream *json.Stream) {
|
||||||
|
|
||||||
stream.WriteRaw(`"`)
|
stream.WriteRaw(`"`)
|
||||||
stream.WriteArrayEnd()
|
stream.WriteArrayEnd()
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func marshalPointJSONIsEmpty(ptr unsafe.Pointer) bool {
|
func marshalPointJSONIsEmpty(ptr unsafe.Pointer) bool {
|
||||||
|
@ -230,25 +229,25 @@ type API interface {
|
||||||
// Config returns the current Prometheus configuration.
|
// Config returns the current Prometheus configuration.
|
||||||
Config(ctx context.Context) (ConfigResult, error)
|
Config(ctx context.Context) (ConfigResult, error)
|
||||||
// DeleteSeries deletes data for a selection of series in a time range.
|
// DeleteSeries deletes data for a selection of series in a time range.
|
||||||
DeleteSeries(ctx context.Context, matches []string, startTime time.Time, endTime time.Time) error
|
DeleteSeries(ctx context.Context, matches []string, startTime, endTime time.Time) error
|
||||||
// Flags returns the flag values that Prometheus was launched with.
|
// Flags returns the flag values that Prometheus was launched with.
|
||||||
Flags(ctx context.Context) (FlagsResult, error)
|
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 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 time.Time, endTime time.Time) ([]string, Warnings, error)
|
LabelNames(ctx context.Context, matches []string, startTime, endTime time.Time) ([]string, Warnings, error)
|
||||||
// LabelValues performs a query for the values of the given label, time range and matchers.
|
// LabelValues performs a query for the values of the given label, time range and matchers.
|
||||||
LabelValues(ctx context.Context, label string, matches []string, startTime time.Time, endTime time.Time) (model.LabelValues, Warnings, error)
|
LabelValues(ctx context.Context, label string, matches []string, startTime, endTime time.Time) (model.LabelValues, Warnings, error)
|
||||||
// Query performs a query for the given time.
|
// Query performs a query for the given time.
|
||||||
Query(ctx context.Context, query string, ts time.Time) (model.Value, Warnings, error)
|
Query(ctx context.Context, query string, ts time.Time, opts ...Option) (model.Value, Warnings, error)
|
||||||
// QueryRange performs a query for the given range.
|
// QueryRange performs a query for the given range.
|
||||||
QueryRange(ctx context.Context, query string, r Range) (model.Value, Warnings, error)
|
QueryRange(ctx context.Context, query string, r Range, opts ...Option) (model.Value, Warnings, error)
|
||||||
// QueryExemplars performs a query for exemplars by the given query and time range.
|
// QueryExemplars performs a query for exemplars by the given query and time range.
|
||||||
QueryExemplars(ctx context.Context, query string, startTime time.Time, endTime time.Time) ([]ExemplarQueryResult, error)
|
QueryExemplars(ctx context.Context, query string, startTime, endTime time.Time) ([]ExemplarQueryResult, error)
|
||||||
// Buildinfo returns various build information properties about the Prometheus server
|
// Buildinfo returns various build information properties about the Prometheus server
|
||||||
Buildinfo(ctx context.Context) (BuildinfoResult, error)
|
Buildinfo(ctx context.Context) (BuildinfoResult, error)
|
||||||
// Runtimeinfo returns the various runtime information properties about the Prometheus server.
|
// Runtimeinfo returns the various runtime information properties about the Prometheus server.
|
||||||
Runtimeinfo(ctx context.Context) (RuntimeinfoResult, error)
|
Runtimeinfo(ctx context.Context) (RuntimeinfoResult, error)
|
||||||
// Series finds series by label matchers.
|
// Series finds series by label matchers.
|
||||||
Series(ctx context.Context, matches []string, startTime time.Time, endTime time.Time) ([]model.LabelSet, Warnings, error)
|
Series(ctx context.Context, matches []string, startTime, endTime time.Time) ([]model.LabelSet, Warnings, error)
|
||||||
// Snapshot creates a snapshot of all current data into snapshots/<datetime>-<rand>
|
// Snapshot creates a snapshot of all current data into snapshots/<datetime>-<rand>
|
||||||
// under the TSDB's data directory and returns the directory as response.
|
// under the TSDB's data directory and returns the directory as response.
|
||||||
Snapshot(ctx context.Context, skipHead bool) (SnapshotResult, error)
|
Snapshot(ctx context.Context, skipHead bool) (SnapshotResult, error)
|
||||||
|
@ -257,9 +256,9 @@ type API interface {
|
||||||
// Targets returns an overview of the current state of the Prometheus target discovery.
|
// Targets returns an overview of the current state of the Prometheus target discovery.
|
||||||
Targets(ctx context.Context) (TargetsResult, error)
|
Targets(ctx context.Context) (TargetsResult, error)
|
||||||
// TargetsMetadata returns metadata about metrics currently scraped by the target.
|
// TargetsMetadata returns metadata about metrics currently scraped by the target.
|
||||||
TargetsMetadata(ctx context.Context, matchTarget string, metric string, limit string) ([]MetricMetadata, error)
|
TargetsMetadata(ctx context.Context, matchTarget, metric, limit string) ([]MetricMetadata, error)
|
||||||
// Metadata returns metadata about metrics currently scraped by the metric name.
|
// Metadata returns metadata about metrics currently scraped by the metric name.
|
||||||
Metadata(ctx context.Context, metric string, limit string) (map[string][]Metadata, error)
|
Metadata(ctx context.Context, metric, limit string) (map[string][]Metadata, error)
|
||||||
// TSDB returns the cardinality statistics.
|
// TSDB returns the cardinality statistics.
|
||||||
TSDB(ctx context.Context) (TSDBResult, error)
|
TSDB(ctx context.Context) (TSDBResult, error)
|
||||||
// WalReplay returns the current replay status of the wal.
|
// WalReplay returns the current replay status of the wal.
|
||||||
|
@ -336,14 +335,15 @@ type RuleGroup struct {
|
||||||
// that rules are returned in by the API.
|
// that rules are returned in by the API.
|
||||||
//
|
//
|
||||||
// Rule types can be determined using a type switch:
|
// Rule types can be determined using a type switch:
|
||||||
// switch v := rule.(type) {
|
//
|
||||||
// case RecordingRule:
|
// switch v := rule.(type) {
|
||||||
// fmt.Print("got a recording rule")
|
// case RecordingRule:
|
||||||
// case AlertingRule:
|
// fmt.Print("got a recording rule")
|
||||||
// fmt.Print("got a alerting rule")
|
// case AlertingRule:
|
||||||
// default:
|
// fmt.Print("got a alerting rule")
|
||||||
// fmt.Printf("unknown rule type %s", v)
|
// default:
|
||||||
// }
|
// fmt.Printf("unknown rule type %s", v)
|
||||||
|
// }
|
||||||
type Rules []interface{}
|
type Rules []interface{}
|
||||||
|
|
||||||
// AlertingRule models a alerting rule.
|
// AlertingRule models a alerting rule.
|
||||||
|
@ -699,7 +699,7 @@ func (h *httpAPI) Config(ctx context.Context) (ConfigResult, error) {
|
||||||
return res, json.Unmarshal(body, &res)
|
return res, json.Unmarshal(body, &res)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *httpAPI) DeleteSeries(ctx context.Context, matches []string, startTime time.Time, endTime time.Time) error {
|
func (h *httpAPI) DeleteSeries(ctx context.Context, matches []string, startTime, endTime time.Time) error {
|
||||||
u := h.client.URL(epDeleteSeries, nil)
|
u := h.client.URL(epDeleteSeries, nil)
|
||||||
q := u.Query()
|
q := u.Query()
|
||||||
|
|
||||||
|
@ -772,7 +772,7 @@ func (h *httpAPI) Runtimeinfo(ctx context.Context) (RuntimeinfoResult, error) {
|
||||||
return res, json.Unmarshal(body, &res)
|
return res, json.Unmarshal(body, &res)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *httpAPI) LabelNames(ctx context.Context, matches []string, startTime time.Time, endTime time.Time) ([]string, Warnings, error) {
|
func (h *httpAPI) LabelNames(ctx context.Context, matches []string, startTime, endTime time.Time) ([]string, Warnings, error) {
|
||||||
u := h.client.URL(epLabels, nil)
|
u := h.client.URL(epLabels, nil)
|
||||||
q := u.Query()
|
q := u.Query()
|
||||||
q.Set("start", formatTime(startTime))
|
q.Set("start", formatTime(startTime))
|
||||||
|
@ -795,7 +795,7 @@ func (h *httpAPI) LabelNames(ctx context.Context, matches []string, startTime ti
|
||||||
return labelNames, w, json.Unmarshal(body, &labelNames)
|
return labelNames, w, json.Unmarshal(body, &labelNames)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *httpAPI) LabelValues(ctx context.Context, label string, matches []string, startTime time.Time, endTime time.Time) (model.LabelValues, Warnings, error) {
|
func (h *httpAPI) LabelValues(ctx context.Context, label string, matches []string, startTime, endTime time.Time) (model.LabelValues, Warnings, error) {
|
||||||
u := h.client.URL(epLabelValues, map[string]string{"name": label})
|
u := h.client.URL(epLabelValues, map[string]string{"name": label})
|
||||||
q := u.Query()
|
q := u.Query()
|
||||||
q.Set("start", formatTime(startTime))
|
q.Set("start", formatTime(startTime))
|
||||||
|
@ -818,10 +818,34 @@ func (h *httpAPI) LabelValues(ctx context.Context, label string, matches []strin
|
||||||
return labelValues, w, json.Unmarshal(body, &labelValues)
|
return labelValues, w, json.Unmarshal(body, &labelValues)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *httpAPI) Query(ctx context.Context, query string, ts time.Time) (model.Value, Warnings, error) {
|
type apiOptions struct {
|
||||||
|
timeout time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
type Option func(c *apiOptions)
|
||||||
|
|
||||||
|
// WithTimeout can be used to provide an optional query evaluation timeout for Query and QueryRange.
|
||||||
|
// https://prometheus.io/docs/prometheus/latest/querying/api/#instant-queries
|
||||||
|
func WithTimeout(timeout time.Duration) Option {
|
||||||
|
return func(o *apiOptions) {
|
||||||
|
o.timeout = timeout
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *httpAPI) Query(ctx context.Context, query string, ts time.Time, opts ...Option) (model.Value, Warnings, error) {
|
||||||
u := h.client.URL(epQuery, nil)
|
u := h.client.URL(epQuery, nil)
|
||||||
q := u.Query()
|
q := u.Query()
|
||||||
|
|
||||||
|
opt := &apiOptions{}
|
||||||
|
for _, o := range opts {
|
||||||
|
o(opt)
|
||||||
|
}
|
||||||
|
|
||||||
|
d := opt.timeout
|
||||||
|
if d > 0 {
|
||||||
|
q.Set("timeout", d.String())
|
||||||
|
}
|
||||||
|
|
||||||
q.Set("query", query)
|
q.Set("query", query)
|
||||||
if !ts.IsZero() {
|
if !ts.IsZero() {
|
||||||
q.Set("time", formatTime(ts))
|
q.Set("time", formatTime(ts))
|
||||||
|
@ -833,10 +857,10 @@ func (h *httpAPI) Query(ctx context.Context, query string, ts time.Time) (model.
|
||||||
}
|
}
|
||||||
|
|
||||||
var qres queryResult
|
var qres queryResult
|
||||||
return model.Value(qres.v), warnings, json.Unmarshal(body, &qres)
|
return qres.v, warnings, json.Unmarshal(body, &qres)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *httpAPI) QueryRange(ctx context.Context, query string, r Range) (model.Value, Warnings, error) {
|
func (h *httpAPI) QueryRange(ctx context.Context, query string, r Range, opts ...Option) (model.Value, Warnings, error) {
|
||||||
u := h.client.URL(epQueryRange, nil)
|
u := h.client.URL(epQueryRange, nil)
|
||||||
q := u.Query()
|
q := u.Query()
|
||||||
|
|
||||||
|
@ -845,6 +869,16 @@ func (h *httpAPI) QueryRange(ctx context.Context, query string, r Range) (model.
|
||||||
q.Set("end", formatTime(r.End))
|
q.Set("end", formatTime(r.End))
|
||||||
q.Set("step", strconv.FormatFloat(r.Step.Seconds(), 'f', -1, 64))
|
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)
|
_, body, warnings, err := h.client.DoGetFallback(ctx, u, q)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, warnings, err
|
return nil, warnings, err
|
||||||
|
@ -852,10 +886,10 @@ func (h *httpAPI) QueryRange(ctx context.Context, query string, r Range) (model.
|
||||||
|
|
||||||
var qres queryResult
|
var qres queryResult
|
||||||
|
|
||||||
return model.Value(qres.v), warnings, json.Unmarshal(body, &qres)
|
return qres.v, warnings, json.Unmarshal(body, &qres)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *httpAPI) Series(ctx context.Context, matches []string, startTime time.Time, endTime time.Time) ([]model.LabelSet, Warnings, error) {
|
func (h *httpAPI) Series(ctx context.Context, matches []string, startTime, endTime time.Time) ([]model.LabelSet, Warnings, error) {
|
||||||
u := h.client.URL(epSeries, nil)
|
u := h.client.URL(epSeries, nil)
|
||||||
q := u.Query()
|
q := u.Query()
|
||||||
|
|
||||||
|
@ -938,7 +972,7 @@ func (h *httpAPI) Targets(ctx context.Context) (TargetsResult, error) {
|
||||||
return res, json.Unmarshal(body, &res)
|
return res, json.Unmarshal(body, &res)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *httpAPI) TargetsMetadata(ctx context.Context, matchTarget string, metric string, limit string) ([]MetricMetadata, error) {
|
func (h *httpAPI) TargetsMetadata(ctx context.Context, matchTarget, metric, limit string) ([]MetricMetadata, error) {
|
||||||
u := h.client.URL(epTargetsMetadata, nil)
|
u := h.client.URL(epTargetsMetadata, nil)
|
||||||
q := u.Query()
|
q := u.Query()
|
||||||
|
|
||||||
|
@ -962,7 +996,7 @@ func (h *httpAPI) TargetsMetadata(ctx context.Context, matchTarget string, metri
|
||||||
return res, json.Unmarshal(body, &res)
|
return res, json.Unmarshal(body, &res)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *httpAPI) Metadata(ctx context.Context, metric string, limit string) (map[string][]Metadata, error) {
|
func (h *httpAPI) Metadata(ctx context.Context, metric, limit string) (map[string][]Metadata, error) {
|
||||||
u := h.client.URL(epMetadata, nil)
|
u := h.client.URL(epMetadata, nil)
|
||||||
q := u.Query()
|
q := u.Query()
|
||||||
|
|
||||||
|
@ -1019,7 +1053,7 @@ func (h *httpAPI) WalReplay(ctx context.Context) (WalReplayStatus, error) {
|
||||||
return res, json.Unmarshal(body, &res)
|
return res, json.Unmarshal(body, &res)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *httpAPI) QueryExemplars(ctx context.Context, query string, startTime time.Time, endTime time.Time) ([]ExemplarQueryResult, error) {
|
func (h *httpAPI) QueryExemplars(ctx context.Context, query string, startTime, endTime time.Time) ([]ExemplarQueryResult, error) {
|
||||||
u := h.client.URL(epQueryExemplars, nil)
|
u := h.client.URL(epQueryExemplars, nil)
|
||||||
q := u.Query()
|
q := u.Query()
|
||||||
|
|
||||||
|
@ -1127,33 +1161,36 @@ func (h *apiClientImpl) Do(ctx context.Context, req *http.Request) (*http.Respon
|
||||||
}
|
}
|
||||||
|
|
||||||
return resp, []byte(result.Data), result.Warnings, err
|
return resp, []byte(result.Data), result.Warnings, err
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// DoGetFallback will attempt to do the request as-is, and on a 405 or 501 it
|
// DoGetFallback will attempt to do the request as-is, and on a 405 or 501 it
|
||||||
// will fallback to a GET request.
|
// will fallback to a GET request.
|
||||||
func (h *apiClientImpl) DoGetFallback(ctx context.Context, u *url.URL, args url.Values) (*http.Response, []byte, Warnings, error) {
|
func (h *apiClientImpl) DoGetFallback(ctx context.Context, u *url.URL, args url.Values) (*http.Response, []byte, Warnings, error) {
|
||||||
req, err := http.NewRequest(http.MethodPost, u.String(), strings.NewReader(args.Encode()))
|
encodedArgs := args.Encode()
|
||||||
|
req, err := http.NewRequest(http.MethodPost, u.String(), strings.NewReader(encodedArgs))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, nil, err
|
return nil, nil, nil, err
|
||||||
}
|
}
|
||||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||||
|
// Following comment originates from https://pkg.go.dev/net/http#Transport
|
||||||
|
// Transport only retries a request upon encountering a network error if the request is
|
||||||
|
// idempotent and either has no body or has its Request.GetBody defined. HTTP requests
|
||||||
|
// are considered idempotent if they have HTTP methods GET, HEAD, OPTIONS, or TRACE; or
|
||||||
|
// if their Header map contains an "Idempotency-Key" or "X-Idempotency-Key" entry. If the
|
||||||
|
// idempotency key value is a zero-length slice, the request is treated as idempotent but
|
||||||
|
// the header is not sent on the wire.
|
||||||
|
req.Header["Idempotency-Key"] = nil
|
||||||
|
|
||||||
resp, body, warnings, err := h.Do(ctx, req)
|
resp, body, warnings, err := h.Do(ctx, req)
|
||||||
if resp != nil && (resp.StatusCode == http.StatusMethodNotAllowed || resp.StatusCode == http.StatusNotImplemented) {
|
if resp != nil && (resp.StatusCode == http.StatusMethodNotAllowed || resp.StatusCode == http.StatusNotImplemented) {
|
||||||
u.RawQuery = args.Encode()
|
u.RawQuery = encodedArgs
|
||||||
req, err = http.NewRequest(http.MethodGet, u.String(), nil)
|
req, err = http.NewRequest(http.MethodGet, u.String(), nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, warnings, err
|
return nil, nil, warnings, err
|
||||||
}
|
}
|
||||||
|
return h.Do(ctx, req)
|
||||||
} else {
|
|
||||||
if err != nil {
|
|
||||||
return resp, body, warnings, err
|
|
||||||
}
|
|
||||||
return resp, body, warnings, nil
|
|
||||||
}
|
}
|
||||||
return h.Do(ctx, req)
|
return resp, body, warnings, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func formatTime(t time.Time) string {
|
func formatTime(t time.Time) string {
|
||||||
|
|
|
@ -17,7 +17,7 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io"
|
||||||
"math"
|
"math"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
|
@ -40,10 +40,8 @@ type apiTest struct {
|
||||||
inRes interface{}
|
inRes interface{}
|
||||||
|
|
||||||
reqPath string
|
reqPath string
|
||||||
reqParam url.Values
|
|
||||||
reqMethod string
|
reqMethod string
|
||||||
res interface{}
|
res interface{}
|
||||||
warnings Warnings
|
|
||||||
err error
|
err error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -55,7 +53,7 @@ type apiTestClient struct {
|
||||||
func (c *apiTestClient) URL(ep string, args map[string]string) *url.URL {
|
func (c *apiTestClient) URL(ep string, args map[string]string) *url.URL {
|
||||||
path := ep
|
path := ep
|
||||||
for k, v := range args {
|
for k, v := range args {
|
||||||
path = strings.Replace(path, ":"+k, v, -1)
|
path = strings.ReplaceAll(path, ":"+k, v)
|
||||||
}
|
}
|
||||||
u := &url.URL{
|
u := &url.URL{
|
||||||
Host: "test:9090",
|
Host: "test:9090",
|
||||||
|
@ -64,8 +62,7 @@ func (c *apiTestClient) URL(ep string, args map[string]string) *url.URL {
|
||||||
return u
|
return u
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *apiTestClient) Do(ctx context.Context, req *http.Request) (*http.Response, []byte, Warnings, error) {
|
func (c *apiTestClient) Do(_ context.Context, req *http.Request) (*http.Response, []byte, Warnings, error) {
|
||||||
|
|
||||||
test := c.curTest
|
test := c.curTest
|
||||||
|
|
||||||
if req.URL.Path != test.reqPath {
|
if req.URL.Path != test.reqPath {
|
||||||
|
@ -101,7 +98,6 @@ func (c *apiTestClient) DoGetFallback(ctx context.Context, u *url.URL, args url.
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAPIs(t *testing.T) {
|
func TestAPIs(t *testing.T) {
|
||||||
|
|
||||||
testTime := time.Now()
|
testTime := time.Now()
|
||||||
|
|
||||||
tc := &apiTestClient{
|
tc := &apiTestClient{
|
||||||
|
@ -131,7 +127,7 @@ func TestAPIs(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
doDeleteSeries := func(matcher string, startTime time.Time, endTime time.Time) func() (interface{}, Warnings, error) {
|
doDeleteSeries := func(matcher string, startTime, endTime time.Time) func() (interface{}, Warnings, error) {
|
||||||
return func() (interface{}, Warnings, error) {
|
return func() (interface{}, Warnings, error) {
|
||||||
return nil, nil, promAPI.DeleteSeries(context.Background(), []string{matcher}, startTime, endTime)
|
return nil, nil, promAPI.DeleteSeries(context.Background(), []string{matcher}, startTime, endTime)
|
||||||
}
|
}
|
||||||
|
@ -158,31 +154,31 @@ func TestAPIs(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
doLabelNames := func(matches []string) func() (interface{}, Warnings, error) {
|
doLabelNames := func(matches []string, startTime, endTime time.Time) func() (interface{}, Warnings, error) {
|
||||||
return func() (interface{}, Warnings, error) {
|
return func() (interface{}, Warnings, error) {
|
||||||
return promAPI.LabelNames(context.Background(), matches, time.Now().Add(-100*time.Hour), time.Now())
|
return promAPI.LabelNames(context.Background(), matches, startTime, endTime)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
doLabelValues := func(matches []string, label string) func() (interface{}, Warnings, error) {
|
doLabelValues := func(matches []string, label string, startTime, endTime time.Time) func() (interface{}, Warnings, error) {
|
||||||
return func() (interface{}, Warnings, error) {
|
return func() (interface{}, Warnings, error) {
|
||||||
return promAPI.LabelValues(context.Background(), label, matches, time.Now().Add(-100*time.Hour), time.Now())
|
return promAPI.LabelValues(context.Background(), label, matches, startTime, endTime)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
doQuery := func(q string, ts time.Time) func() (interface{}, Warnings, error) {
|
doQuery := func(q string, ts time.Time, opts ...Option) func() (interface{}, Warnings, error) {
|
||||||
return func() (interface{}, Warnings, error) {
|
return func() (interface{}, Warnings, error) {
|
||||||
return promAPI.Query(context.Background(), q, ts)
|
return promAPI.Query(context.Background(), q, ts, opts...)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
doQueryRange := func(q string, rng Range) func() (interface{}, Warnings, error) {
|
doQueryRange := func(q string, rng Range, opts ...Option) func() (interface{}, Warnings, error) {
|
||||||
return func() (interface{}, Warnings, error) {
|
return func() (interface{}, Warnings, error) {
|
||||||
return promAPI.QueryRange(context.Background(), q, rng)
|
return promAPI.QueryRange(context.Background(), q, rng, opts...)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
doSeries := func(matcher string, startTime time.Time, endTime time.Time) func() (interface{}, Warnings, error) {
|
doSeries := func(matcher string, startTime, endTime time.Time) func() (interface{}, Warnings, error) {
|
||||||
return 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)
|
||||||
}
|
}
|
||||||
|
@ -209,14 +205,14 @@ func TestAPIs(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
doTargetsMetadata := func(matchTarget string, metric string, limit string) func() (interface{}, Warnings, error) {
|
doTargetsMetadata := func(matchTarget, metric, limit string) func() (interface{}, Warnings, error) {
|
||||||
return func() (interface{}, Warnings, error) {
|
return func() (interface{}, Warnings, error) {
|
||||||
v, err := promAPI.TargetsMetadata(context.Background(), matchTarget, metric, limit)
|
v, err := promAPI.TargetsMetadata(context.Background(), matchTarget, metric, limit)
|
||||||
return v, nil, err
|
return v, nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
doMetadata := func(metric string, limit string) func() (interface{}, Warnings, error) {
|
doMetadata := func(metric, limit string) func() (interface{}, Warnings, error) {
|
||||||
return func() (interface{}, Warnings, error) {
|
return func() (interface{}, Warnings, error) {
|
||||||
v, err := promAPI.Metadata(context.Background(), metric, limit)
|
v, err := promAPI.Metadata(context.Background(), metric, limit)
|
||||||
return v, nil, err
|
return v, nil, err
|
||||||
|
@ -237,7 +233,7 @@ func TestAPIs(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
doQueryExemplars := func(query string, startTime time.Time, endTime time.Time) func() (interface{}, Warnings, error) {
|
doQueryExemplars := func(query string, startTime, endTime time.Time) func() (interface{}, Warnings, error) {
|
||||||
return func() (interface{}, Warnings, error) {
|
return func() (interface{}, Warnings, error) {
|
||||||
v, err := promAPI.QueryExemplars(context.Background(), query, startTime, endTime)
|
v, err := promAPI.QueryExemplars(context.Background(), query, startTime, endTime)
|
||||||
return v, nil, err
|
return v, nil, err
|
||||||
|
@ -246,7 +242,7 @@ func TestAPIs(t *testing.T) {
|
||||||
|
|
||||||
queryTests := []apiTest{
|
queryTests := []apiTest{
|
||||||
{
|
{
|
||||||
do: doQuery("2", testTime),
|
do: doQuery("2", testTime, WithTimeout(5*time.Second)),
|
||||||
inRes: &queryResult{
|
inRes: &queryResult{
|
||||||
Type: model.ValScalar,
|
Type: model.ValScalar,
|
||||||
Result: &model.Scalar{
|
Result: &model.Scalar{
|
||||||
|
@ -257,10 +253,6 @@ func TestAPIs(t *testing.T) {
|
||||||
|
|
||||||
reqMethod: "POST",
|
reqMethod: "POST",
|
||||||
reqPath: "/api/v1/query",
|
reqPath: "/api/v1/query",
|
||||||
reqParam: url.Values{
|
|
||||||
"query": []string{"2"},
|
|
||||||
"time": []string{testTime.Format(time.RFC3339Nano)},
|
|
||||||
},
|
|
||||||
res: &model.Scalar{
|
res: &model.Scalar{
|
||||||
Value: 2,
|
Value: 2,
|
||||||
Timestamp: model.TimeFromUnix(testTime.Unix()),
|
Timestamp: model.TimeFromUnix(testTime.Unix()),
|
||||||
|
@ -272,11 +264,7 @@ func TestAPIs(t *testing.T) {
|
||||||
|
|
||||||
reqMethod: "POST",
|
reqMethod: "POST",
|
||||||
reqPath: "/api/v1/query",
|
reqPath: "/api/v1/query",
|
||||||
reqParam: url.Values{
|
err: errors.New("some error"),
|
||||||
"query": []string{"2"},
|
|
||||||
"time": []string{testTime.Format(time.RFC3339Nano)},
|
|
||||||
},
|
|
||||||
err: fmt.Errorf("some error"),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
do: doQuery("2", testTime),
|
do: doQuery("2", testTime),
|
||||||
|
@ -290,11 +278,7 @@ func TestAPIs(t *testing.T) {
|
||||||
|
|
||||||
reqMethod: "POST",
|
reqMethod: "POST",
|
||||||
reqPath: "/api/v1/query",
|
reqPath: "/api/v1/query",
|
||||||
reqParam: url.Values{
|
err: errors.New("server_error: server error: 500"),
|
||||||
"query": []string{"2"},
|
|
||||||
"time": []string{testTime.Format(time.RFC3339Nano)},
|
|
||||||
},
|
|
||||||
err: errors.New("server_error: server error: 500"),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
do: doQuery("2", testTime),
|
do: doQuery("2", testTime),
|
||||||
|
@ -308,11 +292,7 @@ func TestAPIs(t *testing.T) {
|
||||||
|
|
||||||
reqMethod: "POST",
|
reqMethod: "POST",
|
||||||
reqPath: "/api/v1/query",
|
reqPath: "/api/v1/query",
|
||||||
reqParam: url.Values{
|
err: errors.New("client_error: client error: 404"),
|
||||||
"query": []string{"2"},
|
|
||||||
"time": []string{testTime.Format(time.RFC3339Nano)},
|
|
||||||
},
|
|
||||||
err: errors.New("client_error: client error: 404"),
|
|
||||||
},
|
},
|
||||||
// Warning only.
|
// Warning only.
|
||||||
{
|
{
|
||||||
|
@ -328,15 +308,10 @@ func TestAPIs(t *testing.T) {
|
||||||
|
|
||||||
reqMethod: "POST",
|
reqMethod: "POST",
|
||||||
reqPath: "/api/v1/query",
|
reqPath: "/api/v1/query",
|
||||||
reqParam: url.Values{
|
|
||||||
"query": []string{"2"},
|
|
||||||
"time": []string{testTime.Format(time.RFC3339Nano)},
|
|
||||||
},
|
|
||||||
res: &model.Scalar{
|
res: &model.Scalar{
|
||||||
Value: 2,
|
Value: 2,
|
||||||
Timestamp: model.TimeFromUnix(testTime.Unix()),
|
Timestamp: model.TimeFromUnix(testTime.Unix()),
|
||||||
},
|
},
|
||||||
warnings: []string{"warning"},
|
|
||||||
},
|
},
|
||||||
// Warning + error.
|
// Warning + error.
|
||||||
{
|
{
|
||||||
|
@ -352,114 +327,97 @@ func TestAPIs(t *testing.T) {
|
||||||
|
|
||||||
reqMethod: "POST",
|
reqMethod: "POST",
|
||||||
reqPath: "/api/v1/query",
|
reqPath: "/api/v1/query",
|
||||||
reqParam: url.Values{
|
err: errors.New("client_error: client error: 404"),
|
||||||
"query": []string{"2"},
|
|
||||||
"time": []string{testTime.Format(time.RFC3339Nano)},
|
|
||||||
},
|
|
||||||
err: errors.New("client_error: client error: 404"),
|
|
||||||
warnings: []string{"warning"},
|
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
do: doQueryRange("2", Range{
|
do: doQueryRange("2", Range{
|
||||||
Start: testTime.Add(-time.Minute),
|
Start: testTime.Add(-time.Minute),
|
||||||
End: testTime,
|
End: testTime,
|
||||||
Step: time.Minute,
|
Step: 1 * time.Minute,
|
||||||
}),
|
}, WithTimeout(5*time.Second)),
|
||||||
inErr: fmt.Errorf("some error"),
|
inErr: fmt.Errorf("some error"),
|
||||||
|
|
||||||
reqMethod: "POST",
|
reqMethod: "POST",
|
||||||
reqPath: "/api/v1/query_range",
|
reqPath: "/api/v1/query_range",
|
||||||
reqParam: url.Values{
|
err: errors.New("some error"),
|
||||||
"query": []string{"2"},
|
|
||||||
"start": []string{testTime.Add(-time.Minute).Format(time.RFC3339Nano)},
|
|
||||||
"end": []string{testTime.Format(time.RFC3339Nano)},
|
|
||||||
"step": []string{time.Minute.String()},
|
|
||||||
},
|
|
||||||
err: fmt.Errorf("some error"),
|
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
do: doLabelNames(nil),
|
do: doLabelNames(nil, testTime.Add(-100*time.Hour), testTime),
|
||||||
inRes: []string{"val1", "val2"},
|
inRes: []string{"val1", "val2"},
|
||||||
reqMethod: "GET",
|
reqMethod: "GET",
|
||||||
reqPath: "/api/v1/labels",
|
reqPath: "/api/v1/labels",
|
||||||
res: []string{"val1", "val2"},
|
res: []string{"val1", "val2"},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
do: doLabelNames(nil),
|
do: doLabelNames(nil, testTime.Add(-100*time.Hour), testTime),
|
||||||
inRes: []string{"val1", "val2"},
|
inRes: []string{"val1", "val2"},
|
||||||
inWarnings: []string{"a"},
|
inWarnings: []string{"a"},
|
||||||
reqMethod: "GET",
|
reqMethod: "GET",
|
||||||
reqPath: "/api/v1/labels",
|
reqPath: "/api/v1/labels",
|
||||||
res: []string{"val1", "val2"},
|
res: []string{"val1", "val2"},
|
||||||
warnings: []string{"a"},
|
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
do: doLabelNames(nil),
|
do: doLabelNames(nil, testTime.Add(-100*time.Hour), testTime),
|
||||||
inErr: fmt.Errorf("some error"),
|
inErr: fmt.Errorf("some error"),
|
||||||
reqMethod: "GET",
|
reqMethod: "GET",
|
||||||
reqPath: "/api/v1/labels",
|
reqPath: "/api/v1/labels",
|
||||||
err: fmt.Errorf("some error"),
|
err: errors.New("some error"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
do: doLabelNames(nil),
|
do: doLabelNames(nil, testTime.Add(-100*time.Hour), testTime),
|
||||||
inErr: fmt.Errorf("some error"),
|
inErr: fmt.Errorf("some error"),
|
||||||
inWarnings: []string{"a"},
|
inWarnings: []string{"a"},
|
||||||
reqMethod: "GET",
|
reqMethod: "GET",
|
||||||
reqPath: "/api/v1/labels",
|
reqPath: "/api/v1/labels",
|
||||||
err: fmt.Errorf("some error"),
|
err: errors.New("some error"),
|
||||||
warnings: []string{"a"},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
do: doLabelNames([]string{"up"}),
|
do: doLabelNames([]string{"up"}, testTime.Add(-100*time.Hour), testTime),
|
||||||
inRes: []string{"val1", "val2"},
|
inRes: []string{"val1", "val2"},
|
||||||
reqMethod: "GET",
|
reqMethod: "GET",
|
||||||
reqPath: "/api/v1/labels",
|
reqPath: "/api/v1/labels",
|
||||||
reqParam: url.Values{"match[]": {"up"}},
|
|
||||||
res: []string{"val1", "val2"},
|
res: []string{"val1", "val2"},
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
do: doLabelValues(nil, "mylabel"),
|
do: doLabelValues(nil, "mylabel", testTime.Add(-100*time.Hour), testTime),
|
||||||
inRes: []string{"val1", "val2"},
|
inRes: []string{"val1", "val2"},
|
||||||
reqMethod: "GET",
|
reqMethod: "GET",
|
||||||
reqPath: "/api/v1/label/mylabel/values",
|
reqPath: "/api/v1/label/mylabel/values",
|
||||||
res: model.LabelValues{"val1", "val2"},
|
res: model.LabelValues{"val1", "val2"},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
do: doLabelValues(nil, "mylabel"),
|
do: doLabelValues(nil, "mylabel", testTime.Add(-100*time.Hour), testTime),
|
||||||
inRes: []string{"val1", "val2"},
|
inRes: []string{"val1", "val2"},
|
||||||
inWarnings: []string{"a"},
|
inWarnings: []string{"a"},
|
||||||
reqMethod: "GET",
|
reqMethod: "GET",
|
||||||
reqPath: "/api/v1/label/mylabel/values",
|
reqPath: "/api/v1/label/mylabel/values",
|
||||||
res: model.LabelValues{"val1", "val2"},
|
res: model.LabelValues{"val1", "val2"},
|
||||||
warnings: []string{"a"},
|
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
do: doLabelValues(nil, "mylabel"),
|
do: doLabelValues(nil, "mylabel", testTime.Add(-100*time.Hour), testTime),
|
||||||
inErr: fmt.Errorf("some error"),
|
inErr: fmt.Errorf("some error"),
|
||||||
reqMethod: "GET",
|
reqMethod: "GET",
|
||||||
reqPath: "/api/v1/label/mylabel/values",
|
reqPath: "/api/v1/label/mylabel/values",
|
||||||
err: fmt.Errorf("some error"),
|
err: errors.New("some error"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
do: doLabelValues(nil, "mylabel"),
|
do: doLabelValues(nil, "mylabel", testTime.Add(-100*time.Hour), testTime),
|
||||||
inErr: fmt.Errorf("some error"),
|
inErr: fmt.Errorf("some error"),
|
||||||
inWarnings: []string{"a"},
|
inWarnings: []string{"a"},
|
||||||
reqMethod: "GET",
|
reqMethod: "GET",
|
||||||
reqPath: "/api/v1/label/mylabel/values",
|
reqPath: "/api/v1/label/mylabel/values",
|
||||||
err: fmt.Errorf("some error"),
|
err: errors.New("some error"),
|
||||||
warnings: []string{"a"},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
do: doLabelValues([]string{"up"}, "mylabel"),
|
do: doLabelValues([]string{"up"}, "mylabel", testTime.Add(-100*time.Hour), testTime),
|
||||||
inRes: []string{"val1", "val2"},
|
inRes: []string{"val1", "val2"},
|
||||||
reqMethod: "GET",
|
reqMethod: "GET",
|
||||||
reqPath: "/api/v1/label/mylabel/values",
|
reqPath: "/api/v1/label/mylabel/values",
|
||||||
reqParam: url.Values{"match[]": {"up"}},
|
|
||||||
res: model.LabelValues{"val1", "val2"},
|
res: model.LabelValues{"val1", "val2"},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -469,15 +427,11 @@ func TestAPIs(t *testing.T) {
|
||||||
{
|
{
|
||||||
"__name__": "up",
|
"__name__": "up",
|
||||||
"job": "prometheus",
|
"job": "prometheus",
|
||||||
"instance": "localhost:9090"},
|
"instance": "localhost:9090",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
reqMethod: "GET",
|
reqMethod: "GET",
|
||||||
reqPath: "/api/v1/series",
|
reqPath: "/api/v1/series",
|
||||||
reqParam: url.Values{
|
|
||||||
"match": []string{"up"},
|
|
||||||
"start": []string{testTime.Add(-time.Minute).Format(time.RFC3339Nano)},
|
|
||||||
"end": []string{testTime.Format(time.RFC3339Nano)},
|
|
||||||
},
|
|
||||||
res: []model.LabelSet{
|
res: []model.LabelSet{
|
||||||
{
|
{
|
||||||
"__name__": "up",
|
"__name__": "up",
|
||||||
|
@ -493,16 +447,12 @@ func TestAPIs(t *testing.T) {
|
||||||
{
|
{
|
||||||
"__name__": "up",
|
"__name__": "up",
|
||||||
"job": "prometheus",
|
"job": "prometheus",
|
||||||
"instance": "localhost:9090"},
|
"instance": "localhost:9090",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
inWarnings: []string{"a"},
|
inWarnings: []string{"a"},
|
||||||
reqMethod: "GET",
|
reqMethod: "GET",
|
||||||
reqPath: "/api/v1/series",
|
reqPath: "/api/v1/series",
|
||||||
reqParam: url.Values{
|
|
||||||
"match": []string{"up"},
|
|
||||||
"start": []string{testTime.Add(-time.Minute).Format(time.RFC3339Nano)},
|
|
||||||
"end": []string{testTime.Format(time.RFC3339Nano)},
|
|
||||||
},
|
|
||||||
res: []model.LabelSet{
|
res: []model.LabelSet{
|
||||||
{
|
{
|
||||||
"__name__": "up",
|
"__name__": "up",
|
||||||
|
@ -510,7 +460,6 @@ func TestAPIs(t *testing.T) {
|
||||||
"instance": "localhost:9090",
|
"instance": "localhost:9090",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
warnings: []string{"a"},
|
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
|
@ -518,12 +467,7 @@ func TestAPIs(t *testing.T) {
|
||||||
inErr: fmt.Errorf("some error"),
|
inErr: fmt.Errorf("some error"),
|
||||||
reqMethod: "GET",
|
reqMethod: "GET",
|
||||||
reqPath: "/api/v1/series",
|
reqPath: "/api/v1/series",
|
||||||
reqParam: url.Values{
|
err: errors.New("some error"),
|
||||||
"match": []string{"up"},
|
|
||||||
"start": []string{testTime.Add(-time.Minute).Format(time.RFC3339Nano)},
|
|
||||||
"end": []string{testTime.Format(time.RFC3339Nano)},
|
|
||||||
},
|
|
||||||
err: fmt.Errorf("some error"),
|
|
||||||
},
|
},
|
||||||
// Series with error and warning.
|
// Series with error and warning.
|
||||||
{
|
{
|
||||||
|
@ -532,13 +476,7 @@ func TestAPIs(t *testing.T) {
|
||||||
inWarnings: []string{"a"},
|
inWarnings: []string{"a"},
|
||||||
reqMethod: "GET",
|
reqMethod: "GET",
|
||||||
reqPath: "/api/v1/series",
|
reqPath: "/api/v1/series",
|
||||||
reqParam: url.Values{
|
err: errors.New("some error"),
|
||||||
"match": []string{"up"},
|
|
||||||
"start": []string{testTime.Add(-time.Minute).Format(time.RFC3339Nano)},
|
|
||||||
"end": []string{testTime.Format(time.RFC3339Nano)},
|
|
||||||
},
|
|
||||||
err: fmt.Errorf("some error"),
|
|
||||||
warnings: []string{"a"},
|
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
|
@ -548,9 +486,6 @@ func TestAPIs(t *testing.T) {
|
||||||
},
|
},
|
||||||
reqMethod: "POST",
|
reqMethod: "POST",
|
||||||
reqPath: "/api/v1/admin/tsdb/snapshot",
|
reqPath: "/api/v1/admin/tsdb/snapshot",
|
||||||
reqParam: url.Values{
|
|
||||||
"skip_head": []string{"true"},
|
|
||||||
},
|
|
||||||
res: SnapshotResult{
|
res: SnapshotResult{
|
||||||
Name: "20171210T211224Z-2be650b6d019eb54",
|
Name: "20171210T211224Z-2be650b6d019eb54",
|
||||||
},
|
},
|
||||||
|
@ -561,7 +496,7 @@ func TestAPIs(t *testing.T) {
|
||||||
inErr: fmt.Errorf("some error"),
|
inErr: fmt.Errorf("some error"),
|
||||||
reqMethod: "POST",
|
reqMethod: "POST",
|
||||||
reqPath: "/api/v1/admin/tsdb/snapshot",
|
reqPath: "/api/v1/admin/tsdb/snapshot",
|
||||||
err: fmt.Errorf("some error"),
|
err: errors.New("some error"),
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
|
@ -575,7 +510,7 @@ func TestAPIs(t *testing.T) {
|
||||||
inErr: fmt.Errorf("some error"),
|
inErr: fmt.Errorf("some error"),
|
||||||
reqMethod: "POST",
|
reqMethod: "POST",
|
||||||
reqPath: "/api/v1/admin/tsdb/clean_tombstones",
|
reqPath: "/api/v1/admin/tsdb/clean_tombstones",
|
||||||
err: fmt.Errorf("some error"),
|
err: errors.New("some error"),
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
|
@ -584,15 +519,11 @@ func TestAPIs(t *testing.T) {
|
||||||
{
|
{
|
||||||
"__name__": "up",
|
"__name__": "up",
|
||||||
"job": "prometheus",
|
"job": "prometheus",
|
||||||
"instance": "localhost:9090"},
|
"instance": "localhost:9090",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
reqMethod: "POST",
|
reqMethod: "POST",
|
||||||
reqPath: "/api/v1/admin/tsdb/delete_series",
|
reqPath: "/api/v1/admin/tsdb/delete_series",
|
||||||
reqParam: url.Values{
|
|
||||||
"match": []string{"up"},
|
|
||||||
"start": []string{testTime.Add(-time.Minute).Format(time.RFC3339Nano)},
|
|
||||||
"end": []string{testTime.Format(time.RFC3339Nano)},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
|
@ -600,12 +531,7 @@ func TestAPIs(t *testing.T) {
|
||||||
inErr: fmt.Errorf("some error"),
|
inErr: fmt.Errorf("some error"),
|
||||||
reqMethod: "POST",
|
reqMethod: "POST",
|
||||||
reqPath: "/api/v1/admin/tsdb/delete_series",
|
reqPath: "/api/v1/admin/tsdb/delete_series",
|
||||||
reqParam: url.Values{
|
err: errors.New("some error"),
|
||||||
"match": []string{"up"},
|
|
||||||
"start": []string{testTime.Add(-time.Minute).Format(time.RFC3339Nano)},
|
|
||||||
"end": []string{testTime.Format(time.RFC3339Nano)},
|
|
||||||
},
|
|
||||||
err: fmt.Errorf("some error"),
|
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
|
@ -1064,11 +990,6 @@ func TestAPIs(t *testing.T) {
|
||||||
},
|
},
|
||||||
reqMethod: "GET",
|
reqMethod: "GET",
|
||||||
reqPath: "/api/v1/targets/metadata",
|
reqPath: "/api/v1/targets/metadata",
|
||||||
reqParam: url.Values{
|
|
||||||
"match_target": []string{"{job=\"prometheus\"}"},
|
|
||||||
"metric": []string{"go_goroutines"},
|
|
||||||
"limit": []string{"1"},
|
|
||||||
},
|
|
||||||
res: []MetricMetadata{
|
res: []MetricMetadata{
|
||||||
{
|
{
|
||||||
Target: map[string]string{
|
Target: map[string]string{
|
||||||
|
@ -1087,12 +1008,7 @@ func TestAPIs(t *testing.T) {
|
||||||
inErr: fmt.Errorf("some error"),
|
inErr: fmt.Errorf("some error"),
|
||||||
reqMethod: "GET",
|
reqMethod: "GET",
|
||||||
reqPath: "/api/v1/targets/metadata",
|
reqPath: "/api/v1/targets/metadata",
|
||||||
reqParam: url.Values{
|
err: errors.New("some error"),
|
||||||
"match_target": []string{"{job=\"prometheus\"}"},
|
|
||||||
"metric": []string{"go_goroutines"},
|
|
||||||
"limit": []string{"1"},
|
|
||||||
},
|
|
||||||
err: fmt.Errorf("some error"),
|
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
|
@ -1108,12 +1024,8 @@ func TestAPIs(t *testing.T) {
|
||||||
},
|
},
|
||||||
reqMethod: "GET",
|
reqMethod: "GET",
|
||||||
reqPath: "/api/v1/metadata",
|
reqPath: "/api/v1/metadata",
|
||||||
reqParam: url.Values{
|
|
||||||
"metric": []string{"go_goroutines"},
|
|
||||||
"limit": []string{"1"},
|
|
||||||
},
|
|
||||||
res: map[string][]Metadata{
|
res: map[string][]Metadata{
|
||||||
"go_goroutines": []Metadata{
|
"go_goroutines": {
|
||||||
{
|
{
|
||||||
Type: "gauge",
|
Type: "gauge",
|
||||||
Help: "Number of goroutines that currently exist.",
|
Help: "Number of goroutines that currently exist.",
|
||||||
|
@ -1128,11 +1040,7 @@ func TestAPIs(t *testing.T) {
|
||||||
inErr: fmt.Errorf("some error"),
|
inErr: fmt.Errorf("some error"),
|
||||||
reqMethod: "GET",
|
reqMethod: "GET",
|
||||||
reqPath: "/api/v1/metadata",
|
reqPath: "/api/v1/metadata",
|
||||||
reqParam: url.Values{
|
err: errors.New("some error"),
|
||||||
"metric": []string{""},
|
|
||||||
"limit": []string{"1"},
|
|
||||||
},
|
|
||||||
err: fmt.Errorf("some error"),
|
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
|
@ -1243,8 +1151,8 @@ func TestAPIs(t *testing.T) {
|
||||||
do: doQueryExemplars("tns_request_duration_seconds_bucket", testTime.Add(-1*time.Minute), testTime),
|
do: doQueryExemplars("tns_request_duration_seconds_bucket", testTime.Add(-1*time.Minute), testTime),
|
||||||
reqMethod: "GET",
|
reqMethod: "GET",
|
||||||
reqPath: "/api/v1/query_exemplars",
|
reqPath: "/api/v1/query_exemplars",
|
||||||
inErr: fmt.Errorf("some error"),
|
inErr: errors.New("some error"),
|
||||||
err: fmt.Errorf("some error"),
|
err: errors.New("some error"),
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
|
@ -1320,7 +1228,9 @@ func TestAPIs(t *testing.T) {
|
||||||
if err.Error() != test.err.Error() {
|
if err.Error() != test.err.Error() {
|
||||||
t.Errorf("unexpected error: want %s, got %s", test.err, err)
|
t.Errorf("unexpected error: want %s, got %s", test.err, err)
|
||||||
}
|
}
|
||||||
if apiErr, ok := err.(*Error); ok {
|
|
||||||
|
apiErr := &Error{}
|
||||||
|
if ok := errors.As(err, &apiErr); ok {
|
||||||
if apiErr.Detail != test.inRes {
|
if apiErr.Detail != test.inRes {
|
||||||
t.Errorf("%q should be %q", apiErr.Detail, test.inRes)
|
t.Errorf("%q should be %q", apiErr.Detail, test.inRes)
|
||||||
}
|
}
|
||||||
|
@ -1521,7 +1431,6 @@ func TestAPIClientDo(t *testing.T) {
|
||||||
|
|
||||||
for i, test := range tests {
|
for i, test := range tests {
|
||||||
t.Run(fmt.Sprintf("%d", i), func(t *testing.T) {
|
t.Run(fmt.Sprintf("%d", i), func(t *testing.T) {
|
||||||
|
|
||||||
tc.ch <- test
|
tc.ch <- test
|
||||||
|
|
||||||
_, body, warnings, err := client.Do(context.Background(), tc.req)
|
_, body, warnings, err := client.Do(context.Background(), tc.req)
|
||||||
|
@ -1546,9 +1455,13 @@ func TestAPIClientDo(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if test.expectedErr.Detail != "" {
|
if test.expectedErr.Detail != "" {
|
||||||
apiErr := err.(*Error)
|
apiErr := &Error{}
|
||||||
if apiErr.Detail != test.expectedErr.Detail {
|
if errors.As(err, &apiErr) {
|
||||||
t.Fatalf("expected error detail :%v, but got:%v", apiErr.Detail, test.expectedErr.Detail)
|
if apiErr.Detail != test.expectedErr.Detail {
|
||||||
|
t.Fatalf("expected error detail :%v, but got:%v", apiErr.Detail, test.expectedErr.Detail)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
t.Fatalf("expected v1.Error instance, but got:%T", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1562,7 +1475,6 @@ func TestAPIClientDo(t *testing.T) {
|
||||||
t.Fatalf("expected body :%v, but got:%v", test.expectedBody, string(body))
|
t.Fatalf("expected body :%v, but got:%v", test.expectedBody, string(body))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1679,7 +1591,7 @@ func (c *httpTestClient) Do(ctx context.Context, req *http.Request) (*http.Respo
|
||||||
var body []byte
|
var body []byte
|
||||||
done := make(chan struct{})
|
done := make(chan struct{})
|
||||||
go func() {
|
go func() {
|
||||||
body, err = ioutil.ReadAll(resp.Body)
|
body, err = io.ReadAll(resp.Body)
|
||||||
close(done)
|
close(done)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
|
|
@ -22,9 +22,10 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/prometheus/client_golang/api"
|
|
||||||
v1 "github.com/prometheus/client_golang/api/prometheus/v1"
|
|
||||||
"github.com/prometheus/common/config"
|
"github.com/prometheus/common/config"
|
||||||
|
|
||||||
|
"git.internal/re/client_golang/api"
|
||||||
|
v1 "git.internal/re/client_golang/api/prometheus/v1"
|
||||||
)
|
)
|
||||||
|
|
||||||
func ExampleAPI_query() {
|
func ExampleAPI_query() {
|
||||||
|
@ -39,7 +40,7 @@ func ExampleAPI_query() {
|
||||||
v1api := v1.NewAPI(client)
|
v1api := v1.NewAPI(client)
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
result, warnings, err := v1api.Query(ctx, "up", time.Now())
|
result, warnings, err := v1api.Query(ctx, "up", time.Now(), v1.WithTimeout(5*time.Second))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("Error querying Prometheus: %v\n", err)
|
fmt.Printf("Error querying Prometheus: %v\n", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
|
@ -67,7 +68,7 @@ func ExampleAPI_queryRange() {
|
||||||
End: time.Now(),
|
End: time.Now(),
|
||||||
Step: time.Minute,
|
Step: time.Minute,
|
||||||
}
|
}
|
||||||
result, warnings, err := v1api.QueryRange(ctx, "rate(prometheus_tsdb_head_samples_appended_total[5m])", r)
|
result, warnings, err := v1api.QueryRange(ctx, "rate(prometheus_tsdb_head_samples_appended_total[5m])", r, v1.WithTimeout(5*time.Second))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("Error querying Prometheus: %v\n", err)
|
fmt.Printf("Error querying Prometheus: %v\n", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
|
|
|
@ -0,0 +1,69 @@
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
// A simple example of how to record a latency metric with exemplars, using a fictional id
|
||||||
|
// as a prometheus label.
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"math/rand"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"git.internal/re/client_golang/prometheus"
|
||||||
|
"git.internal/re/client_golang/prometheus/collectors"
|
||||||
|
"git.internal/re/client_golang/prometheus/promhttp"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
requestDurations := prometheus.NewHistogram(prometheus.HistogramOpts{
|
||||||
|
Name: "http_request_duration_seconds",
|
||||||
|
Help: "A histogram of the HTTP request durations in seconds.",
|
||||||
|
Buckets: prometheus.ExponentialBuckets(0.1, 1.5, 5),
|
||||||
|
})
|
||||||
|
|
||||||
|
// Create non-global registry.
|
||||||
|
registry := prometheus.NewRegistry()
|
||||||
|
|
||||||
|
// Add go runtime metrics and process collectors.
|
||||||
|
registry.MustRegister(
|
||||||
|
collectors.NewGoCollector(),
|
||||||
|
collectors.NewProcessCollector(collectors.ProcessCollectorOpts{}),
|
||||||
|
requestDurations,
|
||||||
|
)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
// Record fictional latency.
|
||||||
|
now := time.Now()
|
||||||
|
requestDurations.(prometheus.ExemplarObserver).ObserveWithExemplar(
|
||||||
|
time.Since(now).Seconds(), prometheus.Labels{"dummyID": fmt.Sprint(rand.Intn(100000))},
|
||||||
|
)
|
||||||
|
time.Sleep(600 * time.Millisecond)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Expose /metrics HTTP endpoint using the created custom registry.
|
||||||
|
http.Handle(
|
||||||
|
"/metrics", promhttp.HandlerFor(
|
||||||
|
registry,
|
||||||
|
promhttp.HandlerOpts{
|
||||||
|
EnableOpenMetrics: true,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
// To test: curl -H 'Accept: application/openmetrics-text' localhost:8080/metrics
|
||||||
|
log.Fatalln(http.ListenAndServe(":8080", nil))
|
||||||
|
}
|
|
@ -22,10 +22,11 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"regexp"
|
||||||
|
|
||||||
"github.com/prometheus/client_golang/prometheus"
|
"git.internal/re/client_golang/prometheus"
|
||||||
"github.com/prometheus/client_golang/prometheus/collectors"
|
"git.internal/re/client_golang/prometheus/collectors"
|
||||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
"git.internal/re/client_golang/prometheus/promhttp"
|
||||||
)
|
)
|
||||||
|
|
||||||
var addr = flag.String("listen-address", ":8080", "The address to listen on for HTTP requests.")
|
var addr = flag.String("listen-address", ":8080", "The address to listen on for HTTP requests.")
|
||||||
|
@ -39,7 +40,7 @@ func main() {
|
||||||
// Add Go module build info.
|
// Add Go module build info.
|
||||||
reg.MustRegister(collectors.NewBuildInfoCollector())
|
reg.MustRegister(collectors.NewBuildInfoCollector())
|
||||||
reg.MustRegister(collectors.NewGoCollector(
|
reg.MustRegister(collectors.NewGoCollector(
|
||||||
collectors.WithGoCollections(collectors.GoRuntimeMemStatsCollection | collectors.GoRuntimeMetricsCollection),
|
collectors.WithGoCollectorRuntimeMetrics(collectors.GoRuntimeMetricsRule{Matcher: regexp.MustCompile("/.*")}),
|
||||||
))
|
))
|
||||||
|
|
||||||
// Expose the registered metrics via HTTP.
|
// Expose the registered metrics via HTTP.
|
||||||
|
|
|
@ -0,0 +1,17 @@
|
||||||
|
module github.com/jessicalins/instrumentation-practices-examples/middleware
|
||||||
|
|
||||||
|
go 1.17
|
||||||
|
|
||||||
|
require git.internal/re/client_golang v1.13.1
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/beorn7/perks v1.0.1 // indirect
|
||||||
|
github.com/cespare/xxhash/v2 v2.1.2 // indirect
|
||||||
|
github.com/golang/protobuf v1.5.2 // indirect
|
||||||
|
github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
|
||||||
|
github.com/prometheus/client_model v0.2.0 // indirect
|
||||||
|
github.com/prometheus/common v0.37.0 // indirect
|
||||||
|
github.com/prometheus/procfs v0.8.0 // indirect
|
||||||
|
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a // indirect
|
||||||
|
google.golang.org/protobuf v1.28.1 // indirect
|
||||||
|
)
|
|
@ -0,0 +1,477 @@
|
||||||
|
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||||
|
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||||
|
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
|
||||||
|
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
|
||||||
|
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
|
||||||
|
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
|
||||||
|
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
|
||||||
|
cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
|
||||||
|
cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=
|
||||||
|
cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
|
||||||
|
cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=
|
||||||
|
cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=
|
||||||
|
cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
|
||||||
|
cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
|
||||||
|
cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
|
||||||
|
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
|
||||||
|
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
|
||||||
|
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
|
||||||
|
cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
|
||||||
|
cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
|
||||||
|
cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
|
||||||
|
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
|
||||||
|
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
|
||||||
|
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
|
||||||
|
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
|
||||||
|
cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
|
||||||
|
cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
|
||||||
|
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
|
||||||
|
cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
|
||||||
|
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
|
||||||
|
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
|
||||||
|
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
|
||||||
|
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||||
|
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||||
|
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||||
|
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||||
|
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||||
|
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||||
|
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||||
|
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
|
||||||
|
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||||
|
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
|
||||||
|
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/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||||
|
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||||
|
github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE=
|
||||||
|
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||||
|
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||||
|
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||||
|
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||||
|
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||||
|
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||||
|
github.com/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/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||||
|
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||||
|
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||||
|
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||||
|
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
||||||
|
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||||
|
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||||
|
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||||
|
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||||
|
github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
|
||||||
|
github.com/go-kit/log v0.2.0/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0=
|
||||||
|
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
||||||
|
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
||||||
|
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
|
||||||
|
github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
|
||||||
|
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||||
|
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||||
|
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||||
|
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||||
|
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||||
|
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||||
|
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||||
|
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||||
|
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
|
||||||
|
github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||||
|
github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||||
|
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||||
|
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
|
||||||
|
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
|
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
|
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
|
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||||
|
github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||||
|
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
|
||||||
|
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||||
|
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
||||||
|
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
||||||
|
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
||||||
|
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||||
|
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
|
||||||
|
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||||
|
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||||
|
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||||
|
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
|
||||||
|
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||||
|
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||||
|
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||||
|
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||||
|
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||||
|
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||||
|
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
|
github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
|
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
|
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
|
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
|
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
|
github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
|
||||||
|
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=
|
||||||
|
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||||
|
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||||
|
github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||||
|
github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||||
|
github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||||
|
github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||||
|
github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||||
|
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||||
|
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
||||||
|
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
||||||
|
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||||
|
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||||
|
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||||
|
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
|
||||||
|
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||||
|
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||||
|
github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||||
|
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||||
|
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
||||||
|
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
|
||||||
|
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
||||||
|
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
|
||||||
|
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||||
|
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||||
|
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||||
|
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
||||||
|
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||||
|
github.com/kr/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/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
|
||||||
|
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
|
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||||
|
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||||
|
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||||
|
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||||
|
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||||
|
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
|
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
|
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
git.internal/re/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||||
|
git.internal/re/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
|
||||||
|
git.internal/re/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
|
||||||
|
git.internal/re/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=
|
||||||
|
git.internal/re/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY=
|
||||||
|
git.internal/re/client_golang v1.13.1 h1:3gMjIY2+/hzmqhtUC/aQNYldJA6DtH3CgQvwS+02K1c=
|
||||||
|
git.internal/re/client_golang v1.13.1/go.mod h1:vTeo+zgvILHsnnj/39Ou/1fPN5nJFOEMgftOUOmlvYQ=
|
||||||
|
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||||
|
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||||
|
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||||
|
github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M=
|
||||||
|
github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||||
|
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
||||||
|
github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
|
||||||
|
github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc=
|
||||||
|
github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls=
|
||||||
|
github.com/prometheus/common v0.37.0 h1:ccBbHCgIiT9uSoFY0vX8H3zsNR5eLt17/RQLUvn8pXE=
|
||||||
|
github.com/prometheus/common v0.37.0/go.mod h1:phzohg0JFMnBEFGxTDbfu3QyL5GI8gTQJFhYO5B3mfA=
|
||||||
|
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||||
|
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
||||||
|
github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
|
||||||
|
github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
|
||||||
|
github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
|
||||||
|
github.com/prometheus/procfs v0.8.0 h1:ODq8ZFEaYeCaZOJlZZdJA2AbQR98dSHSM1KW/You5mo=
|
||||||
|
github.com/prometheus/procfs v0.8.0/go.mod h1:z7EfXMXOkbkqb9IINtpCn86r/to3BnA0uaxHdg830/4=
|
||||||
|
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||||
|
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||||
|
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||||
|
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
|
||||||
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||||
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
|
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||||
|
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=
|
||||||
|
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
||||||
|
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
||||||
|
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||||
|
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||||
|
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||||
|
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||||
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
|
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
|
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/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=
|
||||||
|
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
|
||||||
|
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
|
||||||
|
golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||||
|
golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||||
|
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||||
|
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
|
||||||
|
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
|
||||||
|
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||||
|
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||||
|
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||||
|
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||||
|
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||||
|
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||||
|
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||||
|
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||||
|
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||||
|
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
|
||||||
|
golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||||
|
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||||
|
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
|
||||||
|
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
|
||||||
|
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
|
||||||
|
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
|
||||||
|
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||||
|
golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||||
|
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
|
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
|
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
|
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
|
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
|
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||||
|
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||||
|
golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||||
|
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||||
|
golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||||
|
golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||||
|
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||||
|
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-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
|
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||||
|
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||||
|
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||||
|
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
|
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
|
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
|
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
|
golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||||
|
golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=
|
||||||
|
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=
|
||||||
|
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a h1:dGzPydgVsqGcTRVwiLJ1jVbufYwmzD3LfVPLKsKg+0k=
|
||||||
|
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
|
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
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.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
|
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
|
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
|
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
|
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||||
|
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||||
|
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||||
|
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||||
|
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||||
|
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||||
|
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||||
|
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||||
|
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||||
|
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||||
|
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
|
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
|
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
|
golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
|
golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
|
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
|
golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
|
golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
|
golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||||
|
golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||||
|
golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||||
|
golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||||
|
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||||
|
golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||||
|
golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||||
|
golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||||
|
golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||||
|
golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||||
|
golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
|
||||||
|
golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
|
||||||
|
golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
|
||||||
|
golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||||
|
golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||||
|
golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||||
|
golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||||
|
golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||||
|
golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||||
|
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||||
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
|
||||||
|
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
|
||||||
|
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
||||||
|
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
||||||
|
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
||||||
|
google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
||||||
|
google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
||||||
|
google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||||
|
google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||||
|
google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||||
|
google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||||
|
google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||||
|
google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
|
||||||
|
google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
|
||||||
|
google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
|
||||||
|
google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
|
||||||
|
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||||
|
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||||
|
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||||
|
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/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=
|
||||||
|
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||||
|
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||||
|
google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||||
|
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||||
|
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
|
||||||
|
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||||
|
google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||||
|
google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||||
|
google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||||
|
google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||||
|
google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||||
|
google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=
|
||||||
|
google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||||
|
google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||||
|
google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||||
|
google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||||
|
google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||||
|
google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||||
|
google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||||
|
google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||||
|
google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
|
||||||
|
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
|
||||||
|
google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
|
||||||
|
google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||||
|
google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||||
|
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||||
|
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||||
|
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
||||||
|
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
||||||
|
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||||
|
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
|
||||||
|
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||||
|
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||||
|
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||||
|
google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
|
||||||
|
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
|
||||||
|
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||||
|
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||||
|
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||||
|
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||||
|
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||||
|
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
||||||
|
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
||||||
|
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||||
|
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||||
|
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||||
|
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
|
||||||
|
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
||||||
|
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||||
|
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||||
|
google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w=
|
||||||
|
google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||||
|
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||||
|
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||||
|
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=
|
||||||
|
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
|
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
||||||
|
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
||||||
|
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
||||||
|
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
|
||||||
|
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
|
||||||
|
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
|
|
@ -0,0 +1,103 @@
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
// Package httpmiddleware is adapted from
|
||||||
|
// https://github.com/bwplotka/correlator/tree/main/examples/observability/ping/pkg/httpinstrumentation
|
||||||
|
package httpmiddleware
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"git.internal/re/client_golang/prometheus"
|
||||||
|
"git.internal/re/client_golang/prometheus/promauto"
|
||||||
|
"git.internal/re/client_golang/prometheus/promhttp"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Middleware interface {
|
||||||
|
// WrapHandler wraps the given HTTP handler for instrumentation.
|
||||||
|
WrapHandler(handlerName string, handler http.Handler) http.HandlerFunc
|
||||||
|
}
|
||||||
|
|
||||||
|
type middleware struct {
|
||||||
|
buckets []float64
|
||||||
|
registry prometheus.Registerer
|
||||||
|
}
|
||||||
|
|
||||||
|
// WrapHandler wraps the given HTTP handler for instrumentation:
|
||||||
|
// It registers four metric collectors (if not already done) and reports HTTP
|
||||||
|
// metrics to the (newly or already) registered collectors.
|
||||||
|
// Each has a constant label named "handler" with the provided handlerName as
|
||||||
|
// value.
|
||||||
|
func (m *middleware) WrapHandler(handlerName string, handler http.Handler) http.HandlerFunc {
|
||||||
|
reg := prometheus.WrapRegistererWith(prometheus.Labels{"handler": handlerName}, m.registry)
|
||||||
|
|
||||||
|
requestsTotal := promauto.With(reg).NewCounterVec(
|
||||||
|
prometheus.CounterOpts{
|
||||||
|
Name: "http_requests_total",
|
||||||
|
Help: "Tracks the number of HTTP requests.",
|
||||||
|
}, []string{"method", "code"},
|
||||||
|
)
|
||||||
|
requestDuration := promauto.With(reg).NewHistogramVec(
|
||||||
|
prometheus.HistogramOpts{
|
||||||
|
Name: "http_request_duration_seconds",
|
||||||
|
Help: "Tracks the latencies for HTTP requests.",
|
||||||
|
Buckets: m.buckets,
|
||||||
|
},
|
||||||
|
[]string{"method", "code"},
|
||||||
|
)
|
||||||
|
requestSize := promauto.With(reg).NewSummaryVec(
|
||||||
|
prometheus.SummaryOpts{
|
||||||
|
Name: "http_request_size_bytes",
|
||||||
|
Help: "Tracks the size of HTTP requests.",
|
||||||
|
},
|
||||||
|
[]string{"method", "code"},
|
||||||
|
)
|
||||||
|
responseSize := promauto.With(reg).NewSummaryVec(
|
||||||
|
prometheus.SummaryOpts{
|
||||||
|
Name: "http_response_size_bytes",
|
||||||
|
Help: "Tracks the size of HTTP responses.",
|
||||||
|
},
|
||||||
|
[]string{"method", "code"},
|
||||||
|
)
|
||||||
|
|
||||||
|
// Wraps the provided http.Handler to observe the request result with the provided metrics.
|
||||||
|
base := promhttp.InstrumentHandlerCounter(
|
||||||
|
requestsTotal,
|
||||||
|
promhttp.InstrumentHandlerDuration(
|
||||||
|
requestDuration,
|
||||||
|
promhttp.InstrumentHandlerRequestSize(
|
||||||
|
requestSize,
|
||||||
|
promhttp.InstrumentHandlerResponseSize(
|
||||||
|
responseSize,
|
||||||
|
http.HandlerFunc(func(writer http.ResponseWriter, r *http.Request) {
|
||||||
|
handler.ServeHTTP(writer, r)
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
return base.ServeHTTP
|
||||||
|
}
|
||||||
|
|
||||||
|
// New returns a Middleware interface.
|
||||||
|
func New(registry prometheus.Registerer, buckets []float64) Middleware {
|
||||||
|
if buckets == nil {
|
||||||
|
buckets = prometheus.ExponentialBuckets(0.1, 1.5, 5)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &middleware{
|
||||||
|
buckets: buckets,
|
||||||
|
registry: registry,
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,50 @@
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
// Simple example of auto-instrumentation by using an HTTP Middleware with relevant metrics.
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"git.internal/re/client_golang/prometheus"
|
||||||
|
"git.internal/re/client_golang/prometheus/collectors"
|
||||||
|
"git.internal/re/client_golang/prometheus/promhttp"
|
||||||
|
|
||||||
|
"github.com/jessicalins/instrumentation-practices-examples/middleware/httpmiddleware"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
// Create non-global registry.
|
||||||
|
registry := prometheus.NewRegistry()
|
||||||
|
|
||||||
|
// Add go runtime metrics and process collectors.
|
||||||
|
registry.MustRegister(
|
||||||
|
collectors.NewGoCollector(),
|
||||||
|
collectors.NewProcessCollector(collectors.ProcessCollectorOpts{}),
|
||||||
|
)
|
||||||
|
|
||||||
|
// Expose /metrics HTTP endpoint using the created custom registry.
|
||||||
|
http.Handle(
|
||||||
|
"/metrics",
|
||||||
|
httpmiddleware.New(
|
||||||
|
registry, nil).
|
||||||
|
WrapHandler("/metrics", promhttp.HandlerFor(
|
||||||
|
registry,
|
||||||
|
promhttp.HandlerOpts{}),
|
||||||
|
))
|
||||||
|
|
||||||
|
log.Fatalln(http.ListenAndServe(":8080", nil))
|
||||||
|
}
|
|
@ -25,11 +25,52 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/prometheus/client_golang/prometheus"
|
"git.internal/re/client_golang/prometheus"
|
||||||
"github.com/prometheus/client_golang/prometheus/collectors"
|
"git.internal/re/client_golang/prometheus/collectors"
|
||||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
"git.internal/re/client_golang/prometheus/promhttp"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type metrics struct {
|
||||||
|
rpcDurations *prometheus.SummaryVec
|
||||||
|
rpcDurationsHistogram prometheus.Histogram
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewMetrics(reg prometheus.Registerer, normMean, normDomain float64) *metrics {
|
||||||
|
m := &metrics{
|
||||||
|
// Create a summary to track fictional inter service RPC latencies for three
|
||||||
|
// distinct services with different latency distributions. These services are
|
||||||
|
// differentiated via a "service" label.
|
||||||
|
rpcDurations: prometheus.NewSummaryVec(
|
||||||
|
prometheus.SummaryOpts{
|
||||||
|
Name: "rpc_durations_seconds",
|
||||||
|
Help: "RPC latency distributions.",
|
||||||
|
Objectives: map[float64]float64{0.5: 0.05, 0.9: 0.01, 0.99: 0.001},
|
||||||
|
},
|
||||||
|
[]string{"service"},
|
||||||
|
),
|
||||||
|
// The same as above, but now as a histogram, and only for the
|
||||||
|
// normal distribution. The histogram features both conventional
|
||||||
|
// buckets as well as sparse buckets, the latter needed for the
|
||||||
|
// experimental native histograms (ingested by a Prometheus
|
||||||
|
// server v2.40 with the corresponding feature flag
|
||||||
|
// enabled). The conventional buckets are targeted to the
|
||||||
|
// parameters of the normal distribution, with 20 buckets
|
||||||
|
// centered on the mean, each half-sigma wide. The sparse
|
||||||
|
// buckets are always centered on zero, with a growth factor of
|
||||||
|
// one bucket to the next of (at most) 1.1. (The precise factor
|
||||||
|
// is 2^2^-3 = 1.0905077...)
|
||||||
|
rpcDurationsHistogram: prometheus.NewHistogram(prometheus.HistogramOpts{
|
||||||
|
Name: "rpc_durations_histogram_seconds",
|
||||||
|
Help: "RPC latency distributions.",
|
||||||
|
Buckets: prometheus.LinearBuckets(normMean-5*normDomain, .5*normDomain, 20),
|
||||||
|
NativeHistogramBucketFactor: 1.1,
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
reg.MustRegister(m.rpcDurations)
|
||||||
|
reg.MustRegister(m.rpcDurationsHistogram)
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
var (
|
var (
|
||||||
addr = flag.String("listen-address", ":8080", "The address to listen on for HTTP requests.")
|
addr = flag.String("listen-address", ":8080", "The address to listen on for HTTP requests.")
|
||||||
|
@ -41,34 +82,13 @@ func main() {
|
||||||
|
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
var (
|
// Create a non-global registry.
|
||||||
// Create a summary to track fictional interservice RPC latencies for three
|
reg := prometheus.NewRegistry()
|
||||||
// distinct services with different latency distributions. These services are
|
|
||||||
// differentiated via a "service" label.
|
|
||||||
rpcDurations = prometheus.NewSummaryVec(
|
|
||||||
prometheus.SummaryOpts{
|
|
||||||
Name: "rpc_durations_seconds",
|
|
||||||
Help: "RPC latency distributions.",
|
|
||||||
Objectives: map[float64]float64{0.5: 0.05, 0.9: 0.01, 0.99: 0.001},
|
|
||||||
},
|
|
||||||
[]string{"service"},
|
|
||||||
)
|
|
||||||
// The same as above, but now as a histogram, and only for the normal
|
|
||||||
// distribution. The buckets are targeted to the parameters of the
|
|
||||||
// normal distribution, with 20 buckets centered on the mean, each
|
|
||||||
// half-sigma wide.
|
|
||||||
rpcDurationsHistogram = prometheus.NewHistogram(prometheus.HistogramOpts{
|
|
||||||
Name: "rpc_durations_histogram_seconds",
|
|
||||||
Help: "RPC latency distributions.",
|
|
||||||
Buckets: prometheus.LinearBuckets(*normMean-5**normDomain, .5**normDomain, 20),
|
|
||||||
})
|
|
||||||
)
|
|
||||||
|
|
||||||
// Register the summary and the histogram with Prometheus's default registry.
|
// Create new metrics and register them using the custom registry.
|
||||||
prometheus.MustRegister(rpcDurations)
|
m := NewMetrics(reg, *normMean, *normDomain)
|
||||||
prometheus.MustRegister(rpcDurationsHistogram)
|
|
||||||
// Add Go module build info.
|
// Add Go module build info.
|
||||||
prometheus.MustRegister(collectors.NewBuildInfoCollector())
|
reg.MustRegister(collectors.NewBuildInfoCollector())
|
||||||
|
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
|
|
||||||
|
@ -80,7 +100,7 @@ func main() {
|
||||||
go func() {
|
go func() {
|
||||||
for {
|
for {
|
||||||
v := rand.Float64() * *uniformDomain
|
v := rand.Float64() * *uniformDomain
|
||||||
rpcDurations.WithLabelValues("uniform").Observe(v)
|
m.rpcDurations.WithLabelValues("uniform").Observe(v)
|
||||||
time.Sleep(time.Duration(100*oscillationFactor()) * time.Millisecond)
|
time.Sleep(time.Duration(100*oscillationFactor()) * time.Millisecond)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
@ -88,14 +108,14 @@ func main() {
|
||||||
go func() {
|
go func() {
|
||||||
for {
|
for {
|
||||||
v := (rand.NormFloat64() * *normDomain) + *normMean
|
v := (rand.NormFloat64() * *normDomain) + *normMean
|
||||||
rpcDurations.WithLabelValues("normal").Observe(v)
|
m.rpcDurations.WithLabelValues("normal").Observe(v)
|
||||||
// Demonstrate exemplar support with a dummy ID. This
|
// Demonstrate exemplar support with a dummy ID. This
|
||||||
// would be something like a trace ID in a real
|
// would be something like a trace ID in a real
|
||||||
// application. Note the necessary type assertion. We
|
// application. Note the necessary type assertion. We
|
||||||
// already know that rpcDurationsHistogram implements
|
// already know that rpcDurationsHistogram implements
|
||||||
// the ExemplarObserver interface and thus don't need to
|
// the ExemplarObserver interface and thus don't need to
|
||||||
// check the outcome of the type assertion.
|
// check the outcome of the type assertion.
|
||||||
rpcDurationsHistogram.(prometheus.ExemplarObserver).ObserveWithExemplar(
|
m.rpcDurationsHistogram.(prometheus.ExemplarObserver).ObserveWithExemplar(
|
||||||
v, prometheus.Labels{"dummyID": fmt.Sprint(rand.Intn(100000))},
|
v, prometheus.Labels{"dummyID": fmt.Sprint(rand.Intn(100000))},
|
||||||
)
|
)
|
||||||
time.Sleep(time.Duration(75*oscillationFactor()) * time.Millisecond)
|
time.Sleep(time.Duration(75*oscillationFactor()) * time.Millisecond)
|
||||||
|
@ -105,17 +125,19 @@ func main() {
|
||||||
go func() {
|
go func() {
|
||||||
for {
|
for {
|
||||||
v := rand.ExpFloat64() / 1e6
|
v := rand.ExpFloat64() / 1e6
|
||||||
rpcDurations.WithLabelValues("exponential").Observe(v)
|
m.rpcDurations.WithLabelValues("exponential").Observe(v)
|
||||||
time.Sleep(time.Duration(50*oscillationFactor()) * time.Millisecond)
|
time.Sleep(time.Duration(50*oscillationFactor()) * time.Millisecond)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
// Expose the registered metrics via HTTP.
|
// Expose the registered metrics via HTTP.
|
||||||
http.Handle("/metrics", promhttp.HandlerFor(
|
http.Handle("/metrics", promhttp.HandlerFor(
|
||||||
prometheus.DefaultGatherer,
|
reg,
|
||||||
promhttp.HandlerOpts{
|
promhttp.HandlerOpts{
|
||||||
// Opt into OpenMetrics to support exemplars.
|
// Opt into OpenMetrics to support exemplars.
|
||||||
EnableOpenMetrics: true,
|
EnableOpenMetrics: true,
|
||||||
|
// Pass custom registry
|
||||||
|
Registry: reg,
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
log.Fatal(http.ListenAndServe(*addr, nil))
|
log.Fatal(http.ListenAndServe(*addr, nil))
|
||||||
|
|
|
@ -19,13 +19,27 @@ import (
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
"git.internal/re/client_golang/prometheus/collectors"
|
||||||
|
|
||||||
|
"git.internal/re/client_golang/prometheus"
|
||||||
|
"git.internal/re/client_golang/prometheus/promhttp"
|
||||||
)
|
)
|
||||||
|
|
||||||
var addr = flag.String("listen-address", ":8080", "The address to listen on for HTTP requests.")
|
var addr = flag.String("listen-address", ":8080", "The address to listen on for HTTP requests.")
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
http.Handle("/metrics", promhttp.Handler())
|
|
||||||
|
// Create non-global registry.
|
||||||
|
reg := prometheus.NewRegistry()
|
||||||
|
|
||||||
|
// Add go runtime metrics and process collectors.
|
||||||
|
reg.MustRegister(
|
||||||
|
collectors.NewGoCollector(),
|
||||||
|
collectors.NewProcessCollector(collectors.ProcessCollectorOpts{}),
|
||||||
|
)
|
||||||
|
|
||||||
|
// Expose /metrics HTTP endpoint using the created custom registry.
|
||||||
|
http.Handle("/metrics", promhttp.HandlerFor(reg, promhttp.HandlerOpts{Registry: reg}))
|
||||||
log.Fatal(http.ListenAndServe(*addr, nil))
|
log.Fatal(http.ListenAndServe(*addr, nil))
|
||||||
}
|
}
|
||||||
|
|
31
go.mod
31
go.mod
|
@ -1,15 +1,32 @@
|
||||||
module github.com/prometheus/client_golang
|
module git.internal/re/client_golang
|
||||||
|
|
||||||
|
go 1.17
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/beorn7/perks v1.0.1
|
github.com/beorn7/perks v1.0.1
|
||||||
github.com/cespare/xxhash/v2 v2.1.2
|
github.com/cespare/xxhash/v2 v2.1.2
|
||||||
|
github.com/davecgh/go-spew v1.1.1
|
||||||
github.com/golang/protobuf v1.5.2
|
github.com/golang/protobuf v1.5.2
|
||||||
github.com/json-iterator/go v1.1.12
|
github.com/json-iterator/go v1.1.12
|
||||||
github.com/prometheus/client_model v0.2.0
|
github.com/prometheus/client_model v0.3.0
|
||||||
github.com/prometheus/common v0.32.1
|
github.com/prometheus/common v0.37.0
|
||||||
github.com/prometheus/procfs v0.7.3
|
github.com/prometheus/procfs v0.8.0
|
||||||
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9
|
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a
|
||||||
google.golang.org/protobuf v1.26.0
|
google.golang.org/protobuf v1.28.1
|
||||||
)
|
)
|
||||||
|
|
||||||
go 1.13
|
require (
|
||||||
|
github.com/jpillora/backoff v1.0.0 // indirect
|
||||||
|
github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||||
|
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||||
|
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f // indirect
|
||||||
|
github.com/prometheus/client_golang v1.12.1 // indirect
|
||||||
|
golang.org/x/net v0.0.0-20220225172249-27dd8689420f // indirect
|
||||||
|
golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b // indirect
|
||||||
|
golang.org/x/text v0.3.7 // indirect
|
||||||
|
google.golang.org/appengine v1.6.6 // indirect
|
||||||
|
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||||
|
)
|
||||||
|
|
||||||
|
exclude git.internal/re/client_golang v1.12.1
|
||||||
|
|
37
go.sum
37
go.sum
|
@ -64,9 +64,11 @@ github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2
|
||||||
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||||
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||||
github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
|
github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
|
||||||
|
github.com/go-kit/log v0.2.0/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0=
|
||||||
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
||||||
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
||||||
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
|
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
|
||||||
|
github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
|
||||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||||
|
@ -107,8 +109,9 @@ github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
|
||||||
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
|
|
||||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
|
github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
|
||||||
|
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
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 v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
||||||
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
|
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
|
||||||
|
@ -166,22 +169,27 @@ github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXP
|
||||||
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
|
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
|
||||||
github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
|
github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
|
||||||
github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=
|
github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=
|
||||||
|
github.com/prometheus/client_golang v1.12.1 h1:ZiaPsmm9uiBeaSMRznKsCDNtPCS0T3JVDGF+06gjBzk=
|
||||||
|
github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY=
|
||||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||||
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||||
github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M=
|
|
||||||
github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||||
|
github.com/prometheus/client_model v0.3.0 h1:UBgGFHqYdG/TPFD1B1ogZywDqEkwp3fBMvqdiQ7Xew4=
|
||||||
|
github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w=
|
||||||
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
||||||
github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
|
github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
|
||||||
github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc=
|
github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc=
|
||||||
github.com/prometheus/common v0.32.1 h1:hWIdL3N2HoUx3B8j3YN9mWor0qhY/NlEKZEaXxuIRh4=
|
|
||||||
github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls=
|
github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls=
|
||||||
|
github.com/prometheus/common v0.37.0 h1:ccBbHCgIiT9uSoFY0vX8H3zsNR5eLt17/RQLUvn8pXE=
|
||||||
|
github.com/prometheus/common v0.37.0/go.mod h1:phzohg0JFMnBEFGxTDbfu3QyL5GI8gTQJFhYO5B3mfA=
|
||||||
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||||
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
||||||
github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
|
github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
|
||||||
github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
|
github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
|
||||||
github.com/prometheus/procfs v0.7.3 h1:4jVXhlkAyzOScmCkXBTOLRLTz8EeU+eyjrwB/EPq0VU=
|
|
||||||
github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
|
github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
|
||||||
|
github.com/prometheus/procfs v0.8.0 h1:ODq8ZFEaYeCaZOJlZZdJA2AbQR98dSHSM1KW/You5mo=
|
||||||
|
github.com/prometheus/procfs v0.8.0/go.mod h1:z7EfXMXOkbkqb9IINtpCn86r/to3BnA0uaxHdg830/4=
|
||||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||||
|
@ -264,15 +272,18 @@ golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/
|
||||||
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||||
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
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-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||||
golang.org/x/net v0.0.0-20210525063256-abc453219eb5 h1:wjuX4b5yYQnEQHzd+CBcrcC6OVR2J1CN6mUy0oSxIPo=
|
|
||||||
golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
|
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||||
|
golang.org/x/net v0.0.0-20220225172249-27dd8689420f h1:oA4XRj0qtSt8Yo1Zms0CUlsT3KG69V2UGQWPBxujDmc=
|
||||||
|
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
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-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-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-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-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c h1:pkQiBZBvdos9qq4wBAHqlzuZHEXo07pqV06ef90u1WI=
|
|
||||||
golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||||
|
golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b h1:clP8eMhB30EHdc0bd2Twtq6kgU7yl5ub2cQLSdrv1Dg=
|
||||||
|
golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=
|
||||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
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-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
@ -282,6 +293,7 @@ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJ
|
||||||
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
@ -317,16 +329,21 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||||
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9 h1:XfKQ4OlFl8okEOr5UvAqFRVj8pY/4yfcXrddB8qAbU0=
|
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a h1:dGzPydgVsqGcTRVwiLJ1jVbufYwmzD3LfVPLKsKg+0k=
|
||||||
|
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
golang.org/x/text v0.3.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.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
|
|
||||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
|
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
|
||||||
|
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
|
@ -373,7 +390,6 @@ golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
|
|
||||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
|
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
|
||||||
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
|
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
|
||||||
|
@ -450,8 +466,9 @@ google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpAD
|
||||||
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
|
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
|
||||||
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
||||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||||
google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk=
|
|
||||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||||
|
google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w=
|
||||||
|
google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 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-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
See [![Go Reference](https://pkg.go.dev/badge/github.com/prometheus/client_golang/prometheus.svg)](https://pkg.go.dev/github.com/prometheus/client_golang/prometheus).
|
See [![Go Reference](https://pkg.go.dev/badge/git.internal/re/client_golang/prometheus.svg)](https://pkg.go.dev/git.internal/re/client_golang/prometheus).
|
||||||
|
|
|
@ -69,9 +69,9 @@ type Collector interface {
|
||||||
// If a Collector collects the same metrics throughout its lifetime, its
|
// If a Collector collects the same metrics throughout its lifetime, its
|
||||||
// Describe method can simply be implemented as:
|
// Describe method can simply be implemented as:
|
||||||
//
|
//
|
||||||
// func (c customCollector) Describe(ch chan<- *Desc) {
|
// func (c customCollector) Describe(ch chan<- *Desc) {
|
||||||
// DescribeByCollect(c, ch)
|
// DescribeByCollect(c, ch)
|
||||||
// }
|
// }
|
||||||
//
|
//
|
||||||
// However, this will not work if the metrics collected change dynamically over
|
// However, this will not work if the metrics collected change dynamically over
|
||||||
// the lifetime of the Collector in a way that their combined set of descriptors
|
// the lifetime of the Collector in a way that their combined set of descriptors
|
||||||
|
|
|
@ -30,7 +30,6 @@ func (c collectorDescribedByCollect) Describe(ch chan<- *Desc) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDescribeByCollect(t *testing.T) {
|
func TestDescribeByCollect(t *testing.T) {
|
||||||
|
|
||||||
goodCollector := collectorDescribedByCollect{
|
goodCollector := collectorDescribedByCollect{
|
||||||
cnt: NewCounter(CounterOpts{Name: "c1", Help: "help c1"}),
|
cnt: NewCounter(CounterOpts{Name: "c1", Help: "help c1"}),
|
||||||
gge: NewGauge(GaugeOpts{Name: "g1", Help: "help g1"}),
|
gge: NewGauge(GaugeOpts{Name: "g1", Help: "help g1"}),
|
||||||
|
|
|
@ -15,7 +15,7 @@
|
||||||
// conveniently collect process and Go-related metrics.
|
// conveniently collect process and Go-related metrics.
|
||||||
package collectors
|
package collectors
|
||||||
|
|
||||||
import "github.com/prometheus/client_golang/prometheus"
|
import "git.internal/re/client_golang/prometheus"
|
||||||
|
|
||||||
// NewBuildInfoCollector returns a collector collecting a single metric
|
// NewBuildInfoCollector returns a collector collecting a single metric
|
||||||
// "go_build_info" with the constant value 1 and three labels "path", "version",
|
// "go_build_info" with the constant value 1 and three labels "path", "version",
|
||||||
|
@ -25,7 +25,7 @@ import "github.com/prometheus/client_golang/prometheus"
|
||||||
// the source repository (rather than the local file system). This is usually
|
// the source repository (rather than the local file system). This is usually
|
||||||
// accomplished by building from outside of GOPATH, specifying the full address
|
// accomplished by building from outside of GOPATH, specifying the full address
|
||||||
// of the main package, e.g. "GO111MODULE=on go run
|
// of the main package, e.g. "GO111MODULE=on go run
|
||||||
// github.com/prometheus/client_golang/examples/random". If built without Go
|
// git.internal/re/client_golang/examples/random". If built without Go
|
||||||
// module support, all label values will be "unknown". If built with Go module
|
// module support, all label values will be "unknown". If built with Go module
|
||||||
// support but using the source code from the local file system, the "path" will
|
// support but using the source code from the local file system, the "path" will
|
||||||
// be set appropriately, but "checksum" will be empty and "version" will be
|
// be set appropriately, but "checksum" will be empty and "version" will be
|
||||||
|
|
|
@ -16,7 +16,7 @@ package collectors
|
||||||
import (
|
import (
|
||||||
"database/sql"
|
"database/sql"
|
||||||
|
|
||||||
"github.com/prometheus/client_golang/prometheus"
|
"git.internal/re/client_golang/prometheus"
|
||||||
)
|
)
|
||||||
|
|
||||||
type dbStatsCollector struct {
|
type dbStatsCollector struct {
|
||||||
|
@ -101,7 +101,7 @@ func (c *dbStatsCollector) Describe(ch chan<- *prometheus.Desc) {
|
||||||
ch <- c.waitDuration
|
ch <- c.waitDuration
|
||||||
ch <- c.maxIdleClosed
|
ch <- c.maxIdleClosed
|
||||||
ch <- c.maxLifetimeClosed
|
ch <- c.maxLifetimeClosed
|
||||||
c.describeNewInGo115(ch)
|
ch <- c.maxIdleTimeClosed
|
||||||
}
|
}
|
||||||
|
|
||||||
// Collect implements Collector.
|
// Collect implements Collector.
|
||||||
|
@ -115,5 +115,5 @@ func (c *dbStatsCollector) Collect(ch chan<- prometheus.Metric) {
|
||||||
ch <- prometheus.MustNewConstMetric(c.waitDuration, prometheus.CounterValue, stats.WaitDuration.Seconds())
|
ch <- prometheus.MustNewConstMetric(c.waitDuration, prometheus.CounterValue, stats.WaitDuration.Seconds())
|
||||||
ch <- prometheus.MustNewConstMetric(c.maxIdleClosed, prometheus.CounterValue, float64(stats.MaxIdleClosed))
|
ch <- prometheus.MustNewConstMetric(c.maxIdleClosed, prometheus.CounterValue, float64(stats.MaxIdleClosed))
|
||||||
ch <- prometheus.MustNewConstMetric(c.maxLifetimeClosed, prometheus.CounterValue, float64(stats.MaxLifetimeClosed))
|
ch <- prometheus.MustNewConstMetric(c.maxLifetimeClosed, prometheus.CounterValue, float64(stats.MaxLifetimeClosed))
|
||||||
c.collectNewInGo115(ch, stats)
|
ch <- prometheus.MustNewConstMetric(c.maxIdleTimeClosed, prometheus.CounterValue, float64(stats.MaxIdleTimeClosed))
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,10 +15,9 @@ package collectors
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"runtime"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/prometheus/client_golang/prometheus"
|
"git.internal/re/client_golang/prometheus"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestDBStatsCollector(t *testing.T) {
|
func TestDBStatsCollector(t *testing.T) {
|
||||||
|
@ -50,9 +49,7 @@ func TestDBStatsCollector(t *testing.T) {
|
||||||
"go_sql_wait_duration_seconds_total",
|
"go_sql_wait_duration_seconds_total",
|
||||||
"go_sql_max_idle_closed_total",
|
"go_sql_max_idle_closed_total",
|
||||||
"go_sql_max_lifetime_closed_total",
|
"go_sql_max_lifetime_closed_total",
|
||||||
}
|
"go_sql_max_idle_time_closed_total",
|
||||||
if runtime.Version() >= "go1.15" {
|
|
||||||
names = append(names, "go_sql_max_idle_time_closed_total")
|
|
||||||
}
|
}
|
||||||
type result struct {
|
type result struct {
|
||||||
found bool
|
found bool
|
||||||
|
|
|
@ -13,7 +13,7 @@
|
||||||
|
|
||||||
package collectors
|
package collectors
|
||||||
|
|
||||||
import "github.com/prometheus/client_golang/prometheus"
|
import "git.internal/re/client_golang/prometheus"
|
||||||
|
|
||||||
// NewExpvarCollector returns a newly allocated expvar Collector.
|
// NewExpvarCollector returns a newly allocated expvar Collector.
|
||||||
//
|
//
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
|
|
||||||
package collectors
|
package collectors
|
||||||
|
|
||||||
import "github.com/prometheus/client_golang/prometheus"
|
import "git.internal/re/client_golang/prometheus"
|
||||||
|
|
||||||
// NewGoCollector returns a collector that exports metrics about the current Go
|
// NewGoCollector returns a collector that exports metrics about the current Go
|
||||||
// process. This includes memory stats. To collect those, runtime.ReadMemStats
|
// process. This includes memory stats. To collect those, runtime.ReadMemStats
|
||||||
|
|
|
@ -0,0 +1,100 @@
|
||||||
|
// 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 go1.17 && !go1.19
|
||||||
|
// +build go1.17,!go1.19
|
||||||
|
|
||||||
|
package collectors
|
||||||
|
|
||||||
|
func withAllMetrics() []string {
|
||||||
|
return withBaseMetrics([]string{
|
||||||
|
"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",
|
||||||
|
"go_gc_heap_frees_by_size_bytes",
|
||||||
|
"go_gc_heap_frees_bytes_total",
|
||||||
|
"go_gc_heap_frees_objects_total",
|
||||||
|
"go_gc_heap_goal_bytes",
|
||||||
|
"go_gc_heap_objects_objects",
|
||||||
|
"go_gc_heap_tiny_allocs_objects_total",
|
||||||
|
"go_gc_pauses_seconds",
|
||||||
|
"go_memory_classes_heap_free_bytes",
|
||||||
|
"go_memory_classes_heap_objects_bytes",
|
||||||
|
"go_memory_classes_heap_released_bytes",
|
||||||
|
"go_memory_classes_heap_stacks_bytes",
|
||||||
|
"go_memory_classes_heap_unused_bytes",
|
||||||
|
"go_memory_classes_metadata_mcache_free_bytes",
|
||||||
|
"go_memory_classes_metadata_mcache_inuse_bytes",
|
||||||
|
"go_memory_classes_metadata_mspan_free_bytes",
|
||||||
|
"go_memory_classes_metadata_mspan_inuse_bytes",
|
||||||
|
"go_memory_classes_metadata_other_bytes",
|
||||||
|
"go_memory_classes_os_stacks_bytes",
|
||||||
|
"go_memory_classes_other_bytes",
|
||||||
|
"go_memory_classes_profiling_buckets_bytes",
|
||||||
|
"go_memory_classes_total_bytes",
|
||||||
|
"go_sched_goroutines_goroutines",
|
||||||
|
"go_sched_latencies_seconds",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func withGCMetrics() []string {
|
||||||
|
return withBaseMetrics([]string{
|
||||||
|
"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",
|
||||||
|
"go_gc_heap_frees_by_size_bytes",
|
||||||
|
"go_gc_heap_frees_bytes_total",
|
||||||
|
"go_gc_heap_frees_objects_total",
|
||||||
|
"go_gc_heap_goal_bytes",
|
||||||
|
"go_gc_heap_objects_objects",
|
||||||
|
"go_gc_heap_tiny_allocs_objects_total",
|
||||||
|
"go_gc_pauses_seconds",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func withMemoryMetrics() []string {
|
||||||
|
return withBaseMetrics([]string{
|
||||||
|
"go_memory_classes_heap_free_bytes",
|
||||||
|
"go_memory_classes_heap_objects_bytes",
|
||||||
|
"go_memory_classes_heap_released_bytes",
|
||||||
|
"go_memory_classes_heap_stacks_bytes",
|
||||||
|
"go_memory_classes_heap_unused_bytes",
|
||||||
|
"go_memory_classes_metadata_mcache_free_bytes",
|
||||||
|
"go_memory_classes_metadata_mcache_inuse_bytes",
|
||||||
|
"go_memory_classes_metadata_mspan_free_bytes",
|
||||||
|
"go_memory_classes_metadata_mspan_inuse_bytes",
|
||||||
|
"go_memory_classes_metadata_other_bytes",
|
||||||
|
"go_memory_classes_os_stacks_bytes",
|
||||||
|
"go_memory_classes_other_bytes",
|
||||||
|
"go_memory_classes_profiling_buckets_bytes",
|
||||||
|
"go_memory_classes_total_bytes",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func withSchedulerMetrics() []string {
|
||||||
|
return []string{
|
||||||
|
"go_gc_duration_seconds",
|
||||||
|
"go_goroutines",
|
||||||
|
"go_info",
|
||||||
|
"go_memstats_last_gc_time_seconds",
|
||||||
|
"go_sched_goroutines_goroutines",
|
||||||
|
"go_sched_latencies_seconds",
|
||||||
|
"go_threads",
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,107 @@
|
||||||
|
// 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 go1.19 && !go1.20
|
||||||
|
// +build go1.19,!go1.20
|
||||||
|
|
||||||
|
package collectors
|
||||||
|
|
||||||
|
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_gc_heap_allocs_by_size_bytes",
|
||||||
|
"go_gc_heap_allocs_bytes_total",
|
||||||
|
"go_gc_heap_allocs_objects_total",
|
||||||
|
"go_gc_heap_frees_by_size_bytes",
|
||||||
|
"go_gc_heap_frees_bytes_total",
|
||||||
|
"go_gc_heap_frees_objects_total",
|
||||||
|
"go_gc_heap_goal_bytes",
|
||||||
|
"go_gc_heap_objects_objects",
|
||||||
|
"go_gc_heap_tiny_allocs_objects_total",
|
||||||
|
"go_gc_limiter_last_enabled_gc_cycle",
|
||||||
|
"go_gc_pauses_seconds",
|
||||||
|
"go_gc_stack_starting_size_bytes",
|
||||||
|
"go_memory_classes_heap_free_bytes",
|
||||||
|
"go_memory_classes_heap_objects_bytes",
|
||||||
|
"go_memory_classes_heap_released_bytes",
|
||||||
|
"go_memory_classes_heap_stacks_bytes",
|
||||||
|
"go_memory_classes_heap_unused_bytes",
|
||||||
|
"go_memory_classes_metadata_mcache_free_bytes",
|
||||||
|
"go_memory_classes_metadata_mcache_inuse_bytes",
|
||||||
|
"go_memory_classes_metadata_mspan_free_bytes",
|
||||||
|
"go_memory_classes_metadata_mspan_inuse_bytes",
|
||||||
|
"go_memory_classes_metadata_other_bytes",
|
||||||
|
"go_memory_classes_os_stacks_bytes",
|
||||||
|
"go_memory_classes_other_bytes",
|
||||||
|
"go_memory_classes_profiling_buckets_bytes",
|
||||||
|
"go_memory_classes_total_bytes",
|
||||||
|
"go_sched_gomaxprocs_threads",
|
||||||
|
"go_sched_goroutines_goroutines",
|
||||||
|
"go_sched_latencies_seconds",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func withGCMetrics() []string {
|
||||||
|
return withBaseMetrics([]string{
|
||||||
|
"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",
|
||||||
|
"go_gc_heap_frees_by_size_bytes",
|
||||||
|
"go_gc_heap_frees_bytes_total",
|
||||||
|
"go_gc_heap_frees_objects_total",
|
||||||
|
"go_gc_heap_goal_bytes",
|
||||||
|
"go_gc_heap_objects_objects",
|
||||||
|
"go_gc_heap_tiny_allocs_objects_total",
|
||||||
|
"go_gc_limiter_last_enabled_gc_cycle",
|
||||||
|
"go_gc_pauses_seconds",
|
||||||
|
"go_gc_stack_starting_size_bytes",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func withMemoryMetrics() []string {
|
||||||
|
return withBaseMetrics([]string{
|
||||||
|
"go_memory_classes_heap_free_bytes",
|
||||||
|
"go_memory_classes_heap_objects_bytes",
|
||||||
|
"go_memory_classes_heap_released_bytes",
|
||||||
|
"go_memory_classes_heap_stacks_bytes",
|
||||||
|
"go_memory_classes_heap_unused_bytes",
|
||||||
|
"go_memory_classes_metadata_mcache_free_bytes",
|
||||||
|
"go_memory_classes_metadata_mcache_inuse_bytes",
|
||||||
|
"go_memory_classes_metadata_mspan_free_bytes",
|
||||||
|
"go_memory_classes_metadata_mspan_inuse_bytes",
|
||||||
|
"go_memory_classes_metadata_other_bytes",
|
||||||
|
"go_memory_classes_os_stacks_bytes",
|
||||||
|
"go_memory_classes_other_bytes",
|
||||||
|
"go_memory_classes_profiling_buckets_bytes",
|
||||||
|
"go_memory_classes_total_bytes",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func withSchedulerMetrics() []string {
|
||||||
|
return []string{
|
||||||
|
"go_gc_duration_seconds",
|
||||||
|
"go_goroutines",
|
||||||
|
"go_info",
|
||||||
|
"go_memstats_last_gc_time_seconds",
|
||||||
|
"go_sched_gomaxprocs_threads",
|
||||||
|
"go_sched_goroutines_goroutines",
|
||||||
|
"go_sched_latencies_seconds",
|
||||||
|
"go_threads",
|
||||||
|
}
|
||||||
|
}
|
|
@ -16,76 +16,145 @@
|
||||||
|
|
||||||
package collectors
|
package collectors
|
||||||
|
|
||||||
import "github.com/prometheus/client_golang/prometheus"
|
import (
|
||||||
|
"regexp"
|
||||||
|
|
||||||
//nolint:staticcheck // Ignore SA1019 until v2.
|
"git.internal/re/client_golang/prometheus"
|
||||||
type goOptions = prometheus.GoCollectorOptions
|
"git.internal/re/client_golang/prometheus/internal"
|
||||||
type goOption func(o *goOptions)
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// MetricsAll allows all the metrics to be collected from Go runtime.
|
||||||
|
MetricsAll = GoRuntimeMetricsRule{regexp.MustCompile("/.*")}
|
||||||
|
// MetricsGC allows only GC metrics to be collected from Go runtime.
|
||||||
|
// e.g. go_gc_cycles_automatic_gc_cycles_total
|
||||||
|
MetricsGC = GoRuntimeMetricsRule{regexp.MustCompile(`^/gc/.*`)}
|
||||||
|
// MetricsMemory allows only memory metrics to be collected from Go runtime.
|
||||||
|
// e.g. go_memory_classes_heap_free_bytes
|
||||||
|
MetricsMemory = GoRuntimeMetricsRule{regexp.MustCompile(`^/memory/.*`)}
|
||||||
|
// MetricsScheduler allows only scheduler metrics to be collected from Go runtime.
|
||||||
|
// e.g. go_sched_goroutines_goroutines
|
||||||
|
MetricsScheduler = GoRuntimeMetricsRule{regexp.MustCompile(`^/sched/.*`)}
|
||||||
|
)
|
||||||
|
|
||||||
|
// WithGoCollectorMemStatsMetricsDisabled disables metrics that is gathered in runtime.MemStats structure such as:
|
||||||
|
//
|
||||||
|
// 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
|
||||||
|
// go_memstats_heap_sys_bytes
|
||||||
|
// go_memstats_heap_idle_bytes
|
||||||
|
// go_memstats_heap_inuse_bytes
|
||||||
|
// go_memstats_heap_released_bytes
|
||||||
|
// go_memstats_heap_objects
|
||||||
|
// go_memstats_stack_inuse_bytes
|
||||||
|
// go_memstats_stack_sys_bytes
|
||||||
|
// go_memstats_mspan_inuse_bytes
|
||||||
|
// go_memstats_mspan_sys_bytes
|
||||||
|
// go_memstats_mcache_inuse_bytes
|
||||||
|
// go_memstats_mcache_sys_bytes
|
||||||
|
// go_memstats_buck_hash_sys_bytes
|
||||||
|
// go_memstats_gc_sys_bytes
|
||||||
|
// go_memstats_other_sys_bytes
|
||||||
|
// go_memstats_next_gc_bytes
|
||||||
|
//
|
||||||
|
// so the metrics known from pre client_golang v1.12.0,
|
||||||
|
//
|
||||||
|
// NOTE(bwplotka): The above represents runtime.MemStats statistics, but they are
|
||||||
|
// actually implemented using new runtime/metrics package. (except skipped go_memstats_gc_cpu_fraction
|
||||||
|
// -- see https://git.internal/re/client_golang/issues/842#issuecomment-861812034 for explanation).
|
||||||
|
//
|
||||||
|
// Some users might want to disable this on collector level (although you can use scrape relabelling on Prometheus),
|
||||||
|
// because similar metrics can be now obtained using WithGoCollectorRuntimeMetrics. Note that the semantics of new
|
||||||
|
// metrics might be different, plus the names can be change over time with different Go version.
|
||||||
|
//
|
||||||
|
// NOTE(bwplotka): Changing metric names can be tedious at times as the alerts, recording rules and dashboards have to be adjusted.
|
||||||
|
// The old metrics are also very useful, with many guides and books written about how to interpret them.
|
||||||
|
//
|
||||||
|
// As a result our recommendation would be to stick with MemStats like metrics and enable other runtime/metrics if you are interested
|
||||||
|
// in advanced insights Go provides. See ExampleGoCollector_WithAdvancedGoMetrics.
|
||||||
|
func WithGoCollectorMemStatsMetricsDisabled() func(options *internal.GoCollectorOptions) {
|
||||||
|
return func(o *internal.GoCollectorOptions) {
|
||||||
|
o.DisableMemStatsLikeMetrics = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GoRuntimeMetricsRule allow enabling and configuring particular group of runtime/metrics.
|
||||||
|
// TODO(bwplotka): Consider adding ability to adjust buckets.
|
||||||
|
type GoRuntimeMetricsRule struct {
|
||||||
|
// Matcher represents RE2 expression will match the runtime/metrics from https://golang.bg/src/runtime/metrics/description.go
|
||||||
|
// Use `regexp.MustCompile` or `regexp.Compile` to create this field.
|
||||||
|
Matcher *regexp.Regexp
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithGoCollectorRuntimeMetrics allows enabling and configuring particular group of runtime/metrics.
|
||||||
|
// See the list of metrics https://golang.bg/src/runtime/metrics/description.go (pick the Go version you use there!).
|
||||||
|
// You can use this option in repeated manner, which will add new rules. The order of rules is important, the last rule
|
||||||
|
// that matches particular metrics is applied.
|
||||||
|
func WithGoCollectorRuntimeMetrics(rules ...GoRuntimeMetricsRule) func(options *internal.GoCollectorOptions) {
|
||||||
|
rs := make([]internal.GoCollectorRule, len(rules))
|
||||||
|
for i, r := range rules {
|
||||||
|
rs[i] = internal.GoCollectorRule{
|
||||||
|
Matcher: r.Matcher,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return func(o *internal.GoCollectorOptions) {
|
||||||
|
o.RuntimeMetricRules = append(o.RuntimeMetricRules, rs...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithoutGoCollectorRuntimeMetrics allows disabling group of runtime/metrics that you might have added in WithGoCollectorRuntimeMetrics.
|
||||||
|
// It behaves similarly to WithGoCollectorRuntimeMetrics just with deny-list semantics.
|
||||||
|
func WithoutGoCollectorRuntimeMetrics(matchers ...*regexp.Regexp) func(options *internal.GoCollectorOptions) {
|
||||||
|
rs := make([]internal.GoCollectorRule, len(matchers))
|
||||||
|
for i, m := range matchers {
|
||||||
|
rs[i] = internal.GoCollectorRule{
|
||||||
|
Matcher: m,
|
||||||
|
Deny: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return func(o *internal.GoCollectorOptions) {
|
||||||
|
o.RuntimeMetricRules = append(o.RuntimeMetricRules, rs...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GoCollectionOption represents Go collection option flag.
|
||||||
|
// Deprecated.
|
||||||
type GoCollectionOption uint32
|
type GoCollectionOption uint32
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// GoRuntimeMemStatsCollection represents the metrics represented by runtime.MemStats structure such as
|
// GoRuntimeMemStatsCollection represents the metrics represented by runtime.MemStats structure.
|
||||||
// go_memstats_alloc_bytes
|
// Deprecated. Use WithGoCollectorMemStatsMetricsDisabled() function to disable those metrics in the collector.
|
||||||
// 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
|
|
||||||
// go_memstats_heap_sys_bytes
|
|
||||||
// go_memstats_heap_idle_bytes
|
|
||||||
// go_memstats_heap_inuse_bytes
|
|
||||||
// go_memstats_heap_released_bytes
|
|
||||||
// go_memstats_heap_objects
|
|
||||||
// go_memstats_stack_inuse_bytes
|
|
||||||
// go_memstats_stack_sys_bytes
|
|
||||||
// go_memstats_mspan_inuse_bytes
|
|
||||||
// go_memstats_mspan_sys_bytes
|
|
||||||
// go_memstats_mcache_inuse_bytes
|
|
||||||
// go_memstats_mcache_sys_bytes
|
|
||||||
// go_memstats_buck_hash_sys_bytes
|
|
||||||
// go_memstats_gc_sys_bytes
|
|
||||||
// go_memstats_other_sys_bytes
|
|
||||||
// go_memstats_next_gc_bytes
|
|
||||||
// so the metrics known from pre client_golang v1.12.0, except skipped go_memstats_gc_cpu_fraction (see
|
|
||||||
// https://github.com/prometheus/client_golang/issues/842#issuecomment-861812034 for explanation.
|
|
||||||
//
|
|
||||||
// NOTE that this mode represents runtime.MemStats statistics, but they are
|
|
||||||
// actually implemented using new runtime/metrics package.
|
|
||||||
// Deprecated: Use GoRuntimeMetricsCollection instead going forward.
|
|
||||||
GoRuntimeMemStatsCollection GoCollectionOption = 1 << iota
|
GoRuntimeMemStatsCollection GoCollectionOption = 1 << iota
|
||||||
// GoRuntimeMetricsCollection is the new set of metrics represented by runtime/metrics package and follows
|
// GoRuntimeMetricsCollection is the new set of metrics represented by runtime/metrics package.
|
||||||
// consistent naming. The exposed metric set depends on Go version, but it is controlled against
|
// Deprecated. Use WithGoCollectorRuntimeMetrics(GoRuntimeMetricsRule{Matcher: regexp.MustCompile("/.*")})
|
||||||
// unexpected cardinality. This set has overlapping information with GoRuntimeMemStatsCollection, just with
|
// function to enable those metrics in the collector.
|
||||||
// new names. GoRuntimeMetricsCollection is what is recommended for using going forward.
|
|
||||||
GoRuntimeMetricsCollection
|
GoRuntimeMetricsCollection
|
||||||
)
|
)
|
||||||
|
|
||||||
// WithGoCollections allows enabling different collections for Go collector on top of base metrics
|
// WithGoCollections allows enabling different collections for Go collector on top of base metrics.
|
||||||
// like go_goroutines, go_threads, go_gc_duration_seconds, go_memstats_last_gc_time_seconds, go_info.
|
// Deprecated. Use WithGoCollectorRuntimeMetrics() and WithGoCollectorMemStatsMetricsDisabled() instead to control metrics.
|
||||||
//
|
func WithGoCollections(flags GoCollectionOption) func(options *internal.GoCollectorOptions) {
|
||||||
// Check GoRuntimeMemStatsCollection and GoRuntimeMetricsCollection for more details. You can use none,
|
return func(options *internal.GoCollectorOptions) {
|
||||||
// one or more collections at once. For example:
|
if flags&GoRuntimeMemStatsCollection == 0 {
|
||||||
// WithGoCollections(GoRuntimeMemStatsCollection | GoRuntimeMetricsCollection) means both GoRuntimeMemStatsCollection
|
WithGoCollectorMemStatsMetricsDisabled()(options)
|
||||||
// metrics and GoRuntimeMetricsCollection will be exposed.
|
}
|
||||||
//
|
|
||||||
// The current default is GoRuntimeMemStatsCollection, so the compatibility mode with
|
if flags&GoRuntimeMetricsCollection != 0 {
|
||||||
// client_golang pre v1.12 (move to runtime/metrics).
|
WithGoCollectorRuntimeMetrics(GoRuntimeMetricsRule{Matcher: regexp.MustCompile("/.*")})(options)
|
||||||
func WithGoCollections(flags GoCollectionOption) goOption {
|
}
|
||||||
return func(o *goOptions) {
|
|
||||||
o.EnabledCollections = uint32(flags)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewGoCollector returns a collector that exports metrics about the current Go
|
// NewGoCollector returns a collector that exports metrics about the current Go
|
||||||
// process using debug.GCStats using runtime/metrics.
|
// process using debug.GCStats (base metrics) and runtime/metrics (both in MemStats style and new ones).
|
||||||
func NewGoCollector(opts ...goOption) prometheus.Collector {
|
func NewGoCollector(opts ...func(o *internal.GoCollectorOptions)) prometheus.Collector {
|
||||||
//nolint:staticcheck // Ignore SA1019 until v2.
|
//nolint:staticcheck // Ignore SA1019 until v2.
|
||||||
promPkgOpts := make([]func(o *prometheus.GoCollectorOptions), len(opts))
|
return prometheus.NewGoCollector(opts...)
|
||||||
for i, opt := range opts {
|
|
||||||
promPkgOpts[i] = opt
|
|
||||||
}
|
|
||||||
//nolint:staticcheck // Ignore SA1019 until v2.
|
|
||||||
return prometheus.NewGoCollector(promPkgOpts...)
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,15 +18,31 @@ package collectors
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"reflect"
|
||||||
|
"regexp"
|
||||||
|
"sort"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/prometheus/client_golang/prometheus"
|
"git.internal/re/client_golang/prometheus"
|
||||||
|
"git.internal/re/client_golang/prometheus/promhttp"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var baseMetrics = []string{
|
||||||
|
"go_gc_duration_seconds",
|
||||||
|
"go_goroutines",
|
||||||
|
"go_info",
|
||||||
|
"go_memstats_last_gc_time_seconds",
|
||||||
|
"go_threads",
|
||||||
|
}
|
||||||
|
|
||||||
func TestGoCollectorMarshalling(t *testing.T) {
|
func TestGoCollectorMarshalling(t *testing.T) {
|
||||||
reg := prometheus.NewRegistry()
|
reg := prometheus.NewRegistry()
|
||||||
reg.MustRegister(NewGoCollector(
|
reg.MustRegister(NewGoCollector(
|
||||||
WithGoCollections(GoRuntimeMemStatsCollection | GoRuntimeMetricsCollection),
|
WithGoCollectorRuntimeMetrics(GoRuntimeMetricsRule{
|
||||||
|
Matcher: regexp.MustCompile("/.*"),
|
||||||
|
}),
|
||||||
))
|
))
|
||||||
result, err := reg.Gather()
|
result, err := reg.Gather()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -37,3 +53,181 @@ func TestGoCollectorMarshalling(t *testing.T) {
|
||||||
t.Errorf("json marshalling shoud not fail, %v", err)
|
t.Errorf("json marshalling shoud not fail, %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestWithGoCollectorMemStatsMetricsDisabled(t *testing.T) {
|
||||||
|
reg := prometheus.NewRegistry()
|
||||||
|
reg.MustRegister(NewGoCollector(
|
||||||
|
WithGoCollectorMemStatsMetricsDisabled(),
|
||||||
|
))
|
||||||
|
result, err := reg.Gather()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
got := []string{}
|
||||||
|
for _, r := range result {
|
||||||
|
got = append(got, r.GetName())
|
||||||
|
}
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(got, baseMetrics) {
|
||||||
|
t.Errorf("got %v, want %v", got, baseMetrics)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGoCollectorAllowList(t *testing.T) {
|
||||||
|
for _, test := range []struct {
|
||||||
|
name string
|
||||||
|
rules []GoRuntimeMetricsRule
|
||||||
|
expected []string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Without any rules",
|
||||||
|
rules: nil,
|
||||||
|
expected: baseMetrics,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "allow all",
|
||||||
|
rules: []GoRuntimeMetricsRule{MetricsAll},
|
||||||
|
expected: withAllMetrics(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "allow GC",
|
||||||
|
rules: []GoRuntimeMetricsRule{MetricsGC},
|
||||||
|
expected: withGCMetrics(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "allow Memory",
|
||||||
|
rules: []GoRuntimeMetricsRule{MetricsMemory},
|
||||||
|
expected: withMemoryMetrics(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "allow Scheduler",
|
||||||
|
rules: []GoRuntimeMetricsRule{MetricsScheduler},
|
||||||
|
expected: withSchedulerMetrics(),
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
reg := prometheus.NewRegistry()
|
||||||
|
reg.MustRegister(NewGoCollector(
|
||||||
|
WithGoCollectorMemStatsMetricsDisabled(),
|
||||||
|
WithGoCollectorRuntimeMetrics(test.rules...),
|
||||||
|
))
|
||||||
|
result, err := reg.Gather()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
got := []string{}
|
||||||
|
for _, r := range result {
|
||||||
|
got = append(got, r.GetName())
|
||||||
|
}
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(got, test.expected) {
|
||||||
|
t.Errorf("got %v, want %v", got, test.expected)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func withBaseMetrics(metricNames []string) []string {
|
||||||
|
metricNames = append(metricNames, baseMetrics...)
|
||||||
|
sort.Strings(metricNames)
|
||||||
|
return metricNames
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGoCollectorDenyList(t *testing.T) {
|
||||||
|
for _, test := range []struct {
|
||||||
|
name string
|
||||||
|
matchers []*regexp.Regexp
|
||||||
|
expected []string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Without any matchers",
|
||||||
|
matchers: nil,
|
||||||
|
expected: baseMetrics,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "deny all",
|
||||||
|
matchers: []*regexp.Regexp{regexp.MustCompile("/.*")},
|
||||||
|
expected: baseMetrics,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "deny gc and scheduler latency",
|
||||||
|
matchers: []*regexp.Regexp{
|
||||||
|
regexp.MustCompile("^/gc/.*"),
|
||||||
|
regexp.MustCompile("^/sched/latencies:.*"),
|
||||||
|
},
|
||||||
|
expected: baseMetrics,
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
reg := prometheus.NewRegistry()
|
||||||
|
reg.MustRegister(NewGoCollector(
|
||||||
|
WithGoCollectorMemStatsMetricsDisabled(),
|
||||||
|
WithoutGoCollectorRuntimeMetrics(test.matchers...),
|
||||||
|
))
|
||||||
|
result, err := reg.Gather()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
got := []string{}
|
||||||
|
for _, r := range result {
|
||||||
|
got = append(got, r.GetName())
|
||||||
|
}
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(got, test.expected) {
|
||||||
|
t.Errorf("got %v, want %v", got, test.expected)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleGoCollector() {
|
||||||
|
reg := prometheus.NewRegistry()
|
||||||
|
|
||||||
|
// Register the GoCollector with the default options. Only the base metrics will be enabled.
|
||||||
|
reg.MustRegister(NewGoCollector())
|
||||||
|
|
||||||
|
http.Handle("/metrics", promhttp.HandlerFor(reg, promhttp.HandlerOpts{}))
|
||||||
|
log.Fatal(http.ListenAndServe(":8080", nil))
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleGoCollector_WithAdvancedGoMetrics() {
|
||||||
|
reg := prometheus.NewRegistry()
|
||||||
|
|
||||||
|
// Enable Go metrics with pre-defined rules. Or your custom rules.
|
||||||
|
reg.MustRegister(
|
||||||
|
NewGoCollector(
|
||||||
|
WithGoCollectorMemStatsMetricsDisabled(),
|
||||||
|
WithGoCollectorRuntimeMetrics(
|
||||||
|
MetricsScheduler,
|
||||||
|
MetricsMemory,
|
||||||
|
GoRuntimeMetricsRule{
|
||||||
|
Matcher: regexp.MustCompile("^/mycustomrule.*"),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
WithoutGoCollectorRuntimeMetrics(regexp.MustCompile("^/gc/.*")),
|
||||||
|
))
|
||||||
|
|
||||||
|
http.Handle("/metrics", promhttp.HandlerFor(reg, promhttp.HandlerOpts{}))
|
||||||
|
log.Fatal(http.ListenAndServe(":8080", nil))
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleGoCollector_DefaultRegister() {
|
||||||
|
// Unregister the default GoCollector.
|
||||||
|
prometheus.Unregister(NewGoCollector())
|
||||||
|
|
||||||
|
// Register the default GoCollector with a custom config.
|
||||||
|
prometheus.MustRegister(NewGoCollector(WithGoCollectorRuntimeMetrics(
|
||||||
|
MetricsScheduler,
|
||||||
|
MetricsGC,
|
||||||
|
GoRuntimeMetricsRule{
|
||||||
|
Matcher: regexp.MustCompile("^/mycustomrule.*"),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
))
|
||||||
|
|
||||||
|
http.Handle("/metrics", promhttp.Handler())
|
||||||
|
log.Fatal(http.ListenAndServe(":8080", nil))
|
||||||
|
}
|
||||||
|
|
|
@ -13,7 +13,7 @@
|
||||||
|
|
||||||
package collectors
|
package collectors
|
||||||
|
|
||||||
import "github.com/prometheus/client_golang/prometheus"
|
import "git.internal/re/client_golang/prometheus"
|
||||||
|
|
||||||
// ProcessCollectorOpts defines the behavior of a process metrics collector
|
// ProcessCollectorOpts defines the behavior of a process metrics collector
|
||||||
// created with NewProcessCollector.
|
// created with NewProcessCollector.
|
||||||
|
|
|
@ -51,7 +51,7 @@ type Counter interface {
|
||||||
// will lead to a valid (label-less) exemplar. But if Labels is nil, the current
|
// will lead to a valid (label-less) exemplar. But if Labels is nil, the current
|
||||||
// exemplar is left in place. AddWithExemplar panics if the value is < 0, if any
|
// exemplar is left in place. AddWithExemplar panics if the value is < 0, if any
|
||||||
// of the provided labels are invalid, or if the provided labels contain more
|
// of the provided labels are invalid, or if the provided labels contain more
|
||||||
// than 64 runes in total.
|
// than 128 runes in total.
|
||||||
type ExemplarAdder interface {
|
type ExemplarAdder interface {
|
||||||
AddWithExemplar(value float64, exemplar Labels)
|
AddWithExemplar(value float64, exemplar Labels)
|
||||||
}
|
}
|
||||||
|
@ -140,12 +140,13 @@ func (c *counter) get() float64 {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *counter) Write(out *dto.Metric) error {
|
func (c *counter) Write(out *dto.Metric) error {
|
||||||
val := c.get()
|
// Read the Exemplar first and the value second. This is to avoid a race condition
|
||||||
|
// where users see an exemplar for a not-yet-existing observation.
|
||||||
var exemplar *dto.Exemplar
|
var exemplar *dto.Exemplar
|
||||||
if e := c.exemplar.Load(); e != nil {
|
if e := c.exemplar.Load(); e != nil {
|
||||||
exemplar = e.(*dto.Exemplar)
|
exemplar = e.(*dto.Exemplar)
|
||||||
}
|
}
|
||||||
|
val := c.get()
|
||||||
|
|
||||||
return populateMetric(CounterValue, val, c.labelPairs, exemplar, out)
|
return populateMetric(CounterValue, val, c.labelPairs, exemplar, out)
|
||||||
}
|
}
|
||||||
|
@ -245,7 +246,8 @@ func (v *CounterVec) GetMetricWith(labels Labels) (Counter, error) {
|
||||||
// WithLabelValues works as GetMetricWithLabelValues, but panics where
|
// WithLabelValues works as GetMetricWithLabelValues, but panics where
|
||||||
// GetMetricWithLabelValues would have returned an error. Not returning an
|
// GetMetricWithLabelValues would have returned an error. Not returning an
|
||||||
// error allows shortcuts like
|
// error allows shortcuts like
|
||||||
// myVec.WithLabelValues("404", "GET").Add(42)
|
//
|
||||||
|
// myVec.WithLabelValues("404", "GET").Add(42)
|
||||||
func (v *CounterVec) WithLabelValues(lvs ...string) Counter {
|
func (v *CounterVec) WithLabelValues(lvs ...string) Counter {
|
||||||
c, err := v.GetMetricWithLabelValues(lvs...)
|
c, err := v.GetMetricWithLabelValues(lvs...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -256,7 +258,8 @@ func (v *CounterVec) WithLabelValues(lvs ...string) Counter {
|
||||||
|
|
||||||
// With works as GetMetricWith, but panics where GetMetricWithLabels would have
|
// With works as GetMetricWith, but panics where GetMetricWithLabels would have
|
||||||
// returned an error. Not returning an error allows shortcuts like
|
// returned an error. Not returning an error allows shortcuts like
|
||||||
// myVec.With(prometheus.Labels{"code": "404", "method": "GET"}).Add(42)
|
//
|
||||||
|
// myVec.With(prometheus.Labels{"code": "404", "method": "GET"}).Add(42)
|
||||||
func (v *CounterVec) With(labels Labels) Counter {
|
func (v *CounterVec) With(labels Labels) Counter {
|
||||||
c, err := v.GetMetricWith(labels)
|
c, err := v.GetMetricWith(labels)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -16,6 +16,7 @@ package prometheus
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"math"
|
"math"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -231,7 +232,7 @@ func TestCounterExemplar(t *testing.T) {
|
||||||
}
|
}
|
||||||
expectedExemplar := &dto.Exemplar{
|
expectedExemplar := &dto.Exemplar{
|
||||||
Label: []*dto.LabelPair{
|
Label: []*dto.LabelPair{
|
||||||
&dto.LabelPair{Name: proto.String("foo"), Value: proto.String("bar")},
|
{Name: proto.String("foo"), Value: proto.String("bar")},
|
||||||
},
|
},
|
||||||
Value: proto.Float64(42),
|
Value: proto.Float64(42),
|
||||||
Timestamp: ts,
|
Timestamp: ts,
|
||||||
|
@ -262,10 +263,11 @@ func TestCounterExemplar(t *testing.T) {
|
||||||
err = e.(error)
|
err = e.(error)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
// Should panic because of 65 runes.
|
// Should panic because of 129 runes.
|
||||||
counter.AddWithExemplar(42, Labels{
|
counter.AddWithExemplar(42, Labels{
|
||||||
"abcdefghijklmnopqrstuvwxyz": "26+16 characters",
|
"abcdefghijklmnopqrstuvwxyz": "26+16 characters",
|
||||||
"x1234567": "8+15 characters",
|
"x1234567": "8+15 characters",
|
||||||
|
"z": strings.Repeat("x", 63),
|
||||||
})
|
})
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,6 +20,9 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/cespare/xxhash/v2"
|
"github.com/cespare/xxhash/v2"
|
||||||
|
|
||||||
|
"git.internal/re/client_golang/prometheus/internal"
|
||||||
|
|
||||||
//nolint:staticcheck // Ignore SA1019. Need to keep deprecated package for compatibility.
|
//nolint:staticcheck // Ignore SA1019. Need to keep deprecated package for compatibility.
|
||||||
"github.com/golang/protobuf/proto"
|
"github.com/golang/protobuf/proto"
|
||||||
"github.com/prometheus/common/model"
|
"github.com/prometheus/common/model"
|
||||||
|
@ -154,7 +157,7 @@ func NewDesc(fqName, help string, variableLabels []string, constLabels Labels) *
|
||||||
Value: proto.String(v),
|
Value: proto.String(v),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
sort.Sort(labelPairSorter(d.constLabelPairs))
|
sort.Sort(internal.LabelPairSorter(d.constLabelPairs))
|
||||||
return d
|
return d
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -21,55 +21,66 @@
|
||||||
// All exported functions and methods are safe to be used concurrently unless
|
// All exported functions and methods are safe to be used concurrently unless
|
||||||
// specified otherwise.
|
// specified otherwise.
|
||||||
//
|
//
|
||||||
// A Basic Example
|
// # A Basic Example
|
||||||
//
|
//
|
||||||
// As a starting point, a very basic usage example:
|
// As a starting point, a very basic usage example:
|
||||||
//
|
//
|
||||||
// package main
|
// package main
|
||||||
//
|
//
|
||||||
// import (
|
// import (
|
||||||
// "log"
|
// "log"
|
||||||
// "net/http"
|
// "net/http"
|
||||||
//
|
//
|
||||||
// "github.com/prometheus/client_golang/prometheus"
|
// "git.internal/re/client_golang/prometheus"
|
||||||
// "github.com/prometheus/client_golang/prometheus/promhttp"
|
// "git.internal/re/client_golang/prometheus/promhttp"
|
||||||
// )
|
// )
|
||||||
//
|
//
|
||||||
// var (
|
// type metrics struct {
|
||||||
// cpuTemp = prometheus.NewGauge(prometheus.GaugeOpts{
|
// cpuTemp prometheus.Gauge
|
||||||
// Name: "cpu_temperature_celsius",
|
// hdFailures *prometheus.CounterVec
|
||||||
// Help: "Current temperature of the CPU.",
|
// }
|
||||||
// })
|
|
||||||
// hdFailures = prometheus.NewCounterVec(
|
|
||||||
// prometheus.CounterOpts{
|
|
||||||
// Name: "hd_errors_total",
|
|
||||||
// Help: "Number of hard-disk errors.",
|
|
||||||
// },
|
|
||||||
// []string{"device"},
|
|
||||||
// )
|
|
||||||
// )
|
|
||||||
//
|
//
|
||||||
// func init() {
|
// func NewMetrics(reg prometheus.Registerer) *metrics {
|
||||||
// // Metrics have to be registered to be exposed:
|
// m := &metrics{
|
||||||
// prometheus.MustRegister(cpuTemp)
|
// cpuTemp: prometheus.NewGauge(prometheus.GaugeOpts{
|
||||||
// prometheus.MustRegister(hdFailures)
|
// Name: "cpu_temperature_celsius",
|
||||||
// }
|
// Help: "Current temperature of the CPU.",
|
||||||
|
// }),
|
||||||
|
// hdFailures: prometheus.NewCounterVec(
|
||||||
|
// prometheus.CounterOpts{
|
||||||
|
// Name: "hd_errors_total",
|
||||||
|
// Help: "Number of hard-disk errors.",
|
||||||
|
// },
|
||||||
|
// []string{"device"},
|
||||||
|
// ),
|
||||||
|
// }
|
||||||
|
// reg.MustRegister(m.cpuTemp)
|
||||||
|
// reg.MustRegister(m.hdFailures)
|
||||||
|
// return m
|
||||||
|
// }
|
||||||
//
|
//
|
||||||
// func main() {
|
// func main() {
|
||||||
// cpuTemp.Set(65.3)
|
// // Create a non-global registry.
|
||||||
// hdFailures.With(prometheus.Labels{"device":"/dev/sda"}).Inc()
|
// reg := prometheus.NewRegistry()
|
||||||
//
|
//
|
||||||
// // The Handler function provides a default handler to expose metrics
|
// // Create new metrics and register them using the custom registry.
|
||||||
// // via an HTTP server. "/metrics" is the usual endpoint for that.
|
// m := NewMetrics(reg)
|
||||||
// http.Handle("/metrics", promhttp.Handler())
|
// // Set values for the new created metrics.
|
||||||
// log.Fatal(http.ListenAndServe(":8080", nil))
|
// m.cpuTemp.Set(65.3)
|
||||||
// }
|
// m.hdFailures.With(prometheus.Labels{"device":"/dev/sda"}).Inc()
|
||||||
//
|
//
|
||||||
|
// // Expose metrics and custom registry via an HTTP server
|
||||||
|
// // using the HandleFor function. "/metrics" is the usual endpoint for that.
|
||||||
|
// http.Handle("/metrics", promhttp.HandlerFor(reg, promhttp.HandlerOpts{Registry: reg}))
|
||||||
|
// log.Fatal(http.ListenAndServe(":8080", nil))
|
||||||
|
// }
|
||||||
//
|
//
|
||||||
// This is a complete program that exports two metrics, a Gauge and a Counter,
|
// This is a complete program that exports two metrics, a Gauge and a Counter,
|
||||||
// the latter with a label attached to turn it into a (one-dimensional) vector.
|
// the latter with a label attached to turn it into a (one-dimensional) vector.
|
||||||
|
// It register the metrics using a custom registry and exposes them via an HTTP server
|
||||||
|
// on the /metrics endpoint.
|
||||||
//
|
//
|
||||||
// Metrics
|
// # Metrics
|
||||||
//
|
//
|
||||||
// The number of exported identifiers in this package might appear a bit
|
// The number of exported identifiers in this package might appear a bit
|
||||||
// overwhelming. However, in addition to the basic plumbing shown in the example
|
// overwhelming. However, in addition to the basic plumbing shown in the example
|
||||||
|
@ -100,7 +111,7 @@
|
||||||
// To create instances of Metrics and their vector versions, you need a suitable
|
// To create instances of Metrics and their vector versions, you need a suitable
|
||||||
// …Opts struct, i.e. GaugeOpts, CounterOpts, SummaryOpts, or HistogramOpts.
|
// …Opts struct, i.e. GaugeOpts, CounterOpts, SummaryOpts, or HistogramOpts.
|
||||||
//
|
//
|
||||||
// Custom Collectors and constant Metrics
|
// # Custom Collectors and constant Metrics
|
||||||
//
|
//
|
||||||
// While you could create your own implementations of Metric, most likely you
|
// While you could create your own implementations of Metric, most likely you
|
||||||
// will only ever implement the Collector interface on your own. At a first
|
// will only ever implement the Collector interface on your own. At a first
|
||||||
|
@ -141,7 +152,7 @@
|
||||||
// a metric, GaugeFunc, CounterFunc, or UntypedFunc might be interesting
|
// a metric, GaugeFunc, CounterFunc, or UntypedFunc might be interesting
|
||||||
// shortcuts.
|
// shortcuts.
|
||||||
//
|
//
|
||||||
// Advanced Uses of the Registry
|
// # Advanced Uses of the Registry
|
||||||
//
|
//
|
||||||
// While MustRegister is the by far most common way of registering a Collector,
|
// While MustRegister is the by far most common way of registering a Collector,
|
||||||
// sometimes you might want to handle the errors the registration might cause.
|
// sometimes you might want to handle the errors the registration might cause.
|
||||||
|
@ -176,23 +187,23 @@
|
||||||
// NewProcessCollector). With a custom registry, you are in control and decide
|
// NewProcessCollector). With a custom registry, you are in control and decide
|
||||||
// yourself about the Collectors to register.
|
// yourself about the Collectors to register.
|
||||||
//
|
//
|
||||||
// HTTP Exposition
|
// # HTTP Exposition
|
||||||
//
|
//
|
||||||
// The Registry implements the Gatherer interface. The caller of the Gather
|
// The Registry implements the Gatherer interface. The caller of the Gather
|
||||||
// method can then expose the gathered metrics in some way. Usually, the metrics
|
// method can then expose the gathered metrics in some way. Usually, the metrics
|
||||||
// are served via HTTP on the /metrics endpoint. That's happening in the example
|
// are served via HTTP on the /metrics endpoint. That's happening in the example
|
||||||
// above. The tools to expose metrics via HTTP are in the promhttp sub-package.
|
// above. The tools to expose metrics via HTTP are in the promhttp sub-package.
|
||||||
//
|
//
|
||||||
// Pushing to the Pushgateway
|
// # Pushing to the Pushgateway
|
||||||
//
|
//
|
||||||
// Function for pushing to the Pushgateway can be found in the push sub-package.
|
// Function for pushing to the Pushgateway can be found in the push sub-package.
|
||||||
//
|
//
|
||||||
// Graphite Bridge
|
// # Graphite Bridge
|
||||||
//
|
//
|
||||||
// Functions and examples to push metrics from a Gatherer to Graphite can be
|
// Functions and examples to push metrics from a Gatherer to Graphite can be
|
||||||
// found in the graphite sub-package.
|
// found in the graphite sub-package.
|
||||||
//
|
//
|
||||||
// Other Means of Exposition
|
// # Other Means of Exposition
|
||||||
//
|
//
|
||||||
// More ways of exposing metrics can easily be added by following the approaches
|
// More ways of exposing metrics can easily be added by following the approaches
|
||||||
// of the existing implementations.
|
// of the existing implementations.
|
||||||
|
|
|
@ -17,8 +17,8 @@ import (
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/prometheus/client_golang/prometheus"
|
"git.internal/re/client_golang/prometheus"
|
||||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
"git.internal/re/client_golang/prometheus/promhttp"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ClusterManager is an example for a system that might have been built without
|
// ClusterManager is an example for a system that might have been built without
|
||||||
|
|
|
@ -21,7 +21,7 @@ import (
|
||||||
|
|
||||||
dto "github.com/prometheus/client_model/go"
|
dto "github.com/prometheus/client_model/go"
|
||||||
|
|
||||||
"github.com/prometheus/client_golang/prometheus"
|
"git.internal/re/client_golang/prometheus"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Info implements an info pseudo-metric, which is modeled as a Gauge that
|
// Info implements an info pseudo-metric, which is modeled as a Gauge that
|
||||||
|
@ -108,7 +108,6 @@ func (v *InfoVec) MustCurryWith(labels prometheus.Labels) *InfoVec {
|
||||||
}
|
}
|
||||||
|
|
||||||
func ExampleMetricVec() {
|
func ExampleMetricVec() {
|
||||||
|
|
||||||
infoVec := NewInfoVec(
|
infoVec := NewInfoVec(
|
||||||
"library_version_info",
|
"library_version_info",
|
||||||
"Versions of the libraries used in this binary.",
|
"Versions of the libraries used in this binary.",
|
||||||
|
|
|
@ -16,30 +16,28 @@ package prometheus_test
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/prometheus/client_golang/prometheus"
|
"git.internal/re/client_golang/prometheus"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
// apiRequestDuration tracks the duration separate for each HTTP status
|
||||||
// apiRequestDuration tracks the duration separate for each HTTP status
|
// class (1xx, 2xx, ...). This creates a fair amount of time series on
|
||||||
// class (1xx, 2xx, ...). This creates a fair amount of time series on
|
// the Prometheus server. Usually, you would track the duration of
|
||||||
// the Prometheus server. Usually, you would track the duration of
|
// serving HTTP request without partitioning by outcome. Do something
|
||||||
// serving HTTP request without partitioning by outcome. Do something
|
// like this only if needed. Also note how only status classes are
|
||||||
// like this only if needed. Also note how only status classes are
|
// tracked, not every single status code. The latter would create an
|
||||||
// tracked, not every single status code. The latter would create an
|
// even larger amount of time series. Request counters partitioned by
|
||||||
// even larger amount of time series. Request counters partitioned by
|
// status code are usually OK as each counter only creates one time
|
||||||
// status code are usually OK as each counter only creates one time
|
// series. Histograms are way more expensive, so partition with care and
|
||||||
// series. Histograms are way more expensive, so partition with care and
|
// only where you really need separate latency tracking. Partitioning by
|
||||||
// only where you really need separate latency tracking. Partitioning by
|
// status class is only an example. In concrete cases, other partitions
|
||||||
// status class is only an example. In concrete cases, other partitions
|
// might make more sense.
|
||||||
// might make more sense.
|
var apiRequestDuration = prometheus.NewHistogramVec(
|
||||||
apiRequestDuration = prometheus.NewHistogramVec(
|
prometheus.HistogramOpts{
|
||||||
prometheus.HistogramOpts{
|
Name: "api_request_duration_seconds",
|
||||||
Name: "api_request_duration_seconds",
|
Help: "Histogram for the request duration of the public API, partitioned by status class.",
|
||||||
Help: "Histogram for the request duration of the public API, partitioned by status class.",
|
Buckets: prometheus.ExponentialBuckets(0.1, 1.5, 5),
|
||||||
Buckets: prometheus.ExponentialBuckets(0.1, 1.5, 5),
|
},
|
||||||
},
|
[]string{"status_class"},
|
||||||
[]string{"status_class"},
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func handler(w http.ResponseWriter, r *http.Request) {
|
func handler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
|
@ -16,20 +16,18 @@ package prometheus_test
|
||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/prometheus/client_golang/prometheus"
|
"git.internal/re/client_golang/prometheus"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
// If a function is called rarely (i.e. not more often than scrapes
|
||||||
// If a function is called rarely (i.e. not more often than scrapes
|
// happen) or ideally only once (like in a batch job), it can make sense
|
||||||
// happen) or ideally only once (like in a batch job), it can make sense
|
// to use a Gauge for timing the function call. For timing a batch job
|
||||||
// to use a Gauge for timing the function call. For timing a batch job
|
// and pushing the result to a Pushgateway, see also the comprehensive
|
||||||
// and pushing the result to a Pushgateway, see also the comprehensive
|
// example in the push package.
|
||||||
// example in the push package.
|
var funcDuration = prometheus.NewGauge(prometheus.GaugeOpts{
|
||||||
funcDuration = prometheus.NewGauge(prometheus.GaugeOpts{
|
Name: "example_function_duration_seconds",
|
||||||
Name: "example_function_duration_seconds",
|
Help: "Duration of the last call of an example function.",
|
||||||
Help: "Duration of the last call of an example function.",
|
})
|
||||||
})
|
|
||||||
)
|
|
||||||
|
|
||||||
func run() error {
|
func run() error {
|
||||||
// The Set method of the Gauge is used to observe the duration.
|
// The Set method of the Gauge is used to observe the duration.
|
||||||
|
|
|
@ -17,16 +17,14 @@ import (
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/prometheus/client_golang/prometheus"
|
"git.internal/re/client_golang/prometheus"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var requestDuration = prometheus.NewHistogram(prometheus.HistogramOpts{
|
||||||
requestDuration = prometheus.NewHistogram(prometheus.HistogramOpts{
|
Name: "example_request_duration_seconds",
|
||||||
Name: "example_request_duration_seconds",
|
Help: "Histogram for the runtime of a simple example function.",
|
||||||
Help: "Histogram for the runtime of a simple example function.",
|
Buckets: prometheus.LinearBuckets(0.01, 0.01, 10),
|
||||||
Buckets: prometheus.LinearBuckets(0.01, 0.01, 10),
|
})
|
||||||
})
|
|
||||||
)
|
|
||||||
|
|
||||||
func ExampleTimer() {
|
func ExampleTimer() {
|
||||||
// timer times this example function. It uses a Histogram, but a Summary
|
// timer times this example function. It uses a Histogram, but a Summary
|
||||||
|
|
|
@ -15,6 +15,7 @@ package prometheus_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"math"
|
"math"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
@ -24,12 +25,11 @@ import (
|
||||||
|
|
||||||
//nolint:staticcheck // Ignore SA1019. Need to keep deprecated package for compatibility.
|
//nolint:staticcheck // Ignore SA1019. Need to keep deprecated package for compatibility.
|
||||||
"github.com/golang/protobuf/proto"
|
"github.com/golang/protobuf/proto"
|
||||||
|
dto "github.com/prometheus/client_model/go"
|
||||||
"github.com/prometheus/common/expfmt"
|
"github.com/prometheus/common/expfmt"
|
||||||
|
|
||||||
dto "github.com/prometheus/client_model/go"
|
"git.internal/re/client_golang/prometheus"
|
||||||
|
"git.internal/re/client_golang/prometheus/promhttp"
|
||||||
"github.com/prometheus/client_golang/prometheus"
|
|
||||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func ExampleGauge() {
|
func ExampleGauge() {
|
||||||
|
@ -599,13 +599,123 @@ func ExampleNewConstHistogram() {
|
||||||
// >
|
// >
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ExampleNewConstHistogram_WithExemplar() {
|
||||||
|
desc := prometheus.NewDesc(
|
||||||
|
"http_request_duration_seconds",
|
||||||
|
"A histogram of the HTTP request durations.",
|
||||||
|
[]string{"code", "method"},
|
||||||
|
prometheus.Labels{"owner": "example"},
|
||||||
|
)
|
||||||
|
|
||||||
|
// Create a constant histogram from values we got from a 3rd party telemetry system.
|
||||||
|
h := prometheus.MustNewConstHistogram(
|
||||||
|
desc,
|
||||||
|
4711, 403.34,
|
||||||
|
map[float64]uint64{25: 121, 50: 2403, 100: 3221, 200: 4233},
|
||||||
|
"200", "get",
|
||||||
|
)
|
||||||
|
|
||||||
|
// Wrap const histogram with exemplars for each bucket.
|
||||||
|
exemplarTs, _ := time.Parse(time.RFC850, "Monday, 02-Jan-06 15:04:05 GMT")
|
||||||
|
exemplarLabels := prometheus.Labels{"testName": "testVal"}
|
||||||
|
h = prometheus.MustNewMetricWithExemplars(
|
||||||
|
h,
|
||||||
|
prometheus.Exemplar{Labels: exemplarLabels, Timestamp: exemplarTs, Value: 24.0},
|
||||||
|
prometheus.Exemplar{Labels: exemplarLabels, Timestamp: exemplarTs, Value: 42.0},
|
||||||
|
prometheus.Exemplar{Labels: exemplarLabels, Timestamp: exemplarTs, Value: 89.0},
|
||||||
|
prometheus.Exemplar{Labels: exemplarLabels, Timestamp: exemplarTs, Value: 157.0},
|
||||||
|
)
|
||||||
|
|
||||||
|
// 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(proto.MarshalTextString(metric))
|
||||||
|
|
||||||
|
// Output:
|
||||||
|
// label: <
|
||||||
|
// name: "code"
|
||||||
|
// value: "200"
|
||||||
|
// >
|
||||||
|
// label: <
|
||||||
|
// name: "method"
|
||||||
|
// value: "get"
|
||||||
|
// >
|
||||||
|
// label: <
|
||||||
|
// name: "owner"
|
||||||
|
// value: "example"
|
||||||
|
// >
|
||||||
|
// histogram: <
|
||||||
|
// sample_count: 4711
|
||||||
|
// sample_sum: 403.34
|
||||||
|
// bucket: <
|
||||||
|
// cumulative_count: 121
|
||||||
|
// upper_bound: 25
|
||||||
|
// exemplar: <
|
||||||
|
// label: <
|
||||||
|
// name: "testName"
|
||||||
|
// value: "testVal"
|
||||||
|
// >
|
||||||
|
// value: 24
|
||||||
|
// timestamp: <
|
||||||
|
// seconds: 1136214245
|
||||||
|
// >
|
||||||
|
// >
|
||||||
|
// >
|
||||||
|
// bucket: <
|
||||||
|
// cumulative_count: 2403
|
||||||
|
// upper_bound: 50
|
||||||
|
// exemplar: <
|
||||||
|
// label: <
|
||||||
|
// name: "testName"
|
||||||
|
// value: "testVal"
|
||||||
|
// >
|
||||||
|
// value: 42
|
||||||
|
// timestamp: <
|
||||||
|
// seconds: 1136214245
|
||||||
|
// >
|
||||||
|
// >
|
||||||
|
// >
|
||||||
|
// bucket: <
|
||||||
|
// cumulative_count: 3221
|
||||||
|
// upper_bound: 100
|
||||||
|
// exemplar: <
|
||||||
|
// label: <
|
||||||
|
// name: "testName"
|
||||||
|
// value: "testVal"
|
||||||
|
// >
|
||||||
|
// value: 89
|
||||||
|
// timestamp: <
|
||||||
|
// seconds: 1136214245
|
||||||
|
// >
|
||||||
|
// >
|
||||||
|
// >
|
||||||
|
// bucket: <
|
||||||
|
// cumulative_count: 4233
|
||||||
|
// upper_bound: 200
|
||||||
|
// exemplar: <
|
||||||
|
// label: <
|
||||||
|
// name: "testName"
|
||||||
|
// value: "testVal"
|
||||||
|
// >
|
||||||
|
// value: 157
|
||||||
|
// timestamp: <
|
||||||
|
// seconds: 1136214245
|
||||||
|
// >
|
||||||
|
// >
|
||||||
|
// >
|
||||||
|
// >
|
||||||
|
}
|
||||||
|
|
||||||
func ExampleAlreadyRegisteredError() {
|
func ExampleAlreadyRegisteredError() {
|
||||||
reqCounter := prometheus.NewCounter(prometheus.CounterOpts{
|
reqCounter := prometheus.NewCounter(prometheus.CounterOpts{
|
||||||
Name: "requests_total",
|
Name: "requests_total",
|
||||||
Help: "The total number of requests served.",
|
Help: "The total number of requests served.",
|
||||||
})
|
})
|
||||||
if err := prometheus.Register(reqCounter); err != nil {
|
if err := prometheus.Register(reqCounter); err != nil {
|
||||||
if are, ok := err.(prometheus.AlreadyRegisteredError); ok {
|
are := &prometheus.AlreadyRegisteredError{}
|
||||||
|
if errors.As(err, are) {
|
||||||
// A counter for that metric has been registered before.
|
// A counter for that metric has been registered before.
|
||||||
// Use the old counter from now on.
|
// Use the old counter from now on.
|
||||||
reqCounter = are.ExistingCollector.(prometheus.Counter)
|
reqCounter = are.ExistingCollector.(prometheus.Counter)
|
||||||
|
|
|
@ -21,7 +21,7 @@ import (
|
||||||
|
|
||||||
dto "github.com/prometheus/client_model/go"
|
dto "github.com/prometheus/client_model/go"
|
||||||
|
|
||||||
"github.com/prometheus/client_golang/prometheus"
|
"git.internal/re/client_golang/prometheus"
|
||||||
)
|
)
|
||||||
|
|
||||||
func ExampleNewExpvarCollector() {
|
func ExampleNewExpvarCollector() {
|
||||||
|
|
|
@ -210,7 +210,8 @@ func (v *GaugeVec) GetMetricWith(labels Labels) (Gauge, error) {
|
||||||
// WithLabelValues works as GetMetricWithLabelValues, but panics where
|
// WithLabelValues works as GetMetricWithLabelValues, but panics where
|
||||||
// GetMetricWithLabelValues would have returned an error. Not returning an
|
// GetMetricWithLabelValues would have returned an error. Not returning an
|
||||||
// error allows shortcuts like
|
// error allows shortcuts like
|
||||||
// myVec.WithLabelValues("404", "GET").Add(42)
|
//
|
||||||
|
// myVec.WithLabelValues("404", "GET").Add(42)
|
||||||
func (v *GaugeVec) WithLabelValues(lvs ...string) Gauge {
|
func (v *GaugeVec) WithLabelValues(lvs ...string) Gauge {
|
||||||
g, err := v.GetMetricWithLabelValues(lvs...)
|
g, err := v.GetMetricWithLabelValues(lvs...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -221,7 +222,8 @@ func (v *GaugeVec) WithLabelValues(lvs ...string) Gauge {
|
||||||
|
|
||||||
// With works as GetMetricWith, but panics where GetMetricWithLabels would have
|
// With works as GetMetricWith, but panics where GetMetricWithLabels would have
|
||||||
// returned an error. Not returning an error allows shortcuts like
|
// returned an error. Not returning an error allows shortcuts like
|
||||||
// myVec.With(prometheus.Labels{"code": "404", "method": "GET"}).Add(42)
|
//
|
||||||
|
// myVec.With(prometheus.Labels{"code": "404", "method": "GET"}).Add(42)
|
||||||
func (v *GaugeVec) With(labels Labels) Gauge {
|
func (v *GaugeVec) With(labels Labels) Gauge {
|
||||||
g, err := v.GetMetricWith(labels)
|
g, err := v.GetMetricWith(labels)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -25,26 +25,41 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"runtime"
|
"runtime"
|
||||||
"runtime/metrics"
|
"runtime/metrics"
|
||||||
"strconv"
|
|
||||||
"strings"
|
"strings"
|
||||||
"text/template"
|
"text/template"
|
||||||
|
|
||||||
"github.com/prometheus/client_golang/prometheus"
|
"git.internal/re/client_golang/prometheus"
|
||||||
"github.com/prometheus/client_golang/prometheus/internal"
|
"git.internal/re/client_golang/prometheus/internal"
|
||||||
|
|
||||||
|
"github.com/hashicorp/go-version"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
if len(os.Args) != 2 {
|
var givenVersion string
|
||||||
log.Fatal("requires Go version (e.g. go1.17) as an argument")
|
|
||||||
}
|
|
||||||
toolVersion := runtime.Version()
|
toolVersion := runtime.Version()
|
||||||
if majorVersion := toolVersion[:strings.LastIndexByte(toolVersion, '.')]; majorVersion != os.Args[1] {
|
if len(os.Args) != 2 {
|
||||||
log.Fatalf("using Go version %q but expected Go version %q", majorVersion, os.Args[1])
|
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]
|
||||||
}
|
}
|
||||||
version, err := parseVersion(mv)
|
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 {
|
if err != nil {
|
||||||
log.Fatalf("parsing Go version: %v", err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
gv, err := version.NewVersion(strings.TrimPrefix(toolVersion, "go"))
|
||||||
|
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)
|
||||||
|
|
||||||
// Generate code.
|
// Generate code.
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
|
@ -54,7 +69,7 @@ func main() {
|
||||||
Cardinality int
|
Cardinality int
|
||||||
}{
|
}{
|
||||||
Descriptions: metrics.All(),
|
Descriptions: metrics.All(),
|
||||||
GoVersion: version,
|
GoVersion: v,
|
||||||
Cardinality: rmCardinality(),
|
Cardinality: rmCardinality(),
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -68,7 +83,7 @@ func main() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write it to a file.
|
// Write it to a file.
|
||||||
fname := fmt.Sprintf("go_collector_metrics_%s_test.go", version.Abbr())
|
fname := fmt.Sprintf("go_collector_metrics_%s_test.go", v.Abbr())
|
||||||
if err := os.WriteFile(fname, result, 0o644); err != nil {
|
if err := os.WriteFile(fname, result, 0o644); err != nil {
|
||||||
log.Fatalf("writing file: %v", err)
|
log.Fatalf("writing file: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -84,15 +99,6 @@ func (g goVersion) Abbr() string {
|
||||||
return fmt.Sprintf("go1%d", g)
|
return fmt.Sprintf("go1%d", g)
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseVersion(s string) (goVersion, error) {
|
|
||||||
i := strings.IndexRune(s, '.')
|
|
||||||
if i < 0 {
|
|
||||||
return goVersion(-1), fmt.Errorf("bad Go version format")
|
|
||||||
}
|
|
||||||
i, err := strconv.Atoi(s[i+1:])
|
|
||||||
return goVersion(i), err
|
|
||||||
}
|
|
||||||
|
|
||||||
func rmCardinality() int {
|
func rmCardinality() int {
|
||||||
cardinality := 0
|
cardinality := 0
|
||||||
|
|
||||||
|
@ -117,6 +123,7 @@ func rmCardinality() int {
|
||||||
name[strings.IndexRune(name, ':')+1:],
|
name[strings.IndexRune(name, ':')+1:],
|
||||||
)
|
)
|
||||||
cardinality += len(buckets) + 3 // Plus total count, sum, and the implicit infinity bucket.
|
cardinality += len(buckets) + 3 // Plus total count, sum, and the implicit infinity bucket.
|
||||||
|
|
||||||
// runtime/metrics bucket boundaries are lower-bound-inclusive, but
|
// runtime/metrics bucket boundaries are lower-bound-inclusive, but
|
||||||
// always represents each actual *boundary* so Buckets is always
|
// always represents each actual *boundary* so Buckets is always
|
||||||
// 1 longer than Counts, while in Prometheus the mapping is one-to-one,
|
// 1 longer than Counts, while in Prometheus the mapping is one-to-one,
|
||||||
|
@ -128,6 +135,12 @@ func rmCardinality() int {
|
||||||
// We already counted the infinity bucket separately.
|
// We already counted the infinity bucket separately.
|
||||||
cardinality--
|
cardinality--
|
||||||
}
|
}
|
||||||
|
// Prometheus also doesn't have buckets for -Inf, so they need to be omitted.
|
||||||
|
// See the following PR for more information:
|
||||||
|
// https://git.internal/re/client_golang/pull/1049
|
||||||
|
if buckets[0] == math.Inf(-1) {
|
||||||
|
cardinality--
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return cardinality
|
return cardinality
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
// Copyright 2021 The Prometheus Authors
|
// Copyright 2015 The Prometheus Authors
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
// You may obtain a copy of the License at
|
// You may obtain a copy of the License at
|
||||||
|
@ -11,17 +11,16 @@
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
//go:build !go1.15
|
//go:build !js || wasm
|
||||||
// +build !go1.15
|
// +build !js wasm
|
||||||
|
|
||||||
package collectors
|
package prometheus
|
||||||
|
|
||||||
import (
|
import "os"
|
||||||
"database/sql"
|
|
||||||
|
|
||||||
"github.com/prometheus/client_golang/prometheus"
|
func getPIDFn() func() (int, error) {
|
||||||
)
|
pid := os.Getpid()
|
||||||
|
return func() (int, error) {
|
||||||
func (c *dbStatsCollector) describeNewInGo115(ch chan<- *prometheus.Desc) {}
|
return pid, nil
|
||||||
|
}
|
||||||
func (c *dbStatsCollector) collectNewInGo115(ch chan<- prometheus.Metric, stats sql.DBStats) {}
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
// Copyright 2015 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 js && !wasm
|
||||||
|
// +build js,!wasm
|
||||||
|
|
||||||
|
package prometheus
|
||||||
|
|
||||||
|
func getPIDFn() func() (int, error) {
|
||||||
|
return func() (int, error) {
|
||||||
|
return 1, nil
|
||||||
|
}
|
||||||
|
}
|
|
@ -19,6 +19,10 @@ import (
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// 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.
|
||||||
func goRuntimeMemStats() memStatsMetrics {
|
func goRuntimeMemStats() memStatsMetrics {
|
||||||
return memStatsMetrics{
|
return memStatsMetrics{
|
||||||
{
|
{
|
||||||
|
@ -224,7 +228,7 @@ func newBaseGoCollector() baseGoCollector {
|
||||||
"A summary of the pause duration of garbage collection cycles.",
|
"A summary of the pause duration of garbage collection cycles.",
|
||||||
nil, nil),
|
nil, nil),
|
||||||
gcLastTimeDesc: NewDesc(
|
gcLastTimeDesc: NewDesc(
|
||||||
memstatNamespace("last_gc_time_seconds"),
|
"go_memstats_last_gc_time_seconds",
|
||||||
"Number of seconds since 1970 of last garbage collection.",
|
"Number of seconds since 1970 of last garbage collection.",
|
||||||
nil, nil),
|
nil, nil),
|
||||||
goInfoDesc: NewDesc(
|
goInfoDesc: NewDesc(
|
||||||
|
@ -246,8 +250,9 @@ func (c *baseGoCollector) Describe(ch chan<- *Desc) {
|
||||||
// Collect returns the current state of all metrics of the collector.
|
// Collect returns the current state of all metrics of the collector.
|
||||||
func (c *baseGoCollector) Collect(ch chan<- Metric) {
|
func (c *baseGoCollector) Collect(ch chan<- Metric) {
|
||||||
ch <- MustNewConstMetric(c.goroutinesDesc, GaugeValue, float64(runtime.NumGoroutine()))
|
ch <- MustNewConstMetric(c.goroutinesDesc, GaugeValue, float64(runtime.NumGoroutine()))
|
||||||
n, _ := runtime.ThreadCreateProfile(nil)
|
|
||||||
ch <- MustNewConstMetric(c.threadsDesc, GaugeValue, float64(n))
|
n := getRuntimeNumThreads()
|
||||||
|
ch <- MustNewConstMetric(c.threadsDesc, GaugeValue, n)
|
||||||
|
|
||||||
var stats debug.GCStats
|
var stats debug.GCStats
|
||||||
stats.PauseQuantiles = make([]time.Duration, 5)
|
stats.PauseQuantiles = make([]time.Duration, 5)
|
||||||
|
@ -269,7 +274,6 @@ func memstatNamespace(s string) string {
|
||||||
|
|
||||||
// memStatsMetrics provide description, evaluator, runtime/metrics name, and
|
// memStatsMetrics provide description, evaluator, runtime/metrics name, and
|
||||||
// value type for memstat metrics.
|
// value type for memstat metrics.
|
||||||
// TODO(bwplotka): Remove with end Go 1.16 EOL and replace with runtime/metrics.Description
|
|
||||||
type memStatsMetrics []struct {
|
type memStatsMetrics []struct {
|
||||||
desc *Desc
|
desc *Desc
|
||||||
eval func(*runtime.MemStats) float64
|
eval func(*runtime.MemStats) float64
|
||||||
|
|
|
@ -46,7 +46,7 @@ func NewGoCollector() Collector {
|
||||||
eval func(*runtime.MemStats) float64
|
eval func(*runtime.MemStats) float64
|
||||||
valType ValueType
|
valType ValueType
|
||||||
}{
|
}{
|
||||||
// This metric is omitted in Go1.17+, see https://github.com/prometheus/client_golang/issues/842#issuecomment-861812034
|
// This metric is omitted in Go1.17+, see https://git.internal/re/client_golang/issues/842#issuecomment-861812034
|
||||||
desc: NewDesc(
|
desc: NewDesc(
|
||||||
memstatNamespace("gc_cpu_fraction"),
|
memstatNamespace("gc_cpu_fraction"),
|
||||||
"The fraction of this program's available CPU time used by the GC since the program started.",
|
"The fraction of this program's available CPU time used by the GC since the program started.",
|
||||||
|
|
|
@ -27,13 +27,15 @@ import (
|
||||||
"github.com/golang/protobuf/proto"
|
"github.com/golang/protobuf/proto"
|
||||||
dto "github.com/prometheus/client_model/go"
|
dto "github.com/prometheus/client_model/go"
|
||||||
|
|
||||||
"github.com/prometheus/client_golang/prometheus/internal"
|
"git.internal/re/client_golang/prometheus/internal"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
// constants for strings referenced more than once.
|
||||||
goGCHeapTinyAllocsObjects = "/gc/heap/tiny/allocs:objects"
|
goGCHeapTinyAllocsObjects = "/gc/heap/tiny/allocs:objects"
|
||||||
goGCHeapAllocsObjects = "/gc/heap/allocs:objects"
|
goGCHeapAllocsObjects = "/gc/heap/allocs:objects"
|
||||||
goGCHeapFreesObjects = "/gc/heap/frees:objects"
|
goGCHeapFreesObjects = "/gc/heap/frees:objects"
|
||||||
|
goGCHeapFreesBytes = "/gc/heap/frees:bytes"
|
||||||
goGCHeapAllocsBytes = "/gc/heap/allocs:bytes"
|
goGCHeapAllocsBytes = "/gc/heap/allocs:bytes"
|
||||||
goGCHeapObjects = "/gc/heap/objects:objects"
|
goGCHeapObjects = "/gc/heap/objects:objects"
|
||||||
goGCHeapGoalBytes = "/gc/heap/goal:bytes"
|
goGCHeapGoalBytes = "/gc/heap/goal:bytes"
|
||||||
|
@ -53,8 +55,9 @@ const (
|
||||||
goMemoryClassesOtherBytes = "/memory/classes/other:bytes"
|
goMemoryClassesOtherBytes = "/memory/classes/other:bytes"
|
||||||
)
|
)
|
||||||
|
|
||||||
// runtime/metrics names required for runtimeMemStats like logic.
|
// rmNamesForMemStatsMetrics represents runtime/metrics names required to populate goRuntimeMemStats from like logic.
|
||||||
var rmForMemStats = []string{goGCHeapTinyAllocsObjects,
|
var rmNamesForMemStatsMetrics = []string{
|
||||||
|
goGCHeapTinyAllocsObjects,
|
||||||
goGCHeapAllocsObjects,
|
goGCHeapAllocsObjects,
|
||||||
goGCHeapFreesObjects,
|
goGCHeapFreesObjects,
|
||||||
goGCHeapAllocsBytes,
|
goGCHeapAllocsBytes,
|
||||||
|
@ -89,74 +92,90 @@ func bestEffortLookupRM(lookup []string) []metrics.Description {
|
||||||
}
|
}
|
||||||
|
|
||||||
type goCollector struct {
|
type goCollector struct {
|
||||||
opt GoCollectorOptions
|
|
||||||
base baseGoCollector
|
base baseGoCollector
|
||||||
|
|
||||||
// mu protects updates to all fields ensuring a consistent
|
// mu protects updates to all fields ensuring a consistent
|
||||||
// snapshot is always produced by Collect.
|
// snapshot is always produced by Collect.
|
||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
|
|
||||||
// rm... fields all pertain to the runtime/metrics package.
|
// Contains all samples that has to retrieved from runtime/metrics (not all of them will be exposed).
|
||||||
rmSampleBuf []metrics.Sample
|
sampleBuf []metrics.Sample
|
||||||
rmSampleMap map[string]*metrics.Sample
|
// sampleMap allows lookup for MemStats metrics and runtime/metrics histograms for exact sums.
|
||||||
rmMetrics []collectorMetric
|
sampleMap map[string]*metrics.Sample
|
||||||
|
|
||||||
|
// rmExposedMetrics represents all runtime/metrics package metrics
|
||||||
|
// that were configured to be exposed.
|
||||||
|
rmExposedMetrics []collectorMetric
|
||||||
|
rmExactSumMapForHist map[string]string
|
||||||
|
|
||||||
// With Go 1.17, the runtime/metrics package was introduced.
|
// With Go 1.17, the runtime/metrics package was introduced.
|
||||||
// From that point on, metric names produced by the runtime/metrics
|
// From that point on, metric names produced by the runtime/metrics
|
||||||
// package could be generated from runtime/metrics names. However,
|
// package could be generated from runtime/metrics names. However,
|
||||||
// these differ from the old names for the same values.
|
// these differ from the old names for the same values.
|
||||||
//
|
//
|
||||||
// This field exist to export the same values under the old names
|
// This field exists to export the same values under the old names
|
||||||
// as well.
|
// as well.
|
||||||
msMetrics memStatsMetrics
|
msMetrics memStatsMetrics
|
||||||
|
msMetricsEnabled bool
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
type rmMetricDesc struct {
|
||||||
// Those are not exposed due to need to move Go collector to another package in v2.
|
metrics.Description
|
||||||
// See issue https://github.com/prometheus/client_golang/issues/1030.
|
|
||||||
goRuntimeMemStatsCollection uint32 = 1 << iota
|
|
||||||
goRuntimeMetricsCollection
|
|
||||||
)
|
|
||||||
|
|
||||||
// GoCollectorOptions should not be used be directly by anything, except `collectors` package.
|
|
||||||
// Use it via collectors package instead. See issue
|
|
||||||
// https://github.com/prometheus/client_golang/issues/1030.
|
|
||||||
//
|
|
||||||
// Deprecated: Use collectors.WithGoCollections
|
|
||||||
type GoCollectorOptions struct {
|
|
||||||
// EnabledCollection sets what type of collections collector should expose on top of base collection.
|
|
||||||
// By default it's goMemStatsCollection | goRuntimeMetricsCollection.
|
|
||||||
EnabledCollections uint32
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c GoCollectorOptions) isEnabled(flag uint32) bool {
|
func matchRuntimeMetricsRules(rules []internal.GoCollectorRule) []rmMetricDesc {
|
||||||
return c.EnabledCollections&flag != 0
|
var descs []rmMetricDesc
|
||||||
|
for _, d := range metrics.All() {
|
||||||
|
var (
|
||||||
|
deny = true
|
||||||
|
desc rmMetricDesc
|
||||||
|
)
|
||||||
|
|
||||||
|
for _, r := range rules {
|
||||||
|
if !r.Matcher.MatchString(d.Name) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
deny = r.Deny
|
||||||
|
}
|
||||||
|
if deny {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
desc.Description = d
|
||||||
|
descs = append(descs, desc)
|
||||||
|
}
|
||||||
|
return descs
|
||||||
}
|
}
|
||||||
|
|
||||||
const defaultGoCollections = goRuntimeMemStatsCollection
|
func defaultGoCollectorOptions() internal.GoCollectorOptions {
|
||||||
|
return internal.GoCollectorOptions{
|
||||||
|
RuntimeMetricSumForHist: map[string]string{
|
||||||
|
"/gc/heap/allocs-by-size:bytes": goGCHeapAllocsBytes,
|
||||||
|
"/gc/heap/frees-by-size:bytes": goGCHeapFreesBytes,
|
||||||
|
},
|
||||||
|
RuntimeMetricRules: []internal.GoCollectorRule{
|
||||||
|
//{Matcher: regexp.MustCompile("")},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// NewGoCollector is the obsolete version of collectors.NewGoCollector.
|
// NewGoCollector is the obsolete version of collectors.NewGoCollector.
|
||||||
// See there for documentation.
|
// See there for documentation.
|
||||||
//
|
//
|
||||||
// Deprecated: Use collectors.NewGoCollector instead.
|
// Deprecated: Use collectors.NewGoCollector instead.
|
||||||
func NewGoCollector(opts ...func(o *GoCollectorOptions)) Collector {
|
func NewGoCollector(opts ...func(o *internal.GoCollectorOptions)) Collector {
|
||||||
opt := GoCollectorOptions{EnabledCollections: defaultGoCollections}
|
opt := defaultGoCollectorOptions()
|
||||||
for _, o := range opts {
|
for _, o := range opts {
|
||||||
o(&opt)
|
o(&opt)
|
||||||
}
|
}
|
||||||
|
|
||||||
var descriptions []metrics.Description
|
exposedDescriptions := matchRuntimeMetricsRules(opt.RuntimeMetricRules)
|
||||||
if opt.isEnabled(goRuntimeMetricsCollection) {
|
|
||||||
descriptions = metrics.All()
|
|
||||||
} else if opt.isEnabled(goRuntimeMemStatsCollection) {
|
|
||||||
descriptions = bestEffortLookupRM(rmForMemStats)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Collect all histogram samples so that we can get their buckets.
|
// Collect all histogram samples so that we can get their buckets.
|
||||||
// The API guarantees that the buckets are always fixed for the lifetime
|
// The API guarantees that the buckets are always fixed for the lifetime
|
||||||
// of the process.
|
// of the process.
|
||||||
var histograms []metrics.Sample
|
var histograms []metrics.Sample
|
||||||
for _, d := range descriptions {
|
for _, d := range exposedDescriptions {
|
||||||
if d.Kind == metrics.KindFloat64Histogram {
|
if d.Kind == metrics.KindFloat64Histogram {
|
||||||
histograms = append(histograms, metrics.Sample{Name: d.Name})
|
histograms = append(histograms, metrics.Sample{Name: d.Name})
|
||||||
}
|
}
|
||||||
|
@ -171,13 +190,14 @@ func NewGoCollector(opts ...func(o *GoCollectorOptions)) Collector {
|
||||||
bucketsMap[histograms[i].Name] = histograms[i].Value.Float64Histogram().Buckets
|
bucketsMap[histograms[i].Name] = histograms[i].Value.Float64Histogram().Buckets
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate a Desc and ValueType for each runtime/metrics metric.
|
// Generate a collector for each exposed runtime/metrics metric.
|
||||||
metricSet := make([]collectorMetric, 0, len(descriptions))
|
metricSet := make([]collectorMetric, 0, len(exposedDescriptions))
|
||||||
sampleBuf := make([]metrics.Sample, 0, len(descriptions))
|
// SampleBuf is used for reading from runtime/metrics.
|
||||||
sampleMap := make(map[string]*metrics.Sample, len(descriptions))
|
// We are assuming the largest case to have stable pointers for sampleMap purposes.
|
||||||
for i := range descriptions {
|
sampleBuf := make([]metrics.Sample, 0, len(exposedDescriptions)+len(opt.RuntimeMetricSumForHist)+len(rmNamesForMemStatsMetrics))
|
||||||
d := &descriptions[i]
|
sampleMap := make(map[string]*metrics.Sample, len(exposedDescriptions))
|
||||||
namespace, subsystem, name, ok := internal.RuntimeMetricsToProm(d)
|
for _, d := range exposedDescriptions {
|
||||||
|
namespace, subsystem, name, ok := internal.RuntimeMetricsToProm(&d.Description)
|
||||||
if !ok {
|
if !ok {
|
||||||
// Just ignore this metric; we can't do anything with it here.
|
// Just ignore this metric; we can't do anything with it here.
|
||||||
// If a user decides to use the latest version of Go, we don't want
|
// If a user decides to use the latest version of Go, we don't want
|
||||||
|
@ -185,19 +205,17 @@ func NewGoCollector(opts ...func(o *GoCollectorOptions)) Collector {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set up sample buffer for reading, and a map
|
|
||||||
// for quick lookup of sample values.
|
|
||||||
sampleBuf = append(sampleBuf, metrics.Sample{Name: d.Name})
|
sampleBuf = append(sampleBuf, metrics.Sample{Name: d.Name})
|
||||||
sampleMap[d.Name] = &sampleBuf[len(sampleBuf)-1]
|
sampleMap[d.Name] = &sampleBuf[len(sampleBuf)-1]
|
||||||
|
|
||||||
var m collectorMetric
|
var m collectorMetric
|
||||||
if d.Kind == metrics.KindFloat64Histogram {
|
if d.Kind == metrics.KindFloat64Histogram {
|
||||||
_, hasSum := rmExactSumMap[d.Name]
|
_, hasSum := opt.RuntimeMetricSumForHist[d.Name]
|
||||||
unit := d.Name[strings.IndexRune(d.Name, ':')+1:]
|
unit := d.Name[strings.IndexRune(d.Name, ':')+1:]
|
||||||
m = newBatchHistogram(
|
m = newBatchHistogram(
|
||||||
NewDesc(
|
NewDesc(
|
||||||
BuildFQName(namespace, subsystem, name),
|
BuildFQName(namespace, subsystem, name),
|
||||||
d.Description,
|
d.Description.Description,
|
||||||
nil,
|
nil,
|
||||||
nil,
|
nil,
|
||||||
),
|
),
|
||||||
|
@ -209,30 +227,61 @@ func NewGoCollector(opts ...func(o *GoCollectorOptions)) Collector {
|
||||||
Namespace: namespace,
|
Namespace: namespace,
|
||||||
Subsystem: subsystem,
|
Subsystem: subsystem,
|
||||||
Name: name,
|
Name: name,
|
||||||
Help: d.Description,
|
Help: d.Description.Description,
|
||||||
})
|
},
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
m = NewGauge(GaugeOpts{
|
m = NewGauge(GaugeOpts{
|
||||||
Namespace: namespace,
|
Namespace: namespace,
|
||||||
Subsystem: subsystem,
|
Subsystem: subsystem,
|
||||||
Name: name,
|
Name: name,
|
||||||
Help: d.Description,
|
Help: d.Description.Description,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
metricSet = append(metricSet, m)
|
metricSet = append(metricSet, m)
|
||||||
}
|
}
|
||||||
|
|
||||||
var msMetrics memStatsMetrics
|
// Add exact sum metrics to sampleBuf if not added before.
|
||||||
if opt.isEnabled(goRuntimeMemStatsCollection) {
|
for _, h := range histograms {
|
||||||
msMetrics = goRuntimeMemStats()
|
sumMetric, ok := opt.RuntimeMetricSumForHist[h.Name]
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, ok := sampleMap[sumMetric]; ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
sampleBuf = append(sampleBuf, metrics.Sample{Name: sumMetric})
|
||||||
|
sampleMap[sumMetric] = &sampleBuf[len(sampleBuf)-1]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
msMetrics memStatsMetrics
|
||||||
|
msDescriptions []metrics.Description
|
||||||
|
)
|
||||||
|
|
||||||
|
if !opt.DisableMemStatsLikeMetrics {
|
||||||
|
msMetrics = goRuntimeMemStats()
|
||||||
|
msDescriptions = bestEffortLookupRM(rmNamesForMemStatsMetrics)
|
||||||
|
|
||||||
|
// Check if metric was not exposed before and if not, add to sampleBuf.
|
||||||
|
for _, mdDesc := range msDescriptions {
|
||||||
|
if _, ok := sampleMap[mdDesc.Name]; ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
sampleBuf = append(sampleBuf, metrics.Sample{Name: mdDesc.Name})
|
||||||
|
sampleMap[mdDesc.Name] = &sampleBuf[len(sampleBuf)-1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return &goCollector{
|
return &goCollector{
|
||||||
opt: opt,
|
base: newBaseGoCollector(),
|
||||||
base: newBaseGoCollector(),
|
sampleBuf: sampleBuf,
|
||||||
rmSampleBuf: sampleBuf,
|
sampleMap: sampleMap,
|
||||||
rmSampleMap: sampleMap,
|
rmExposedMetrics: metricSet,
|
||||||
rmMetrics: metricSet,
|
rmExactSumMapForHist: opt.RuntimeMetricSumForHist,
|
||||||
msMetrics: msMetrics,
|
msMetrics: msMetrics,
|
||||||
|
msMetricsEnabled: !opt.DisableMemStatsLikeMetrics,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -242,7 +291,7 @@ func (c *goCollector) Describe(ch chan<- *Desc) {
|
||||||
for _, i := range c.msMetrics {
|
for _, i := range c.msMetrics {
|
||||||
ch <- i.desc
|
ch <- i.desc
|
||||||
}
|
}
|
||||||
for _, m := range c.rmMetrics {
|
for _, m := range c.rmExposedMetrics {
|
||||||
ch <- m.Desc()
|
ch <- m.Desc()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -252,8 +301,12 @@ func (c *goCollector) Collect(ch chan<- Metric) {
|
||||||
// Collect base non-memory metrics.
|
// Collect base non-memory metrics.
|
||||||
c.base.Collect(ch)
|
c.base.Collect(ch)
|
||||||
|
|
||||||
|
if len(c.sampleBuf) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// Collect must be thread-safe, so prevent concurrent use of
|
// Collect must be thread-safe, so prevent concurrent use of
|
||||||
// rmSampleBuf. Just read into rmSampleBuf but write all the data
|
// sampleBuf elements. Just read into sampleBuf but write all the data
|
||||||
// we get into our Metrics or MemStats.
|
// we get into our Metrics or MemStats.
|
||||||
//
|
//
|
||||||
// This lock also ensures that the Metrics we send out are all from
|
// This lock also ensures that the Metrics we send out are all from
|
||||||
|
@ -267,44 +320,43 @@ func (c *goCollector) Collect(ch chan<- Metric) {
|
||||||
c.mu.Lock()
|
c.mu.Lock()
|
||||||
defer c.mu.Unlock()
|
defer c.mu.Unlock()
|
||||||
|
|
||||||
if len(c.rmSampleBuf) > 0 {
|
// Populate runtime/metrics sample buffer.
|
||||||
// Populate runtime/metrics sample buffer.
|
metrics.Read(c.sampleBuf)
|
||||||
metrics.Read(c.rmSampleBuf)
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.opt.isEnabled(goRuntimeMetricsCollection) {
|
// Collect all our runtime/metrics user chose to expose from sampleBuf (if any).
|
||||||
// Collect all our metrics from rmSampleBuf.
|
for i, metric := range c.rmExposedMetrics {
|
||||||
for i, sample := range c.rmSampleBuf {
|
// We created samples for exposed metrics first in order, so indexes match.
|
||||||
// N.B. switch on concrete type because it's significantly more efficient
|
sample := c.sampleBuf[i]
|
||||||
// than checking for the Counter and Gauge interface implementations. In
|
|
||||||
// this case, we control all the types here.
|
// N.B. switch on concrete type because it's significantly more efficient
|
||||||
switch m := c.rmMetrics[i].(type) {
|
// than checking for the Counter and Gauge interface implementations. In
|
||||||
case *counter:
|
// this case, we control all the types here.
|
||||||
// Guard against decreases. This should never happen, but a failure
|
switch m := metric.(type) {
|
||||||
// to do so will result in a panic, which is a harsh consequence for
|
case *counter:
|
||||||
// a metrics collection bug.
|
// Guard against decreases. This should never happen, but a failure
|
||||||
v0, v1 := m.get(), unwrapScalarRMValue(sample.Value)
|
// to do so will result in a panic, which is a harsh consequence for
|
||||||
if v1 > v0 {
|
// a metrics collection bug.
|
||||||
m.Add(unwrapScalarRMValue(sample.Value) - m.get())
|
v0, v1 := m.get(), unwrapScalarRMValue(sample.Value)
|
||||||
}
|
if v1 > v0 {
|
||||||
m.Collect(ch)
|
m.Add(unwrapScalarRMValue(sample.Value) - m.get())
|
||||||
case *gauge:
|
|
||||||
m.Set(unwrapScalarRMValue(sample.Value))
|
|
||||||
m.Collect(ch)
|
|
||||||
case *batchHistogram:
|
|
||||||
m.update(sample.Value.Float64Histogram(), c.exactSumFor(sample.Name))
|
|
||||||
m.Collect(ch)
|
|
||||||
default:
|
|
||||||
panic("unexpected metric type")
|
|
||||||
}
|
}
|
||||||
|
m.Collect(ch)
|
||||||
|
case *gauge:
|
||||||
|
m.Set(unwrapScalarRMValue(sample.Value))
|
||||||
|
m.Collect(ch)
|
||||||
|
case *batchHistogram:
|
||||||
|
m.update(sample.Value.Float64Histogram(), c.exactSumFor(sample.Name))
|
||||||
|
m.Collect(ch)
|
||||||
|
default:
|
||||||
|
panic("unexpected metric type")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ms is a dummy MemStats that we populate ourselves so that we can
|
if c.msMetricsEnabled {
|
||||||
// populate the old metrics from it if goMemStatsCollection is enabled.
|
// ms is a dummy MemStats that we populate ourselves so that we can
|
||||||
if c.opt.isEnabled(goRuntimeMemStatsCollection) {
|
// populate the old metrics from it if goMemStatsCollection is enabled.
|
||||||
var ms runtime.MemStats
|
var ms runtime.MemStats
|
||||||
memStatsFromRM(&ms, c.rmSampleMap)
|
memStatsFromRM(&ms, c.sampleMap)
|
||||||
for _, i := range c.msMetrics {
|
for _, i := range c.msMetrics {
|
||||||
ch <- MustNewConstMetric(i.desc, i.valType, i.eval(&ms))
|
ch <- MustNewConstMetric(i.desc, i.valType, i.eval(&ms))
|
||||||
}
|
}
|
||||||
|
@ -335,11 +387,6 @@ func unwrapScalarRMValue(v metrics.Value) float64 {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var rmExactSumMap = map[string]string{
|
|
||||||
"/gc/heap/allocs-by-size:bytes": "/gc/heap/allocs:bytes",
|
|
||||||
"/gc/heap/frees-by-size:bytes": "/gc/heap/frees:bytes",
|
|
||||||
}
|
|
||||||
|
|
||||||
// exactSumFor takes a runtime/metrics metric name (that is assumed to
|
// exactSumFor takes a runtime/metrics metric name (that is assumed to
|
||||||
// be of kind KindFloat64Histogram) and returns its exact sum and whether
|
// be of kind KindFloat64Histogram) and returns its exact sum and whether
|
||||||
// its exact sum exists.
|
// its exact sum exists.
|
||||||
|
@ -347,11 +394,11 @@ var rmExactSumMap = map[string]string{
|
||||||
// The runtime/metrics API for histograms doesn't currently expose exact
|
// The runtime/metrics API for histograms doesn't currently expose exact
|
||||||
// sums, but some of the other metrics are in fact exact sums of histograms.
|
// sums, but some of the other metrics are in fact exact sums of histograms.
|
||||||
func (c *goCollector) exactSumFor(rmName string) float64 {
|
func (c *goCollector) exactSumFor(rmName string) float64 {
|
||||||
sumName, ok := rmExactSumMap[rmName]
|
sumName, ok := c.rmExactSumMapForHist[rmName]
|
||||||
if !ok {
|
if !ok {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
s, ok := c.rmSampleMap[sumName]
|
s, ok := c.sampleMap[sumName]
|
||||||
if !ok {
|
if !ok {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
@ -400,7 +447,7 @@ func memStatsFromRM(ms *runtime.MemStats, rm map[string]*metrics.Sample) {
|
||||||
// N.B. GCCPUFraction is intentionally omitted. This metric is not useful,
|
// N.B. GCCPUFraction is intentionally omitted. This metric is not useful,
|
||||||
// and often misleading due to the fact that it's an average over the lifetime
|
// and often misleading due to the fact that it's an average over the lifetime
|
||||||
// of the process.
|
// of the process.
|
||||||
// See https://github.com/prometheus/client_golang/issues/842#issuecomment-861812034
|
// See https://git.internal/re/client_golang/issues/842#issuecomment-861812034
|
||||||
// for more details.
|
// for more details.
|
||||||
ms.GCCPUFraction = 0
|
ms.GCCPUFraction = 0
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,6 +19,7 @@ package prometheus
|
||||||
import (
|
import (
|
||||||
"math"
|
"math"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"regexp"
|
||||||
"runtime"
|
"runtime"
|
||||||
"runtime/metrics"
|
"runtime/metrics"
|
||||||
"sync"
|
"sync"
|
||||||
|
@ -26,13 +27,22 @@ import (
|
||||||
|
|
||||||
dto "github.com/prometheus/client_model/go"
|
dto "github.com/prometheus/client_model/go"
|
||||||
|
|
||||||
"github.com/prometheus/client_golang/prometheus/internal"
|
"git.internal/re/client_golang/prometheus/internal"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestRmForMemStats(t *testing.T) {
|
func TestRmForMemStats(t *testing.T) {
|
||||||
if got, want := len(bestEffortLookupRM(rmForMemStats)), len(rmForMemStats); got != want {
|
descs := bestEffortLookupRM(rmNamesForMemStatsMetrics)
|
||||||
|
|
||||||
|
if got, want := len(descs), len(rmNamesForMemStatsMetrics); got != want {
|
||||||
t.Errorf("got %d, want %d metrics", got, want)
|
t.Errorf("got %d, want %d metrics", got, want)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for _, d := range descs {
|
||||||
|
// We don't expect histograms there.
|
||||||
|
if d.Kind == metrics.KindFloat64Histogram {
|
||||||
|
t.Errorf("we don't expect to use histograms for MemStats metrics, got %v", d.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func expectedBaseMetrics() map[string]struct{} {
|
func expectedBaseMetrics() map[string]struct{} {
|
||||||
|
@ -64,30 +74,43 @@ func addExpectedRuntimeMetrics(metrics map[string]struct{}) map[string]struct{}
|
||||||
return metrics
|
return metrics
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGoCollector(t *testing.T) {
|
func TestGoCollector_ExposedMetrics(t *testing.T) {
|
||||||
for _, tcase := range []struct {
|
for _, tcase := range []struct {
|
||||||
collections uint32
|
opts internal.GoCollectorOptions
|
||||||
expectedFQNameSet map[string]struct{}
|
expectedFQNameSet map[string]struct{}
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
collections: 0,
|
opts: internal.GoCollectorOptions{
|
||||||
|
DisableMemStatsLikeMetrics: true,
|
||||||
|
},
|
||||||
expectedFQNameSet: expectedBaseMetrics(),
|
expectedFQNameSet: expectedBaseMetrics(),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
collections: goRuntimeMemStatsCollection,
|
// Default, only MemStats.
|
||||||
expectedFQNameSet: addExpectedRuntimeMemStats(expectedBaseMetrics()),
|
expectedFQNameSet: addExpectedRuntimeMemStats(expectedBaseMetrics()),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
collections: goRuntimeMetricsCollection,
|
// Get all runtime/metrics without MemStats.
|
||||||
|
opts: internal.GoCollectorOptions{
|
||||||
|
DisableMemStatsLikeMetrics: true,
|
||||||
|
RuntimeMetricRules: []internal.GoCollectorRule{
|
||||||
|
{Matcher: regexp.MustCompile("/.*")},
|
||||||
|
},
|
||||||
|
},
|
||||||
expectedFQNameSet: addExpectedRuntimeMetrics(expectedBaseMetrics()),
|
expectedFQNameSet: addExpectedRuntimeMetrics(expectedBaseMetrics()),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
collections: goRuntimeMemStatsCollection | goRuntimeMetricsCollection,
|
// Get all runtime/metrics and MemStats.
|
||||||
|
opts: internal.GoCollectorOptions{
|
||||||
|
RuntimeMetricRules: []internal.GoCollectorRule{
|
||||||
|
{Matcher: regexp.MustCompile("/.*")},
|
||||||
|
},
|
||||||
|
},
|
||||||
expectedFQNameSet: addExpectedRuntimeMemStats(addExpectedRuntimeMetrics(expectedBaseMetrics())),
|
expectedFQNameSet: addExpectedRuntimeMemStats(addExpectedRuntimeMetrics(expectedBaseMetrics())),
|
||||||
},
|
},
|
||||||
} {
|
} {
|
||||||
if ok := t.Run("", func(t *testing.T) {
|
if ok := t.Run("", func(t *testing.T) {
|
||||||
goMetrics := collectGoMetrics(t, tcase.collections)
|
goMetrics := collectGoMetrics(t, tcase.opts)
|
||||||
goMetricSet := make(map[string]Metric)
|
goMetricSet := make(map[string]Metric)
|
||||||
for _, m := range goMetrics {
|
for _, m := range goMetrics {
|
||||||
goMetricSet[m.Desc().fqName] = m
|
goMetricSet[m.Desc().fqName] = m
|
||||||
|
@ -118,7 +141,11 @@ func TestGoCollector(t *testing.T) {
|
||||||
var sink interface{}
|
var sink interface{}
|
||||||
|
|
||||||
func TestBatchHistogram(t *testing.T) {
|
func TestBatchHistogram(t *testing.T) {
|
||||||
goMetrics := collectGoMetrics(t, goRuntimeMetricsCollection)
|
goMetrics := collectGoMetrics(t, internal.GoCollectorOptions{
|
||||||
|
RuntimeMetricRules: []internal.GoCollectorRule{
|
||||||
|
{Matcher: regexp.MustCompile("/.*")},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
var mhist Metric
|
var mhist Metric
|
||||||
for _, m := range goMetrics {
|
for _, m := range goMetrics {
|
||||||
|
@ -145,7 +172,8 @@ func TestBatchHistogram(t *testing.T) {
|
||||||
for i := 0; i < 100; i++ {
|
for i := 0; i < 100; i++ {
|
||||||
sink = make([]byte, 128)
|
sink = make([]byte, 128)
|
||||||
}
|
}
|
||||||
collectGoMetrics(t, defaultGoCollections)
|
|
||||||
|
collectGoMetrics(t, defaultGoCollectorOptions())
|
||||||
for i, v := range hist.counts {
|
for i, v := range hist.counts {
|
||||||
if v != countsCopy[i] {
|
if v != countsCopy[i] {
|
||||||
t.Error("counts changed during new collection")
|
t.Error("counts changed during new collection")
|
||||||
|
@ -194,11 +222,13 @@ func TestBatchHistogram(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func collectGoMetrics(t *testing.T, enabledCollections uint32) []Metric {
|
func collectGoMetrics(t *testing.T, opts internal.GoCollectorOptions) []Metric {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
c := NewGoCollector(func(o *GoCollectorOptions) {
|
c := NewGoCollector(func(o *internal.GoCollectorOptions) {
|
||||||
o.EnabledCollections = enabledCollections
|
o.DisableMemStatsLikeMetrics = opts.DisableMemStatsLikeMetrics
|
||||||
|
o.RuntimeMetricSumForHist = opts.RuntimeMetricSumForHist
|
||||||
|
o.RuntimeMetricRules = opts.RuntimeMetricRules
|
||||||
}).(*goCollector)
|
}).(*goCollector)
|
||||||
|
|
||||||
// Collect all metrics.
|
// Collect all metrics.
|
||||||
|
@ -222,7 +252,7 @@ func collectGoMetrics(t *testing.T, enabledCollections uint32) []Metric {
|
||||||
|
|
||||||
func TestMemStatsEquivalence(t *testing.T) {
|
func TestMemStatsEquivalence(t *testing.T) {
|
||||||
var msReal, msFake runtime.MemStats
|
var msReal, msFake runtime.MemStats
|
||||||
descs := bestEffortLookupRM(rmForMemStats)
|
descs := bestEffortLookupRM(rmNamesForMemStatsMetrics)
|
||||||
|
|
||||||
samples := make([]metrics.Sample, len(descs))
|
samples := make([]metrics.Sample, len(descs))
|
||||||
samplesMap := make(map[string]*metrics.Sample)
|
samplesMap := make(map[string]*metrics.Sample)
|
||||||
|
@ -269,7 +299,12 @@ func TestMemStatsEquivalence(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestExpectedRuntimeMetrics(t *testing.T) {
|
func TestExpectedRuntimeMetrics(t *testing.T) {
|
||||||
goMetrics := collectGoMetrics(t, goRuntimeMetricsCollection)
|
goMetrics := collectGoMetrics(t, internal.GoCollectorOptions{
|
||||||
|
DisableMemStatsLikeMetrics: true,
|
||||||
|
RuntimeMetricRules: []internal.GoCollectorRule{
|
||||||
|
{Matcher: regexp.MustCompile("/.*")},
|
||||||
|
},
|
||||||
|
})
|
||||||
goMetricSet := make(map[string]Metric)
|
goMetricSet := make(map[string]Metric)
|
||||||
for _, m := range goMetrics {
|
for _, m := range goMetrics {
|
||||||
goMetricSet[m.Desc().fqName] = m
|
goMetricSet[m.Desc().fqName] = m
|
||||||
|
|
|
@ -0,0 +1,45 @@
|
||||||
|
// Code generated by gen_go_collector_metrics_set.go; DO NOT EDIT.
|
||||||
|
//go:generate go run gen_go_collector_metrics_set.go go1.19
|
||||||
|
|
||||||
|
//go:build go1.19 && !go1.20
|
||||||
|
// +build go1.19,!go1.20
|
||||||
|
|
||||||
|
package prometheus
|
||||||
|
|
||||||
|
var expectedRuntimeMetrics = map[string]string{
|
||||||
|
"/cgo/go-to-c-calls:calls": "go_cgo_go_to_c_calls_calls_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",
|
||||||
|
}
|
||||||
|
|
||||||
|
const expectedRuntimeMetricsCardinality = 81
|
|
@ -30,7 +30,7 @@ import (
|
||||||
|
|
||||||
dto "github.com/prometheus/client_model/go"
|
dto "github.com/prometheus/client_model/go"
|
||||||
|
|
||||||
"github.com/prometheus/client_golang/prometheus"
|
"git.internal/re/client_golang/prometheus"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -197,14 +197,16 @@ func writeMetrics(w io.Writer, mfs []*dto.MetricFamily, useTags bool, prefix str
|
||||||
|
|
||||||
buf := bufio.NewWriter(w)
|
buf := bufio.NewWriter(w)
|
||||||
for _, s := range vec {
|
for _, s := range vec {
|
||||||
for _, c := range prefix {
|
if prefix != "" {
|
||||||
if _, err := buf.WriteRune(c); err != nil {
|
for _, c := range prefix {
|
||||||
|
if _, err := buf.WriteRune(c); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := buf.WriteByte('.'); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if err := buf.WriteByte('.'); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := writeMetric(buf, s.Metric, useTags); err != nil {
|
if err := writeMetric(buf, s.Metric, useTags); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -238,9 +240,8 @@ func writeMetric(buf *bufio.Writer, m model.Metric, useTags bool) error {
|
||||||
}
|
}
|
||||||
if useTags {
|
if useTags {
|
||||||
return writeTags(buf, m)
|
return writeTags(buf, m)
|
||||||
} else {
|
|
||||||
return writeLabels(buf, m, numLabels)
|
|
||||||
}
|
}
|
||||||
|
return writeLabels(buf, m, numLabels)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,7 +31,7 @@ import (
|
||||||
|
|
||||||
"github.com/prometheus/common/model"
|
"github.com/prometheus/common/model"
|
||||||
|
|
||||||
"github.com/prometheus/client_golang/prometheus"
|
"git.internal/re/client_golang/prometheus"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestSanitize(t *testing.T) {
|
func TestSanitize(t *testing.T) {
|
||||||
|
@ -101,6 +101,7 @@ func testWriteSummary(t *testing.T, useTags bool) {
|
||||||
{prefix: "prefix"},
|
{prefix: "prefix"},
|
||||||
{prefix: "pre/fix"},
|
{prefix: "pre/fix"},
|
||||||
{prefix: "pre.fix"},
|
{prefix: "pre.fix"},
|
||||||
|
{prefix: ""},
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -141,10 +142,15 @@ func testWriteSummary(t *testing.T, useTags bool) {
|
||||||
t.Fatalf("error: %v", err)
|
t.Fatalf("error: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
wantWithPrefix := fmt.Sprintf(want,
|
var wantWithPrefix string
|
||||||
tc.prefix, tc.prefix, tc.prefix, tc.prefix, tc.prefix,
|
if tc.prefix == "" {
|
||||||
tc.prefix, tc.prefix, tc.prefix, tc.prefix, tc.prefix,
|
wantWithPrefix = strings.ReplaceAll(want, "%s.", "")
|
||||||
)
|
} else {
|
||||||
|
wantWithPrefix = fmt.Sprintf(want,
|
||||||
|
tc.prefix, tc.prefix, tc.prefix, tc.prefix, tc.prefix,
|
||||||
|
tc.prefix, tc.prefix, tc.prefix, tc.prefix, tc.prefix,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
got := buf.String()
|
got := buf.String()
|
||||||
|
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -20,6 +20,7 @@ import (
|
||||||
"runtime"
|
"runtime"
|
||||||
"sort"
|
"sort"
|
||||||
"sync"
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
"testing"
|
"testing"
|
||||||
"testing/quick"
|
"testing/quick"
|
||||||
"time"
|
"time"
|
||||||
|
@ -28,6 +29,8 @@ import (
|
||||||
"github.com/golang/protobuf/proto"
|
"github.com/golang/protobuf/proto"
|
||||||
"google.golang.org/protobuf/types/known/timestamppb"
|
"google.golang.org/protobuf/types/known/timestamppb"
|
||||||
|
|
||||||
|
"git.internal/re/client_golang/prometheus/internal"
|
||||||
|
|
||||||
dto "github.com/prometheus/client_model/go"
|
dto "github.com/prometheus/client_model/go"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -167,7 +170,7 @@ func TestHistogramConcurrency(t *testing.T) {
|
||||||
start.Add(1)
|
start.Add(1)
|
||||||
end.Add(concLevel)
|
end.Add(concLevel)
|
||||||
|
|
||||||
sum := NewHistogram(HistogramOpts{
|
his := NewHistogram(HistogramOpts{
|
||||||
Name: "test_histogram",
|
Name: "test_histogram",
|
||||||
Help: "helpless",
|
Help: "helpless",
|
||||||
Buckets: testBuckets,
|
Buckets: testBuckets,
|
||||||
|
@ -188,9 +191,9 @@ func TestHistogramConcurrency(t *testing.T) {
|
||||||
start.Wait()
|
start.Wait()
|
||||||
for _, v := range vals {
|
for _, v := range vals {
|
||||||
if n%2 == 0 {
|
if n%2 == 0 {
|
||||||
sum.Observe(v)
|
his.Observe(v)
|
||||||
} else {
|
} else {
|
||||||
sum.(ExemplarObserver).ObserveWithExemplar(v, Labels{"foo": "bar"})
|
his.(ExemplarObserver).ObserveWithExemplar(v, Labels{"foo": "bar"})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
end.Done()
|
end.Done()
|
||||||
|
@ -201,7 +204,7 @@ func TestHistogramConcurrency(t *testing.T) {
|
||||||
end.Wait()
|
end.Wait()
|
||||||
|
|
||||||
m := &dto.Metric{}
|
m := &dto.Metric{}
|
||||||
sum.Write(m)
|
his.Write(m)
|
||||||
if got, want := int(*m.Histogram.SampleCount), total; got != want {
|
if got, want := int(*m.Histogram.SampleCount), total; got != want {
|
||||||
t.Errorf("got sample count %d, want %d", got, want)
|
t.Errorf("got sample count %d, want %d", got, want)
|
||||||
}
|
}
|
||||||
|
@ -354,13 +357,13 @@ func TestBuckets(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
got = ExponentialBucketsRange(1, 100, 10)
|
got = ExponentialBucketsRange(1, 100, 10)
|
||||||
want = []float64{1.0, 1.6681005372000588, 2.782559402207125,
|
want = []float64{
|
||||||
4.641588833612779, 7.742636826811273, 12.915496650148842,
|
1.0, 1.6681, 2.7825, 4.6415, 7.7426, 12.9154, 21.5443,
|
||||||
21.544346900318846, 35.93813663804629, 59.94842503189414,
|
35.9381, 59.9484, 100.0000,
|
||||||
100.00000000000007,
|
|
||||||
}
|
}
|
||||||
if !reflect.DeepEqual(got, want) {
|
const epsilon = 0.0001
|
||||||
t.Errorf("exponential buckets range: got %v, want %v", got, want)
|
if !internal.AlmostEqualFloat64s(got, want, epsilon) {
|
||||||
|
t.Errorf("exponential buckets range: got %v, want %v (epsilon %f)", got, want, epsilon)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -424,24 +427,24 @@ func TestHistogramExemplar(t *testing.T) {
|
||||||
}
|
}
|
||||||
expectedExemplars := []*dto.Exemplar{
|
expectedExemplars := []*dto.Exemplar{
|
||||||
nil,
|
nil,
|
||||||
&dto.Exemplar{
|
{
|
||||||
Label: []*dto.LabelPair{
|
Label: []*dto.LabelPair{
|
||||||
&dto.LabelPair{Name: proto.String("id"), Value: proto.String("2")},
|
{Name: proto.String("id"), Value: proto.String("2")},
|
||||||
},
|
},
|
||||||
Value: proto.Float64(1.6),
|
Value: proto.Float64(1.6),
|
||||||
Timestamp: ts,
|
Timestamp: ts,
|
||||||
},
|
},
|
||||||
nil,
|
nil,
|
||||||
&dto.Exemplar{
|
{
|
||||||
Label: []*dto.LabelPair{
|
Label: []*dto.LabelPair{
|
||||||
&dto.LabelPair{Name: proto.String("id"), Value: proto.String("3")},
|
{Name: proto.String("id"), Value: proto.String("3")},
|
||||||
},
|
},
|
||||||
Value: proto.Float64(4),
|
Value: proto.Float64(4),
|
||||||
Timestamp: ts,
|
Timestamp: ts,
|
||||||
},
|
},
|
||||||
&dto.Exemplar{
|
{
|
||||||
Label: []*dto.LabelPair{
|
Label: []*dto.LabelPair{
|
||||||
&dto.LabelPair{Name: proto.String("id"), Value: proto.String("4")},
|
{Name: proto.String("id"), Value: proto.String("4")},
|
||||||
},
|
},
|
||||||
Value: proto.Float64(4.5),
|
Value: proto.Float64(4.5),
|
||||||
Timestamp: ts,
|
Timestamp: ts,
|
||||||
|
@ -466,3 +469,408 @@ func TestHistogramExemplar(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestNativeHistogram(t *testing.T) {
|
||||||
|
scenarios := []struct {
|
||||||
|
name string
|
||||||
|
observations []float64 // With simulated interval of 1m.
|
||||||
|
factor float64
|
||||||
|
zeroThreshold float64
|
||||||
|
maxBuckets uint32
|
||||||
|
minResetDuration time.Duration
|
||||||
|
maxZeroThreshold float64
|
||||||
|
want string // String representation of protobuf.
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "no sparse buckets",
|
||||||
|
observations: []float64{1, 2, 3},
|
||||||
|
factor: 1,
|
||||||
|
want: `sample_count:3 sample_sum:6 bucket:<cumulative_count:0 upper_bound:0.005 > bucket:<cumulative_count:0 upper_bound:0.01 > bucket:<cumulative_count:0 upper_bound:0.025 > bucket:<cumulative_count:0 upper_bound:0.05 > bucket:<cumulative_count:0 upper_bound:0.1 > bucket:<cumulative_count:0 upper_bound:0.25 > bucket:<cumulative_count:0 upper_bound:0.5 > bucket:<cumulative_count:1 upper_bound:1 > bucket:<cumulative_count:2 upper_bound:2.5 > bucket:<cumulative_count:3 upper_bound:5 > bucket:<cumulative_count:3 upper_bound:10 > `, // Has conventional buckets because there are no sparse buckets.
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "factor 1.1 results in schema 3",
|
||||||
|
observations: []float64{0, 1, 2, 3},
|
||||||
|
factor: 1.1,
|
||||||
|
want: `sample_count:4 sample_sum:6 schema:3 zero_threshold:2.938735877055719e-39 zero_count:1 positive_span:<offset:0 length:1 > positive_span:<offset:7 length:1 > positive_span:<offset:4 length:1 > positive_delta:1 positive_delta:0 positive_delta:0 `,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "factor 1.2 results in schema 2",
|
||||||
|
observations: []float64{0, 1, 1.2, 1.4, 1.8, 2},
|
||||||
|
factor: 1.2,
|
||||||
|
want: `sample_count:6 sample_sum:7.4 schema:2 zero_threshold:2.938735877055719e-39 zero_count:1 positive_span:<offset:0 length:5 > positive_delta:1 positive_delta:-1 positive_delta:2 positive_delta:-2 positive_delta:2 `,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "factor 4 results in schema -1",
|
||||||
|
observations: []float64{
|
||||||
|
0.5, 1, // Bucket 0: (0.25, 1]
|
||||||
|
1.5, 2, 3, 3.5, // Bucket 1: (1, 4]
|
||||||
|
5, 6, 7, // Bucket 2: (4, 16]
|
||||||
|
33.33, // Bucket 3: (16, 64]
|
||||||
|
},
|
||||||
|
factor: 4,
|
||||||
|
want: `sample_count:10 sample_sum:62.83 schema:-1 zero_threshold:2.938735877055719e-39 zero_count:0 positive_span:<offset:0 length:4 > positive_delta:2 positive_delta:2 positive_delta:-1 positive_delta:-2 `,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "factor 17 results in schema -2",
|
||||||
|
observations: []float64{
|
||||||
|
0.5, 1, // Bucket 0: (0.0625, 1]
|
||||||
|
1.5, 2, 3, 3.5, 5, 6, 7, // Bucket 1: (1, 16]
|
||||||
|
33.33, // Bucket 2: (16, 256]
|
||||||
|
},
|
||||||
|
factor: 17,
|
||||||
|
want: `sample_count:10 sample_sum:62.83 schema:-2 zero_threshold:2.938735877055719e-39 zero_count:0 positive_span:<offset:0 length:3 > positive_delta:2 positive_delta:5 positive_delta:-6 `,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "negative buckets",
|
||||||
|
observations: []float64{0, -1, -1.2, -1.4, -1.8, -2},
|
||||||
|
factor: 1.2,
|
||||||
|
want: `sample_count:6 sample_sum:-7.4 schema:2 zero_threshold:2.938735877055719e-39 zero_count:1 negative_span:<offset:0 length:5 > negative_delta:1 negative_delta:-1 negative_delta:2 negative_delta:-2 negative_delta:2 `,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "negative and positive buckets",
|
||||||
|
observations: []float64{0, -1, -1.2, -1.4, -1.8, -2, 1, 1.2, 1.4, 1.8, 2},
|
||||||
|
factor: 1.2,
|
||||||
|
want: `sample_count:11 sample_sum:0 schema:2 zero_threshold:2.938735877055719e-39 zero_count:1 negative_span:<offset:0 length:5 > negative_delta:1 negative_delta:-1 negative_delta:2 negative_delta:-2 negative_delta:2 positive_span:<offset:0 length:5 > positive_delta:1 positive_delta:-1 positive_delta:2 positive_delta:-2 positive_delta:2 `,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "wide zero bucket",
|
||||||
|
observations: []float64{0, -1, -1.2, -1.4, -1.8, -2, 1, 1.2, 1.4, 1.8, 2},
|
||||||
|
factor: 1.2,
|
||||||
|
zeroThreshold: 1.4,
|
||||||
|
want: `sample_count:11 sample_sum:0 schema:2 zero_threshold:1.4 zero_count:7 negative_span:<offset:4 length:1 > negative_delta:2 positive_span:<offset:4 length:1 > positive_delta:2 `,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "NaN observation",
|
||||||
|
observations: []float64{0, 1, 1.2, 1.4, 1.8, 2, math.NaN()},
|
||||||
|
factor: 1.2,
|
||||||
|
want: `sample_count:7 sample_sum:nan schema:2 zero_threshold:2.938735877055719e-39 zero_count:1 positive_span:<offset:0 length:5 > positive_delta:1 positive_delta:-1 positive_delta:2 positive_delta:-2 positive_delta:2 `,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "+Inf observation",
|
||||||
|
observations: []float64{0, 1, 1.2, 1.4, 1.8, 2, math.Inf(+1)},
|
||||||
|
factor: 1.2,
|
||||||
|
want: `sample_count:7 sample_sum:inf schema:2 zero_threshold:2.938735877055719e-39 zero_count:1 positive_span:<offset:0 length:5 > positive_span:<offset:4092 length:1 > positive_delta:1 positive_delta:-1 positive_delta:2 positive_delta:-2 positive_delta:2 positive_delta:-1 `,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "-Inf observation",
|
||||||
|
observations: []float64{0, 1, 1.2, 1.4, 1.8, 2, math.Inf(-1)},
|
||||||
|
factor: 1.2,
|
||||||
|
want: `sample_count:7 sample_sum:-inf schema:2 zero_threshold:2.938735877055719e-39 zero_count:1 negative_span:<offset:4097 length:1 > negative_delta:1 positive_span:<offset:0 length:5 > positive_delta:1 positive_delta:-1 positive_delta:2 positive_delta:-2 positive_delta:2 `,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "limited buckets but nothing triggered",
|
||||||
|
observations: []float64{0, 1, 1.2, 1.4, 1.8, 2},
|
||||||
|
factor: 1.2,
|
||||||
|
maxBuckets: 4,
|
||||||
|
want: `sample_count:6 sample_sum:7.4 schema:2 zero_threshold:2.938735877055719e-39 zero_count:1 positive_span:<offset:0 length:5 > positive_delta:1 positive_delta:-1 positive_delta:2 positive_delta:-2 positive_delta:2 `,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "buckets limited by halving resolution",
|
||||||
|
observations: []float64{0, 1, 1.1, 1.2, 1.4, 1.8, 2, 3},
|
||||||
|
factor: 1.2,
|
||||||
|
maxBuckets: 4,
|
||||||
|
want: `sample_count:8 sample_sum:11.5 schema:1 zero_threshold:2.938735877055719e-39 zero_count:1 positive_span:<offset:0 length:5 > positive_delta:1 positive_delta:2 positive_delta:-1 positive_delta:-2 positive_delta:1 `,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "buckets limited by widening the zero bucket",
|
||||||
|
observations: []float64{0, 1, 1.1, 1.2, 1.4, 1.8, 2, 3},
|
||||||
|
factor: 1.2,
|
||||||
|
maxBuckets: 4,
|
||||||
|
maxZeroThreshold: 1.2,
|
||||||
|
want: `sample_count:8 sample_sum:11.5 schema:2 zero_threshold:1 zero_count:2 positive_span:<offset:1 length:7 > positive_delta:1 positive_delta:1 positive_delta:-2 positive_delta:2 positive_delta:-2 positive_delta:0 positive_delta:1 `,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "buckets limited by widening the zero bucket twice",
|
||||||
|
observations: []float64{0, 1, 1.1, 1.2, 1.4, 1.8, 2, 3, 4},
|
||||||
|
factor: 1.2,
|
||||||
|
maxBuckets: 4,
|
||||||
|
maxZeroThreshold: 1.2,
|
||||||
|
want: `sample_count:9 sample_sum:15.5 schema:2 zero_threshold:1.189207115002721 zero_count:3 positive_span:<offset:2 length:7 > positive_delta:2 positive_delta:-2 positive_delta:2 positive_delta:-2 positive_delta:0 positive_delta:1 positive_delta:0 `,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "buckets limited by reset",
|
||||||
|
observations: []float64{0, 1, 1.1, 1.2, 1.4, 1.8, 2, 3, 4},
|
||||||
|
factor: 1.2,
|
||||||
|
maxBuckets: 4,
|
||||||
|
maxZeroThreshold: 1.2,
|
||||||
|
minResetDuration: 5 * time.Minute,
|
||||||
|
want: `sample_count:2 sample_sum:7 schema:2 zero_threshold:2.938735877055719e-39 zero_count:0 positive_span:<offset:7 length:2 > positive_delta:1 positive_delta:0 `,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "limited buckets but nothing triggered, negative observations",
|
||||||
|
observations: []float64{0, -1, -1.2, -1.4, -1.8, -2},
|
||||||
|
factor: 1.2,
|
||||||
|
maxBuckets: 4,
|
||||||
|
want: `sample_count:6 sample_sum:-7.4 schema:2 zero_threshold:2.938735877055719e-39 zero_count:1 negative_span:<offset:0 length:5 > negative_delta:1 negative_delta:-1 negative_delta:2 negative_delta:-2 negative_delta:2 `,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "buckets limited by halving resolution, negative observations",
|
||||||
|
observations: []float64{0, -1, -1.1, -1.2, -1.4, -1.8, -2, -3},
|
||||||
|
factor: 1.2,
|
||||||
|
maxBuckets: 4,
|
||||||
|
want: `sample_count:8 sample_sum:-11.5 schema:1 zero_threshold:2.938735877055719e-39 zero_count:1 negative_span:<offset:0 length:5 > negative_delta:1 negative_delta:2 negative_delta:-1 negative_delta:-2 negative_delta:1 `,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "buckets limited by widening the zero bucket, negative observations",
|
||||||
|
observations: []float64{0, -1, -1.1, -1.2, -1.4, -1.8, -2, -3},
|
||||||
|
factor: 1.2,
|
||||||
|
maxBuckets: 4,
|
||||||
|
maxZeroThreshold: 1.2,
|
||||||
|
want: `sample_count:8 sample_sum:-11.5 schema:2 zero_threshold:1 zero_count:2 negative_span:<offset:1 length:7 > negative_delta:1 negative_delta:1 negative_delta:-2 negative_delta:2 negative_delta:-2 negative_delta:0 negative_delta:1 `,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "buckets limited by widening the zero bucket twice, negative observations",
|
||||||
|
observations: []float64{0, -1, -1.1, -1.2, -1.4, -1.8, -2, -3, -4},
|
||||||
|
factor: 1.2,
|
||||||
|
maxBuckets: 4,
|
||||||
|
maxZeroThreshold: 1.2,
|
||||||
|
want: `sample_count:9 sample_sum:-15.5 schema:2 zero_threshold:1.189207115002721 zero_count:3 negative_span:<offset:2 length:7 > negative_delta:2 negative_delta:-2 negative_delta:2 negative_delta:-2 negative_delta:0 negative_delta:1 negative_delta:0 `,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "buckets limited by reset, negative observations",
|
||||||
|
observations: []float64{0, -1, -1.1, -1.2, -1.4, -1.8, -2, -3, -4},
|
||||||
|
factor: 1.2,
|
||||||
|
maxBuckets: 4,
|
||||||
|
maxZeroThreshold: 1.2,
|
||||||
|
minResetDuration: 5 * time.Minute,
|
||||||
|
want: `sample_count:2 sample_sum:-7 schema:2 zero_threshold:2.938735877055719e-39 zero_count:0 negative_span:<offset:7 length:2 > negative_delta:1 negative_delta:0 `,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "buckets limited by halving resolution, then reset",
|
||||||
|
observations: []float64{0, 1, 1.1, 1.2, 1.4, 1.8, 2, 5, 5.1, 3, 4},
|
||||||
|
factor: 1.2,
|
||||||
|
maxBuckets: 4,
|
||||||
|
minResetDuration: 9 * time.Minute,
|
||||||
|
want: `sample_count:2 sample_sum:7 schema:2 zero_threshold:2.938735877055719e-39 zero_count:0 positive_span:<offset:7 length:2 > positive_delta:1 positive_delta:0 `,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "buckets limited by widening the zero bucket, then reset",
|
||||||
|
observations: []float64{0, 1, 1.1, 1.2, 1.4, 1.8, 2, 5, 5.1, 3, 4},
|
||||||
|
factor: 1.2,
|
||||||
|
maxBuckets: 4,
|
||||||
|
maxZeroThreshold: 1.2,
|
||||||
|
minResetDuration: 9 * time.Minute,
|
||||||
|
want: `sample_count:2 sample_sum:7 schema:2 zero_threshold:2.938735877055719e-39 zero_count:0 positive_span:<offset:7 length:2 > positive_delta:1 positive_delta:0 `,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, s := range scenarios {
|
||||||
|
t.Run(s.name, func(t *testing.T) {
|
||||||
|
his := NewHistogram(HistogramOpts{
|
||||||
|
Name: "name",
|
||||||
|
Help: "help",
|
||||||
|
NativeHistogramBucketFactor: s.factor,
|
||||||
|
NativeHistogramZeroThreshold: s.zeroThreshold,
|
||||||
|
NativeHistogramMaxBucketNumber: s.maxBuckets,
|
||||||
|
NativeHistogramMinResetDuration: s.minResetDuration,
|
||||||
|
NativeHistogramMaxZeroThreshold: s.maxZeroThreshold,
|
||||||
|
})
|
||||||
|
ts := time.Now().Add(30 * time.Second)
|
||||||
|
now := func() time.Time {
|
||||||
|
return ts
|
||||||
|
}
|
||||||
|
his.(*histogram).now = now
|
||||||
|
for _, o := range s.observations {
|
||||||
|
his.Observe(o)
|
||||||
|
ts = ts.Add(time.Minute)
|
||||||
|
}
|
||||||
|
m := &dto.Metric{}
|
||||||
|
if err := his.Write(m); err != nil {
|
||||||
|
t.Fatal("unexpected error writing metric", err)
|
||||||
|
}
|
||||||
|
got := m.Histogram.String()
|
||||||
|
if s.want != got {
|
||||||
|
t.Errorf("want histogram %q, got %q", s.want, got)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNativeHistogramConcurrency(t *testing.T) {
|
||||||
|
if testing.Short() {
|
||||||
|
t.Skip("Skipping test in short mode.")
|
||||||
|
}
|
||||||
|
|
||||||
|
rand.Seed(42)
|
||||||
|
|
||||||
|
it := func(n uint32) bool {
|
||||||
|
mutations := int(n%1e4 + 1e4)
|
||||||
|
concLevel := int(n%5 + 1)
|
||||||
|
total := mutations * concLevel
|
||||||
|
|
||||||
|
var start, end sync.WaitGroup
|
||||||
|
start.Add(1)
|
||||||
|
end.Add(concLevel)
|
||||||
|
|
||||||
|
his := NewHistogram(HistogramOpts{
|
||||||
|
Name: "test_native_histogram",
|
||||||
|
Help: "This help is sparse.",
|
||||||
|
NativeHistogramBucketFactor: 1.05,
|
||||||
|
NativeHistogramZeroThreshold: 0.0000001,
|
||||||
|
NativeHistogramMaxBucketNumber: 50,
|
||||||
|
NativeHistogramMinResetDuration: time.Hour, // Comment out to test for totals below.
|
||||||
|
NativeHistogramMaxZeroThreshold: 0.001,
|
||||||
|
})
|
||||||
|
|
||||||
|
ts := time.Now().Add(30 * time.Second).Unix()
|
||||||
|
now := func() time.Time {
|
||||||
|
return time.Unix(atomic.LoadInt64(&ts), 0)
|
||||||
|
}
|
||||||
|
his.(*histogram).now = now
|
||||||
|
|
||||||
|
allVars := make([]float64, total)
|
||||||
|
var sampleSum float64
|
||||||
|
for i := 0; i < concLevel; i++ {
|
||||||
|
vals := make([]float64, mutations)
|
||||||
|
for j := 0; j < mutations; j++ {
|
||||||
|
v := rand.NormFloat64()
|
||||||
|
vals[j] = v
|
||||||
|
allVars[i*mutations+j] = v
|
||||||
|
sampleSum += v
|
||||||
|
}
|
||||||
|
|
||||||
|
go func(vals []float64) {
|
||||||
|
start.Wait()
|
||||||
|
for _, v := range vals {
|
||||||
|
// An observation every 1 to 10 seconds.
|
||||||
|
atomic.AddInt64(&ts, rand.Int63n(10)+1)
|
||||||
|
his.Observe(v)
|
||||||
|
}
|
||||||
|
end.Done()
|
||||||
|
}(vals)
|
||||||
|
}
|
||||||
|
sort.Float64s(allVars)
|
||||||
|
start.Done()
|
||||||
|
end.Wait()
|
||||||
|
|
||||||
|
m := &dto.Metric{}
|
||||||
|
his.Write(m)
|
||||||
|
|
||||||
|
// Uncomment these tests for totals only if you have disabled histogram resets above.
|
||||||
|
//
|
||||||
|
// if got, want := int(*m.Histogram.SampleCount), total; got != want {
|
||||||
|
// t.Errorf("got sample count %d, want %d", got, want)
|
||||||
|
// }
|
||||||
|
// if got, want := *m.Histogram.SampleSum, sampleSum; math.Abs((got-want)/want) > 0.001 {
|
||||||
|
// t.Errorf("got sample sum %f, want %f", got, want)
|
||||||
|
// }
|
||||||
|
|
||||||
|
sumBuckets := int(m.Histogram.GetZeroCount())
|
||||||
|
current := 0
|
||||||
|
for _, delta := range m.Histogram.GetNegativeDelta() {
|
||||||
|
current += int(delta)
|
||||||
|
if current < 0 {
|
||||||
|
t.Fatalf("negative bucket population negative: %d", current)
|
||||||
|
}
|
||||||
|
sumBuckets += current
|
||||||
|
}
|
||||||
|
current = 0
|
||||||
|
for _, delta := range m.Histogram.GetPositiveDelta() {
|
||||||
|
current += int(delta)
|
||||||
|
if current < 0 {
|
||||||
|
t.Fatalf("positive bucket population negative: %d", current)
|
||||||
|
}
|
||||||
|
sumBuckets += current
|
||||||
|
}
|
||||||
|
if got, want := sumBuckets, int(*m.Histogram.SampleCount); got != want {
|
||||||
|
t.Errorf("got bucket population sum %d, want %d", got, want)
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := quick.Check(it, nil); err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetLe(t *testing.T) {
|
||||||
|
scenarios := []struct {
|
||||||
|
key int
|
||||||
|
schema int32
|
||||||
|
want float64
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
key: -1,
|
||||||
|
schema: -1,
|
||||||
|
want: 0.25,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 0,
|
||||||
|
schema: -1,
|
||||||
|
want: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 1,
|
||||||
|
schema: -1,
|
||||||
|
want: 4,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 512,
|
||||||
|
schema: -1,
|
||||||
|
want: math.MaxFloat64,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 513,
|
||||||
|
schema: -1,
|
||||||
|
want: math.Inf(+1),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: -1,
|
||||||
|
schema: 0,
|
||||||
|
want: 0.5,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 0,
|
||||||
|
schema: 0,
|
||||||
|
want: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 1,
|
||||||
|
schema: 0,
|
||||||
|
want: 2,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 1024,
|
||||||
|
schema: 0,
|
||||||
|
want: math.MaxFloat64,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 1025,
|
||||||
|
schema: 0,
|
||||||
|
want: math.Inf(+1),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: -1,
|
||||||
|
schema: 2,
|
||||||
|
want: 0.8408964152537144,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 0,
|
||||||
|
schema: 2,
|
||||||
|
want: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 1,
|
||||||
|
schema: 2,
|
||||||
|
want: 1.189207115002721,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 4096,
|
||||||
|
schema: 2,
|
||||||
|
want: math.MaxFloat64,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 4097,
|
||||||
|
schema: 2,
|
||||||
|
want: math.Inf(+1),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, s := range scenarios {
|
||||||
|
got := getLe(s.key, s.schema)
|
||||||
|
if s.want != got {
|
||||||
|
t.Errorf("%d. key %d, schema %d, want upper bound of %g, got %g", i, s.key, s.schema, s.want, got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,60 @@
|
||||||
|
// Copyright (c) 2015 Björn Rabenstein
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in all
|
||||||
|
// copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
// SOFTWARE.
|
||||||
|
//
|
||||||
|
// The code in this package is copy/paste to avoid a dependency. Hence this file
|
||||||
|
// carries the copyright of the original repo.
|
||||||
|
// https://github.com/beorn7/floats
|
||||||
|
package internal
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math"
|
||||||
|
)
|
||||||
|
|
||||||
|
// minNormalFloat64 is the smallest positive normal value of type float64.
|
||||||
|
var minNormalFloat64 = math.Float64frombits(0x0010000000000000)
|
||||||
|
|
||||||
|
// AlmostEqualFloat64 returns true if a and b are equal within a relative error
|
||||||
|
// of epsilon. See http://floating-point-gui.de/errors/comparison/ for the
|
||||||
|
// details of the applied method.
|
||||||
|
func AlmostEqualFloat64(a, b, epsilon float64) bool {
|
||||||
|
if a == b {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
absA := math.Abs(a)
|
||||||
|
absB := math.Abs(b)
|
||||||
|
diff := math.Abs(a - b)
|
||||||
|
if a == 0 || b == 0 || absA+absB < minNormalFloat64 {
|
||||||
|
return diff < epsilon*minNormalFloat64
|
||||||
|
}
|
||||||
|
return diff/math.Min(absA+absB, math.MaxFloat64) < epsilon
|
||||||
|
}
|
||||||
|
|
||||||
|
// AlmostEqualFloat64s is the slice form of AlmostEqualFloat64.
|
||||||
|
func AlmostEqualFloat64s(a, b []float64, epsilon float64) bool {
|
||||||
|
if len(a) != len(b) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for i := range a {
|
||||||
|
if !AlmostEqualFloat64(a[i], b[i], epsilon) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
|
@ -0,0 +1,654 @@
|
||||||
|
// 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.
|
||||||
|
//
|
||||||
|
// It provides tools to compare sequences of strings and generate textual diffs.
|
||||||
|
//
|
||||||
|
// Maintaining `GetUnifiedDiffString` here because original repository
|
||||||
|
// (https://github.com/pmezard/go-difflib) is no loger maintained.
|
||||||
|
package internal
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func min(a, b int) int {
|
||||||
|
if a < b {
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
func max(a, b int) int {
|
||||||
|
if a > b {
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
func calculateRatio(matches, length int) float64 {
|
||||||
|
if length > 0 {
|
||||||
|
return 2.0 * float64(matches) / float64(length)
|
||||||
|
}
|
||||||
|
return 1.0
|
||||||
|
}
|
||||||
|
|
||||||
|
type Match struct {
|
||||||
|
A int
|
||||||
|
B int
|
||||||
|
Size int
|
||||||
|
}
|
||||||
|
|
||||||
|
type OpCode struct {
|
||||||
|
Tag byte
|
||||||
|
I1 int
|
||||||
|
I2 int
|
||||||
|
J1 int
|
||||||
|
J2 int
|
||||||
|
}
|
||||||
|
|
||||||
|
// SequenceMatcher compares sequence of strings. The basic
|
||||||
|
// algorithm predates, and is a little fancier than, an algorithm
|
||||||
|
// published in the late 1980's by Ratcliff and Obershelp under the
|
||||||
|
// hyperbolic name "gestalt pattern matching". The basic idea is to find
|
||||||
|
// the longest contiguous matching subsequence that contains no "junk"
|
||||||
|
// elements (R-O doesn't address junk). The same idea is then applied
|
||||||
|
// recursively to the pieces of the sequences to the left and to the right
|
||||||
|
// of the matching subsequence. This does not yield minimal edit
|
||||||
|
// sequences, but does tend to yield matches that "look right" to people.
|
||||||
|
//
|
||||||
|
// SequenceMatcher tries to compute a "human-friendly diff" between two
|
||||||
|
// sequences. Unlike e.g. UNIX(tm) diff, the fundamental notion is the
|
||||||
|
// longest *contiguous* & junk-free matching subsequence. That's what
|
||||||
|
// catches peoples' eyes. The Windows(tm) windiff has another interesting
|
||||||
|
// notion, pairing up elements that appear uniquely in each sequence.
|
||||||
|
// That, and the method here, appear to yield more intuitive difference
|
||||||
|
// reports than does diff. This method appears to be the least vulnerable
|
||||||
|
// to synching up on blocks of "junk lines", though (like blank lines in
|
||||||
|
// ordinary text files, or maybe "<P>" lines in HTML files). That may be
|
||||||
|
// because this is the only method of the 3 that has a *concept* of
|
||||||
|
// "junk" <wink>.
|
||||||
|
//
|
||||||
|
// Timing: Basic R-O is cubic time worst case and quadratic time expected
|
||||||
|
// case. SequenceMatcher is quadratic time for the worst case and has
|
||||||
|
// expected-case behavior dependent in a complicated way on how many
|
||||||
|
// elements the sequences have in common; best case time is linear.
|
||||||
|
type SequenceMatcher struct {
|
||||||
|
a []string
|
||||||
|
b []string
|
||||||
|
b2j map[string][]int
|
||||||
|
IsJunk func(string) bool
|
||||||
|
autoJunk bool
|
||||||
|
bJunk map[string]struct{}
|
||||||
|
matchingBlocks []Match
|
||||||
|
fullBCount map[string]int
|
||||||
|
bPopular map[string]struct{}
|
||||||
|
opCodes []OpCode
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewMatcher(a, b []string) *SequenceMatcher {
|
||||||
|
m := SequenceMatcher{autoJunk: true}
|
||||||
|
m.SetSeqs(a, b)
|
||||||
|
return &m
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewMatcherWithJunk(a, b []string, autoJunk bool,
|
||||||
|
isJunk func(string) bool,
|
||||||
|
) *SequenceMatcher {
|
||||||
|
m := SequenceMatcher{IsJunk: isJunk, autoJunk: autoJunk}
|
||||||
|
m.SetSeqs(a, b)
|
||||||
|
return &m
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set two sequences to be compared.
|
||||||
|
func (m *SequenceMatcher) SetSeqs(a, b []string) {
|
||||||
|
m.SetSeq1(a)
|
||||||
|
m.SetSeq2(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the first sequence to be compared. The second sequence to be compared is
|
||||||
|
// not changed.
|
||||||
|
//
|
||||||
|
// SequenceMatcher computes and caches detailed information about the second
|
||||||
|
// sequence, so if you want to compare one sequence S against many sequences,
|
||||||
|
// use .SetSeq2(s) once and call .SetSeq1(x) repeatedly for each of the other
|
||||||
|
// sequences.
|
||||||
|
//
|
||||||
|
// See also SetSeqs() and SetSeq2().
|
||||||
|
func (m *SequenceMatcher) SetSeq1(a []string) {
|
||||||
|
if &a == &m.a {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
m.a = a
|
||||||
|
m.matchingBlocks = nil
|
||||||
|
m.opCodes = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the second sequence to be compared. The first sequence to be compared is
|
||||||
|
// not changed.
|
||||||
|
func (m *SequenceMatcher) SetSeq2(b []string) {
|
||||||
|
if &b == &m.b {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
m.b = b
|
||||||
|
m.matchingBlocks = nil
|
||||||
|
m.opCodes = nil
|
||||||
|
m.fullBCount = nil
|
||||||
|
m.chainB()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *SequenceMatcher) chainB() {
|
||||||
|
// Populate line -> index mapping
|
||||||
|
b2j := map[string][]int{}
|
||||||
|
for i, s := range m.b {
|
||||||
|
indices := b2j[s]
|
||||||
|
indices = append(indices, i)
|
||||||
|
b2j[s] = indices
|
||||||
|
}
|
||||||
|
|
||||||
|
// Purge junk elements
|
||||||
|
m.bJunk = map[string]struct{}{}
|
||||||
|
if m.IsJunk != nil {
|
||||||
|
junk := m.bJunk
|
||||||
|
for s := range b2j {
|
||||||
|
if m.IsJunk(s) {
|
||||||
|
junk[s] = struct{}{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for s := range junk {
|
||||||
|
delete(b2j, s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Purge remaining popular elements
|
||||||
|
popular := map[string]struct{}{}
|
||||||
|
n := len(m.b)
|
||||||
|
if m.autoJunk && n >= 200 {
|
||||||
|
ntest := n/100 + 1
|
||||||
|
for s, indices := range b2j {
|
||||||
|
if len(indices) > ntest {
|
||||||
|
popular[s] = struct{}{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for s := range popular {
|
||||||
|
delete(b2j, s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
m.bPopular = popular
|
||||||
|
m.b2j = b2j
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *SequenceMatcher) isBJunk(s string) bool {
|
||||||
|
_, ok := m.bJunk[s]
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find longest matching block in a[alo:ahi] and b[blo:bhi].
|
||||||
|
//
|
||||||
|
// If IsJunk is not defined:
|
||||||
|
//
|
||||||
|
// Return (i,j,k) such that a[i:i+k] is equal to b[j:j+k], where
|
||||||
|
//
|
||||||
|
// alo <= i <= i+k <= ahi
|
||||||
|
// blo <= j <= j+k <= bhi
|
||||||
|
//
|
||||||
|
// and for all (i',j',k') meeting those conditions,
|
||||||
|
//
|
||||||
|
// k >= k'
|
||||||
|
// i <= i'
|
||||||
|
// and if i == i', j <= j'
|
||||||
|
//
|
||||||
|
// In other words, of all maximal matching blocks, return one that
|
||||||
|
// starts earliest in a, and of all those maximal matching blocks that
|
||||||
|
// start earliest in a, return the one that starts earliest in b.
|
||||||
|
//
|
||||||
|
// If IsJunk is defined, first the longest matching block is
|
||||||
|
// determined as above, but with the additional restriction that no
|
||||||
|
// junk element appears in the block. Then that block is extended as
|
||||||
|
// far as possible by matching (only) junk elements on both sides. So
|
||||||
|
// the resulting block never matches on junk except as identical junk
|
||||||
|
// happens to be adjacent to an "interesting" match.
|
||||||
|
//
|
||||||
|
// If no blocks match, return (alo, blo, 0).
|
||||||
|
func (m *SequenceMatcher) findLongestMatch(alo, ahi, blo, bhi int) Match {
|
||||||
|
// CAUTION: stripping common prefix or suffix would be incorrect.
|
||||||
|
// E.g.,
|
||||||
|
// ab
|
||||||
|
// acab
|
||||||
|
// Longest matching block is "ab", but if common prefix is
|
||||||
|
// stripped, it's "a" (tied with "b"). UNIX(tm) diff does so
|
||||||
|
// strip, so ends up claiming that ab is changed to acab by
|
||||||
|
// inserting "ca" in the middle. That's minimal but unintuitive:
|
||||||
|
// "it's obvious" that someone inserted "ac" at the front.
|
||||||
|
// Windiff ends up at the same place as diff, but by pairing up
|
||||||
|
// the unique 'b's and then matching the first two 'a's.
|
||||||
|
besti, bestj, bestsize := alo, blo, 0
|
||||||
|
|
||||||
|
// find longest junk-free match
|
||||||
|
// during an iteration of the loop, j2len[j] = length of longest
|
||||||
|
// junk-free match ending with a[i-1] and b[j]
|
||||||
|
j2len := map[int]int{}
|
||||||
|
for i := alo; i != ahi; i++ {
|
||||||
|
// look at all instances of a[i] in b; note that because
|
||||||
|
// b2j has no junk keys, the loop is skipped if a[i] is junk
|
||||||
|
newj2len := map[int]int{}
|
||||||
|
for _, j := range m.b2j[m.a[i]] {
|
||||||
|
// a[i] matches b[j]
|
||||||
|
if j < blo {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if j >= bhi {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
k := j2len[j-1] + 1
|
||||||
|
newj2len[j] = k
|
||||||
|
if k > bestsize {
|
||||||
|
besti, bestj, bestsize = i-k+1, j-k+1, k
|
||||||
|
}
|
||||||
|
}
|
||||||
|
j2len = newj2len
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extend the best by non-junk elements on each end. In particular,
|
||||||
|
// "popular" non-junk elements aren't in b2j, which greatly speeds
|
||||||
|
// the inner loop above, but also means "the best" match so far
|
||||||
|
// doesn't contain any junk *or* popular non-junk elements.
|
||||||
|
for besti > alo && bestj > blo && !m.isBJunk(m.b[bestj-1]) &&
|
||||||
|
m.a[besti-1] == m.b[bestj-1] {
|
||||||
|
besti, bestj, bestsize = besti-1, bestj-1, bestsize+1
|
||||||
|
}
|
||||||
|
for besti+bestsize < ahi && bestj+bestsize < bhi &&
|
||||||
|
!m.isBJunk(m.b[bestj+bestsize]) &&
|
||||||
|
m.a[besti+bestsize] == m.b[bestj+bestsize] {
|
||||||
|
bestsize++
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now that we have a wholly interesting match (albeit possibly
|
||||||
|
// empty!), we may as well suck up the matching junk on each
|
||||||
|
// side of it too. Can't think of a good reason not to, and it
|
||||||
|
// saves post-processing the (possibly considerable) expense of
|
||||||
|
// figuring out what to do with it. In the case of an empty
|
||||||
|
// interesting match, this is clearly the right thing to do,
|
||||||
|
// because no other kind of match is possible in the regions.
|
||||||
|
for besti > alo && bestj > blo && m.isBJunk(m.b[bestj-1]) &&
|
||||||
|
m.a[besti-1] == m.b[bestj-1] {
|
||||||
|
besti, bestj, bestsize = besti-1, bestj-1, bestsize+1
|
||||||
|
}
|
||||||
|
for besti+bestsize < ahi && bestj+bestsize < bhi &&
|
||||||
|
m.isBJunk(m.b[bestj+bestsize]) &&
|
||||||
|
m.a[besti+bestsize] == m.b[bestj+bestsize] {
|
||||||
|
bestsize++
|
||||||
|
}
|
||||||
|
|
||||||
|
return Match{A: besti, B: bestj, Size: bestsize}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return list of triples describing matching subsequences.
|
||||||
|
//
|
||||||
|
// Each triple is of the form (i, j, n), and means that
|
||||||
|
// a[i:i+n] == b[j:j+n]. The triples are monotonically increasing in
|
||||||
|
// i and in j. It's also guaranteed that if (i, j, n) and (i', j', n') are
|
||||||
|
// adjacent triples in the list, and the second is not the last triple in the
|
||||||
|
// list, then i+n != i' or j+n != j'. IOW, adjacent triples never describe
|
||||||
|
// adjacent equal blocks.
|
||||||
|
//
|
||||||
|
// The last triple is a dummy, (len(a), len(b), 0), and is the only
|
||||||
|
// triple with n==0.
|
||||||
|
func (m *SequenceMatcher) GetMatchingBlocks() []Match {
|
||||||
|
if m.matchingBlocks != nil {
|
||||||
|
return m.matchingBlocks
|
||||||
|
}
|
||||||
|
|
||||||
|
var matchBlocks func(alo, ahi, blo, bhi int, matched []Match) []Match
|
||||||
|
matchBlocks = func(alo, ahi, blo, bhi int, matched []Match) []Match {
|
||||||
|
match := m.findLongestMatch(alo, ahi, blo, bhi)
|
||||||
|
i, j, k := match.A, match.B, match.Size
|
||||||
|
if match.Size > 0 {
|
||||||
|
if alo < i && blo < j {
|
||||||
|
matched = matchBlocks(alo, i, blo, j, matched)
|
||||||
|
}
|
||||||
|
matched = append(matched, match)
|
||||||
|
if i+k < ahi && j+k < bhi {
|
||||||
|
matched = matchBlocks(i+k, ahi, j+k, bhi, matched)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return matched
|
||||||
|
}
|
||||||
|
matched := matchBlocks(0, len(m.a), 0, len(m.b), nil)
|
||||||
|
|
||||||
|
// It's possible that we have adjacent equal blocks in the
|
||||||
|
// matching_blocks list now.
|
||||||
|
nonAdjacent := []Match{}
|
||||||
|
i1, j1, k1 := 0, 0, 0
|
||||||
|
for _, b := range matched {
|
||||||
|
// Is this block adjacent to i1, j1, k1?
|
||||||
|
i2, j2, k2 := b.A, b.B, b.Size
|
||||||
|
if i1+k1 == i2 && j1+k1 == j2 {
|
||||||
|
// Yes, so collapse them -- this just increases the length of
|
||||||
|
// the first block by the length of the second, and the first
|
||||||
|
// block so lengthened remains the block to compare against.
|
||||||
|
k1 += k2
|
||||||
|
} else {
|
||||||
|
// Not adjacent. Remember the first block (k1==0 means it's
|
||||||
|
// the dummy we started with), and make the second block the
|
||||||
|
// new block to compare against.
|
||||||
|
if k1 > 0 {
|
||||||
|
nonAdjacent = append(nonAdjacent, Match{i1, j1, k1})
|
||||||
|
}
|
||||||
|
i1, j1, k1 = i2, j2, k2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if k1 > 0 {
|
||||||
|
nonAdjacent = append(nonAdjacent, Match{i1, j1, k1})
|
||||||
|
}
|
||||||
|
|
||||||
|
nonAdjacent = append(nonAdjacent, Match{len(m.a), len(m.b), 0})
|
||||||
|
m.matchingBlocks = nonAdjacent
|
||||||
|
return m.matchingBlocks
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return list of 5-tuples describing how to turn a into b.
|
||||||
|
//
|
||||||
|
// Each tuple is of the form (tag, i1, i2, j1, j2). The first tuple
|
||||||
|
// has i1 == j1 == 0, and remaining tuples have i1 == the i2 from the
|
||||||
|
// tuple preceding it, and likewise for j1 == the previous j2.
|
||||||
|
//
|
||||||
|
// The tags are characters, with these meanings:
|
||||||
|
//
|
||||||
|
// 'r' (replace): a[i1:i2] should be replaced by b[j1:j2]
|
||||||
|
//
|
||||||
|
// 'd' (delete): a[i1:i2] should be deleted, j1==j2 in this case.
|
||||||
|
//
|
||||||
|
// 'i' (insert): b[j1:j2] should be inserted at a[i1:i1], i1==i2 in this case.
|
||||||
|
//
|
||||||
|
// 'e' (equal): a[i1:i2] == b[j1:j2]
|
||||||
|
func (m *SequenceMatcher) GetOpCodes() []OpCode {
|
||||||
|
if m.opCodes != nil {
|
||||||
|
return m.opCodes
|
||||||
|
}
|
||||||
|
i, j := 0, 0
|
||||||
|
matching := m.GetMatchingBlocks()
|
||||||
|
opCodes := make([]OpCode, 0, len(matching))
|
||||||
|
for _, m := range matching {
|
||||||
|
// invariant: we've pumped out correct diffs to change
|
||||||
|
// a[:i] into b[:j], and the next matching block is
|
||||||
|
// a[ai:ai+size] == b[bj:bj+size]. So we need to pump
|
||||||
|
// out a diff to change a[i:ai] into b[j:bj], pump out
|
||||||
|
// the matching block, and move (i,j) beyond the match
|
||||||
|
ai, bj, size := m.A, m.B, m.Size
|
||||||
|
tag := byte(0)
|
||||||
|
if i < ai && j < bj {
|
||||||
|
tag = 'r'
|
||||||
|
} else if i < ai {
|
||||||
|
tag = 'd'
|
||||||
|
} else if j < bj {
|
||||||
|
tag = 'i'
|
||||||
|
}
|
||||||
|
if tag > 0 {
|
||||||
|
opCodes = append(opCodes, OpCode{tag, i, ai, j, bj})
|
||||||
|
}
|
||||||
|
i, j = ai+size, bj+size
|
||||||
|
// the list of matching blocks is terminated by a
|
||||||
|
// sentinel with size 0
|
||||||
|
if size > 0 {
|
||||||
|
opCodes = append(opCodes, OpCode{'e', ai, i, bj, j})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
m.opCodes = opCodes
|
||||||
|
return m.opCodes
|
||||||
|
}
|
||||||
|
|
||||||
|
// Isolate change clusters by eliminating ranges with no changes.
|
||||||
|
//
|
||||||
|
// Return a generator of groups with up to n lines of context.
|
||||||
|
// Each group is in the same format as returned by GetOpCodes().
|
||||||
|
func (m *SequenceMatcher) GetGroupedOpCodes(n int) [][]OpCode {
|
||||||
|
if n < 0 {
|
||||||
|
n = 3
|
||||||
|
}
|
||||||
|
codes := m.GetOpCodes()
|
||||||
|
if len(codes) == 0 {
|
||||||
|
codes = []OpCode{{'e', 0, 1, 0, 1}}
|
||||||
|
}
|
||||||
|
// Fixup leading and trailing groups if they show no changes.
|
||||||
|
if codes[0].Tag == 'e' {
|
||||||
|
c := codes[0]
|
||||||
|
i1, i2, j1, j2 := c.I1, c.I2, c.J1, c.J2
|
||||||
|
codes[0] = OpCode{c.Tag, max(i1, i2-n), i2, max(j1, j2-n), j2}
|
||||||
|
}
|
||||||
|
if codes[len(codes)-1].Tag == 'e' {
|
||||||
|
c := codes[len(codes)-1]
|
||||||
|
i1, i2, j1, j2 := c.I1, c.I2, c.J1, c.J2
|
||||||
|
codes[len(codes)-1] = OpCode{c.Tag, i1, min(i2, i1+n), j1, min(j2, j1+n)}
|
||||||
|
}
|
||||||
|
nn := n + n
|
||||||
|
groups := [][]OpCode{}
|
||||||
|
group := []OpCode{}
|
||||||
|
for _, c := range codes {
|
||||||
|
i1, i2, j1, j2 := c.I1, c.I2, c.J1, c.J2
|
||||||
|
// End the current group and start a new one whenever
|
||||||
|
// there is a large range with no changes.
|
||||||
|
if c.Tag == 'e' && i2-i1 > nn {
|
||||||
|
group = append(group, OpCode{
|
||||||
|
c.Tag, i1, min(i2, i1+n),
|
||||||
|
j1, min(j2, j1+n),
|
||||||
|
})
|
||||||
|
groups = append(groups, group)
|
||||||
|
group = []OpCode{}
|
||||||
|
i1, j1 = max(i1, i2-n), max(j1, j2-n)
|
||||||
|
}
|
||||||
|
group = append(group, OpCode{c.Tag, i1, i2, j1, j2})
|
||||||
|
}
|
||||||
|
if len(group) > 0 && !(len(group) == 1 && group[0].Tag == 'e') {
|
||||||
|
groups = append(groups, group)
|
||||||
|
}
|
||||||
|
return groups
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return a measure of the sequences' similarity (float in [0,1]).
|
||||||
|
//
|
||||||
|
// Where T is the total number of elements in both sequences, and
|
||||||
|
// M is the number of matches, this is 2.0*M / T.
|
||||||
|
// Note that this is 1 if the sequences are identical, and 0 if
|
||||||
|
// they have nothing in common.
|
||||||
|
//
|
||||||
|
// .Ratio() is expensive to compute if you haven't already computed
|
||||||
|
// .GetMatchingBlocks() or .GetOpCodes(), in which case you may
|
||||||
|
// want to try .QuickRatio() or .RealQuickRation() first to get an
|
||||||
|
// upper bound.
|
||||||
|
func (m *SequenceMatcher) Ratio() float64 {
|
||||||
|
matches := 0
|
||||||
|
for _, m := range m.GetMatchingBlocks() {
|
||||||
|
matches += m.Size
|
||||||
|
}
|
||||||
|
return calculateRatio(matches, len(m.a)+len(m.b))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return an upper bound on ratio() relatively quickly.
|
||||||
|
//
|
||||||
|
// This isn't defined beyond that it is an upper bound on .Ratio(), and
|
||||||
|
// is faster to compute.
|
||||||
|
func (m *SequenceMatcher) QuickRatio() float64 {
|
||||||
|
// viewing a and b as multisets, set matches to the cardinality
|
||||||
|
// of their intersection; this counts the number of matches
|
||||||
|
// without regard to order, so is clearly an upper bound
|
||||||
|
if m.fullBCount == nil {
|
||||||
|
m.fullBCount = map[string]int{}
|
||||||
|
for _, s := range m.b {
|
||||||
|
m.fullBCount[s]++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// avail[x] is the number of times x appears in 'b' less the
|
||||||
|
// number of times we've seen it in 'a' so far ... kinda
|
||||||
|
avail := map[string]int{}
|
||||||
|
matches := 0
|
||||||
|
for _, s := range m.a {
|
||||||
|
n, ok := avail[s]
|
||||||
|
if !ok {
|
||||||
|
n = m.fullBCount[s]
|
||||||
|
}
|
||||||
|
avail[s] = n - 1
|
||||||
|
if n > 0 {
|
||||||
|
matches++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return calculateRatio(matches, len(m.a)+len(m.b))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return an upper bound on ratio() very quickly.
|
||||||
|
//
|
||||||
|
// This isn't defined beyond that it is an upper bound on .Ratio(), and
|
||||||
|
// is faster to compute than either .Ratio() or .QuickRatio().
|
||||||
|
func (m *SequenceMatcher) RealQuickRatio() float64 {
|
||||||
|
la, lb := len(m.a), len(m.b)
|
||||||
|
return calculateRatio(min(la, lb), la+lb)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert range to the "ed" format
|
||||||
|
func formatRangeUnified(start, stop int) string {
|
||||||
|
// Per the diff spec at http://www.unix.org/single_unix_specification/
|
||||||
|
beginning := start + 1 // lines start numbering with one
|
||||||
|
length := stop - start
|
||||||
|
if length == 1 {
|
||||||
|
return fmt.Sprintf("%d", beginning)
|
||||||
|
}
|
||||||
|
if length == 0 {
|
||||||
|
beginning-- // empty ranges begin at line just before the range
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%d,%d", beginning, length)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unified diff parameters
|
||||||
|
type UnifiedDiff struct {
|
||||||
|
A []string // First sequence lines
|
||||||
|
FromFile string // First file name
|
||||||
|
FromDate string // First file time
|
||||||
|
B []string // Second sequence lines
|
||||||
|
ToFile string // Second file name
|
||||||
|
ToDate string // Second file time
|
||||||
|
Eol string // Headers end of line, defaults to LF
|
||||||
|
Context int // Number of context lines
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compare two sequences of lines; generate the delta as a unified diff.
|
||||||
|
//
|
||||||
|
// Unified diffs are a compact way of showing line changes and a few
|
||||||
|
// lines of context. The number of context lines is set by 'n' which
|
||||||
|
// defaults to three.
|
||||||
|
//
|
||||||
|
// By default, the diff control lines (those with ---, +++, or @@) are
|
||||||
|
// created with a trailing newline. This is helpful so that inputs
|
||||||
|
// created from file.readlines() result in diffs that are suitable for
|
||||||
|
// file.writelines() since both the inputs and outputs have trailing
|
||||||
|
// newlines.
|
||||||
|
//
|
||||||
|
// For inputs that do not have trailing newlines, set the lineterm
|
||||||
|
// argument to "" so that the output will be uniformly newline free.
|
||||||
|
//
|
||||||
|
// The unidiff format normally has a header for filenames and modification
|
||||||
|
// times. Any or all of these may be specified using strings for
|
||||||
|
// 'fromfile', 'tofile', 'fromfiledate', and 'tofiledate'.
|
||||||
|
// The modification times are normally expressed in the ISO 8601 format.
|
||||||
|
func WriteUnifiedDiff(writer io.Writer, diff UnifiedDiff) error {
|
||||||
|
buf := bufio.NewWriter(writer)
|
||||||
|
defer buf.Flush()
|
||||||
|
wf := func(format string, args ...interface{}) error {
|
||||||
|
_, err := buf.WriteString(fmt.Sprintf(format, args...))
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
ws := func(s string) error {
|
||||||
|
_, err := buf.WriteString(s)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(diff.Eol) == 0 {
|
||||||
|
diff.Eol = "\n"
|
||||||
|
}
|
||||||
|
|
||||||
|
started := false
|
||||||
|
m := NewMatcher(diff.A, diff.B)
|
||||||
|
for _, g := range m.GetGroupedOpCodes(diff.Context) {
|
||||||
|
if !started {
|
||||||
|
started = true
|
||||||
|
fromDate := ""
|
||||||
|
if len(diff.FromDate) > 0 {
|
||||||
|
fromDate = "\t" + diff.FromDate
|
||||||
|
}
|
||||||
|
toDate := ""
|
||||||
|
if len(diff.ToDate) > 0 {
|
||||||
|
toDate = "\t" + diff.ToDate
|
||||||
|
}
|
||||||
|
if diff.FromFile != "" || diff.ToFile != "" {
|
||||||
|
err := wf("--- %s%s%s", diff.FromFile, fromDate, diff.Eol)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = wf("+++ %s%s%s", diff.ToFile, toDate, diff.Eol)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
first, last := g[0], g[len(g)-1]
|
||||||
|
range1 := formatRangeUnified(first.I1, last.I2)
|
||||||
|
range2 := formatRangeUnified(first.J1, last.J2)
|
||||||
|
if err := wf("@@ -%s +%s @@%s", range1, range2, diff.Eol); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, c := range g {
|
||||||
|
i1, i2, j1, j2 := c.I1, c.I2, c.J1, c.J2
|
||||||
|
if c.Tag == 'e' {
|
||||||
|
for _, line := range diff.A[i1:i2] {
|
||||||
|
if err := ws(" " + line); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if c.Tag == 'r' || c.Tag == 'd' {
|
||||||
|
for _, line := range diff.A[i1:i2] {
|
||||||
|
if err := ws("-" + line); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if c.Tag == 'r' || c.Tag == 'i' {
|
||||||
|
for _, line := range diff.B[j1:j2] {
|
||||||
|
if err := ws("+" + line); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Like WriteUnifiedDiff but returns the diff a string.
|
||||||
|
func GetUnifiedDiffString(diff UnifiedDiff) (string, error) {
|
||||||
|
w := &bytes.Buffer{}
|
||||||
|
err := WriteUnifiedDiff(w, diff)
|
||||||
|
return w.String(), err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Split a string on "\n" while preserving them. The output can be used
|
||||||
|
// as input for UnifiedDiff and ContextDiff structures.
|
||||||
|
func SplitLines(s string) []string {
|
||||||
|
lines := strings.SplitAfter(s, "\n")
|
||||||
|
lines[len(lines)-1] += "\n"
|
||||||
|
return lines
|
||||||
|
}
|
|
@ -0,0 +1,266 @@
|
||||||
|
// 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.
|
||||||
|
package internal
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"math"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func assertAlmostEqual(t *testing.T, a, b float64, places int) {
|
||||||
|
if math.Abs(a-b) > math.Pow10(-places) {
|
||||||
|
t.Errorf("%.7f != %.7f", a, b)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func assertEqual(t *testing.T, a, b interface{}) {
|
||||||
|
if !reflect.DeepEqual(a, b) {
|
||||||
|
t.Errorf("%v != %v", a, b)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func splitChars(s string) []string {
|
||||||
|
chars := make([]string, 0, len(s))
|
||||||
|
// Assume ASCII inputs
|
||||||
|
for i := 0; i != len(s); i++ {
|
||||||
|
chars = append(chars, string(s[i]))
|
||||||
|
}
|
||||||
|
return chars
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSequenceMatcherRatio(t *testing.T) {
|
||||||
|
s := NewMatcher(splitChars("abcd"), splitChars("bcde"))
|
||||||
|
assertEqual(t, s.Ratio(), 0.75)
|
||||||
|
assertEqual(t, s.QuickRatio(), 0.75)
|
||||||
|
assertEqual(t, s.RealQuickRatio(), 1.0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetOptCodes(t *testing.T) {
|
||||||
|
a := "qabxcd"
|
||||||
|
b := "abycdf"
|
||||||
|
s := NewMatcher(splitChars(a), splitChars(b))
|
||||||
|
w := &bytes.Buffer{}
|
||||||
|
for _, op := range s.GetOpCodes() {
|
||||||
|
fmt.Fprintf(w, "%s a[%d:%d], (%s) b[%d:%d] (%s)\n", string(op.Tag),
|
||||||
|
op.I1, op.I2, a[op.I1:op.I2], op.J1, op.J2, b[op.J1:op.J2])
|
||||||
|
}
|
||||||
|
result := w.String()
|
||||||
|
expected := `d a[0:1], (q) b[0:0] ()
|
||||||
|
e a[1:3], (ab) b[0:2] (ab)
|
||||||
|
r a[3:4], (x) b[2:3] (y)
|
||||||
|
e a[4:6], (cd) b[3:5] (cd)
|
||||||
|
i a[6:6], () b[5:6] (f)
|
||||||
|
`
|
||||||
|
if expected != result {
|
||||||
|
t.Errorf("unexpected op codes: \n%s", result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGroupedOpCodes(t *testing.T) {
|
||||||
|
a := []string{}
|
||||||
|
for i := 0; i != 39; i++ {
|
||||||
|
a = append(a, fmt.Sprintf("%02d", i))
|
||||||
|
}
|
||||||
|
b := []string{}
|
||||||
|
b = append(b, a[:8]...)
|
||||||
|
b = append(b, " i")
|
||||||
|
b = append(b, a[8:19]...)
|
||||||
|
b = append(b, " x")
|
||||||
|
b = append(b, a[20:22]...)
|
||||||
|
b = append(b, a[27:34]...)
|
||||||
|
b = append(b, " y")
|
||||||
|
b = append(b, a[35:]...)
|
||||||
|
s := NewMatcher(a, b)
|
||||||
|
w := &bytes.Buffer{}
|
||||||
|
for _, g := range s.GetGroupedOpCodes(-1) {
|
||||||
|
fmt.Fprintf(w, "group\n")
|
||||||
|
for _, op := range g {
|
||||||
|
fmt.Fprintf(w, " %s, %d, %d, %d, %d\n", string(op.Tag),
|
||||||
|
op.I1, op.I2, op.J1, op.J2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result := w.String()
|
||||||
|
expected := `group
|
||||||
|
e, 5, 8, 5, 8
|
||||||
|
i, 8, 8, 8, 9
|
||||||
|
e, 8, 11, 9, 12
|
||||||
|
group
|
||||||
|
e, 16, 19, 17, 20
|
||||||
|
r, 19, 20, 20, 21
|
||||||
|
e, 20, 22, 21, 23
|
||||||
|
d, 22, 27, 23, 23
|
||||||
|
e, 27, 30, 23, 26
|
||||||
|
group
|
||||||
|
e, 31, 34, 27, 30
|
||||||
|
r, 34, 35, 30, 31
|
||||||
|
e, 35, 38, 31, 34
|
||||||
|
`
|
||||||
|
if expected != result {
|
||||||
|
t.Errorf("unexpected op codes: \n%s", result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleGetUnifiedDiffCode() {
|
||||||
|
a := `one
|
||||||
|
two
|
||||||
|
three
|
||||||
|
four
|
||||||
|
fmt.Printf("%s,%T",a,b)`
|
||||||
|
b := `zero
|
||||||
|
one
|
||||||
|
three
|
||||||
|
four`
|
||||||
|
diff := UnifiedDiff{
|
||||||
|
A: SplitLines(a),
|
||||||
|
B: SplitLines(b),
|
||||||
|
FromFile: "Original",
|
||||||
|
FromDate: "2005-01-26 23:30:50",
|
||||||
|
ToFile: "Current",
|
||||||
|
ToDate: "2010-04-02 10:20:52",
|
||||||
|
Context: 3,
|
||||||
|
}
|
||||||
|
result, _ := GetUnifiedDiffString(diff)
|
||||||
|
fmt.Println(strings.ReplaceAll(result, "\t", " "))
|
||||||
|
// Output:
|
||||||
|
// --- Original 2005-01-26 23:30:50
|
||||||
|
// +++ Current 2010-04-02 10:20:52
|
||||||
|
// @@ -1,5 +1,4 @@
|
||||||
|
// +zero
|
||||||
|
// one
|
||||||
|
// -two
|
||||||
|
// three
|
||||||
|
// four
|
||||||
|
// -fmt.Printf("%s,%T",a,b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func rep(s string, count int) string {
|
||||||
|
return strings.Repeat(s, count)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWithAsciiOneInsert(t *testing.T) {
|
||||||
|
sm := NewMatcher(splitChars(rep("b", 100)),
|
||||||
|
splitChars("a"+rep("b", 100)))
|
||||||
|
assertAlmostEqual(t, sm.Ratio(), 0.995, 3)
|
||||||
|
assertEqual(t, sm.GetOpCodes(),
|
||||||
|
[]OpCode{{'i', 0, 0, 0, 1}, {'e', 0, 100, 1, 101}})
|
||||||
|
assertEqual(t, len(sm.bPopular), 0)
|
||||||
|
|
||||||
|
sm = NewMatcher(splitChars(rep("b", 100)),
|
||||||
|
splitChars(rep("b", 50)+"a"+rep("b", 50)))
|
||||||
|
assertAlmostEqual(t, sm.Ratio(), 0.995, 3)
|
||||||
|
assertEqual(t, sm.GetOpCodes(),
|
||||||
|
[]OpCode{{'e', 0, 50, 0, 50}, {'i', 50, 50, 50, 51}, {'e', 50, 100, 51, 101}})
|
||||||
|
assertEqual(t, len(sm.bPopular), 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWithAsciiOnDelete(t *testing.T) {
|
||||||
|
sm := NewMatcher(splitChars(rep("a", 40)+"c"+rep("b", 40)),
|
||||||
|
splitChars(rep("a", 40)+rep("b", 40)))
|
||||||
|
assertAlmostEqual(t, sm.Ratio(), 0.994, 3)
|
||||||
|
assertEqual(t, sm.GetOpCodes(),
|
||||||
|
[]OpCode{{'e', 0, 40, 0, 40}, {'d', 40, 41, 40, 40}, {'e', 41, 81, 40, 80}})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWithAsciiBJunk(t *testing.T) {
|
||||||
|
isJunk := func(s string) bool {
|
||||||
|
return s == " "
|
||||||
|
}
|
||||||
|
sm := NewMatcherWithJunk(splitChars(rep("a", 40)+rep("b", 40)),
|
||||||
|
splitChars(rep("a", 44)+rep("b", 40)), true, isJunk)
|
||||||
|
assertEqual(t, sm.bJunk, map[string]struct{}{})
|
||||||
|
|
||||||
|
sm = NewMatcherWithJunk(splitChars(rep("a", 40)+rep("b", 40)),
|
||||||
|
splitChars(rep("a", 44)+rep("b", 40)+rep(" ", 20)), false, isJunk)
|
||||||
|
assertEqual(t, sm.bJunk, map[string]struct{}{" ": {}})
|
||||||
|
|
||||||
|
isJunk = func(s string) bool {
|
||||||
|
return s == " " || s == "b"
|
||||||
|
}
|
||||||
|
sm = NewMatcherWithJunk(splitChars(rep("a", 40)+rep("b", 40)),
|
||||||
|
splitChars(rep("a", 44)+rep("b", 40)+rep(" ", 20)), false, isJunk)
|
||||||
|
assertEqual(t, sm.bJunk, map[string]struct{}{" ": {}, "b": {}})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSFBugsRatioForNullSeqn(t *testing.T) {
|
||||||
|
sm := NewMatcher(nil, nil)
|
||||||
|
assertEqual(t, sm.Ratio(), 1.0)
|
||||||
|
assertEqual(t, sm.QuickRatio(), 1.0)
|
||||||
|
assertEqual(t, sm.RealQuickRatio(), 1.0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSFBugsComparingEmptyLists(t *testing.T) {
|
||||||
|
groups := NewMatcher(nil, nil).GetGroupedOpCodes(-1)
|
||||||
|
assertEqual(t, len(groups), 0)
|
||||||
|
diff := UnifiedDiff{
|
||||||
|
FromFile: "Original",
|
||||||
|
ToFile: "Current",
|
||||||
|
Context: 3,
|
||||||
|
}
|
||||||
|
result, err := GetUnifiedDiffString(diff)
|
||||||
|
assertEqual(t, err, nil)
|
||||||
|
assertEqual(t, result, "")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestOutputFormatRangeFormatUnified(t *testing.T) {
|
||||||
|
// Per the diff spec at http://www.unix.org/single_unix_specification/
|
||||||
|
//
|
||||||
|
// Each <range> field shall be of the form:
|
||||||
|
// %1d", <beginning line number> if the range contains exactly one line,
|
||||||
|
// and:
|
||||||
|
// "%1d,%1d", <beginning line number>, <number of lines> otherwise.
|
||||||
|
// If a range is empty, its beginning line number shall be the number of
|
||||||
|
// the line just before the range, or 0 if the empty range starts the file.
|
||||||
|
fm := formatRangeUnified
|
||||||
|
assertEqual(t, fm(3, 3), "3,0")
|
||||||
|
assertEqual(t, fm(3, 4), "4")
|
||||||
|
assertEqual(t, fm(3, 5), "4,2")
|
||||||
|
assertEqual(t, fm(3, 6), "4,3")
|
||||||
|
assertEqual(t, fm(0, 0), "0,0")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSplitLines(t *testing.T) {
|
||||||
|
allTests := []struct {
|
||||||
|
input string
|
||||||
|
want []string
|
||||||
|
}{
|
||||||
|
{"foo", []string{"foo\n"}},
|
||||||
|
{"foo\nbar", []string{"foo\n", "bar\n"}},
|
||||||
|
{"foo\nbar\n", []string{"foo\n", "bar\n", "\n"}},
|
||||||
|
}
|
||||||
|
for _, test := range allTests {
|
||||||
|
assertEqual(t, SplitLines(test.input), test.want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func benchmarkSplitLines(b *testing.B, count int) {
|
||||||
|
str := strings.Repeat("foo\n", count)
|
||||||
|
|
||||||
|
b.ResetTimer()
|
||||||
|
|
||||||
|
n := 0
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
n += len(SplitLines(str))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkSplitLines100(b *testing.B) {
|
||||||
|
benchmarkSplitLines(b, 100)
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkSplitLines10000(b *testing.B) {
|
||||||
|
benchmarkSplitLines(b, 10000)
|
||||||
|
}
|
|
@ -0,0 +1,32 @@
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
package internal
|
||||||
|
|
||||||
|
import "regexp"
|
||||||
|
|
||||||
|
type GoCollectorRule struct {
|
||||||
|
Matcher *regexp.Regexp
|
||||||
|
Deny bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// GoCollectorOptions should not be used be directly by anything, except `collectors` package.
|
||||||
|
// Use it via collectors package instead. See issue
|
||||||
|
// https://git.internal/re/client_golang/issues/1030.
|
||||||
|
//
|
||||||
|
// This is internal, so external users only can use it via `collector.WithGoCollector*` methods
|
||||||
|
type GoCollectorOptions struct {
|
||||||
|
DisableMemStatsLikeMetrics bool
|
||||||
|
RuntimeMetricSumForHist map[string]string
|
||||||
|
RuntimeMetricRules []GoCollectorRule
|
||||||
|
}
|
|
@ -61,9 +61,9 @@ func RuntimeMetricsToProm(d *metrics.Description) (string, string, string, bool)
|
||||||
// name has - replaced with _ and is concatenated with the unit and
|
// name has - replaced with _ and is concatenated with the unit and
|
||||||
// other data.
|
// other data.
|
||||||
name = strings.ReplaceAll(name, "-", "_")
|
name = strings.ReplaceAll(name, "-", "_")
|
||||||
name = name + "_" + unit
|
name += "_" + unit
|
||||||
if d.Cumulative && d.Kind != metrics.KindFloat64Histogram {
|
if d.Cumulative && d.Kind != metrics.KindFloat64Histogram {
|
||||||
name = name + "_total"
|
name += "_total"
|
||||||
}
|
}
|
||||||
|
|
||||||
valid := model.IsValidMetricName(model.LabelValue(namespace + "_" + subsystem + "_" + name))
|
valid := model.IsValidMetricName(model.LabelValue(namespace + "_" + subsystem + "_" + name))
|
||||||
|
|
|
@ -19,18 +19,34 @@ import (
|
||||||
dto "github.com/prometheus/client_model/go"
|
dto "github.com/prometheus/client_model/go"
|
||||||
)
|
)
|
||||||
|
|
||||||
// metricSorter is a sortable slice of *dto.Metric.
|
// LabelPairSorter implements sort.Interface. It is used to sort a slice of
|
||||||
type metricSorter []*dto.Metric
|
// dto.LabelPair pointers.
|
||||||
|
type LabelPairSorter []*dto.LabelPair
|
||||||
|
|
||||||
func (s metricSorter) Len() int {
|
func (s LabelPairSorter) Len() int {
|
||||||
return len(s)
|
return len(s)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s metricSorter) Swap(i, j int) {
|
func (s LabelPairSorter) Swap(i, j int) {
|
||||||
s[i], s[j] = s[j], s[i]
|
s[i], s[j] = s[j], s[i]
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s metricSorter) Less(i, j int) bool {
|
func (s LabelPairSorter) Less(i, j int) bool {
|
||||||
|
return s[i].GetName() < s[j].GetName()
|
||||||
|
}
|
||||||
|
|
||||||
|
// MetricSorter is a sortable slice of *dto.Metric.
|
||||||
|
type MetricSorter []*dto.Metric
|
||||||
|
|
||||||
|
func (s MetricSorter) Len() int {
|
||||||
|
return len(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s MetricSorter) Swap(i, j int) {
|
||||||
|
s[i], s[j] = s[j], s[i]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s MetricSorter) Less(i, j int) bool {
|
||||||
if len(s[i].Label) != len(s[j].Label) {
|
if len(s[i].Label) != len(s[j].Label) {
|
||||||
// This should not happen. The metrics are
|
// This should not happen. The metrics are
|
||||||
// inconsistent. However, we have to deal with the fact, as
|
// inconsistent. However, we have to deal with the fact, as
|
||||||
|
@ -68,7 +84,7 @@ func (s metricSorter) Less(i, j int) bool {
|
||||||
// the slice, with the contained Metrics sorted within each MetricFamily.
|
// the slice, with the contained Metrics sorted within each MetricFamily.
|
||||||
func NormalizeMetricFamilies(metricFamiliesByName map[string]*dto.MetricFamily) []*dto.MetricFamily {
|
func NormalizeMetricFamilies(metricFamiliesByName map[string]*dto.MetricFamily) []*dto.MetricFamily {
|
||||||
for _, mf := range metricFamiliesByName {
|
for _, mf := range metricFamiliesByName {
|
||||||
sort.Sort(metricSorter(mf.Metric))
|
sort.Sort(MetricSorter(mf.Metric))
|
||||||
}
|
}
|
||||||
names := make([]string, 0, len(metricFamiliesByName))
|
names := make([]string, 0, len(metricFamiliesByName))
|
||||||
for name, mf := range metricFamiliesByName {
|
for name, mf := range metricFamiliesByName {
|
||||||
|
|
|
@ -25,7 +25,8 @@ import (
|
||||||
// Labels represents a collection of label name -> value mappings. This type is
|
// Labels represents a collection of label name -> value mappings. This type is
|
||||||
// commonly used with the With(Labels) and GetMetricWith(Labels) methods of
|
// commonly used with the With(Labels) and GetMetricWith(Labels) methods of
|
||||||
// metric vector Collectors, e.g.:
|
// metric vector Collectors, e.g.:
|
||||||
// myVec.With(Labels{"code": "404", "method": "GET"}).Add(42)
|
//
|
||||||
|
// myVec.With(Labels{"code": "404", "method": "GET"}).Add(42)
|
||||||
//
|
//
|
||||||
// The other use-case is the specification of constant label pairs in Opts or to
|
// The other use-case is the specification of constant label pairs in Opts or to
|
||||||
// create a Desc.
|
// create a Desc.
|
||||||
|
@ -39,7 +40,7 @@ var errInconsistentCardinality = errors.New("inconsistent label cardinality")
|
||||||
|
|
||||||
func makeInconsistentCardinalityError(fqName string, labels, labelValues []string) error {
|
func makeInconsistentCardinalityError(fqName string, labels, labelValues []string) error {
|
||||||
return fmt.Errorf(
|
return fmt.Errorf(
|
||||||
"%s: %q has %d variable labels named %q but %d values %q were provided",
|
"%w: %q has %d variable labels named %q but %d values %q were provided",
|
||||||
errInconsistentCardinality, fqName,
|
errInconsistentCardinality, fqName,
|
||||||
len(labels), labels,
|
len(labels), labels,
|
||||||
len(labelValues), labelValues,
|
len(labelValues), labelValues,
|
||||||
|
@ -49,7 +50,7 @@ func makeInconsistentCardinalityError(fqName string, labels, labelValues []strin
|
||||||
func validateValuesInLabels(labels Labels, expectedNumberOfValues int) error {
|
func validateValuesInLabels(labels Labels, expectedNumberOfValues int) error {
|
||||||
if len(labels) != expectedNumberOfValues {
|
if len(labels) != expectedNumberOfValues {
|
||||||
return fmt.Errorf(
|
return fmt.Errorf(
|
||||||
"%s: expected %d label values but got %d in %#v",
|
"%w: expected %d label values but got %d in %#v",
|
||||||
errInconsistentCardinality, expectedNumberOfValues,
|
errInconsistentCardinality, expectedNumberOfValues,
|
||||||
len(labels), labels,
|
len(labels), labels,
|
||||||
)
|
)
|
||||||
|
@ -67,7 +68,7 @@ func validateValuesInLabels(labels Labels, expectedNumberOfValues int) error {
|
||||||
func validateLabelValues(vals []string, expectedNumberOfValues int) error {
|
func validateLabelValues(vals []string, expectedNumberOfValues int) error {
|
||||||
if len(vals) != expectedNumberOfValues {
|
if len(vals) != expectedNumberOfValues {
|
||||||
return fmt.Errorf(
|
return fmt.Errorf(
|
||||||
"%s: expected %d label values but got %d in %#v",
|
"%w: expected %d label values but got %d in %#v",
|
||||||
errInconsistentCardinality, expectedNumberOfValues,
|
errInconsistentCardinality, expectedNumberOfValues,
|
||||||
len(vals), vals,
|
len(vals), vals,
|
||||||
)
|
)
|
||||||
|
|
|
@ -14,6 +14,9 @@
|
||||||
package prometheus
|
package prometheus
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
|
"math"
|
||||||
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -115,22 +118,6 @@ func BuildFQName(namespace, subsystem, name string) string {
|
||||||
return name
|
return name
|
||||||
}
|
}
|
||||||
|
|
||||||
// labelPairSorter implements sort.Interface. It is used to sort a slice of
|
|
||||||
// dto.LabelPair pointers.
|
|
||||||
type labelPairSorter []*dto.LabelPair
|
|
||||||
|
|
||||||
func (s labelPairSorter) Len() int {
|
|
||||||
return len(s)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s labelPairSorter) Swap(i, j int) {
|
|
||||||
s[i], s[j] = s[j], s[i]
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s labelPairSorter) Less(i, j int) bool {
|
|
||||||
return s[i].GetName() < s[j].GetName()
|
|
||||||
}
|
|
||||||
|
|
||||||
type invalidMetric struct {
|
type invalidMetric struct {
|
||||||
desc *Desc
|
desc *Desc
|
||||||
err error
|
err error
|
||||||
|
@ -174,3 +161,96 @@ func (m timestampedMetric) Write(pb *dto.Metric) error {
|
||||||
func NewMetricWithTimestamp(t time.Time, m Metric) Metric {
|
func NewMetricWithTimestamp(t time.Time, m Metric) Metric {
|
||||||
return timestampedMetric{Metric: m, t: t}
|
return timestampedMetric{Metric: m, t: t}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type withExemplarsMetric struct {
|
||||||
|
Metric
|
||||||
|
|
||||||
|
exemplars []*dto.Exemplar
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *withExemplarsMetric) Write(pb *dto.Metric) error {
|
||||||
|
if err := m.Metric.Write(pb); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case pb.Counter != nil:
|
||||||
|
pb.Counter.Exemplar = m.exemplars[len(m.exemplars)-1]
|
||||||
|
case pb.Histogram != nil:
|
||||||
|
for _, e := range m.exemplars {
|
||||||
|
// pb.Histogram.Bucket are sorted by UpperBound.
|
||||||
|
i := sort.Search(len(pb.Histogram.Bucket), func(i int) bool {
|
||||||
|
return pb.Histogram.Bucket[i].GetUpperBound() >= e.GetValue()
|
||||||
|
})
|
||||||
|
if i < len(pb.Histogram.Bucket) {
|
||||||
|
pb.Histogram.Bucket[i].Exemplar = e
|
||||||
|
} else {
|
||||||
|
// The +Inf bucket should be explicitly added if there is an exemplar for it, similar to non-const histogram logic in https://git.internal/re/client_golang/blob/main/prometheus/histogram.go#L357-L365.
|
||||||
|
b := &dto.Bucket{
|
||||||
|
CumulativeCount: proto.Uint64(pb.Histogram.GetSampleCount()),
|
||||||
|
UpperBound: proto.Float64(math.Inf(1)),
|
||||||
|
Exemplar: e,
|
||||||
|
}
|
||||||
|
pb.Histogram.Bucket = append(pb.Histogram.Bucket, b)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
// TODO(bwplotka): Implement Gauge?
|
||||||
|
return errors.New("cannot inject exemplar into Gauge, Summary or Untyped")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exemplar is easier to use, user-facing representation of *dto.Exemplar.
|
||||||
|
type Exemplar struct {
|
||||||
|
Value float64
|
||||||
|
Labels Labels
|
||||||
|
// Optional.
|
||||||
|
// Default value (time.Time{}) indicates its empty, which should be
|
||||||
|
// understood as time.Now() time at the moment of creation of metric.
|
||||||
|
Timestamp time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMetricWithExemplars returns a new Metric wrapping the provided Metric with given
|
||||||
|
// exemplars. Exemplars are validated.
|
||||||
|
//
|
||||||
|
// Only last applicable exemplar is injected from the list.
|
||||||
|
// For example for Counter it means last exemplar is injected.
|
||||||
|
// For Histogram, it means last applicable exemplar for each bucket is injected.
|
||||||
|
//
|
||||||
|
// NewMetricWithExemplars works best with MustNewConstMetric and
|
||||||
|
// MustNewConstHistogram, see example.
|
||||||
|
func NewMetricWithExemplars(m Metric, exemplars ...Exemplar) (Metric, error) {
|
||||||
|
if len(exemplars) == 0 {
|
||||||
|
return nil, errors.New("no exemplar was passed for NewMetricWithExemplars")
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
now = time.Now()
|
||||||
|
exs = make([]*dto.Exemplar, len(exemplars))
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
for i, e := range exemplars {
|
||||||
|
ts := e.Timestamp
|
||||||
|
if ts == (time.Time{}) {
|
||||||
|
ts = now
|
||||||
|
}
|
||||||
|
exs[i], err = newExemplar(e.Value, ts, e.Labels)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &withExemplarsMetric{Metric: m, exemplars: exs}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// MustNewMetricWithExemplars is a version of NewMetricWithExemplars that panics where
|
||||||
|
// NewMetricWithExemplars would have returned an error.
|
||||||
|
func MustNewMetricWithExemplars(m Metric, exemplars ...Exemplar) Metric {
|
||||||
|
ret, err := NewMetricWithExemplars(m, exemplars...)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
|
@ -13,7 +13,14 @@
|
||||||
|
|
||||||
package prometheus
|
package prometheus
|
||||||
|
|
||||||
import "testing"
|
import (
|
||||||
|
"math"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
//nolint:staticcheck // Ignore SA1019. Need to keep deprecated package for compatibility.
|
||||||
|
"github.com/golang/protobuf/proto"
|
||||||
|
dto "github.com/prometheus/client_model/go"
|
||||||
|
)
|
||||||
|
|
||||||
func TestBuildFQName(t *testing.T) {
|
func TestBuildFQName(t *testing.T) {
|
||||||
scenarios := []struct{ namespace, subsystem, name, result string }{
|
scenarios := []struct{ namespace, subsystem, name, result string }{
|
||||||
|
@ -33,3 +40,53 @@ func TestBuildFQName(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestWithExemplarsMetric(t *testing.T) {
|
||||||
|
t.Run("histogram", func(t *testing.T) {
|
||||||
|
// Create a constant histogram from values we got from a 3rd party telemetry system.
|
||||||
|
h := MustNewConstHistogram(
|
||||||
|
NewDesc("http_request_duration_seconds", "A histogram of the HTTP request durations.", nil, nil),
|
||||||
|
4711, 403.34,
|
||||||
|
// Four buckets, but we expect five as the +Inf bucket will be created if we see value outside of those buckets.
|
||||||
|
map[float64]uint64{25: 121, 50: 2403, 100: 3221, 200: 4233},
|
||||||
|
)
|
||||||
|
|
||||||
|
m := &withExemplarsMetric{Metric: h, exemplars: []*dto.Exemplar{
|
||||||
|
{Value: proto.Float64(2000.0)}, // Unordered exemplars.
|
||||||
|
{Value: proto.Float64(500.0)},
|
||||||
|
{Value: proto.Float64(42.0)},
|
||||||
|
{Value: proto.Float64(157.0)},
|
||||||
|
{Value: proto.Float64(100.0)},
|
||||||
|
{Value: proto.Float64(89.0)},
|
||||||
|
{Value: proto.Float64(24.0)},
|
||||||
|
{Value: proto.Float64(25.1)},
|
||||||
|
}}
|
||||||
|
metric := dto.Metric{}
|
||||||
|
if err := m.Write(&metric); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if want, got := 5, len(metric.GetHistogram().Bucket); want != got {
|
||||||
|
t.Errorf("want %v, got %v", want, got)
|
||||||
|
}
|
||||||
|
|
||||||
|
expectedExemplarVals := []float64{24.0, 25.1, 89.0, 157.0, 500.0}
|
||||||
|
for i, b := range metric.GetHistogram().Bucket {
|
||||||
|
if b.Exemplar == nil {
|
||||||
|
t.Errorf("Expected exemplar for bucket %v, got nil", i)
|
||||||
|
}
|
||||||
|
if want, got := expectedExemplarVals[i], *metric.GetHistogram().Bucket[i].Exemplar.Value; want != got {
|
||||||
|
t.Errorf("%v: want %v, got %v", i, want, got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
infBucket := metric.GetHistogram().Bucket[len(metric.GetHistogram().Bucket)-1]
|
||||||
|
|
||||||
|
if want, got := math.Inf(1), infBucket.GetUpperBound(); want != got {
|
||||||
|
t.Errorf("want %v, got %v", want, got)
|
||||||
|
}
|
||||||
|
|
||||||
|
if want, got := uint64(4711), infBucket.GetCumulativeCount(); want != got {
|
||||||
|
t.Errorf("want %v, got %v", want, got)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,25 @@
|
||||||
|
// Copyright 2018 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 !js || wasm
|
||||||
|
// +build !js wasm
|
||||||
|
|
||||||
|
package prometheus
|
||||||
|
|
||||||
|
import "runtime"
|
||||||
|
|
||||||
|
// getRuntimeNumThreads returns the number of open OS threads.
|
||||||
|
func getRuntimeNumThreads() float64 {
|
||||||
|
n, _ := runtime.ThreadCreateProfile(nil)
|
||||||
|
return float64(n)
|
||||||
|
}
|
|
@ -0,0 +1,22 @@
|
||||||
|
// Copyright 2018 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 js && !wasm
|
||||||
|
// +build js,!wasm
|
||||||
|
|
||||||
|
package prometheus
|
||||||
|
|
||||||
|
// getRuntimeNumThreads returns the number of open OS threads.
|
||||||
|
func getRuntimeNumThreads() float64 {
|
||||||
|
return 1
|
||||||
|
}
|
|
@ -58,7 +58,7 @@ type ObserverVec interface {
|
||||||
// current time as timestamp, and the provided Labels. Empty Labels will lead to
|
// current time as timestamp, and the provided Labels. Empty Labels will lead to
|
||||||
// a valid (label-less) exemplar. But if Labels is nil, the current exemplar is
|
// a valid (label-less) exemplar. But if Labels is nil, the current exemplar is
|
||||||
// left in place. ObserveWithExemplar panics if any of the provided labels are
|
// left in place. ObserveWithExemplar panics if any of the provided labels are
|
||||||
// invalid or if the provided labels contain more than 64 runes in total.
|
// invalid or if the provided labels contain more than 128 runes in total.
|
||||||
type ExemplarObserver interface {
|
type ExemplarObserver interface {
|
||||||
ObserveWithExemplar(value float64, exemplar Labels)
|
ObserveWithExemplar(value float64, exemplar Labels)
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,7 +16,6 @@ package prometheus
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
@ -104,8 +103,7 @@ func NewProcessCollector(opts ProcessCollectorOpts) Collector {
|
||||||
}
|
}
|
||||||
|
|
||||||
if opts.PidFn == nil {
|
if opts.PidFn == nil {
|
||||||
pid := os.Getpid()
|
c.pidFn = getPIDFn()
|
||||||
c.pidFn = func() (int, error) { return pid, nil }
|
|
||||||
} else {
|
} else {
|
||||||
c.pidFn = opts.PidFn
|
c.pidFn = opts.PidFn
|
||||||
}
|
}
|
||||||
|
@ -152,13 +150,13 @@ func (c *processCollector) reportError(ch chan<- Metric, desc *Desc, err error)
|
||||||
// It is meant to be used for the PidFn field in ProcessCollectorOpts.
|
// It is meant to be used for the PidFn field in ProcessCollectorOpts.
|
||||||
func NewPidFileFn(pidFilePath string) func() (int, error) {
|
func NewPidFileFn(pidFilePath string) func() (int, error) {
|
||||||
return func() (int, error) {
|
return func() (int, error) {
|
||||||
content, err := ioutil.ReadFile(pidFilePath)
|
content, err := os.ReadFile(pidFilePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, fmt.Errorf("can't read pid file %q: %+v", pidFilePath, err)
|
return 0, fmt.Errorf("can't read pid file %q: %w", pidFilePath, err)
|
||||||
}
|
}
|
||||||
pid, err := strconv.Atoi(strings.TrimSpace(string(content)))
|
pid, err := strconv.Atoi(strings.TrimSpace(string(content)))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, fmt.Errorf("can't parse pid file %q: %+v", pidFilePath, err)
|
return 0, fmt.Errorf("can't parse pid file %q: %w", pidFilePath, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return pid, nil
|
return pid, nil
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
// Copyright 2021 The Prometheus Authors
|
// Copyright 2019 The Prometheus Authors
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
// You may obtain a copy of the License at
|
// You may obtain a copy of the License at
|
||||||
|
@ -11,21 +11,16 @@
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
//go:build go1.15
|
//go:build js
|
||||||
// +build go1.15
|
// +build js
|
||||||
|
|
||||||
package collectors
|
package prometheus
|
||||||
|
|
||||||
import (
|
func canCollectProcess() bool {
|
||||||
"database/sql"
|
return false
|
||||||
|
|
||||||
"github.com/prometheus/client_golang/prometheus"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (c *dbStatsCollector) describeNewInGo115(ch chan<- *prometheus.Desc) {
|
|
||||||
ch <- c.maxIdleTimeClosed
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *dbStatsCollector) collectNewInGo115(ch chan<- prometheus.Metric, stats sql.DBStats) {
|
func (c *processCollector) processCollect(ch chan<- Metric) {
|
||||||
ch <- prometheus.MustNewConstMetric(c.maxIdleTimeClosed, prometheus.CounterValue, float64(stats.MaxIdleTimeClosed))
|
// noop on this platform
|
||||||
|
return
|
||||||
}
|
}
|
|
@ -11,8 +11,8 @@
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
//go:build !windows
|
//go:build !windows && !js
|
||||||
// +build !windows
|
// +build !windows,!js
|
||||||
|
|
||||||
package prometheus
|
package prometheus
|
||||||
|
|
||||||
|
|
|
@ -14,114 +14,114 @@
|
||||||
// Package promauto provides alternative constructors for the fundamental
|
// Package promauto provides alternative constructors for the fundamental
|
||||||
// Prometheus metric types and their …Vec and …Func variants. The difference to
|
// Prometheus metric types and their …Vec and …Func variants. The difference to
|
||||||
// their counterparts in the prometheus package is that the promauto
|
// their counterparts in the prometheus package is that the promauto
|
||||||
// constructors return Collectors that are already registered with a
|
// constructors register the Collectors with a registry before returning them.
|
||||||
// registry. There are two sets of constructors. The constructors in the first
|
// There are two sets of constructors. The constructors in the first set are
|
||||||
// set are top-level functions, while the constructors in the other set are
|
// top-level functions, while the constructors in the other set are methods of
|
||||||
// methods of the Factory type. The top-level function return Collectors
|
// the Factory type. The top-level function return Collectors registered with
|
||||||
// registered with the global registry (prometheus.DefaultRegisterer), while the
|
// the global registry (prometheus.DefaultRegisterer), while the methods return
|
||||||
// methods return Collectors registered with the registry the Factory was
|
// Collectors registered with the registry the Factory was constructed with. All
|
||||||
// constructed with. All constructors panic if the registration fails.
|
// constructors panic if the registration fails.
|
||||||
//
|
//
|
||||||
// The following example is a complete program to create a histogram of normally
|
// The following example is a complete program to create a histogram of normally
|
||||||
// distributed random numbers from the math/rand package:
|
// distributed random numbers from the math/rand package:
|
||||||
//
|
//
|
||||||
// package main
|
// package main
|
||||||
//
|
//
|
||||||
// import (
|
// import (
|
||||||
// "math/rand"
|
// "math/rand"
|
||||||
// "net/http"
|
// "net/http"
|
||||||
//
|
//
|
||||||
// "github.com/prometheus/client_golang/prometheus"
|
// "git.internal/re/client_golang/prometheus"
|
||||||
// "github.com/prometheus/client_golang/prometheus/promauto"
|
// "git.internal/re/client_golang/prometheus/promauto"
|
||||||
// "github.com/prometheus/client_golang/prometheus/promhttp"
|
// "git.internal/re/client_golang/prometheus/promhttp"
|
||||||
// )
|
// )
|
||||||
//
|
//
|
||||||
// var histogram = promauto.NewHistogram(prometheus.HistogramOpts{
|
// var histogram = promauto.NewHistogram(prometheus.HistogramOpts{
|
||||||
// Name: "random_numbers",
|
// Name: "random_numbers",
|
||||||
// Help: "A histogram of normally distributed random numbers.",
|
// Help: "A histogram of normally distributed random numbers.",
|
||||||
// Buckets: prometheus.LinearBuckets(-3, .1, 61),
|
// Buckets: prometheus.LinearBuckets(-3, .1, 61),
|
||||||
// })
|
// })
|
||||||
//
|
//
|
||||||
// func Random() {
|
// func Random() {
|
||||||
// for {
|
// for {
|
||||||
// histogram.Observe(rand.NormFloat64())
|
// histogram.Observe(rand.NormFloat64())
|
||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
//
|
//
|
||||||
// func main() {
|
// func main() {
|
||||||
// go Random()
|
// go Random()
|
||||||
// http.Handle("/metrics", promhttp.Handler())
|
// http.Handle("/metrics", promhttp.Handler())
|
||||||
// http.ListenAndServe(":1971", nil)
|
// http.ListenAndServe(":1971", nil)
|
||||||
// }
|
// }
|
||||||
//
|
//
|
||||||
// Prometheus's version of a minimal hello-world program:
|
// Prometheus's version of a minimal hello-world program:
|
||||||
//
|
//
|
||||||
// package main
|
// package main
|
||||||
//
|
//
|
||||||
// import (
|
// import (
|
||||||
// "fmt"
|
// "fmt"
|
||||||
// "net/http"
|
// "net/http"
|
||||||
//
|
//
|
||||||
// "github.com/prometheus/client_golang/prometheus"
|
// "git.internal/re/client_golang/prometheus"
|
||||||
// "github.com/prometheus/client_golang/prometheus/promauto"
|
// "git.internal/re/client_golang/prometheus/promauto"
|
||||||
// "github.com/prometheus/client_golang/prometheus/promhttp"
|
// "git.internal/re/client_golang/prometheus/promhttp"
|
||||||
// )
|
// )
|
||||||
//
|
//
|
||||||
// func main() {
|
// func main() {
|
||||||
// http.Handle("/", promhttp.InstrumentHandlerCounter(
|
// http.Handle("/", promhttp.InstrumentHandlerCounter(
|
||||||
// promauto.NewCounterVec(
|
// promauto.NewCounterVec(
|
||||||
// prometheus.CounterOpts{
|
// prometheus.CounterOpts{
|
||||||
// Name: "hello_requests_total",
|
// Name: "hello_requests_total",
|
||||||
// Help: "Total number of hello-world requests by HTTP code.",
|
// Help: "Total number of hello-world requests by HTTP code.",
|
||||||
// },
|
// },
|
||||||
// []string{"code"},
|
// []string{"code"},
|
||||||
// ),
|
// ),
|
||||||
// http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
// http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
// fmt.Fprint(w, "Hello, world!")
|
// fmt.Fprint(w, "Hello, world!")
|
||||||
// }),
|
// }),
|
||||||
// ))
|
// ))
|
||||||
// http.Handle("/metrics", promhttp.Handler())
|
// http.Handle("/metrics", promhttp.Handler())
|
||||||
// http.ListenAndServe(":1971", nil)
|
// http.ListenAndServe(":1971", nil)
|
||||||
// }
|
// }
|
||||||
//
|
//
|
||||||
// A Factory is created with the With(prometheus.Registerer) function, which
|
// A Factory is created with the With(prometheus.Registerer) function, which
|
||||||
// enables two usage pattern. With(prometheus.Registerer) can be called once per
|
// enables two usage pattern. With(prometheus.Registerer) can be called once per
|
||||||
// line:
|
// line:
|
||||||
//
|
//
|
||||||
// var (
|
// var (
|
||||||
// reg = prometheus.NewRegistry()
|
// reg = prometheus.NewRegistry()
|
||||||
// randomNumbers = promauto.With(reg).NewHistogram(prometheus.HistogramOpts{
|
// randomNumbers = promauto.With(reg).NewHistogram(prometheus.HistogramOpts{
|
||||||
// Name: "random_numbers",
|
// Name: "random_numbers",
|
||||||
// Help: "A histogram of normally distributed random numbers.",
|
// Help: "A histogram of normally distributed random numbers.",
|
||||||
// Buckets: prometheus.LinearBuckets(-3, .1, 61),
|
// Buckets: prometheus.LinearBuckets(-3, .1, 61),
|
||||||
// })
|
// })
|
||||||
// requestCount = promauto.With(reg).NewCounterVec(
|
// requestCount = promauto.With(reg).NewCounterVec(
|
||||||
// prometheus.CounterOpts{
|
// prometheus.CounterOpts{
|
||||||
// Name: "http_requests_total",
|
// Name: "http_requests_total",
|
||||||
// Help: "Total number of HTTP requests by status code and method.",
|
// Help: "Total number of HTTP requests by status code and method.",
|
||||||
// },
|
// },
|
||||||
// []string{"code", "method"},
|
// []string{"code", "method"},
|
||||||
// )
|
// )
|
||||||
// )
|
// )
|
||||||
//
|
//
|
||||||
// Or it can be used to create a Factory once to be used multiple times:
|
// Or it can be used to create a Factory once to be used multiple times:
|
||||||
//
|
//
|
||||||
// var (
|
// var (
|
||||||
// reg = prometheus.NewRegistry()
|
// reg = prometheus.NewRegistry()
|
||||||
// factory = promauto.With(reg)
|
// factory = promauto.With(reg)
|
||||||
// randomNumbers = factory.NewHistogram(prometheus.HistogramOpts{
|
// randomNumbers = factory.NewHistogram(prometheus.HistogramOpts{
|
||||||
// Name: "random_numbers",
|
// Name: "random_numbers",
|
||||||
// Help: "A histogram of normally distributed random numbers.",
|
// Help: "A histogram of normally distributed random numbers.",
|
||||||
// Buckets: prometheus.LinearBuckets(-3, .1, 61),
|
// Buckets: prometheus.LinearBuckets(-3, .1, 61),
|
||||||
// })
|
// })
|
||||||
// requestCount = factory.NewCounterVec(
|
// requestCount = factory.NewCounterVec(
|
||||||
// prometheus.CounterOpts{
|
// prometheus.CounterOpts{
|
||||||
// Name: "http_requests_total",
|
// Name: "http_requests_total",
|
||||||
// Help: "Total number of HTTP requests by status code and method.",
|
// Help: "Total number of HTTP requests by status code and method.",
|
||||||
// },
|
// },
|
||||||
// []string{"code", "method"},
|
// []string{"code", "method"},
|
||||||
// )
|
// )
|
||||||
// )
|
// )
|
||||||
//
|
//
|
||||||
// This appears very handy. So why are these constructors locked away in a
|
// This appears very handy. So why are these constructors locked away in a
|
||||||
// separate package?
|
// separate package?
|
||||||
|
@ -159,7 +159,7 @@
|
||||||
// Enjoy promauto responsibly!
|
// Enjoy promauto responsibly!
|
||||||
package promauto
|
package promauto
|
||||||
|
|
||||||
import "github.com/prometheus/client_golang/prometheus"
|
import "git.internal/re/client_golang/prometheus"
|
||||||
|
|
||||||
// NewCounter works like the function of the same name in the prometheus package
|
// NewCounter works like the function of the same name in the prometheus package
|
||||||
// but it automatically registers the Counter with the
|
// but it automatically registers the Counter with the
|
||||||
|
|
|
@ -16,7 +16,7 @@ package promauto
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/prometheus/client_golang/prometheus"
|
"git.internal/re/client_golang/prometheus"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestNil(t *testing.T) {
|
func TestNil(t *testing.T) {
|
||||||
|
|
|
@ -76,16 +76,19 @@ func (r *responseWriterDelegator) Write(b []byte) (int, error) {
|
||||||
return n, err
|
return n, err
|
||||||
}
|
}
|
||||||
|
|
||||||
type closeNotifierDelegator struct{ *responseWriterDelegator }
|
type (
|
||||||
type flusherDelegator struct{ *responseWriterDelegator }
|
closeNotifierDelegator struct{ *responseWriterDelegator }
|
||||||
type hijackerDelegator struct{ *responseWriterDelegator }
|
flusherDelegator struct{ *responseWriterDelegator }
|
||||||
type readerFromDelegator struct{ *responseWriterDelegator }
|
hijackerDelegator struct{ *responseWriterDelegator }
|
||||||
type pusherDelegator struct{ *responseWriterDelegator }
|
readerFromDelegator struct{ *responseWriterDelegator }
|
||||||
|
pusherDelegator struct{ *responseWriterDelegator }
|
||||||
|
)
|
||||||
|
|
||||||
func (d closeNotifierDelegator) CloseNotify() <-chan bool {
|
func (d closeNotifierDelegator) CloseNotify() <-chan bool {
|
||||||
//nolint:staticcheck // Ignore SA1019. http.CloseNotifier is deprecated but we keep it here to not break existing users.
|
//nolint:staticcheck // Ignore SA1019. http.CloseNotifier is deprecated but we keep it here to not break existing users.
|
||||||
return d.ResponseWriter.(http.CloseNotifier).CloseNotify()
|
return d.ResponseWriter.(http.CloseNotifier).CloseNotify()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d flusherDelegator) Flush() {
|
func (d flusherDelegator) Flush() {
|
||||||
// If applicable, call WriteHeader here so that observeWriteHeader is
|
// If applicable, call WriteHeader here so that observeWriteHeader is
|
||||||
// handled appropriately.
|
// handled appropriately.
|
||||||
|
@ -94,9 +97,11 @@ func (d flusherDelegator) Flush() {
|
||||||
}
|
}
|
||||||
d.ResponseWriter.(http.Flusher).Flush()
|
d.ResponseWriter.(http.Flusher).Flush()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d hijackerDelegator) Hijack() (net.Conn, *bufio.ReadWriter, error) {
|
func (d hijackerDelegator) Hijack() (net.Conn, *bufio.ReadWriter, error) {
|
||||||
return d.ResponseWriter.(http.Hijacker).Hijack()
|
return d.ResponseWriter.(http.Hijacker).Hijack()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d readerFromDelegator) ReadFrom(re io.Reader) (int64, error) {
|
func (d readerFromDelegator) ReadFrom(re io.Reader) (int64, error) {
|
||||||
// If applicable, call WriteHeader here so that observeWriteHeader is
|
// If applicable, call WriteHeader here so that observeWriteHeader is
|
||||||
// handled appropriately.
|
// handled appropriately.
|
||||||
|
@ -107,6 +112,7 @@ func (d readerFromDelegator) ReadFrom(re io.Reader) (int64, error) {
|
||||||
d.written += n
|
d.written += n
|
||||||
return n, err
|
return n, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d pusherDelegator) Push(target string, opts *http.PushOptions) error {
|
func (d pusherDelegator) Push(target string, opts *http.PushOptions) error {
|
||||||
return d.ResponseWriter.(http.Pusher).Push(target, opts)
|
return d.ResponseWriter.(http.Pusher).Push(target, opts)
|
||||||
}
|
}
|
||||||
|
@ -261,7 +267,7 @@ func init() {
|
||||||
http.Flusher
|
http.Flusher
|
||||||
}{d, pusherDelegator{d}, hijackerDelegator{d}, flusherDelegator{d}}
|
}{d, pusherDelegator{d}, hijackerDelegator{d}, flusherDelegator{d}}
|
||||||
}
|
}
|
||||||
pickDelegator[pusher+hijacker+flusher+closeNotifier] = func(d *responseWriterDelegator) delegator { //23
|
pickDelegator[pusher+hijacker+flusher+closeNotifier] = func(d *responseWriterDelegator) delegator { // 23
|
||||||
return struct {
|
return struct {
|
||||||
*responseWriterDelegator
|
*responseWriterDelegator
|
||||||
http.Pusher
|
http.Pusher
|
||||||
|
|
|
@ -33,6 +33,7 @@ package promhttp
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"compress/gzip"
|
"compress/gzip"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
@ -42,7 +43,7 @@ import (
|
||||||
|
|
||||||
"github.com/prometheus/common/expfmt"
|
"github.com/prometheus/common/expfmt"
|
||||||
|
|
||||||
"github.com/prometheus/client_golang/prometheus"
|
"git.internal/re/client_golang/prometheus"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -84,6 +85,13 @@ func Handler() http.Handler {
|
||||||
// instrumentation. Use the InstrumentMetricHandler function to apply the same
|
// instrumentation. Use the InstrumentMetricHandler function to apply the same
|
||||||
// kind of instrumentation as it is used by the Handler function.
|
// kind of instrumentation as it is used by the Handler function.
|
||||||
func HandlerFor(reg prometheus.Gatherer, opts HandlerOpts) http.Handler {
|
func HandlerFor(reg prometheus.Gatherer, opts HandlerOpts) http.Handler {
|
||||||
|
return HandlerForTransactional(prometheus.ToTransactionalGatherer(reg), opts)
|
||||||
|
}
|
||||||
|
|
||||||
|
// HandlerForTransactional is like HandlerFor, but it uses transactional gather, which
|
||||||
|
// can safely change in-place returned *dto.MetricFamily before call to `Gather` and after
|
||||||
|
// call to `done` of that `Gather`.
|
||||||
|
func HandlerForTransactional(reg prometheus.TransactionalGatherer, opts HandlerOpts) http.Handler {
|
||||||
var (
|
var (
|
||||||
inFlightSem chan struct{}
|
inFlightSem chan struct{}
|
||||||
errCnt = prometheus.NewCounterVec(
|
errCnt = prometheus.NewCounterVec(
|
||||||
|
@ -103,7 +111,8 @@ func HandlerFor(reg prometheus.Gatherer, opts HandlerOpts) http.Handler {
|
||||||
errCnt.WithLabelValues("gathering")
|
errCnt.WithLabelValues("gathering")
|
||||||
errCnt.WithLabelValues("encoding")
|
errCnt.WithLabelValues("encoding")
|
||||||
if err := opts.Registry.Register(errCnt); err != nil {
|
if err := opts.Registry.Register(errCnt); err != nil {
|
||||||
if are, ok := err.(prometheus.AlreadyRegisteredError); ok {
|
are := &prometheus.AlreadyRegisteredError{}
|
||||||
|
if errors.As(err, are) {
|
||||||
errCnt = are.ExistingCollector.(*prometheus.CounterVec)
|
errCnt = are.ExistingCollector.(*prometheus.CounterVec)
|
||||||
} else {
|
} else {
|
||||||
panic(err)
|
panic(err)
|
||||||
|
@ -123,7 +132,8 @@ func HandlerFor(reg prometheus.Gatherer, opts HandlerOpts) http.Handler {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
mfs, err := reg.Gather()
|
mfs, done, err := reg.Gather()
|
||||||
|
defer done()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if opts.ErrorLog != nil {
|
if opts.ErrorLog != nil {
|
||||||
opts.ErrorLog.Println("error gathering metrics:", err)
|
opts.ErrorLog.Println("error gathering metrics:", err)
|
||||||
|
@ -242,7 +252,8 @@ func InstrumentMetricHandler(reg prometheus.Registerer, handler http.Handler) ht
|
||||||
cnt.WithLabelValues("500")
|
cnt.WithLabelValues("500")
|
||||||
cnt.WithLabelValues("503")
|
cnt.WithLabelValues("503")
|
||||||
if err := reg.Register(cnt); err != nil {
|
if err := reg.Register(cnt); err != nil {
|
||||||
if are, ok := err.(prometheus.AlreadyRegisteredError); ok {
|
are := &prometheus.AlreadyRegisteredError{}
|
||||||
|
if errors.As(err, are) {
|
||||||
cnt = are.ExistingCollector.(*prometheus.CounterVec)
|
cnt = are.ExistingCollector.(*prometheus.CounterVec)
|
||||||
} else {
|
} else {
|
||||||
panic(err)
|
panic(err)
|
||||||
|
@ -254,7 +265,8 @@ func InstrumentMetricHandler(reg prometheus.Registerer, handler http.Handler) ht
|
||||||
Help: "Current number of scrapes being served.",
|
Help: "Current number of scrapes being served.",
|
||||||
})
|
})
|
||||||
if err := reg.Register(gge); err != nil {
|
if err := reg.Register(gge); err != nil {
|
||||||
if are, ok := err.(prometheus.AlreadyRegisteredError); ok {
|
are := &prometheus.AlreadyRegisteredError{}
|
||||||
|
if errors.As(err, are) {
|
||||||
gge = are.ExistingCollector.(prometheus.Gauge)
|
gge = are.ExistingCollector.(prometheus.Gauge)
|
||||||
} else {
|
} else {
|
||||||
panic(err)
|
panic(err)
|
||||||
|
|
|
@ -16,6 +16,7 @@ package promhttp
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
|
@ -23,7 +24,9 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/prometheus/client_golang/prometheus"
|
dto "github.com/prometheus/client_model/go"
|
||||||
|
|
||||||
|
"git.internal/re/client_golang/prometheus"
|
||||||
)
|
)
|
||||||
|
|
||||||
type errorCollector struct{}
|
type errorCollector struct{}
|
||||||
|
@ -56,8 +59,19 @@ func (b blockingCollector) Collect(ch chan<- prometheus.Metric) {
|
||||||
<-b.Block
|
<-b.Block
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestHandlerErrorHandling(t *testing.T) {
|
type mockTransactionGatherer struct {
|
||||||
|
g prometheus.Gatherer
|
||||||
|
gatherInvoked int
|
||||||
|
doneInvoked int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *mockTransactionGatherer) Gather() (_ []*dto.MetricFamily, done func(), err error) {
|
||||||
|
g.gatherInvoked++
|
||||||
|
mfs, err := g.g.Gather()
|
||||||
|
return mfs, func() { g.doneInvoked++ }, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHandlerErrorHandling(t *testing.T) {
|
||||||
// Create a registry that collects a MetricFamily with two elements,
|
// Create a registry that collects a MetricFamily with two elements,
|
||||||
// another with one, and reports an error. Further down, we'll use the
|
// another with one, and reports an error. Further down, we'll use the
|
||||||
// same registry in the HandlerOpts.
|
// same registry in the HandlerOpts.
|
||||||
|
@ -90,21 +104,30 @@ func TestHandlerErrorHandling(t *testing.T) {
|
||||||
request, _ := http.NewRequest("GET", "/", nil)
|
request, _ := http.NewRequest("GET", "/", nil)
|
||||||
request.Header.Add("Accept", "test/plain")
|
request.Header.Add("Accept", "test/plain")
|
||||||
|
|
||||||
errorHandler := HandlerFor(reg, HandlerOpts{
|
mReg := &mockTransactionGatherer{g: reg}
|
||||||
|
errorHandler := HandlerForTransactional(mReg, HandlerOpts{
|
||||||
ErrorLog: logger,
|
ErrorLog: logger,
|
||||||
ErrorHandling: HTTPErrorOnError,
|
ErrorHandling: HTTPErrorOnError,
|
||||||
Registry: reg,
|
Registry: reg,
|
||||||
})
|
})
|
||||||
continueHandler := HandlerFor(reg, HandlerOpts{
|
continueHandler := HandlerForTransactional(mReg, HandlerOpts{
|
||||||
ErrorLog: logger,
|
ErrorLog: logger,
|
||||||
ErrorHandling: ContinueOnError,
|
ErrorHandling: ContinueOnError,
|
||||||
Registry: reg,
|
Registry: reg,
|
||||||
})
|
})
|
||||||
panicHandler := HandlerFor(reg, HandlerOpts{
|
panicHandler := HandlerForTransactional(mReg, HandlerOpts{
|
||||||
ErrorLog: logger,
|
ErrorLog: logger,
|
||||||
ErrorHandling: PanicOnError,
|
ErrorHandling: PanicOnError,
|
||||||
Registry: reg,
|
Registry: reg,
|
||||||
})
|
})
|
||||||
|
// Expect gatherer not touched.
|
||||||
|
if got := mReg.gatherInvoked; got != 0 {
|
||||||
|
t.Fatalf("unexpected number of gather invokes, want 0, got %d", got)
|
||||||
|
}
|
||||||
|
if got := mReg.doneInvoked; got != 0 {
|
||||||
|
t.Fatalf("unexpected number of done invokes, want 0, got %d", got)
|
||||||
|
}
|
||||||
|
|
||||||
wantMsg := `error gathering metrics: error collecting metric Desc{fqName: "invalid_metric", help: "not helpful", constLabels: {}, variableLabels: []}: collect error
|
wantMsg := `error gathering metrics: error collecting metric Desc{fqName: "invalid_metric", help: "not helpful", constLabels: {}, variableLabels: []}: collect error
|
||||||
`
|
`
|
||||||
wantErrorBody := `An error has occurred while serving metrics:
|
wantErrorBody := `An error has occurred while serving metrics:
|
||||||
|
@ -140,25 +163,39 @@ the_count 0
|
||||||
`
|
`
|
||||||
|
|
||||||
errorHandler.ServeHTTP(writer, request)
|
errorHandler.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.StatusInternalServerError; got != want {
|
if got, want := writer.Code, http.StatusInternalServerError; got != want {
|
||||||
t.Errorf("got HTTP status code %d, want %d", got, want)
|
t.Errorf("got HTTP status code %d, want %d", got, want)
|
||||||
}
|
}
|
||||||
if got := logBuf.String(); got != wantMsg {
|
if got, want := logBuf.String(), wantMsg; got != want {
|
||||||
t.Errorf("got log message:\n%s\nwant log message:\n%s\n", got, wantMsg)
|
t.Errorf("got log buf %q, want %q", got, want)
|
||||||
}
|
}
|
||||||
if got := writer.Body.String(); got != wantErrorBody {
|
if got, want := writer.Body.String(), wantErrorBody; got != want {
|
||||||
t.Errorf("got body:\n%s\nwant body:\n%s\n", got, wantErrorBody)
|
t.Errorf("got body %q, want %q", got, want)
|
||||||
}
|
}
|
||||||
|
|
||||||
logBuf.Reset()
|
logBuf.Reset()
|
||||||
writer.Body.Reset()
|
writer.Body.Reset()
|
||||||
writer.Code = http.StatusOK
|
writer.Code = http.StatusOK
|
||||||
|
|
||||||
continueHandler.ServeHTTP(writer, request)
|
continueHandler.ServeHTTP(writer, request)
|
||||||
|
|
||||||
|
if got := mReg.gatherInvoked; got != 2 {
|
||||||
|
t.Fatalf("unexpected number of gather invokes, want 2, got %d", got)
|
||||||
|
}
|
||||||
|
if got := mReg.doneInvoked; got != 2 {
|
||||||
|
t.Fatalf("unexpected number of done invokes, want 2, got %d", got)
|
||||||
|
}
|
||||||
if got, want := writer.Code, http.StatusOK; got != want {
|
if got, want := writer.Code, http.StatusOK; got != want {
|
||||||
t.Errorf("got HTTP status code %d, want %d", got, want)
|
t.Errorf("got HTTP status code %d, want %d", got, want)
|
||||||
}
|
}
|
||||||
if got := logBuf.String(); got != wantMsg {
|
if got, want := logBuf.String(), wantMsg; got != want {
|
||||||
t.Errorf("got log message %q, want %q", got, wantMsg)
|
t.Errorf("got log buf %q, want %q", got, want)
|
||||||
}
|
}
|
||||||
if got := writer.Body.String(); got != wantOKBody1 && got != wantOKBody2 {
|
if got := writer.Body.String(); got != wantOKBody1 && got != wantOKBody2 {
|
||||||
t.Errorf("got body %q, want either %q or %q", got, wantOKBody1, wantOKBody2)
|
t.Errorf("got body %q, want either %q or %q", got, wantOKBody1, wantOKBody2)
|
||||||
|
@ -168,20 +205,34 @@ the_count 0
|
||||||
if err := recover(); err == nil {
|
if err := recover(); err == nil {
|
||||||
t.Error("expected panic from panicHandler")
|
t.Error("expected panic from panicHandler")
|
||||||
}
|
}
|
||||||
|
if got := mReg.gatherInvoked; got != 3 {
|
||||||
|
t.Fatalf("unexpected number of gather invokes, want 3, got %d", got)
|
||||||
|
}
|
||||||
|
if got := mReg.doneInvoked; got != 3 {
|
||||||
|
t.Fatalf("unexpected number of done invokes, want 3, got %d", got)
|
||||||
|
}
|
||||||
}()
|
}()
|
||||||
panicHandler.ServeHTTP(writer, request)
|
panicHandler.ServeHTTP(writer, request)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestInstrumentMetricHandler(t *testing.T) {
|
func TestInstrumentMetricHandler(t *testing.T) {
|
||||||
reg := prometheus.NewRegistry()
|
reg := prometheus.NewRegistry()
|
||||||
handler := InstrumentMetricHandler(reg, HandlerFor(reg, HandlerOpts{}))
|
mReg := &mockTransactionGatherer{g: reg}
|
||||||
|
handler := InstrumentMetricHandler(reg, HandlerForTransactional(mReg, HandlerOpts{}))
|
||||||
// Do it again to test idempotency.
|
// Do it again to test idempotency.
|
||||||
InstrumentMetricHandler(reg, HandlerFor(reg, HandlerOpts{}))
|
InstrumentMetricHandler(reg, HandlerForTransactional(mReg, HandlerOpts{}))
|
||||||
writer := httptest.NewRecorder()
|
writer := httptest.NewRecorder()
|
||||||
request, _ := http.NewRequest("GET", "/", nil)
|
request, _ := http.NewRequest("GET", "/", nil)
|
||||||
request.Header.Add("Accept", "test/plain")
|
request.Header.Add("Accept", "test/plain")
|
||||||
|
|
||||||
handler.ServeHTTP(writer, request)
|
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 {
|
if got, want := writer.Code, http.StatusOK; got != want {
|
||||||
t.Errorf("got HTTP status code %d, want %d", got, want)
|
t.Errorf("got HTTP status code %d, want %d", got, want)
|
||||||
}
|
}
|
||||||
|
@ -195,19 +246,28 @@ func TestInstrumentMetricHandler(t *testing.T) {
|
||||||
t.Errorf("got body %q, does not contain %q", got, want)
|
t.Errorf("got body %q, does not contain %q", got, want)
|
||||||
}
|
}
|
||||||
|
|
||||||
writer.Body.Reset()
|
for i := 0; i < 100; i++ {
|
||||||
handler.ServeHTTP(writer, request)
|
writer.Body.Reset()
|
||||||
if got, want := writer.Code, http.StatusOK; got != want {
|
handler.ServeHTTP(writer, request)
|
||||||
t.Errorf("got HTTP status code %d, want %d", got, want)
|
|
||||||
}
|
|
||||||
|
|
||||||
want = "promhttp_metric_handler_requests_in_flight 1\n"
|
if got, want := mReg.gatherInvoked, i+2; got != want {
|
||||||
if got := writer.Body.String(); !strings.Contains(got, want) {
|
t.Fatalf("unexpected number of gather invokes, want %d, got %d", want, got)
|
||||||
t.Errorf("got body %q, does not contain %q", got, want)
|
}
|
||||||
}
|
if got, want := mReg.doneInvoked, i+2; got != want {
|
||||||
want = "promhttp_metric_handler_requests_total{code=\"200\"} 1\n"
|
t.Fatalf("unexpected number of done invokes, want %d, got %d", want, got)
|
||||||
if got := writer.Body.String(); !strings.Contains(got, want) {
|
}
|
||||||
t.Errorf("got body %q, does not contain %q", got, want)
|
if got, want := writer.Code, http.StatusOK; got != want {
|
||||||
|
t.Errorf("got HTTP status code %d, want %d", 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)
|
||||||
|
}
|
||||||
|
want = fmt.Sprintf("promhttp_metric_handler_requests_total{code=\"200\"} %d\n", i+1)
|
||||||
|
if got := writer.Body.String(); !strings.Contains(got, want) {
|
||||||
|
t.Errorf("got body %q, does not contain %q", got, want)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -19,7 +19,7 @@ import (
|
||||||
"net/http/httptrace"
|
"net/http/httptrace"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/prometheus/client_golang/prometheus"
|
"git.internal/re/client_golang/prometheus"
|
||||||
)
|
)
|
||||||
|
|
||||||
// The RoundTripperFunc type is an adapter to allow the use of ordinary
|
// The RoundTripperFunc type is an adapter to allow the use of ordinary
|
||||||
|
@ -38,11 +38,11 @@ func (rt RoundTripperFunc) RoundTrip(r *http.Request) (*http.Response, error) {
|
||||||
//
|
//
|
||||||
// See the example for ExampleInstrumentRoundTripperDuration for example usage.
|
// See the example for ExampleInstrumentRoundTripperDuration for example usage.
|
||||||
func InstrumentRoundTripperInFlight(gauge prometheus.Gauge, next http.RoundTripper) RoundTripperFunc {
|
func InstrumentRoundTripperInFlight(gauge prometheus.Gauge, next http.RoundTripper) RoundTripperFunc {
|
||||||
return RoundTripperFunc(func(r *http.Request) (*http.Response, error) {
|
return func(r *http.Request) (*http.Response, error) {
|
||||||
gauge.Inc()
|
gauge.Inc()
|
||||||
defer gauge.Dec()
|
defer gauge.Dec()
|
||||||
return next.RoundTrip(r)
|
return next.RoundTrip(r)
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// InstrumentRoundTripperCounter is a middleware that wraps the provided
|
// InstrumentRoundTripperCounter is a middleware that wraps the provided
|
||||||
|
@ -59,22 +59,28 @@ func InstrumentRoundTripperInFlight(gauge prometheus.Gauge, next http.RoundTripp
|
||||||
// If the wrapped RoundTripper panics or returns a non-nil error, the Counter
|
// If the wrapped RoundTripper panics or returns a non-nil error, the Counter
|
||||||
// is not incremented.
|
// is not incremented.
|
||||||
//
|
//
|
||||||
|
// Use with WithExemplarFromContext to instrument the exemplars on the counter of requests.
|
||||||
|
//
|
||||||
// See the example for ExampleInstrumentRoundTripperDuration for example usage.
|
// See the example for ExampleInstrumentRoundTripperDuration for example usage.
|
||||||
func InstrumentRoundTripperCounter(counter *prometheus.CounterVec, next http.RoundTripper, opts ...Option) RoundTripperFunc {
|
func InstrumentRoundTripperCounter(counter *prometheus.CounterVec, next http.RoundTripper, opts ...Option) RoundTripperFunc {
|
||||||
rtOpts := &option{}
|
rtOpts := defaultOptions()
|
||||||
for _, o := range opts {
|
for _, o := range opts {
|
||||||
o(rtOpts)
|
o.apply(rtOpts)
|
||||||
}
|
}
|
||||||
|
|
||||||
code, method := checkLabels(counter)
|
code, method := checkLabels(counter)
|
||||||
|
|
||||||
return RoundTripperFunc(func(r *http.Request) (*http.Response, error) {
|
return func(r *http.Request) (*http.Response, error) {
|
||||||
resp, err := next.RoundTrip(r)
|
resp, err := next.RoundTrip(r)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
counter.With(labels(code, method, r.Method, resp.StatusCode, rtOpts.extraMethods...)).Inc()
|
addWithExemplar(
|
||||||
|
counter.With(labels(code, method, r.Method, resp.StatusCode, rtOpts.extraMethods...)),
|
||||||
|
1,
|
||||||
|
rtOpts.getExemplarFn(r.Context()),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
return resp, err
|
return resp, err
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// InstrumentRoundTripperDuration is a middleware that wraps the provided
|
// InstrumentRoundTripperDuration is a middleware that wraps the provided
|
||||||
|
@ -94,24 +100,30 @@ func InstrumentRoundTripperCounter(counter *prometheus.CounterVec, next http.Rou
|
||||||
// If the wrapped RoundTripper panics or returns a non-nil error, no values are
|
// If the wrapped RoundTripper panics or returns a non-nil error, no values are
|
||||||
// reported.
|
// reported.
|
||||||
//
|
//
|
||||||
|
// Use with WithExemplarFromContext to instrument the exemplars on the duration histograms.
|
||||||
|
//
|
||||||
// Note that this method is only guaranteed to never observe negative durations
|
// Note that this method is only guaranteed to never observe negative durations
|
||||||
// if used with Go1.9+.
|
// if used with Go1.9+.
|
||||||
func InstrumentRoundTripperDuration(obs prometheus.ObserverVec, next http.RoundTripper, opts ...Option) RoundTripperFunc {
|
func InstrumentRoundTripperDuration(obs prometheus.ObserverVec, next http.RoundTripper, opts ...Option) RoundTripperFunc {
|
||||||
rtOpts := &option{}
|
rtOpts := defaultOptions()
|
||||||
for _, o := range opts {
|
for _, o := range opts {
|
||||||
o(rtOpts)
|
o.apply(rtOpts)
|
||||||
}
|
}
|
||||||
|
|
||||||
code, method := checkLabels(obs)
|
code, method := checkLabels(obs)
|
||||||
|
|
||||||
return RoundTripperFunc(func(r *http.Request) (*http.Response, error) {
|
return func(r *http.Request) (*http.Response, error) {
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
resp, err := next.RoundTrip(r)
|
resp, err := next.RoundTrip(r)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
obs.With(labels(code, method, r.Method, resp.StatusCode, rtOpts.extraMethods...)).Observe(time.Since(start).Seconds())
|
observeWithExemplar(
|
||||||
|
obs.With(labels(code, method, r.Method, resp.StatusCode, rtOpts.extraMethods...)),
|
||||||
|
time.Since(start).Seconds(),
|
||||||
|
rtOpts.getExemplarFn(r.Context()),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
return resp, err
|
return resp, err
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// InstrumentTrace is used to offer flexibility in instrumenting the available
|
// InstrumentTrace is used to offer flexibility in instrumenting the available
|
||||||
|
@ -149,7 +161,7 @@ type InstrumentTrace struct {
|
||||||
//
|
//
|
||||||
// See the example for ExampleInstrumentRoundTripperDuration for example usage.
|
// See the example for ExampleInstrumentRoundTripperDuration for example usage.
|
||||||
func InstrumentRoundTripperTrace(it *InstrumentTrace, next http.RoundTripper) RoundTripperFunc {
|
func InstrumentRoundTripperTrace(it *InstrumentTrace, next http.RoundTripper) RoundTripperFunc {
|
||||||
return RoundTripperFunc(func(r *http.Request) (*http.Response, error) {
|
return func(r *http.Request) (*http.Response, error) {
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
|
|
||||||
trace := &httptrace.ClientTrace{
|
trace := &httptrace.ClientTrace{
|
||||||
|
@ -231,5 +243,5 @@ func InstrumentRoundTripperTrace(it *InstrumentTrace, next http.RoundTripper) Ro
|
||||||
r = r.WithContext(httptrace.WithClientTrace(r.Context(), trace))
|
r = r.WithContext(httptrace.WithClientTrace(r.Context(), trace))
|
||||||
|
|
||||||
return next.RoundTrip(r)
|
return next.RoundTrip(r)
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,14 +18,20 @@ import (
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
|
"reflect"
|
||||||
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/prometheus/client_golang/prometheus"
|
"git.internal/re/client_golang/prometheus"
|
||||||
|
"git.internal/re/client_golang/prometheus/testutil"
|
||||||
|
|
||||||
|
dto "github.com/prometheus/client_model/go"
|
||||||
|
"google.golang.org/protobuf/proto"
|
||||||
)
|
)
|
||||||
|
|
||||||
func makeInstrumentedClient() (*http.Client, *prometheus.Registry) {
|
func makeInstrumentedClient(opts ...Option) (*http.Client, *prometheus.Registry) {
|
||||||
client := http.DefaultClient
|
client := http.DefaultClient
|
||||||
client.Timeout = 1 * time.Second
|
client.Timeout = 1 * time.Second
|
||||||
|
|
||||||
|
@ -91,13 +97,91 @@ func makeInstrumentedClient() (*http.Client, *prometheus.Registry) {
|
||||||
client.Transport = InstrumentRoundTripperInFlight(inFlightGauge,
|
client.Transport = InstrumentRoundTripperInFlight(inFlightGauge,
|
||||||
InstrumentRoundTripperCounter(counter,
|
InstrumentRoundTripperCounter(counter,
|
||||||
InstrumentRoundTripperTrace(trace,
|
InstrumentRoundTripperTrace(trace,
|
||||||
InstrumentRoundTripperDuration(histVec, http.DefaultTransport),
|
InstrumentRoundTripperDuration(histVec, http.DefaultTransport, opts...),
|
||||||
),
|
),
|
||||||
),
|
opts...),
|
||||||
)
|
)
|
||||||
return client, reg
|
return client, reg
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func labelsToLabelPair(l prometheus.Labels) []*dto.LabelPair {
|
||||||
|
ret := make([]*dto.LabelPair, 0, len(l))
|
||||||
|
for k, v := range l {
|
||||||
|
ret = append(ret, &dto.LabelPair{Name: proto.String(k), Value: proto.String(v)})
|
||||||
|
}
|
||||||
|
sort.Slice(ret, func(i, j int) bool {
|
||||||
|
return *ret[i].Name < *ret[j].Name
|
||||||
|
})
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
func assetMetricAndExemplars(
|
||||||
|
t *testing.T,
|
||||||
|
reg *prometheus.Registry,
|
||||||
|
expectedNumMetrics int,
|
||||||
|
expectedExemplar []*dto.LabelPair,
|
||||||
|
) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
mfs, err := reg.Gather()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if want, got := expectedNumMetrics, len(mfs); want != got {
|
||||||
|
t.Fatalf("unexpected number of metric families gathered, want %d, got %d", want, got)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, mf := range mfs {
|
||||||
|
if len(mf.Metric) == 0 {
|
||||||
|
t.Errorf("metric family %s must not be empty", mf.GetName())
|
||||||
|
}
|
||||||
|
for _, m := range mf.GetMetric() {
|
||||||
|
if c := m.GetCounter(); c != nil {
|
||||||
|
if len(expectedExemplar) == 0 {
|
||||||
|
if c.Exemplar != nil {
|
||||||
|
t.Errorf("expected no exemplar on the counter %v%v, got %v", mf.GetName(), m.Label, c.Exemplar.String())
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.Exemplar == nil {
|
||||||
|
t.Errorf("expected exemplar %v on the counter %v%v, got none", expectedExemplar, mf.GetName(), m.Label)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if got := c.Exemplar.Label; !reflect.DeepEqual(expectedExemplar, got) {
|
||||||
|
t.Errorf("expected exemplar %v on the counter %v%v, got %v", expectedExemplar, mf.GetName(), m.Label, got)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if h := m.GetHistogram(); h != nil {
|
||||||
|
found := false
|
||||||
|
for _, b := range h.GetBucket() {
|
||||||
|
if len(expectedExemplar) == 0 {
|
||||||
|
if b.Exemplar != nil {
|
||||||
|
t.Errorf("expected no exemplar on histogram %v%v bkt %v, got %v", mf.GetName(), m.Label, b.GetUpperBound(), b.Exemplar.String())
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if b.Exemplar == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if got := b.Exemplar.Label; !reflect.DeepEqual(expectedExemplar, got) {
|
||||||
|
t.Errorf("expected exemplar %v on the histogram %v%v on bkt %v, got %v", expectedExemplar, mf.GetName(), m.Label, b.GetUpperBound(), got)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(expectedExemplar) > 0 && !found {
|
||||||
|
t.Errorf("expected exemplar %v on at least one bucket of the histogram %v%v, got none", expectedExemplar, mf.GetName(), m.Label)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestClientMiddlewareAPI(t *testing.T) {
|
func TestClientMiddlewareAPI(t *testing.T) {
|
||||||
client, reg := makeInstrumentedClient()
|
client, reg := makeInstrumentedClient()
|
||||||
backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
@ -111,21 +195,28 @@ func TestClientMiddlewareAPI(t *testing.T) {
|
||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
|
|
||||||
mfs, err := reg.Gather()
|
assetMetricAndExemplars(t, reg, 3, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestClientMiddlewareAPI_WithExemplars(t *testing.T) {
|
||||||
|
exemplar := prometheus.Labels{"traceID": "example situation observed by this metric"}
|
||||||
|
|
||||||
|
client, reg := makeInstrumentedClient(WithExemplarFromContext(func(_ context.Context) prometheus.Labels { return exemplar }))
|
||||||
|
backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
}))
|
||||||
|
defer backend.Close()
|
||||||
|
|
||||||
|
resp, err := client.Get(backend.URL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
if want, got := 3, len(mfs); want != got {
|
defer resp.Body.Close()
|
||||||
t.Fatalf("unexpected number of metric families gathered, want %d, got %d", want, got)
|
|
||||||
}
|
assetMetricAndExemplars(t, reg, 3, labelsToLabelPair(exemplar))
|
||||||
for _, mf := range mfs {
|
|
||||||
if len(mf.Metric) == 0 {
|
|
||||||
t.Errorf("metric family %s must not be empty", mf.GetName())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestClientMiddlewareAPIWithRequestContext(t *testing.T) {
|
func TestClientMiddlewareAPI_WithRequestContext(t *testing.T) {
|
||||||
client, reg := makeInstrumentedClient()
|
client, reg := makeInstrumentedClient()
|
||||||
backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
w.WriteHeader(http.StatusOK)
|
w.WriteHeader(http.StatusOK)
|
||||||
|
@ -160,6 +251,19 @@ func TestClientMiddlewareAPIWithRequestContext(t *testing.T) {
|
||||||
t.Errorf("metric family %s must not be empty", mf.GetName())
|
t.Errorf("metric family %s must not be empty", mf.GetName())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// make sure counters aren't double-incremented (see #1117)
|
||||||
|
expected := `
|
||||||
|
# HELP client_api_requests_total A counter for requests from the wrapped client.
|
||||||
|
# TYPE client_api_requests_total counter
|
||||||
|
client_api_requests_total{code="200",method="get"} 1
|
||||||
|
`
|
||||||
|
|
||||||
|
if err := testutil.GatherAndCompare(reg, strings.NewReader(expected),
|
||||||
|
"client_api_requests_total",
|
||||||
|
); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestClientMiddlewareAPIWithRequestContextTimeout(t *testing.T) {
|
func TestClientMiddlewareAPIWithRequestContextTimeout(t *testing.T) {
|
||||||
|
|
|
@ -22,12 +22,32 @@ import (
|
||||||
|
|
||||||
dto "github.com/prometheus/client_model/go"
|
dto "github.com/prometheus/client_model/go"
|
||||||
|
|
||||||
"github.com/prometheus/client_golang/prometheus"
|
"git.internal/re/client_golang/prometheus"
|
||||||
)
|
)
|
||||||
|
|
||||||
// magicString is used for the hacky label test in checkLabels. Remove once fixed.
|
// magicString is used for the hacky label test in checkLabels. Remove once fixed.
|
||||||
const magicString = "zZgWfBxLqvG8kc8IMv3POi2Bb0tZI3vAnBx+gBaFi9FyPzB/CzKUer1yufDa"
|
const magicString = "zZgWfBxLqvG8kc8IMv3POi2Bb0tZI3vAnBx+gBaFi9FyPzB/CzKUer1yufDa"
|
||||||
|
|
||||||
|
// observeWithExemplar is a wrapper for [prometheus.ExemplarAdder.ExemplarObserver],
|
||||||
|
// which falls back to [prometheus.Observer.Observe] if no labels are provided.
|
||||||
|
func observeWithExemplar(obs prometheus.Observer, val float64, labels map[string]string) {
|
||||||
|
if labels == nil {
|
||||||
|
obs.Observe(val)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
obs.(prometheus.ExemplarObserver).ObserveWithExemplar(val, labels)
|
||||||
|
}
|
||||||
|
|
||||||
|
// addWithExemplar is a wrapper for [prometheus.ExemplarAdder.AddWithExemplar],
|
||||||
|
// which falls back to [prometheus.Counter.Add] if no labels are provided.
|
||||||
|
func addWithExemplar(obs prometheus.Counter, val float64, labels map[string]string) {
|
||||||
|
if labels == nil {
|
||||||
|
obs.Add(val)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
obs.(prometheus.ExemplarAdder).AddWithExemplar(val, labels)
|
||||||
|
}
|
||||||
|
|
||||||
// InstrumentHandlerInFlight is a middleware that wraps the provided
|
// InstrumentHandlerInFlight is a middleware that wraps the provided
|
||||||
// http.Handler. It sets the provided prometheus.Gauge to the number of
|
// http.Handler. It sets the provided prometheus.Gauge to the number of
|
||||||
// requests currently handled by the wrapped http.Handler.
|
// requests currently handled by the wrapped http.Handler.
|
||||||
|
@ -48,7 +68,7 @@ func InstrumentHandlerInFlight(g prometheus.Gauge, next http.Handler) http.Handl
|
||||||
// names are "code" and "method". The function panics otherwise. For the "method"
|
// names are "code" and "method". The function panics otherwise. For the "method"
|
||||||
// label a predefined default label value set is used to filter given values.
|
// label a predefined default label value set is used to filter given values.
|
||||||
// Values besides predefined values will count as `unknown` method.
|
// Values besides predefined values will count as `unknown` method.
|
||||||
//`WithExtraMethods` can be used to add more methods to the set. The Observe
|
// `WithExtraMethods` can be used to add more methods to the set. The Observe
|
||||||
// method of the Observer in the ObserverVec is called with the request duration
|
// method of the Observer in the ObserverVec is called with the request duration
|
||||||
// in seconds. Partitioning happens by HTTP status code and/or HTTP method if
|
// in seconds. Partitioning happens by HTTP status code and/or HTTP method if
|
||||||
// the respective instance label names are present in the ObserverVec. For
|
// the respective instance label names are present in the ObserverVec. For
|
||||||
|
@ -62,28 +82,37 @@ func InstrumentHandlerInFlight(g prometheus.Gauge, next http.Handler) http.Handl
|
||||||
// Note that this method is only guaranteed to never observe negative durations
|
// Note that this method is only guaranteed to never observe negative durations
|
||||||
// if used with Go1.9+.
|
// if used with Go1.9+.
|
||||||
func InstrumentHandlerDuration(obs prometheus.ObserverVec, next http.Handler, opts ...Option) http.HandlerFunc {
|
func InstrumentHandlerDuration(obs prometheus.ObserverVec, next http.Handler, opts ...Option) http.HandlerFunc {
|
||||||
mwOpts := &option{}
|
hOpts := defaultOptions()
|
||||||
for _, o := range opts {
|
for _, o := range opts {
|
||||||
o(mwOpts)
|
o.apply(hOpts)
|
||||||
}
|
}
|
||||||
|
|
||||||
code, method := checkLabels(obs)
|
code, method := checkLabels(obs)
|
||||||
|
|
||||||
if code {
|
if code {
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
d := newDelegator(w, nil)
|
d := newDelegator(w, nil)
|
||||||
next.ServeHTTP(d, r)
|
next.ServeHTTP(d, r)
|
||||||
|
|
||||||
obs.With(labels(code, method, r.Method, d.Status(), mwOpts.extraMethods...)).Observe(time.Since(now).Seconds())
|
observeWithExemplar(
|
||||||
})
|
obs.With(labels(code, method, r.Method, d.Status(), hOpts.extraMethods...)),
|
||||||
|
time.Since(now).Seconds(),
|
||||||
|
hOpts.getExemplarFn(r.Context()),
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
next.ServeHTTP(w, r)
|
next.ServeHTTP(w, r)
|
||||||
obs.With(labels(code, method, r.Method, 0, mwOpts.extraMethods...)).Observe(time.Since(now).Seconds())
|
|
||||||
})
|
observeWithExemplar(
|
||||||
|
obs.With(labels(code, method, r.Method, 0, hOpts.extraMethods...)),
|
||||||
|
time.Since(now).Seconds(),
|
||||||
|
hOpts.getExemplarFn(r.Context()),
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// InstrumentHandlerCounter is a middleware that wraps the provided http.Handler
|
// InstrumentHandlerCounter is a middleware that wraps the provided http.Handler
|
||||||
|
@ -104,25 +133,34 @@ func InstrumentHandlerDuration(obs prometheus.ObserverVec, next http.Handler, op
|
||||||
//
|
//
|
||||||
// See the example for InstrumentHandlerDuration for example usage.
|
// See the example for InstrumentHandlerDuration for example usage.
|
||||||
func InstrumentHandlerCounter(counter *prometheus.CounterVec, next http.Handler, opts ...Option) http.HandlerFunc {
|
func InstrumentHandlerCounter(counter *prometheus.CounterVec, next http.Handler, opts ...Option) http.HandlerFunc {
|
||||||
mwOpts := &option{}
|
hOpts := defaultOptions()
|
||||||
for _, o := range opts {
|
for _, o := range opts {
|
||||||
o(mwOpts)
|
o.apply(hOpts)
|
||||||
}
|
}
|
||||||
|
|
||||||
code, method := checkLabels(counter)
|
code, method := checkLabels(counter)
|
||||||
|
|
||||||
if code {
|
if code {
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
d := newDelegator(w, nil)
|
d := newDelegator(w, nil)
|
||||||
next.ServeHTTP(d, r)
|
next.ServeHTTP(d, r)
|
||||||
counter.With(labels(code, method, r.Method, d.Status(), mwOpts.extraMethods...)).Inc()
|
|
||||||
})
|
addWithExemplar(
|
||||||
|
counter.With(labels(code, method, r.Method, d.Status(), hOpts.extraMethods...)),
|
||||||
|
1,
|
||||||
|
hOpts.getExemplarFn(r.Context()),
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
next.ServeHTTP(w, r)
|
next.ServeHTTP(w, r)
|
||||||
counter.With(labels(code, method, r.Method, 0, mwOpts.extraMethods...)).Inc()
|
addWithExemplar(
|
||||||
})
|
counter.With(labels(code, method, r.Method, 0, hOpts.extraMethods...)),
|
||||||
|
1,
|
||||||
|
hOpts.getExemplarFn(r.Context()),
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// InstrumentHandlerTimeToWriteHeader is a middleware that wraps the provided
|
// InstrumentHandlerTimeToWriteHeader is a middleware that wraps the provided
|
||||||
|
@ -148,20 +186,24 @@ func InstrumentHandlerCounter(counter *prometheus.CounterVec, next http.Handler,
|
||||||
//
|
//
|
||||||
// See the example for InstrumentHandlerDuration for example usage.
|
// See the example for InstrumentHandlerDuration for example usage.
|
||||||
func InstrumentHandlerTimeToWriteHeader(obs prometheus.ObserverVec, next http.Handler, opts ...Option) http.HandlerFunc {
|
func InstrumentHandlerTimeToWriteHeader(obs prometheus.ObserverVec, next http.Handler, opts ...Option) http.HandlerFunc {
|
||||||
mwOpts := &option{}
|
hOpts := defaultOptions()
|
||||||
for _, o := range opts {
|
for _, o := range opts {
|
||||||
o(mwOpts)
|
o.apply(hOpts)
|
||||||
}
|
}
|
||||||
|
|
||||||
code, method := checkLabels(obs)
|
code, method := checkLabels(obs)
|
||||||
|
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
d := newDelegator(w, func(status int) {
|
d := newDelegator(w, func(status int) {
|
||||||
obs.With(labels(code, method, r.Method, status, mwOpts.extraMethods...)).Observe(time.Since(now).Seconds())
|
observeWithExemplar(
|
||||||
|
obs.With(labels(code, method, r.Method, status, hOpts.extraMethods...)),
|
||||||
|
time.Since(now).Seconds(),
|
||||||
|
hOpts.getExemplarFn(r.Context()),
|
||||||
|
)
|
||||||
})
|
})
|
||||||
next.ServeHTTP(d, r)
|
next.ServeHTTP(d, r)
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// InstrumentHandlerRequestSize is a middleware that wraps the provided
|
// InstrumentHandlerRequestSize is a middleware that wraps the provided
|
||||||
|
@ -184,27 +226,34 @@ func InstrumentHandlerTimeToWriteHeader(obs prometheus.ObserverVec, next http.Ha
|
||||||
//
|
//
|
||||||
// See the example for InstrumentHandlerDuration for example usage.
|
// See the example for InstrumentHandlerDuration for example usage.
|
||||||
func InstrumentHandlerRequestSize(obs prometheus.ObserverVec, next http.Handler, opts ...Option) http.HandlerFunc {
|
func InstrumentHandlerRequestSize(obs prometheus.ObserverVec, next http.Handler, opts ...Option) http.HandlerFunc {
|
||||||
mwOpts := &option{}
|
hOpts := defaultOptions()
|
||||||
for _, o := range opts {
|
for _, o := range opts {
|
||||||
o(mwOpts)
|
o.apply(hOpts)
|
||||||
}
|
}
|
||||||
|
|
||||||
code, method := checkLabels(obs)
|
code, method := checkLabels(obs)
|
||||||
|
|
||||||
if code {
|
if code {
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
d := newDelegator(w, nil)
|
d := newDelegator(w, nil)
|
||||||
next.ServeHTTP(d, r)
|
next.ServeHTTP(d, r)
|
||||||
size := computeApproximateRequestSize(r)
|
size := computeApproximateRequestSize(r)
|
||||||
obs.With(labels(code, method, r.Method, d.Status(), mwOpts.extraMethods...)).Observe(float64(size))
|
observeWithExemplar(
|
||||||
})
|
obs.With(labels(code, method, r.Method, d.Status(), hOpts.extraMethods...)),
|
||||||
|
float64(size),
|
||||||
|
hOpts.getExemplarFn(r.Context()),
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
next.ServeHTTP(w, r)
|
next.ServeHTTP(w, r)
|
||||||
size := computeApproximateRequestSize(r)
|
size := computeApproximateRequestSize(r)
|
||||||
obs.With(labels(code, method, r.Method, 0, mwOpts.extraMethods...)).Observe(float64(size))
|
observeWithExemplar(
|
||||||
})
|
obs.With(labels(code, method, r.Method, 0, hOpts.extraMethods...)),
|
||||||
|
float64(size),
|
||||||
|
hOpts.getExemplarFn(r.Context()),
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// InstrumentHandlerResponseSize is a middleware that wraps the provided
|
// InstrumentHandlerResponseSize is a middleware that wraps the provided
|
||||||
|
@ -227,9 +276,9 @@ func InstrumentHandlerRequestSize(obs prometheus.ObserverVec, next http.Handler,
|
||||||
//
|
//
|
||||||
// See the example for InstrumentHandlerDuration for example usage.
|
// See the example for InstrumentHandlerDuration for example usage.
|
||||||
func InstrumentHandlerResponseSize(obs prometheus.ObserverVec, next http.Handler, opts ...Option) http.Handler {
|
func InstrumentHandlerResponseSize(obs prometheus.ObserverVec, next http.Handler, opts ...Option) http.Handler {
|
||||||
mwOpts := &option{}
|
hOpts := defaultOptions()
|
||||||
for _, o := range opts {
|
for _, o := range opts {
|
||||||
o(mwOpts)
|
o.apply(hOpts)
|
||||||
}
|
}
|
||||||
|
|
||||||
code, method := checkLabels(obs)
|
code, method := checkLabels(obs)
|
||||||
|
@ -237,7 +286,11 @@ func InstrumentHandlerResponseSize(obs prometheus.ObserverVec, next http.Handler
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
d := newDelegator(w, nil)
|
d := newDelegator(w, nil)
|
||||||
next.ServeHTTP(d, r)
|
next.ServeHTTP(d, r)
|
||||||
obs.With(labels(code, method, r.Method, d.Status(), mwOpts.extraMethods...)).Observe(float64(d.Written()))
|
observeWithExemplar(
|
||||||
|
obs.With(labels(code, method, r.Method, d.Status(), hOpts.extraMethods...)),
|
||||||
|
float64(d.Written()),
|
||||||
|
hOpts.getExemplarFn(r.Context()),
|
||||||
|
)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -246,7 +299,7 @@ func InstrumentHandlerResponseSize(obs prometheus.ObserverVec, next http.Handler
|
||||||
// Collector does not have a Desc or has more than one Desc or its Desc is
|
// Collector does not have a Desc or has more than one Desc or its Desc is
|
||||||
// invalid. It also panics if the Collector has any non-const, non-curried
|
// invalid. It also panics if the Collector has any non-const, non-curried
|
||||||
// labels that are not named "code" or "method".
|
// labels that are not named "code" or "method".
|
||||||
func checkLabels(c prometheus.Collector) (code bool, method bool) {
|
func checkLabels(c prometheus.Collector) (code, method bool) {
|
||||||
// TODO(beorn7): Remove this hacky way to check for instance labels
|
// TODO(beorn7): Remove this hacky way to check for instance labels
|
||||||
// once Descriptors can have their dimensionality queried.
|
// once Descriptors can have their dimensionality queried.
|
||||||
var (
|
var (
|
||||||
|
|
|
@ -14,13 +14,14 @@
|
||||||
package promhttp
|
package promhttp
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"io"
|
"io"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/prometheus/client_golang/prometheus"
|
"git.internal/re/client_golang/prometheus"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestLabelCheck(t *testing.T) {
|
func TestLabelCheck(t *testing.T) {
|
||||||
|
@ -145,7 +146,6 @@ func TestLabelCheck(t *testing.T) {
|
||||||
},
|
},
|
||||||
append(sc.varLabels, sc.curriedLabels...),
|
append(sc.varLabels, sc.curriedLabels...),
|
||||||
))
|
))
|
||||||
//nolint:typecheck // Ignore declared but unused error.
|
|
||||||
for _, l := range sc.curriedLabels {
|
for _, l := range sc.curriedLabels {
|
||||||
c = c.MustCurryWith(prometheus.Labels{l: "dummy"})
|
c = c.MustCurryWith(prometheus.Labels{l: "dummy"})
|
||||||
o = o.MustCurryWith(prometheus.Labels{l: "dummy"})
|
o = o.MustCurryWith(prometheus.Labels{l: "dummy"})
|
||||||
|
@ -279,7 +279,7 @@ func TestLabels(t *testing.T) {
|
||||||
ok: false,
|
ok: false,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
checkLabels := func(labels []string) (gotCode bool, gotMethod bool) {
|
checkLabels := func(labels []string) (gotCode, gotMethod bool) {
|
||||||
for _, label := range labels {
|
for _, label := range labels {
|
||||||
switch label {
|
switch label {
|
||||||
case "code":
|
case "code":
|
||||||
|
@ -321,7 +321,7 @@ func TestLabels(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMiddlewareAPI(t *testing.T) {
|
func makeInstrumentedHandler(handler http.HandlerFunc, opts ...Option) (http.Handler, *prometheus.Registry) {
|
||||||
reg := prometheus.NewRegistry()
|
reg := prometheus.NewRegistry()
|
||||||
|
|
||||||
inFlightGauge := prometheus.NewGauge(prometheus.GaugeOpts{
|
inFlightGauge := prometheus.NewGauge(prometheus.GaugeOpts{
|
||||||
|
@ -366,25 +366,43 @@ func TestMiddlewareAPI(t *testing.T) {
|
||||||
[]string{},
|
[]string{},
|
||||||
)
|
)
|
||||||
|
|
||||||
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
w.Write([]byte("OK"))
|
|
||||||
})
|
|
||||||
|
|
||||||
reg.MustRegister(inFlightGauge, counter, histVec, responseSize, writeHeaderVec)
|
reg.MustRegister(inFlightGauge, counter, histVec, responseSize, writeHeaderVec)
|
||||||
|
|
||||||
chain := InstrumentHandlerInFlight(inFlightGauge,
|
return InstrumentHandlerInFlight(inFlightGauge,
|
||||||
InstrumentHandlerCounter(counter,
|
InstrumentHandlerCounter(counter,
|
||||||
InstrumentHandlerDuration(histVec,
|
InstrumentHandlerDuration(histVec,
|
||||||
InstrumentHandlerTimeToWriteHeader(writeHeaderVec,
|
InstrumentHandlerTimeToWriteHeader(writeHeaderVec,
|
||||||
InstrumentHandlerResponseSize(responseSize, handler),
|
InstrumentHandlerResponseSize(responseSize, handler, opts...),
|
||||||
),
|
opts...),
|
||||||
),
|
opts...),
|
||||||
),
|
opts...),
|
||||||
)
|
), reg
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMiddlewareAPI(t *testing.T) {
|
||||||
|
chain, reg := makeInstrumentedHandler(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
_, _ = w.Write([]byte("OK"))
|
||||||
|
})
|
||||||
|
|
||||||
r, _ := http.NewRequest("GET", "www.example.com", nil)
|
r, _ := http.NewRequest("GET", "www.example.com", nil)
|
||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
chain.ServeHTTP(w, r)
|
chain.ServeHTTP(w, r)
|
||||||
|
|
||||||
|
assetMetricAndExemplars(t, reg, 5, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMiddlewareAPI_WithExemplars(t *testing.T) {
|
||||||
|
exemplar := prometheus.Labels{"traceID": "example situation observed by this metric"}
|
||||||
|
|
||||||
|
chain, reg := makeInstrumentedHandler(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
_, _ = w.Write([]byte("OK"))
|
||||||
|
}, WithExemplarFromContext(func(_ context.Context) prometheus.Labels { return exemplar }))
|
||||||
|
|
||||||
|
r, _ := http.NewRequest("GET", "www.example.com", nil)
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
chain.ServeHTTP(w, r)
|
||||||
|
|
||||||
|
assetMetricAndExemplars(t, reg, 5, labelsToLabelPair(exemplar))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestInstrumentTimeToFirstWrite(t *testing.T) {
|
func TestInstrumentTimeToFirstWrite(t *testing.T) {
|
||||||
|
|
|
@ -13,19 +13,46 @@
|
||||||
|
|
||||||
package promhttp
|
package promhttp
|
||||||
|
|
||||||
// Option are used to configure a middleware or round tripper..
|
import (
|
||||||
type Option func(*option)
|
"context"
|
||||||
|
|
||||||
type option struct {
|
"git.internal/re/client_golang/prometheus"
|
||||||
extraMethods []string
|
)
|
||||||
|
|
||||||
|
// Option are used to configure both handler (middleware) or round tripper.
|
||||||
|
type Option interface {
|
||||||
|
apply(*options)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// options store options for both a handler or round tripper.
|
||||||
|
type options struct {
|
||||||
|
extraMethods []string
|
||||||
|
getExemplarFn func(requestCtx context.Context) prometheus.Labels
|
||||||
|
}
|
||||||
|
|
||||||
|
func defaultOptions() *options {
|
||||||
|
return &options{getExemplarFn: func(ctx context.Context) prometheus.Labels { return nil }}
|
||||||
|
}
|
||||||
|
|
||||||
|
type optionApplyFunc func(*options)
|
||||||
|
|
||||||
|
func (o optionApplyFunc) apply(opt *options) { o(opt) }
|
||||||
|
|
||||||
// WithExtraMethods adds additional HTTP methods to the list of allowed methods.
|
// WithExtraMethods adds additional HTTP methods to the list of allowed methods.
|
||||||
// See https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods for the default list.
|
// See https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods for the default list.
|
||||||
//
|
//
|
||||||
// See the example for ExampleInstrumentHandlerWithExtraMethods for example usage.
|
// See the example for ExampleInstrumentHandlerWithExtraMethods for example usage.
|
||||||
func WithExtraMethods(methods ...string) Option {
|
func WithExtraMethods(methods ...string) Option {
|
||||||
return func(o *option) {
|
return optionApplyFunc(func(o *options) {
|
||||||
o.extraMethods = methods
|
o.extraMethods = methods
|
||||||
}
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithExemplarFromContext adds allows to put a hook to all counter and histogram metrics.
|
||||||
|
// If the hook function returns non-nil labels, exemplars will be added for that request, otherwise metric
|
||||||
|
// will get instrumented without exemplar.
|
||||||
|
func WithExemplarFromContext(getExemplarFn func(requestCtx context.Context) prometheus.Labels) Option {
|
||||||
|
return optionApplyFunc(func(o *options) {
|
||||||
|
o.getExemplarFn = getExemplarFn
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,7 +17,7 @@ import (
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/prometheus/client_golang/prometheus"
|
"git.internal/re/client_golang/prometheus"
|
||||||
)
|
)
|
||||||
|
|
||||||
func ExampleInstrumentHandlerWithExtraMethods() {
|
func ExampleInstrumentHandlerWithExtraMethods() {
|
||||||
|
@ -50,11 +50,10 @@ func ExampleInstrumentHandlerWithExtraMethods() {
|
||||||
|
|
||||||
// Instrument the handlers with all the metrics, injecting the "handler"
|
// Instrument the handlers with all the metrics, injecting the "handler"
|
||||||
// label by currying.
|
// label by currying.
|
||||||
pullChain :=
|
pullChain := InstrumentHandlerDuration(duration.MustCurryWith(prometheus.Labels{"handler": "pull"}),
|
||||||
InstrumentHandlerDuration(duration.MustCurryWith(prometheus.Labels{"handler": "pull"}),
|
InstrumentHandlerCounter(counter, pullHandler, opts),
|
||||||
InstrumentHandlerCounter(counter, pullHandler, opts),
|
opts,
|
||||||
opts,
|
)
|
||||||
)
|
|
||||||
|
|
||||||
http.Handle("/metrics", Handler())
|
http.Handle("/metrics", Handler())
|
||||||
http.Handle("/pull", pullChain)
|
http.Handle("/pull", pullChain)
|
||||||
|
|
|
@ -17,8 +17,8 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/prometheus/client_golang/prometheus"
|
"git.internal/re/client_golang/prometheus"
|
||||||
"github.com/prometheus/client_golang/prometheus/push"
|
"git.internal/re/client_golang/prometheus/push"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|
|
@ -16,8 +16,8 @@ package push_test
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/prometheus/client_golang/prometheus"
|
"git.internal/re/client_golang/prometheus"
|
||||||
"github.com/prometheus/client_golang/prometheus/push"
|
"git.internal/re/client_golang/prometheus/push"
|
||||||
)
|
)
|
||||||
|
|
||||||
func ExamplePusher_Push() {
|
func ExamplePusher_Push() {
|
||||||
|
|
|
@ -15,17 +15,17 @@
|
||||||
// builder approach. Create a Pusher with New and then add the various options
|
// builder approach. Create a Pusher with New and then add the various options
|
||||||
// by using its methods, finally calling Add or Push, like this:
|
// by using its methods, finally calling Add or Push, like this:
|
||||||
//
|
//
|
||||||
// // Easy case:
|
// // Easy case:
|
||||||
// push.New("http://example.org/metrics", "my_job").Gatherer(myRegistry).Push()
|
// push.New("http://example.org/metrics", "my_job").Gatherer(myRegistry).Push()
|
||||||
//
|
//
|
||||||
// // Complex case:
|
// // Complex case:
|
||||||
// push.New("http://example.org/metrics", "my_job").
|
// push.New("http://example.org/metrics", "my_job").
|
||||||
// Collector(myCollector1).
|
// Collector(myCollector1).
|
||||||
// Collector(myCollector2).
|
// Collector(myCollector2).
|
||||||
// Grouping("zone", "xy").
|
// Grouping("zone", "xy").
|
||||||
// Client(&myHTTPClient).
|
// Client(&myHTTPClient).
|
||||||
// BasicAuth("top", "secret").
|
// BasicAuth("top", "secret").
|
||||||
// Add()
|
// Add()
|
||||||
//
|
//
|
||||||
// See the examples section for more detailed examples.
|
// See the examples section for more detailed examples.
|
||||||
//
|
//
|
||||||
|
@ -36,10 +36,11 @@ package push
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"context"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"strings"
|
"strings"
|
||||||
|
@ -47,7 +48,7 @@ import (
|
||||||
"github.com/prometheus/common/expfmt"
|
"github.com/prometheus/common/expfmt"
|
||||||
"github.com/prometheus/common/model"
|
"github.com/prometheus/common/model"
|
||||||
|
|
||||||
"github.com/prometheus/client_golang/prometheus"
|
"git.internal/re/client_golang/prometheus"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -97,9 +98,7 @@ func New(url, job string) *Pusher {
|
||||||
if !strings.Contains(url, "://") {
|
if !strings.Contains(url, "://") {
|
||||||
url = "http://" + url
|
url = "http://" + url
|
||||||
}
|
}
|
||||||
if strings.HasSuffix(url, "/") {
|
url = strings.TrimSuffix(url, "/")
|
||||||
url = url[:len(url)-1]
|
|
||||||
}
|
|
||||||
|
|
||||||
return &Pusher{
|
return &Pusher{
|
||||||
error: err,
|
error: err,
|
||||||
|
@ -123,14 +122,28 @@ func New(url, job string) *Pusher {
|
||||||
// Push returns the first error encountered by any method call (including this
|
// Push returns the first error encountered by any method call (including this
|
||||||
// one) in the lifetime of the Pusher.
|
// one) in the lifetime of the Pusher.
|
||||||
func (p *Pusher) Push() error {
|
func (p *Pusher) Push() error {
|
||||||
return p.push(http.MethodPut)
|
return p.push(context.Background(), http.MethodPut)
|
||||||
|
}
|
||||||
|
|
||||||
|
// PushContext is like Push but includes a context.
|
||||||
|
//
|
||||||
|
// If the context expires before HTTP request is complete, an error is returned.
|
||||||
|
func (p *Pusher) PushContext(ctx context.Context) error {
|
||||||
|
return p.push(ctx, http.MethodPut)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add works like push, but only previously pushed metrics with the same name
|
// Add works like push, but only previously pushed metrics with the same name
|
||||||
// (and the same job and other grouping labels) will be replaced. (It uses HTTP
|
// (and the same job and other grouping labels) will be replaced. (It uses HTTP
|
||||||
// method “POST” to push to the Pushgateway.)
|
// method “POST” to push to the Pushgateway.)
|
||||||
func (p *Pusher) Add() error {
|
func (p *Pusher) Add() error {
|
||||||
return p.push(http.MethodPost)
|
return p.push(context.Background(), http.MethodPost)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddContext is like Add but includes a context.
|
||||||
|
//
|
||||||
|
// If the context expires before HTTP request is complete, an error is returned.
|
||||||
|
func (p *Pusher) AddContext(ctx context.Context) error {
|
||||||
|
return p.push(ctx, http.MethodPost)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Gatherer adds a Gatherer to the Pusher, from which metrics will be gathered
|
// Gatherer adds a Gatherer to the Pusher, from which metrics will be gathered
|
||||||
|
@ -155,6 +168,11 @@ func (p *Pusher) Collector(c prometheus.Collector) *Pusher {
|
||||||
return p
|
return p
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Error returns the error that was encountered.
|
||||||
|
func (p *Pusher) Error() error {
|
||||||
|
return p.error
|
||||||
|
}
|
||||||
|
|
||||||
// Grouping adds a label pair to the grouping key of the Pusher, replacing any
|
// Grouping adds a label pair to the grouping key of the Pusher, replacing any
|
||||||
// previously added label pair with the same label name. Note that setting any
|
// previously added label pair with the same label name. Note that setting any
|
||||||
// labels in the grouping key that are already contained in the metrics to push
|
// labels in the grouping key that are already contained in the metrics to push
|
||||||
|
@ -227,13 +245,13 @@ func (p *Pusher) Delete() error {
|
||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
if resp.StatusCode != http.StatusAccepted {
|
if resp.StatusCode != http.StatusAccepted {
|
||||||
body, _ := ioutil.ReadAll(resp.Body) // Ignore any further error as this is for an error message only.
|
body, _ := io.ReadAll(resp.Body) // Ignore any further error as this is for an error message only.
|
||||||
return fmt.Errorf("unexpected status code %d while deleting %s: %s", resp.StatusCode, p.fullURL(), body)
|
return fmt.Errorf("unexpected status code %d while deleting %s: %s", resp.StatusCode, p.fullURL(), body)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Pusher) push(method string) error {
|
func (p *Pusher) push(ctx context.Context, method string) error {
|
||||||
if p.error != nil {
|
if p.error != nil {
|
||||||
return p.error
|
return p.error
|
||||||
}
|
}
|
||||||
|
@ -258,9 +276,13 @@ func (p *Pusher) push(method string) error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
enc.Encode(mf)
|
if err := enc.Encode(mf); err != nil {
|
||||||
|
return fmt.Errorf(
|
||||||
|
"failed to encode metric familty %s, error is %w",
|
||||||
|
mf.GetName(), err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
req, err := http.NewRequest(method, p.fullURL(), buf)
|
req, err := http.NewRequestWithContext(ctx, method, p.fullURL(), buf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -275,7 +297,7 @@ func (p *Pusher) push(method string) error {
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
// Depending on version and configuration of the PGW, StatusOK or StatusAccepted may be returned.
|
// Depending on version and configuration of the PGW, StatusOK or StatusAccepted may be returned.
|
||||||
if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusAccepted {
|
if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusAccepted {
|
||||||
body, _ := ioutil.ReadAll(resp.Body) // Ignore any further error as this is for an error message only.
|
body, _ := io.ReadAll(resp.Body) // Ignore any further error as this is for an error message only.
|
||||||
return fmt.Errorf("unexpected status code %d while pushing to %s: %s", resp.StatusCode, p.fullURL(), body)
|
return fmt.Errorf("unexpected status code %d while pushing to %s: %s", resp.StatusCode, p.fullURL(), body)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|
|
@ -15,18 +15,18 @@ package push
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"io/ioutil"
|
"errors"
|
||||||
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/prometheus/common/expfmt"
|
"github.com/prometheus/common/expfmt"
|
||||||
|
|
||||||
"github.com/prometheus/client_golang/prometheus"
|
"git.internal/re/client_golang/prometheus"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestPush(t *testing.T) {
|
func TestPush(t *testing.T) {
|
||||||
|
|
||||||
var (
|
var (
|
||||||
lastMethod string
|
lastMethod string
|
||||||
lastBody []byte
|
lastBody []byte
|
||||||
|
@ -39,7 +39,7 @@ func TestPush(t *testing.T) {
|
||||||
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
lastMethod = r.Method
|
lastMethod = r.Method
|
||||||
var err error
|
var err error
|
||||||
lastBody, err = ioutil.ReadAll(r.Body)
|
lastBody, err = io.ReadAll(r.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -201,8 +201,8 @@ func TestPush(t *testing.T) {
|
||||||
Push(); err == nil {
|
Push(); err == nil {
|
||||||
t.Error("push with empty job succeeded")
|
t.Error("push with empty job succeeded")
|
||||||
} else {
|
} else {
|
||||||
if got, want := err, errJobEmpty; got != want {
|
if want := errJobEmpty; !errors.Is(err, want) {
|
||||||
t.Errorf("got error %q, want %q", got, want)
|
t.Errorf("got error %q, want %q", err, want)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -15,8 +15,8 @@ package prometheus
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
@ -32,7 +32,7 @@ import (
|
||||||
|
|
||||||
dto "github.com/prometheus/client_model/go"
|
dto "github.com/prometheus/client_model/go"
|
||||||
|
|
||||||
"github.com/prometheus/client_golang/prometheus/internal"
|
"git.internal/re/client_golang/prometheus/internal"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -252,9 +252,12 @@ func (errs MultiError) MaybeUnwrap() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Registry registers Prometheus collectors, collects their metrics, and gathers
|
// Registry registers Prometheus collectors, collects their metrics, and gathers
|
||||||
// them into MetricFamilies for exposition. It implements both Registerer and
|
// them into MetricFamilies for exposition. It implements Registerer, Gatherer,
|
||||||
// Gatherer. The zero value is not usable. Create instances with NewRegistry or
|
// and Collector. The zero value is not usable. Create instances with
|
||||||
// NewPedanticRegistry.
|
// NewRegistry or NewPedanticRegistry.
|
||||||
|
//
|
||||||
|
// Registry implements Collector to allow it to be used for creating groups of
|
||||||
|
// metrics. See the Grouping example for how this can be done.
|
||||||
type Registry struct {
|
type Registry struct {
|
||||||
mtx sync.RWMutex
|
mtx sync.RWMutex
|
||||||
collectorsByID map[uint64]Collector // ID is a hash of the descIDs.
|
collectorsByID map[uint64]Collector // ID is a hash of the descIDs.
|
||||||
|
@ -289,7 +292,7 @@ func (r *Registry) Register(c Collector) error {
|
||||||
|
|
||||||
// Is the descriptor valid at all?
|
// Is the descriptor valid at all?
|
||||||
if desc.err != nil {
|
if desc.err != nil {
|
||||||
return fmt.Errorf("descriptor %s is invalid: %s", desc, desc.err)
|
return fmt.Errorf("descriptor %s is invalid: %w", desc, desc.err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Is the descID unique?
|
// Is the descID unique?
|
||||||
|
@ -407,6 +410,14 @@ func (r *Registry) MustRegister(cs ...Collector) {
|
||||||
|
|
||||||
// Gather implements Gatherer.
|
// Gather implements Gatherer.
|
||||||
func (r *Registry) Gather() ([]*dto.MetricFamily, error) {
|
func (r *Registry) Gather() ([]*dto.MetricFamily, error) {
|
||||||
|
r.mtx.RLock()
|
||||||
|
|
||||||
|
if len(r.collectorsByID) == 0 && len(r.uncheckedCollectors) == 0 {
|
||||||
|
// Fast path.
|
||||||
|
r.mtx.RUnlock()
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
checkedMetricChan = make(chan Metric, capMetricChan)
|
checkedMetricChan = make(chan Metric, capMetricChan)
|
||||||
uncheckedMetricChan = make(chan Metric, capMetricChan)
|
uncheckedMetricChan = make(chan Metric, capMetricChan)
|
||||||
|
@ -416,7 +427,6 @@ func (r *Registry) Gather() ([]*dto.MetricFamily, error) {
|
||||||
registeredDescIDs map[uint64]struct{} // Only used for pedantic checks
|
registeredDescIDs map[uint64]struct{} // Only used for pedantic checks
|
||||||
)
|
)
|
||||||
|
|
||||||
r.mtx.RLock()
|
|
||||||
goroutineBudget := len(r.collectorsByID) + len(r.uncheckedCollectors)
|
goroutineBudget := len(r.collectorsByID) + len(r.uncheckedCollectors)
|
||||||
metricFamiliesByName := make(map[string]*dto.MetricFamily, len(r.dimHashesByName))
|
metricFamiliesByName := make(map[string]*dto.MetricFamily, len(r.dimHashesByName))
|
||||||
checkedCollectors := make(chan Collector, len(r.collectorsByID))
|
checkedCollectors := make(chan Collector, len(r.collectorsByID))
|
||||||
|
@ -549,6 +559,31 @@ func (r *Registry) Gather() ([]*dto.MetricFamily, error) {
|
||||||
return internal.NormalizeMetricFamilies(metricFamiliesByName), errs.MaybeUnwrap()
|
return internal.NormalizeMetricFamilies(metricFamiliesByName), errs.MaybeUnwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Describe implements Collector.
|
||||||
|
func (r *Registry) Describe(ch chan<- *Desc) {
|
||||||
|
r.mtx.RLock()
|
||||||
|
defer r.mtx.RUnlock()
|
||||||
|
|
||||||
|
// Only report the checked Collectors; unchecked collectors don't report any
|
||||||
|
// Desc.
|
||||||
|
for _, c := range r.collectorsByID {
|
||||||
|
c.Describe(ch)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Collect implements Collector.
|
||||||
|
func (r *Registry) Collect(ch chan<- Metric) {
|
||||||
|
r.mtx.RLock()
|
||||||
|
defer r.mtx.RUnlock()
|
||||||
|
|
||||||
|
for _, c := range r.collectorsByID {
|
||||||
|
c.Collect(ch)
|
||||||
|
}
|
||||||
|
for _, c := range r.uncheckedCollectors {
|
||||||
|
c.Collect(ch)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// WriteToTextfile calls Gather on the provided Gatherer, encodes the result in the
|
// WriteToTextfile calls Gather on the provided Gatherer, encodes the result in the
|
||||||
// Prometheus text format, and writes it to a temporary file. Upon success, the
|
// Prometheus text format, and writes it to a temporary file. Upon success, the
|
||||||
// temporary file is renamed to the provided filename.
|
// temporary file is renamed to the provided filename.
|
||||||
|
@ -556,7 +591,7 @@ func (r *Registry) Gather() ([]*dto.MetricFamily, error) {
|
||||||
// This is intended for use with the textfile collector of the node exporter.
|
// This is intended for use with the textfile collector of the node exporter.
|
||||||
// Note that the node exporter expects the filename to be suffixed with ".prom".
|
// Note that the node exporter expects the filename to be suffixed with ".prom".
|
||||||
func WriteToTextfile(filename string, g Gatherer) error {
|
func WriteToTextfile(filename string, g Gatherer) error {
|
||||||
tmp, err := ioutil.TempFile(filepath.Dir(filename), filepath.Base(filename))
|
tmp, err := os.CreateTemp(filepath.Dir(filename), filepath.Base(filename))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -575,7 +610,7 @@ func WriteToTextfile(filename string, g Gatherer) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := os.Chmod(tmp.Name(), 0644); err != nil {
|
if err := os.Chmod(tmp.Name(), 0o644); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return os.Rename(tmp.Name(), filename)
|
return os.Rename(tmp.Name(), filename)
|
||||||
|
@ -596,7 +631,7 @@ func processMetric(
|
||||||
}
|
}
|
||||||
dtoMetric := &dto.Metric{}
|
dtoMetric := &dto.Metric{}
|
||||||
if err := metric.Write(dtoMetric); err != nil {
|
if err := metric.Write(dtoMetric); err != nil {
|
||||||
return fmt.Errorf("error collecting metric %v: %s", desc, err)
|
return fmt.Errorf("error collecting metric %v: %w", desc, err)
|
||||||
}
|
}
|
||||||
metricFamily, ok := metricFamiliesByName[desc.fqName]
|
metricFamily, ok := metricFamiliesByName[desc.fqName]
|
||||||
if ok { // Existing name.
|
if ok { // Existing name.
|
||||||
|
@ -718,12 +753,13 @@ func (gs Gatherers) Gather() ([]*dto.MetricFamily, error) {
|
||||||
for i, g := range gs {
|
for i, g := range gs {
|
||||||
mfs, err := g.Gather()
|
mfs, err := g.Gather()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if multiErr, ok := err.(MultiError); ok {
|
multiErr := MultiError{}
|
||||||
|
if errors.As(err, &multiErr) {
|
||||||
for _, err := range multiErr {
|
for _, err := range multiErr {
|
||||||
errs = append(errs, fmt.Errorf("[from Gatherer #%d] %s", i+1, err))
|
errs = append(errs, fmt.Errorf("[from Gatherer #%d] %w", i+1, err))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
errs = append(errs, fmt.Errorf("[from Gatherer #%d] %s", i+1, err))
|
errs = append(errs, fmt.Errorf("[from Gatherer #%d] %w", i+1, err))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for _, mf := range mfs {
|
for _, mf := range mfs {
|
||||||
|
@ -884,11 +920,11 @@ func checkMetricConsistency(
|
||||||
h.Write(separatorByteSlice)
|
h.Write(separatorByteSlice)
|
||||||
// Make sure label pairs are sorted. We depend on it for the consistency
|
// Make sure label pairs are sorted. We depend on it for the consistency
|
||||||
// check.
|
// check.
|
||||||
if !sort.IsSorted(labelPairSorter(dtoMetric.Label)) {
|
if !sort.IsSorted(internal.LabelPairSorter(dtoMetric.Label)) {
|
||||||
// We cannot sort dtoMetric.Label in place as it is immutable by contract.
|
// We cannot sort dtoMetric.Label in place as it is immutable by contract.
|
||||||
copiedLabels := make([]*dto.LabelPair, len(dtoMetric.Label))
|
copiedLabels := make([]*dto.LabelPair, len(dtoMetric.Label))
|
||||||
copy(copiedLabels, dtoMetric.Label)
|
copy(copiedLabels, dtoMetric.Label)
|
||||||
sort.Sort(labelPairSorter(copiedLabels))
|
sort.Sort(internal.LabelPairSorter(copiedLabels))
|
||||||
dtoMetric.Label = copiedLabels
|
dtoMetric.Label = copiedLabels
|
||||||
}
|
}
|
||||||
for _, lp := range dtoMetric.Label {
|
for _, lp := range dtoMetric.Label {
|
||||||
|
@ -935,7 +971,7 @@ func checkDescConsistency(
|
||||||
metricFamily.GetName(), dtoMetric, desc,
|
metricFamily.GetName(), dtoMetric, desc,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
sort.Sort(labelPairSorter(lpsFromDesc))
|
sort.Sort(internal.LabelPairSorter(lpsFromDesc))
|
||||||
for i, lpFromDesc := range lpsFromDesc {
|
for i, lpFromDesc := range lpsFromDesc {
|
||||||
lpFromMetric := dtoMetric.Label[i]
|
lpFromMetric := dtoMetric.Label[i]
|
||||||
if lpFromDesc.GetName() != lpFromMetric.GetName() ||
|
if lpFromDesc.GetName() != lpFromMetric.GetName() ||
|
||||||
|
@ -948,3 +984,89 @@ func checkDescConsistency(
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var _ TransactionalGatherer = &MultiTRegistry{}
|
||||||
|
|
||||||
|
// MultiTRegistry is a TransactionalGatherer that joins gathered metrics from multiple
|
||||||
|
// transactional gatherers.
|
||||||
|
//
|
||||||
|
// It is caller responsibility to ensure two registries have mutually exclusive metric families,
|
||||||
|
// no deduplication will happen.
|
||||||
|
type MultiTRegistry struct {
|
||||||
|
tGatherers []TransactionalGatherer
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMultiTRegistry creates MultiTRegistry.
|
||||||
|
func NewMultiTRegistry(tGatherers ...TransactionalGatherer) *MultiTRegistry {
|
||||||
|
return &MultiTRegistry{
|
||||||
|
tGatherers: tGatherers,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gather implements TransactionalGatherer interface.
|
||||||
|
func (r *MultiTRegistry) Gather() (mfs []*dto.MetricFamily, done func(), err error) {
|
||||||
|
errs := MultiError{}
|
||||||
|
|
||||||
|
dFns := make([]func(), 0, len(r.tGatherers))
|
||||||
|
// TODO(bwplotka): Implement concurrency for those?
|
||||||
|
for _, g := range r.tGatherers {
|
||||||
|
// TODO(bwplotka): Check for duplicates?
|
||||||
|
m, d, err := g.Gather()
|
||||||
|
errs.Append(err)
|
||||||
|
|
||||||
|
mfs = append(mfs, m...)
|
||||||
|
dFns = append(dFns, d)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(bwplotka): Consider sort in place, given metric family in gather is sorted already.
|
||||||
|
sort.Slice(mfs, func(i, j int) bool {
|
||||||
|
return *mfs[i].Name < *mfs[j].Name
|
||||||
|
})
|
||||||
|
return mfs, func() {
|
||||||
|
for _, d := range dFns {
|
||||||
|
d()
|
||||||
|
}
|
||||||
|
}, errs.MaybeUnwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
// TransactionalGatherer represents transactional gatherer that can be triggered to notify gatherer that memory
|
||||||
|
// used by metric family is no longer used by a caller. This allows implementations with cache.
|
||||||
|
type TransactionalGatherer interface {
|
||||||
|
// Gather returns metrics in a lexicographically sorted slice
|
||||||
|
// of uniquely named MetricFamily protobufs. Gather ensures that the
|
||||||
|
// returned slice is valid and self-consistent so that it can be used
|
||||||
|
// for valid exposition. As an exception to the strict consistency
|
||||||
|
// requirements described for metric.Desc, Gather will tolerate
|
||||||
|
// different sets of label names for metrics of the same metric family.
|
||||||
|
//
|
||||||
|
// Even if an error occurs, Gather attempts to gather as many metrics as
|
||||||
|
// possible. Hence, if a non-nil error is returned, the returned
|
||||||
|
// MetricFamily slice could be nil (in case of a fatal error that
|
||||||
|
// prevented any meaningful metric collection) or contain a number of
|
||||||
|
// MetricFamily protobufs, some of which might be incomplete, and some
|
||||||
|
// might be missing altogether. The returned error (which might be a
|
||||||
|
// MultiError) explains the details. Note that this is mostly useful for
|
||||||
|
// debugging purposes. If the gathered protobufs are to be used for
|
||||||
|
// exposition in actual monitoring, it is almost always better to not
|
||||||
|
// expose an incomplete result and instead disregard the returned
|
||||||
|
// MetricFamily protobufs in case the returned error is non-nil.
|
||||||
|
//
|
||||||
|
// Important: done is expected to be triggered (even if the error occurs!)
|
||||||
|
// once caller does not need returned slice of dto.MetricFamily.
|
||||||
|
Gather() (_ []*dto.MetricFamily, done func(), err error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToTransactionalGatherer transforms Gatherer to transactional one with noop as done function.
|
||||||
|
func ToTransactionalGatherer(g Gatherer) TransactionalGatherer {
|
||||||
|
return &noTransactionGatherer{g: g}
|
||||||
|
}
|
||||||
|
|
||||||
|
type noTransactionGatherer struct {
|
||||||
|
g Gatherer
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gather implements TransactionalGatherer interface.
|
||||||
|
func (g *noTransactionGatherer) Gather() (_ []*dto.MetricFamily, done func(), err error) {
|
||||||
|
mfs, err := g.g.Gather()
|
||||||
|
return mfs, func() {}, err
|
||||||
|
}
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue