forked from mirror/client_golang
Compare commits
201 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 | 5da7b61481 | |
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 | |
Kemal Akkoyun | e203144f43 | |
Kemal Akkoyun | 0e136d10da | |
Kemal Akkoyun | a27b6d74f6 | |
Kemal Akkoyun | 5fe1d33cea | |
Kemal Akkoyun | 049d0fe55b | |
Bartlomiej Plotka | 7eb9d111f9 | |
Bartlomiej Plotka | d498b3cdd9 | |
alissa-tung | 585540a010 | |
Kemal Akkoyun | 39cf574e99 | |
Michael Knyszek | 9b785b0349 | |
Bryan Boreham | 5a529ae06b | |
Michael Knyszek | d32edd6083 | |
Michael Knyszek | 772b89389c | |
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 | |
Kemal Akkoyun | 08a53e57a2 | |
Kemal Akkoyun | 2ce58a71db | |
Kemal Akkoyun | 5bd9ee52dc | |
Kemal Akkoyun | 9075cdf616 | |
Michael Knyszek | 22da9497b8 | |
beorn7 | 70253f4dd0 | |
beorn7 | 5b19c553c3 | |
Łukasz Mierzwa | dc1559e8ef | |
Kemal Akkoyun | e6e54e8082 | |
Kemal Akkoyun | 98fbd996b0 | |
prombot | 70a41d58f8 | |
Kemal Akkoyun | dbf420e7ce | |
Kemal Akkoyun | 1d09783cb9 | |
Manuel Rüger | 8535b62021 | |
José Carlos Chávez | d28a147d3c | |
Ben Ye | 440c09d3ec | |
Björn Rabenstein | 6d5cf25fcc | |
Gökhan Özeloğlu | 55320aa8b0 | |
Kemal Akkoyun | 1b145cad68 | |
beorn7 | f78dc19a65 | |
prombot | a7919ad927 | |
David Trudgian | 679eb0d315 | |
Kemal Akkoyun | 35ee299e50 | |
prombot | 6cd53c4d83 | |
Björn Rabenstein | dfbcc28fff | |
beorn7 | 263be8dab7 | |
beorn7 | 24099603bc | |
beorn7 | 84fcafffb1 | |
Seth Bunce | 2261d5cda1 | |
PrometheusBot | 20eef74042 | |
beorn7 | 9ef5f90a76 | |
Kemal Akkoyun | cb5c8ff250 | |
Kemal Akkoyun | 26d4b4c53e | |
Kemal Akkoyun | c7175cc5a5 | |
beorn7 | aa6f67a9e6 | |
prombot | e23b9ce547 | |
Björn Rabenstein | 43f31c25a8 | |
beorn7 | 514234486b | |
beorn7 | 6c4e0ef740 | |
beorn7 | 31318b7523 | |
SuperQ | 6d946b5526 | |
Sivabalan Thirunavukkarasu | 5426b512dd | |
beorn7 | 5aa8534cd0 | |
Kemal Akkoyun | 196536534f | |
Kemal Akkoyun | 64a9c512d4 | |
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
|
||||
orbs:
|
||||
go: circleci/go@0.2.0
|
||||
prometheus: prometheus/prometheus@0.11.0
|
||||
go: circleci/go@1.7.1
|
||||
prometheus: prometheus/prometheus@0.16.0
|
||||
jobs:
|
||||
test:
|
||||
parameters:
|
||||
|
@ -17,8 +17,7 @@ jobs:
|
|||
type: boolean
|
||||
default: true
|
||||
docker:
|
||||
- image: circleci/golang:<< parameters.go_version >>
|
||||
working_directory: /go/src/github.com/prometheus/client_golang
|
||||
- image: cimg/go:<< parameters.go_version >>
|
||||
steps:
|
||||
- checkout
|
||||
- when:
|
||||
|
@ -48,20 +47,16 @@ workflows:
|
|||
jobs:
|
||||
# Refer to README.md for the currently supported versions.
|
||||
- test:
|
||||
name: go-1-13
|
||||
go_version: "1.13"
|
||||
name: go-1-17
|
||||
go_version: "1.17"
|
||||
run_lint: true
|
||||
- test:
|
||||
name: go-1-14
|
||||
go_version: "1.14"
|
||||
name: go-1-18
|
||||
go_version: "1.18"
|
||||
run_lint: true
|
||||
- test:
|
||||
name: go-1-15
|
||||
go_version: "1.15"
|
||||
run_lint: true
|
||||
- test:
|
||||
name: go-1-16
|
||||
go_version: "1.16"
|
||||
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.
|
||||
|
|
|
@ -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
|
|
@ -0,0 +1,30 @@
|
|||
name: golangci-lint
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- "go.sum"
|
||||
- "go.mod"
|
||||
- "**.go"
|
||||
- "scripts/errcheck_excludes.txt"
|
||||
- ".github/workflows/golangci-lint.yml"
|
||||
- ".golangci.yml"
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
golangci:
|
||||
name: lint
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
- name: install Go
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
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
|
|
@ -1,6 +1,7 @@
|
|||
# Examples
|
||||
examples/simple/simple
|
||||
examples/random/random
|
||||
examples/gocollector/gocollector
|
||||
|
||||
# Typical backup/temporary files of editors
|
||||
*~
|
||||
|
@ -11,7 +12,7 @@ examples/random/random
|
|||
vendor/
|
||||
|
||||
# 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
|
||||
*.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:
|
||||
enable:
|
||||
- deadcode
|
||||
- depguard
|
||||
- durationcheck
|
||||
- errorlint
|
||||
- exportloopref
|
||||
- gofmt
|
||||
- gofumpt
|
||||
- goimports
|
||||
- gosimple
|
||||
- ineffassign
|
||||
- misspell
|
||||
- nolintlint
|
||||
- predeclared
|
||||
- revive
|
||||
- staticcheck
|
||||
disable-all: true
|
||||
- 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
|
||||
|
|
85
CHANGELOG.md
85
CHANGELOG.md
|
@ -1,9 +1,69 @@
|
|||
## Unreleased
|
||||
|
||||
## 1.14.0 / 2022-11-08
|
||||
|
||||
* [FEATURE] Add Support for Native Histograms. #1150
|
||||
* [CHANGE] Extend `prometheus.Registry` to implement `prometheus.Collector` interface. #1103
|
||||
|
||||
## 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.
|
||||
* [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_frees_by_size_bytes_total` -> `go_gc_heap_allocs_by_size_bytes`
|
||||
* `go_gc_pauses_seconds_total` -> `go_gc_pauses_seconds`.
|
||||
* [CHANCE] Removed `-Inf` buckets from new Go Collector histograms.
|
||||
|
||||
## 1.12.1 / 2022-01-29
|
||||
|
||||
* [BUGFIX] Make the Go 1.17 collector concurrency-safe #969
|
||||
* Use simpler locking in the Go 1.17 collector #975
|
||||
* [BUGFIX] Reduce granularity of histogram buckets for Go 1.17 collector #974
|
||||
* [ENHANCEMENT] API client: make HTTP reads more efficient #976
|
||||
|
||||
## 1.12.0 / 2022-01-19
|
||||
|
||||
* [CHANGE] example/random: Move flags and metrics into main() #935
|
||||
* [FEATURE] API client: Support wal replay status api #944
|
||||
* [FEATURE] Use the runtime/metrics package for the Go collector for 1.17+ #955
|
||||
* [ENHANCEMENT] API client: Update /api/v1/status/tsdb to include headStats #925
|
||||
* [ENHANCEMENT] promhttp: Check validity of method and code label values #962
|
||||
|
||||
## 1.11.0 / 2021-06-07
|
||||
|
||||
* [ENHANCEMENT] API client: Add newer fields to Rules API #855
|
||||
* [ENHANCEMENT] API client: Add missing fields to Targets API #856
|
||||
* [FEATURE] API client: Add exemplars API support #861
|
||||
* [CHANGE] Add new collectors package #862
|
||||
* [CHANGE] Add new collectors package. #862
|
||||
* [CHANGE] `prometheus.NewExpvarCollector` is deprecated, use `collectors.NewExpvarCollector` instead. #862
|
||||
* [CHANGE] `prometheus.NewGoCollector` is deprecated, use `collectors.NewGoCollector` instead. #862
|
||||
* [CHANGE] `prometheus.NewBuildInfoCollector` is deprecated, use `collectors.NewBuildInfoCollector` instead. #862
|
||||
* [FEATURE] Add new collector for database/sql#DBStats. #866
|
||||
* [FEATURE] API client: Add exemplars API support. #861
|
||||
* [ENHANCEMENT] API client: Add newer fields to Rules API. #855
|
||||
* [ENHANCEMENT] API client: Add missing fields to Targets API. #856
|
||||
|
||||
## 1.10.0 / 2021-03-18
|
||||
|
||||
|
@ -21,7 +81,7 @@
|
|||
|
||||
* [CHANGE] API client: Use `time.Time` rather than `string` for timestamps in `RuntimeinfoResult`. #777
|
||||
* [FEATURE] Export `MetricVec` to facilitate implementation of vectors of custom `Metric` types. #803
|
||||
* [FEATURE API client: Support `/status/tsdb` endpoint. #773
|
||||
* [FEATURE] API client: Support `/status/tsdb` endpoint. #773
|
||||
* [ENHANCEMENT] API client: Enable GET fallback on status code 501. #802
|
||||
* [ENHANCEMENT] Remove `Metric` references after reslicing to free up more memory. #784
|
||||
|
||||
|
@ -101,6 +161,7 @@ _This release removes all previously deprecated features, resulting in the break
|
|||
* [BUGFIX] Make `AlreadyRegisteredError` usable for wrapped registries. #607
|
||||
|
||||
## 0.9.4 / 2019-06-07
|
||||
|
||||
* [CHANGE] API client: Switch to alert values as strings. #585
|
||||
* [FEATURE] Add a collector for Go module build information. #595
|
||||
* [FEATURE] promhttp: Add an counter for internal errors during HTTP exposition. #594
|
||||
|
@ -110,6 +171,7 @@ _This release removes all previously deprecated features, resulting in the break
|
|||
* [BUGFIX] Reduce test flakiness. #573
|
||||
|
||||
## 0.9.3 / 2019-05-16
|
||||
|
||||
* [CHANGE] Required Go version is now 1.9+. #561
|
||||
* [FEATURE] API client: Add POST with get fallback for Query/QueryRange. #557
|
||||
* [FEATURE] API client: Add alerts endpoint. #552
|
||||
|
@ -131,6 +193,7 @@ _This release removes all previously deprecated features, resulting in the break
|
|||
* [BUGFIX] API client: Deal with discovered labels properly. #529
|
||||
|
||||
## 0.9.2 / 2018-12-06
|
||||
|
||||
* [FEATURE] Support for Go modules. #501
|
||||
* [FEATURE] `Timer.ObserveDuration` returns observed duration. #509
|
||||
* [ENHANCEMENT] Improved doc comments and error messages. #504
|
||||
|
@ -139,6 +202,7 @@ _This release removes all previously deprecated features, resulting in the break
|
|||
#498
|
||||
|
||||
## 0.9.1 / 2018-11-03
|
||||
|
||||
* [FEATURE] Add `WriteToTextfile` function to facilitate the creation of
|
||||
*.prom files for the textfile collector of the node exporter. #489
|
||||
* [ENHANCEMENT] More descriptive error messages for inconsistent label
|
||||
|
@ -151,6 +215,7 @@ _This release removes all previously deprecated features, resulting in the break
|
|||
#479
|
||||
|
||||
## 0.9.0 / 2018-10-15
|
||||
|
||||
* [CHANGE] Go1.6 is no longer supported.
|
||||
* [CHANGE] More refinements of the `Registry` consistency checks: Duplicated
|
||||
labels are now detected, but inconsistent label dimensions are now allowed.
|
||||
|
@ -203,6 +268,7 @@ _This release removes all previously deprecated features, resulting in the break
|
|||
responses. #476 #414
|
||||
|
||||
## 0.8.0 / 2016-08-17
|
||||
|
||||
* [CHANGE] Registry is doing more consistency checks. This might break
|
||||
existing setups that used to export inconsistent metrics.
|
||||
* [CHANGE] Pushing to Pushgateway moved to package `push` and changed to allow
|
||||
|
@ -226,6 +292,7 @@ _This release removes all previously deprecated features, resulting in the break
|
|||
* [BUGFIX] Handle collisions in MetricVec.
|
||||
|
||||
## 0.7.0 / 2015-07-27
|
||||
|
||||
* [CHANGE] Rename ExporterLabelPrefix to ExportedLabelPrefix.
|
||||
* [BUGFIX] Closed gaps in metric consistency check.
|
||||
* [BUGFIX] Validate LabelName/LabelSet on JSON unmarshaling.
|
||||
|
@ -236,12 +303,14 @@ _This release removes all previously deprecated features, resulting in the break
|
|||
* [ENHANCEMENT] Change responseWriterDelegator.written to int64.
|
||||
|
||||
## 0.6.0 / 2015-06-01
|
||||
|
||||
* [CHANGE] Rename process_goroutines to go_goroutines.
|
||||
* [ENHANCEMENT] Validate label names during YAML decoding.
|
||||
* [ENHANCEMENT] Add LabelName regular expression.
|
||||
* [BUGFIX] Ensure alignment of struct members for 32-bit systems.
|
||||
|
||||
## 0.5.0 / 2015-05-06
|
||||
|
||||
* [BUGFIX] Removed a weakness in the fingerprinting aka signature code.
|
||||
This makes fingerprinting slower and more allocation-heavy, but the
|
||||
weakness was too severe to be tolerated.
|
||||
|
@ -260,6 +329,7 @@ _This release removes all previously deprecated features, resulting in the break
|
|||
* [CHANGE] A number of new reserved labels and prefixes.
|
||||
|
||||
## 0.4.0 / 2015-04-08
|
||||
|
||||
* [CHANGE] Return NaN when Summaries have no observations yet.
|
||||
* [BUGFIX] Properly handle Summary decay upon Write().
|
||||
* [BUGFIX] Fix the documentation link to the consumption library.
|
||||
|
@ -269,16 +339,19 @@ _This release removes all previously deprecated features, resulting in the break
|
|||
* [MAINTENANCE] Adjusted to changes in matttproud/golang_protobuf_extensions.
|
||||
|
||||
## 0.3.2 / 2015-03-11
|
||||
|
||||
* [BUGFIX] Fixed the receiver type of COWMetric.Set(). This method is
|
||||
only used by the Prometheus server internally.
|
||||
* [CLEANUP] Added licenses of vendored code left out by godep.
|
||||
|
||||
## 0.3.1 / 2015-03-04
|
||||
|
||||
* [ENHANCEMENT] Switched fingerprinting functions from own free list to
|
||||
sync.Pool.
|
||||
* [CHANGE] Makefile uses Go 1.4.2 now (only relevant for examples and tests).
|
||||
|
||||
## 0.3.0 / 2015-03-03
|
||||
|
||||
* [CHANGE] Changed the fingerprinting for metrics. THIS WILL INVALIDATE ALL
|
||||
PERSISTED FINGERPRINTS. IF YOU COMPILE THE PROMETHEUS SERVER WITH THIS
|
||||
VERSION, YOU HAVE TO WIPE THE PREVIOUSLY CREATED STORAGE.
|
||||
|
@ -293,6 +366,7 @@ _This release removes all previously deprecated features, resulting in the break
|
|||
require fewer allocations than the ones currently used by the server.
|
||||
|
||||
## 0.2.0 / 2015-02-23
|
||||
|
||||
* [FEATURE] Introduce new Histagram metric type.
|
||||
* [CHANGE] Ignore process collector errors for now (better error handling
|
||||
pending).
|
||||
|
@ -309,5 +383,6 @@ _This release removes all previously deprecated features, resulting in the break
|
|||
* [CLEANUP] Updated vendoring of beorn7/perks.
|
||||
|
||||
## 0.1.0 / 2015-02-02
|
||||
|
||||
* [CLEANUP] Introduced semantic versioning and changelog. From now on,
|
||||
changes will be reported in this file.
|
||||
|
|
|
@ -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).
|
||||
|
|
27
Dockerfile
27
Dockerfile
|
@ -1,23 +1,34 @@
|
|||
# This Dockerfile builds an image for a client_golang example.
|
||||
#
|
||||
# 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.
|
||||
FROM golang:1 AS builder
|
||||
WORKDIR /go/src/github.com/prometheus/client_golang
|
||||
WORKDIR /go/src/git.internal/re/client_golang
|
||||
COPY . .
|
||||
WORKDIR /go/src/github.com/prometheus/client_golang/prometheus
|
||||
WORKDIR /go/src/git.internal/re/client_golang/prometheus
|
||||
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'
|
||||
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'
|
||||
WORKDIR /go/src/git.internal/re/client_golang/examples/gocollector
|
||||
RUN CGO_ENABLED=0 GOOS=linux go build -a -tags netgo -ldflags '-w'
|
||||
|
||||
# Final image.
|
||||
FROM quay.io/prometheus/busybox:latest
|
||||
LABEL maintainer="The Prometheus Authors <prometheus-developers@googlegroups.com>"
|
||||
COPY --from=builder /go/src/github.com/prometheus/client_golang/examples/random \
|
||||
/go/src/github.com/prometheus/client_golang/examples/simple ./
|
||||
COPY --from=builder /go/src/git.internal/re/client_golang/examples/random \
|
||||
/go/src/git.internal/re/client_golang/examples/simple \
|
||||
/go/src/git.internal/re/client_golang/examples/gocollector ./
|
||||
EXPOSE 8080
|
||||
CMD ["echo", "Please run an example. Either /random or /simple"]
|
||||
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
|
||||
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))
|
||||
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
|
||||
pkgs = ./...
|
||||
|
||||
|
@ -78,17 +55,23 @@ ifneq ($(shell which gotestsum),)
|
|||
endif
|
||||
endif
|
||||
|
||||
PROMU_VERSION ?= 0.12.0
|
||||
PROMU_VERSION ?= 0.13.0
|
||||
PROMU_URL := https://github.com/prometheus/promu/releases/download/v$(PROMU_VERSION)/promu-$(PROMU_VERSION).$(GO_BUILD_PLATFORM).tar.gz
|
||||
|
||||
GOLANGCI_LINT :=
|
||||
GOLANGCI_LINT_OPTS ?=
|
||||
GOLANGCI_LINT_VERSION ?= v1.39.0
|
||||
GOLANGCI_LINT_VERSION ?= v1.45.2
|
||||
# golangci-lint only supports linux, darwin and windows platforms on i386/amd64.
|
||||
# windows isn't included here because of the path separator being different.
|
||||
ifeq ($(GOHOSTOS),$(filter $(GOHOSTOS),linux darwin))
|
||||
ifeq ($(GOHOSTARCH),$(filter $(GOHOSTARCH),amd64 i386))
|
||||
# If we're in CI and there is an Actions file, that means the linter
|
||||
# is being run in Actions, so we don't need to run it here.
|
||||
ifeq (,$(CIRCLE_JOB))
|
||||
GOLANGCI_LINT := $(FIRST_GOPATH)/bin/golangci-lint
|
||||
else ifeq (,$(wildcard .github/workflows/golangci-lint.yml))
|
||||
GOLANGCI_LINT := $(FIRST_GOPATH)/bin/golangci-lint
|
||||
endif
|
||||
endif
|
||||
endif
|
||||
|
||||
|
@ -118,7 +101,7 @@ endif
|
|||
%: common-% ;
|
||||
|
||||
.PHONY: common-all
|
||||
common-all: precheck style check_license lint unused build test
|
||||
common-all: precheck style check_license lint yamllint unused build test
|
||||
|
||||
.PHONY: common-style
|
||||
common-style:
|
||||
|
@ -144,32 +127,25 @@ common-check_license:
|
|||
.PHONY: common-deps
|
||||
common-deps:
|
||||
@echo ">> getting dependencies"
|
||||
ifdef GO111MODULE
|
||||
GO111MODULE=$(GO111MODULE) $(GO) mod download
|
||||
else
|
||||
$(GO) get $(GOOPTS) -t ./...
|
||||
endif
|
||||
$(GO) mod download
|
||||
|
||||
.PHONY: update-go-deps
|
||||
update-go-deps:
|
||||
@echo ">> updating Go dependencies"
|
||||
@for m in $$($(GO) list -mod=readonly -m -f '{{ if and (not .Indirect) (not .Main)}}{{.Path}}{{end}}' all); do \
|
||||
$(GO) get $$m; \
|
||||
$(GO) get -d $$m; \
|
||||
done
|
||||
GO111MODULE=$(GO111MODULE) $(GO) mod tidy
|
||||
ifneq (,$(wildcard vendor))
|
||||
GO111MODULE=$(GO111MODULE) $(GO) mod vendor
|
||||
endif
|
||||
$(GO) mod tidy
|
||||
|
||||
.PHONY: common-test-short
|
||||
common-test-short: $(GOTEST_DIR)
|
||||
@echo ">> running short tests"
|
||||
GO111MODULE=$(GO111MODULE) $(GOTEST) -short $(GOOPTS) $(pkgs)
|
||||
$(GOTEST) -short $(GOOPTS) $(pkgs)
|
||||
|
||||
.PHONY: common-test
|
||||
common-test: $(GOTEST_DIR)
|
||||
@echo ">> running all tests"
|
||||
GO111MODULE=$(GO111MODULE) $(GOTEST) $(test-flags) $(GOOPTS) $(pkgs)
|
||||
$(GOTEST) $(test-flags) $(GOOPTS) $(pkgs)
|
||||
|
||||
$(GOTEST_DIR):
|
||||
@mkdir -p $@
|
||||
|
@ -177,25 +153,30 @@ $(GOTEST_DIR):
|
|||
.PHONY: common-format
|
||||
common-format:
|
||||
@echo ">> formatting code"
|
||||
GO111MODULE=$(GO111MODULE) $(GO) fmt $(pkgs)
|
||||
$(GO) fmt $(pkgs)
|
||||
|
||||
.PHONY: common-vet
|
||||
common-vet:
|
||||
@echo ">> vetting code"
|
||||
GO111MODULE=$(GO111MODULE) $(GO) vet $(GOOPTS) $(pkgs)
|
||||
$(GO) vet $(GOOPTS) $(pkgs)
|
||||
|
||||
.PHONY: common-lint
|
||||
common-lint: $(GOLANGCI_LINT)
|
||||
ifdef GOLANGCI_LINT
|
||||
@echo ">> running golangci-lint"
|
||||
ifdef GO111MODULE
|
||||
# 'go list' needs to be executed before staticcheck to prepopulate the modules cache.
|
||||
# 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
|
||||
GO111MODULE=$(GO111MODULE) $(GOLANGCI_LINT) run $(GOLANGCI_LINT_OPTS) $(pkgs)
|
||||
else
|
||||
$(GOLANGCI_LINT) run $(pkgs)
|
||||
$(GO) list -e -compiled -test=true -export=false -deps=true -find=false -tags= -- ./... > /dev/null
|
||||
$(GOLANGCI_LINT) run $(GOLANGCI_LINT_OPTS) $(pkgs)
|
||||
endif
|
||||
|
||||
.PHONY: common-yamllint
|
||||
common-yamllint:
|
||||
@echo ">> running yamllint on all YAML files in the repository"
|
||||
ifeq (, $(shell which yamllint))
|
||||
@echo "yamllint not installed so skipping"
|
||||
else
|
||||
yamllint .
|
||||
endif
|
||||
|
||||
# For backward-compatibility.
|
||||
|
@ -203,28 +184,15 @@ endif
|
|||
common-staticcheck: lint
|
||||
|
||||
.PHONY: common-unused
|
||||
common-unused: $(GOVENDOR)
|
||||
ifdef GOVENDOR
|
||||
@echo ">> running check for unused packages"
|
||||
@$(GOVENDOR) list +unused | grep . && exit 1 || echo 'No unused packages'
|
||||
else
|
||||
ifdef GO111MODULE
|
||||
common-unused:
|
||||
@echo ">> running check for unused/missing packages in go.mod"
|
||||
GO111MODULE=$(GO111MODULE) $(GO) mod tidy
|
||||
ifeq (,$(wildcard vendor))
|
||||
$(GO) mod tidy
|
||||
@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
|
||||
common-build: promu
|
||||
@echo ">> building binaries"
|
||||
GO111MODULE=$(GO111MODULE) $(PROMU) build --prefix $(PREFIX) $(PROMU_BINARIES)
|
||||
$(PROMU) build --prefix $(PREFIX) $(PROMU_BINARIES)
|
||||
|
||||
.PHONY: common-tarball
|
||||
common-tarball: promu
|
||||
|
@ -280,12 +248,6 @@ $(GOLANGCI_LINT):
|
|||
| sh -s -- -b $(FIRST_GOPATH)/bin $(GOLANGCI_LINT_VERSION)
|
||||
endif
|
||||
|
||||
ifdef GOVENDOR
|
||||
.PHONY: $(GOVENDOR)
|
||||
$(GOVENDOR):
|
||||
GOOS= GOARCH= $(GO) get -u github.com/kardianos/govendor
|
||||
endif
|
||||
|
||||
.PHONY: precheck
|
||||
precheck::
|
||||
|
||||
|
|
50
README.md
50
README.md
|
@ -1,15 +1,16 @@
|
|||
# Prometheus Go client library
|
||||
|
||||
[![Build Status](https://travis-ci.org/prometheus/client_golang.svg?branch=master)](https://travis-ci.org/prometheus/client_golang)
|
||||
[![Go Report Card](https://goreportcard.com/badge/github.com/prometheus/client_golang)](https://goreportcard.com/report/github.com/prometheus/client_golang)
|
||||
[![go-doc](https://godoc.org/github.com/prometheus/client_golang?status.svg)](https://godoc.org/github.com/prometheus/client_golang)
|
||||
[![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/git.internal/re/client_golang)](https://goreportcard.com/report/git.internal/re/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
|
||||
[Prometheus](http://prometheus.io). It has two separate parts, one for
|
||||
instrumenting application code, and one for creating clients that talk to the
|
||||
Prometheus HTTP API.
|
||||
|
||||
__This library requires Go1.13 or later.__
|
||||
__This library requires Go1.17 or later.__
|
||||
|
||||
## Important note about releases and stability
|
||||
|
||||
|
@ -22,33 +23,33 @@ CHANGELOG.md.
|
|||
|
||||
Features that require breaking changes in the stable parts of the repository
|
||||
are being batched up and tracked in the [v2
|
||||
milestone](https://github.com/prometheus/client_golang/milestone/2). The v2
|
||||
milestone](https://git.internal/re/client_golang/milestone/2). The v2
|
||||
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
|
||||
reached. In view of the widespread use of this repository, v1 and v2 will
|
||||
coexist for a while to enable a convenient transition.
|
||||
|
||||
## Instrumenting applications
|
||||
|
||||
[![code-coverage](http://gocover.io/_badge/github.com/prometheus/client_golang/prometheus)](http://gocover.io/github.com/prometheus/client_golang/prometheus) [![go-doc](https://godoc.org/github.com/prometheus/client_golang/prometheus?status.svg)](https://godoc.org/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
|
||||
[`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
|
||||
[guide](https://prometheus.io/docs/guides/go-application/) on the Prometheus
|
||||
website to learn more about instrumenting applications.
|
||||
|
||||
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.
|
||||
|
||||
## 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-doc](https://godoc.org/github.com/prometheus/client_golang/api/prometheus?status.svg)](https://godoc.org/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
|
||||
[`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
|
||||
[Prometheus HTTP API](http://prometheus.io/docs/querying/api/). It allows you
|
||||
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`?
|
||||
|
||||
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
|
||||
[`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
|
||||
|
||||
See the [contributing guidelines](CONTRIBUTING.md) and the
|
||||
[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
|
||||
found here:
|
||||
|
||||
https://prometheus.io/docs/operating/security/
|
||||
<https://prometheus.io/docs/operating/security/>
|
||||
|
|
|
@ -15,8 +15,9 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"io/ioutil"
|
||||
"errors"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
@ -40,6 +41,10 @@ type Config struct {
|
|||
// The address of the Prometheus to connect to.
|
||||
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
|
||||
// provided, DefaultRoundTripper will be used.
|
||||
RoundTripper http.RoundTripper
|
||||
|
@ -52,6 +57,22 @@ func (cfg *Config) roundTripper() http.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.
|
||||
type Client interface {
|
||||
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, "/")
|
||||
|
||||
if err := cfg.validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &httpClient{
|
||||
endpoint: u,
|
||||
client: http.Client{Transport: cfg.roundTripper()},
|
||||
client: cfg.client(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
@ -84,7 +109,7 @@ func (c *httpClient) URL(ep string, args map[string]string) *url.URL {
|
|||
|
||||
for arg, val := range args {
|
||||
arg = ":" + arg
|
||||
p = strings.Replace(p, arg, val, -1)
|
||||
p = strings.ReplaceAll(p, arg, val)
|
||||
}
|
||||
|
||||
u := *c.endpoint
|
||||
|
@ -111,7 +136,9 @@ func (c *httpClient) Do(ctx context.Context, req *http.Request) (*http.Response,
|
|||
var body []byte
|
||||
done := make(chan struct{})
|
||||
go func() {
|
||||
body, err = ioutil.ReadAll(resp.Body)
|
||||
var buf bytes.Buffer
|
||||
_, err = buf.ReadFrom(resp.Body)
|
||||
body = buf.Bytes()
|
||||
close(done)
|
||||
}()
|
||||
|
||||
|
|
|
@ -14,7 +14,11 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"testing"
|
||||
)
|
||||
|
@ -111,3 +115,49 @@ func TestClientURL(t *testing.T) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Serve any http request with a response of N KB of spaces.
|
||||
type serveSpaces struct {
|
||||
sizeKB int
|
||||
}
|
||||
|
||||
func (t serveSpaces) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||
kb := bytes.Repeat([]byte{' '}, 1024)
|
||||
for i := 0; i < t.sizeKB; i++ {
|
||||
w.Write(kb)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkClient(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
ctx := context.Background()
|
||||
|
||||
for _, sizeKB := range []int{4, 50, 1000, 2000} {
|
||||
b.Run(fmt.Sprintf("%dKB", sizeKB), func(b *testing.B) {
|
||||
testServer := httptest.NewServer(serveSpaces{sizeKB})
|
||||
defer testServer.Close()
|
||||
|
||||
client, err := NewClient(Config{
|
||||
Address: testServer.URL,
|
||||
})
|
||||
if err != nil {
|
||||
b.Fatalf("Failed to initialize client: %v", err)
|
||||
}
|
||||
url, err := url.Parse(testServer.URL + "/prometheus/api/v1/query?query=up")
|
||||
if err != nil {
|
||||
b.Fatalf("Failed to parse url: %v", err)
|
||||
}
|
||||
req := &http.Request{
|
||||
URL: url,
|
||||
}
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, _, err := client.Do(ctx, req)
|
||||
if err != nil {
|
||||
b.Fatalf("Query failed: %v", err)
|
||||
}
|
||||
}
|
||||
b.StopTimer()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -31,7 +31,7 @@ import (
|
|||
|
||||
"github.com/prometheus/common/model"
|
||||
|
||||
"github.com/prometheus/client_golang/api"
|
||||
"git.internal/re/client_golang/api"
|
||||
)
|
||||
|
||||
func init() {
|
||||
|
@ -109,7 +109,6 @@ func marshalPointJSON(ptr unsafe.Pointer, stream *json.Stream) {
|
|||
|
||||
stream.WriteRaw(`"`)
|
||||
stream.WriteArrayEnd()
|
||||
|
||||
}
|
||||
|
||||
func marshalPointJSONIsEmpty(ptr unsafe.Pointer) bool {
|
||||
|
@ -139,6 +138,7 @@ const (
|
|||
epBuildinfo = apiPrefix + "/status/buildinfo"
|
||||
epRuntimeinfo = apiPrefix + "/status/runtimeinfo"
|
||||
epTSDB = apiPrefix + "/status/tsdb"
|
||||
epWalReplay = apiPrefix + "/status/walreplay"
|
||||
)
|
||||
|
||||
// AlertState models the state of an alert.
|
||||
|
@ -229,25 +229,25 @@ type API interface {
|
|||
// Config returns the current Prometheus configuration.
|
||||
Config(ctx context.Context) (ConfigResult, error)
|
||||
// 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(ctx context.Context) (FlagsResult, error)
|
||||
// LabelNames returns the unique label names present in the block in sorted order by given time range and matchers.
|
||||
LabelNames(ctx context.Context, matches []string, startTime 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(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(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(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(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(ctx context.Context) (BuildinfoResult, error)
|
||||
// Runtimeinfo returns the various runtime information properties about the Prometheus server.
|
||||
Runtimeinfo(ctx context.Context) (RuntimeinfoResult, error)
|
||||
// Series finds series by label matchers.
|
||||
Series(ctx context.Context, matches []string, startTime 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>
|
||||
// under the TSDB's data directory and returns the directory as response.
|
||||
Snapshot(ctx context.Context, skipHead bool) (SnapshotResult, error)
|
||||
|
@ -256,11 +256,13 @@ type API interface {
|
|||
// Targets returns an overview of the current state of the Prometheus target discovery.
|
||||
Targets(ctx context.Context) (TargetsResult, error)
|
||||
// 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(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(ctx context.Context) (TSDBResult, error)
|
||||
// WalReplay returns the current replay status of the wal.
|
||||
WalReplay(ctx context.Context) (WalReplayStatus, error)
|
||||
}
|
||||
|
||||
// AlertsResult contains the result from querying the alerts endpoint.
|
||||
|
@ -303,8 +305,6 @@ type RuntimeinfoResult struct {
|
|||
CWD string `json:"CWD"`
|
||||
ReloadConfigSuccess bool `json:"reloadConfigSuccess"`
|
||||
LastConfigTime time.Time `json:"lastConfigTime"`
|
||||
ChunkCount int `json:"chunkCount"`
|
||||
TimeSeriesCount int `json:"timeSeriesCount"`
|
||||
CorruptionCount int `json:"corruptionCount"`
|
||||
GoroutineCount int `json:"goroutineCount"`
|
||||
GOMAXPROCS int `json:"GOMAXPROCS"`
|
||||
|
@ -335,6 +335,7 @@ type RuleGroup struct {
|
|||
// that rules are returned in by the API.
|
||||
//
|
||||
// Rule types can be determined using a type switch:
|
||||
//
|
||||
// switch v := rule.(type) {
|
||||
// case RecordingRule:
|
||||
// fmt.Print("got a recording rule")
|
||||
|
@ -431,12 +432,29 @@ type queryResult struct {
|
|||
|
||||
// TSDBResult contains the result from querying the tsdb endpoint.
|
||||
type TSDBResult struct {
|
||||
HeadStats TSDBHeadStats `json:"headStats"`
|
||||
SeriesCountByMetricName []Stat `json:"seriesCountByMetricName"`
|
||||
LabelValueCountByLabelName []Stat `json:"labelValueCountByLabelName"`
|
||||
MemoryInBytesByLabelName []Stat `json:"memoryInBytesByLabelName"`
|
||||
SeriesCountByLabelValuePair []Stat `json:"seriesCountByLabelValuePair"`
|
||||
}
|
||||
|
||||
// TSDBHeadStats contains TSDB stats
|
||||
type TSDBHeadStats struct {
|
||||
NumSeries int `json:"numSeries"`
|
||||
NumLabelPairs int `json:"numLabelPairs"`
|
||||
ChunkCount int `json:"chunkCount"`
|
||||
MinTime int `json:"minTime"`
|
||||
MaxTime int `json:"maxTime"`
|
||||
}
|
||||
|
||||
// WalReplayStatus represents the wal replay status.
|
||||
type WalReplayStatus struct {
|
||||
Min int `json:"min"`
|
||||
Max int `json:"max"`
|
||||
Current int `json:"current"`
|
||||
}
|
||||
|
||||
// Stat models information about statistic value.
|
||||
type Stat struct {
|
||||
Name string `json:"name"`
|
||||
|
@ -681,7 +699,7 @@ func (h *httpAPI) Config(ctx context.Context) (ConfigResult, error) {
|
|||
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)
|
||||
q := u.Query()
|
||||
|
||||
|
@ -754,7 +772,7 @@ func (h *httpAPI) Runtimeinfo(ctx context.Context) (RuntimeinfoResult, error) {
|
|||
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)
|
||||
q := u.Query()
|
||||
q.Set("start", formatTime(startTime))
|
||||
|
@ -777,7 +795,7 @@ func (h *httpAPI) LabelNames(ctx context.Context, matches []string, startTime ti
|
|||
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})
|
||||
q := u.Query()
|
||||
q.Set("start", formatTime(startTime))
|
||||
|
@ -800,10 +818,34 @@ func (h *httpAPI) LabelValues(ctx context.Context, label string, matches []strin
|
|||
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)
|
||||
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)
|
||||
if !ts.IsZero() {
|
||||
q.Set("time", formatTime(ts))
|
||||
|
@ -815,10 +857,10 @@ func (h *httpAPI) Query(ctx context.Context, query string, ts time.Time) (model.
|
|||
}
|
||||
|
||||
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)
|
||||
q := u.Query()
|
||||
|
||||
|
@ -827,6 +869,16 @@ func (h *httpAPI) QueryRange(ctx context.Context, query string, r Range) (model.
|
|||
q.Set("end", formatTime(r.End))
|
||||
q.Set("step", strconv.FormatFloat(r.Step.Seconds(), 'f', -1, 64))
|
||||
|
||||
opt := &apiOptions{}
|
||||
for _, o := range opts {
|
||||
o(opt)
|
||||
}
|
||||
|
||||
d := opt.timeout
|
||||
if d > 0 {
|
||||
q.Set("timeout", d.String())
|
||||
}
|
||||
|
||||
_, body, warnings, err := h.client.DoGetFallback(ctx, u, q)
|
||||
if err != nil {
|
||||
return nil, warnings, err
|
||||
|
@ -834,10 +886,10 @@ func (h *httpAPI) QueryRange(ctx context.Context, query string, r Range) (model.
|
|||
|
||||
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)
|
||||
q := u.Query()
|
||||
|
||||
|
@ -920,7 +972,7 @@ func (h *httpAPI) Targets(ctx context.Context) (TargetsResult, error) {
|
|||
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)
|
||||
q := u.Query()
|
||||
|
||||
|
@ -944,7 +996,7 @@ func (h *httpAPI) TargetsMetadata(ctx context.Context, matchTarget string, metri
|
|||
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)
|
||||
q := u.Query()
|
||||
|
||||
|
@ -984,7 +1036,24 @@ func (h *httpAPI) TSDB(ctx context.Context) (TSDBResult, error) {
|
|||
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) WalReplay(ctx context.Context) (WalReplayStatus, error) {
|
||||
u := h.client.URL(epWalReplay, nil)
|
||||
|
||||
req, err := http.NewRequest(http.MethodGet, u.String(), nil)
|
||||
if err != nil {
|
||||
return WalReplayStatus{}, err
|
||||
}
|
||||
|
||||
_, body, _, err := h.client.Do(ctx, req)
|
||||
if err != nil {
|
||||
return WalReplayStatus{}, err
|
||||
}
|
||||
|
||||
var res WalReplayStatus
|
||||
return res, json.Unmarshal(body, &res)
|
||||
}
|
||||
|
||||
func (h *httpAPI) QueryExemplars(ctx context.Context, query string, startTime, endTime time.Time) ([]ExemplarQueryResult, error) {
|
||||
u := h.client.URL(epQueryExemplars, nil)
|
||||
q := u.Query()
|
||||
|
||||
|
@ -1092,34 +1161,37 @@ func (h *apiClientImpl) Do(ctx context.Context, req *http.Request) (*http.Respon
|
|||
}
|
||||
|
||||
return resp, []byte(result.Data), result.Warnings, err
|
||||
|
||||
}
|
||||
|
||||
// DoGetFallback will attempt to do the request as-is, and on a 405 or 501 it
|
||||
// will fallback to a GET request.
|
||||
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 {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
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)
|
||||
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)
|
||||
if err != nil {
|
||||
return nil, nil, warnings, err
|
||||
}
|
||||
|
||||
} 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 {
|
||||
return strconv.FormatFloat(float64(t.Unix())+float64(t.Nanosecond())/1e9, 'f', -1, 64)
|
||||
|
|
|
@ -17,7 +17,7 @@ import (
|
|||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"io"
|
||||
"math"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
|
@ -40,10 +40,8 @@ type apiTest struct {
|
|||
inRes interface{}
|
||||
|
||||
reqPath string
|
||||
reqParam url.Values
|
||||
reqMethod string
|
||||
res interface{}
|
||||
warnings Warnings
|
||||
err error
|
||||
}
|
||||
|
||||
|
@ -55,7 +53,7 @@ type apiTestClient struct {
|
|||
func (c *apiTestClient) URL(ep string, args map[string]string) *url.URL {
|
||||
path := ep
|
||||
for k, v := range args {
|
||||
path = strings.Replace(path, ":"+k, v, -1)
|
||||
path = strings.ReplaceAll(path, ":"+k, v)
|
||||
}
|
||||
u := &url.URL{
|
||||
Host: "test:9090",
|
||||
|
@ -64,8 +62,7 @@ func (c *apiTestClient) URL(ep string, args map[string]string) *url.URL {
|
|||
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
|
||||
|
||||
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) {
|
||||
|
||||
testTime := time.Now()
|
||||
|
||||
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 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 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 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 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 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 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) {
|
||||
v, err := promAPI.TargetsMetadata(context.Background(), matchTarget, metric, limit)
|
||||
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) {
|
||||
v, err := promAPI.Metadata(context.Background(), metric, limit)
|
||||
return v, nil, err
|
||||
|
@ -230,7 +226,14 @@ func TestAPIs(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
doQueryExemplars := func(query string, startTime time.Time, endTime time.Time) func() (interface{}, Warnings, error) {
|
||||
doWalReply := func() func() (interface{}, Warnings, error) {
|
||||
return func() (interface{}, Warnings, error) {
|
||||
v, err := promAPI.WalReplay(context.Background())
|
||||
return v, nil, err
|
||||
}
|
||||
}
|
||||
|
||||
doQueryExemplars := func(query string, startTime, endTime time.Time) func() (interface{}, Warnings, error) {
|
||||
return func() (interface{}, Warnings, error) {
|
||||
v, err := promAPI.QueryExemplars(context.Background(), query, startTime, endTime)
|
||||
return v, nil, err
|
||||
|
@ -239,7 +242,7 @@ func TestAPIs(t *testing.T) {
|
|||
|
||||
queryTests := []apiTest{
|
||||
{
|
||||
do: doQuery("2", testTime),
|
||||
do: doQuery("2", testTime, WithTimeout(5*time.Second)),
|
||||
inRes: &queryResult{
|
||||
Type: model.ValScalar,
|
||||
Result: &model.Scalar{
|
||||
|
@ -250,10 +253,6 @@ func TestAPIs(t *testing.T) {
|
|||
|
||||
reqMethod: "POST",
|
||||
reqPath: "/api/v1/query",
|
||||
reqParam: url.Values{
|
||||
"query": []string{"2"},
|
||||
"time": []string{testTime.Format(time.RFC3339Nano)},
|
||||
},
|
||||
res: &model.Scalar{
|
||||
Value: 2,
|
||||
Timestamp: model.TimeFromUnix(testTime.Unix()),
|
||||
|
@ -265,11 +264,7 @@ func TestAPIs(t *testing.T) {
|
|||
|
||||
reqMethod: "POST",
|
||||
reqPath: "/api/v1/query",
|
||||
reqParam: url.Values{
|
||||
"query": []string{"2"},
|
||||
"time": []string{testTime.Format(time.RFC3339Nano)},
|
||||
},
|
||||
err: fmt.Errorf("some error"),
|
||||
err: errors.New("some error"),
|
||||
},
|
||||
{
|
||||
do: doQuery("2", testTime),
|
||||
|
@ -283,10 +278,6 @@ func TestAPIs(t *testing.T) {
|
|||
|
||||
reqMethod: "POST",
|
||||
reqPath: "/api/v1/query",
|
||||
reqParam: url.Values{
|
||||
"query": []string{"2"},
|
||||
"time": []string{testTime.Format(time.RFC3339Nano)},
|
||||
},
|
||||
err: errors.New("server_error: server error: 500"),
|
||||
},
|
||||
{
|
||||
|
@ -301,10 +292,6 @@ func TestAPIs(t *testing.T) {
|
|||
|
||||
reqMethod: "POST",
|
||||
reqPath: "/api/v1/query",
|
||||
reqParam: url.Values{
|
||||
"query": []string{"2"},
|
||||
"time": []string{testTime.Format(time.RFC3339Nano)},
|
||||
},
|
||||
err: errors.New("client_error: client error: 404"),
|
||||
},
|
||||
// Warning only.
|
||||
|
@ -321,15 +308,10 @@ func TestAPIs(t *testing.T) {
|
|||
|
||||
reqMethod: "POST",
|
||||
reqPath: "/api/v1/query",
|
||||
reqParam: url.Values{
|
||||
"query": []string{"2"},
|
||||
"time": []string{testTime.Format(time.RFC3339Nano)},
|
||||
},
|
||||
res: &model.Scalar{
|
||||
Value: 2,
|
||||
Timestamp: model.TimeFromUnix(testTime.Unix()),
|
||||
},
|
||||
warnings: []string{"warning"},
|
||||
},
|
||||
// Warning + error.
|
||||
{
|
||||
|
@ -345,114 +327,97 @@ func TestAPIs(t *testing.T) {
|
|||
|
||||
reqMethod: "POST",
|
||||
reqPath: "/api/v1/query",
|
||||
reqParam: url.Values{
|
||||
"query": []string{"2"},
|
||||
"time": []string{testTime.Format(time.RFC3339Nano)},
|
||||
},
|
||||
err: errors.New("client_error: client error: 404"),
|
||||
warnings: []string{"warning"},
|
||||
},
|
||||
|
||||
{
|
||||
do: doQueryRange("2", Range{
|
||||
Start: testTime.Add(-time.Minute),
|
||||
End: testTime,
|
||||
Step: time.Minute,
|
||||
}),
|
||||
Step: 1 * time.Minute,
|
||||
}, WithTimeout(5*time.Second)),
|
||||
inErr: fmt.Errorf("some error"),
|
||||
|
||||
reqMethod: "POST",
|
||||
reqPath: "/api/v1/query_range",
|
||||
reqParam: url.Values{
|
||||
"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"),
|
||||
err: errors.New("some error"),
|
||||
},
|
||||
|
||||
{
|
||||
do: doLabelNames(nil),
|
||||
do: doLabelNames(nil, testTime.Add(-100*time.Hour), testTime),
|
||||
inRes: []string{"val1", "val2"},
|
||||
reqMethod: "GET",
|
||||
reqPath: "/api/v1/labels",
|
||||
res: []string{"val1", "val2"},
|
||||
},
|
||||
{
|
||||
do: doLabelNames(nil),
|
||||
do: doLabelNames(nil, testTime.Add(-100*time.Hour), testTime),
|
||||
inRes: []string{"val1", "val2"},
|
||||
inWarnings: []string{"a"},
|
||||
reqMethod: "GET",
|
||||
reqPath: "/api/v1/labels",
|
||||
res: []string{"val1", "val2"},
|
||||
warnings: []string{"a"},
|
||||
},
|
||||
|
||||
{
|
||||
do: doLabelNames(nil),
|
||||
do: doLabelNames(nil, testTime.Add(-100*time.Hour), testTime),
|
||||
inErr: fmt.Errorf("some error"),
|
||||
reqMethod: "GET",
|
||||
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"),
|
||||
inWarnings: []string{"a"},
|
||||
reqMethod: "GET",
|
||||
reqPath: "/api/v1/labels",
|
||||
err: fmt.Errorf("some error"),
|
||||
warnings: []string{"a"},
|
||||
err: errors.New("some error"),
|
||||
},
|
||||
{
|
||||
do: doLabelNames([]string{"up"}),
|
||||
do: doLabelNames([]string{"up"}, testTime.Add(-100*time.Hour), testTime),
|
||||
inRes: []string{"val1", "val2"},
|
||||
reqMethod: "GET",
|
||||
reqPath: "/api/v1/labels",
|
||||
reqParam: url.Values{"match[]": {"up"}},
|
||||
res: []string{"val1", "val2"},
|
||||
},
|
||||
|
||||
{
|
||||
do: doLabelValues(nil, "mylabel"),
|
||||
do: doLabelValues(nil, "mylabel", testTime.Add(-100*time.Hour), testTime),
|
||||
inRes: []string{"val1", "val2"},
|
||||
reqMethod: "GET",
|
||||
reqPath: "/api/v1/label/mylabel/values",
|
||||
res: model.LabelValues{"val1", "val2"},
|
||||
},
|
||||
{
|
||||
do: doLabelValues(nil, "mylabel"),
|
||||
do: doLabelValues(nil, "mylabel", testTime.Add(-100*time.Hour), testTime),
|
||||
inRes: []string{"val1", "val2"},
|
||||
inWarnings: []string{"a"},
|
||||
reqMethod: "GET",
|
||||
reqPath: "/api/v1/label/mylabel/values",
|
||||
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"),
|
||||
reqMethod: "GET",
|
||||
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"),
|
||||
inWarnings: []string{"a"},
|
||||
reqMethod: "GET",
|
||||
reqPath: "/api/v1/label/mylabel/values",
|
||||
err: fmt.Errorf("some error"),
|
||||
warnings: []string{"a"},
|
||||
err: errors.New("some error"),
|
||||
},
|
||||
{
|
||||
do: doLabelValues([]string{"up"}, "mylabel"),
|
||||
do: doLabelValues([]string{"up"}, "mylabel", testTime.Add(-100*time.Hour), testTime),
|
||||
inRes: []string{"val1", "val2"},
|
||||
reqMethod: "GET",
|
||||
reqPath: "/api/v1/label/mylabel/values",
|
||||
reqParam: url.Values{"match[]": {"up"}},
|
||||
res: model.LabelValues{"val1", "val2"},
|
||||
},
|
||||
|
||||
|
@ -462,15 +427,11 @@ func TestAPIs(t *testing.T) {
|
|||
{
|
||||
"__name__": "up",
|
||||
"job": "prometheus",
|
||||
"instance": "localhost:9090"},
|
||||
"instance": "localhost:9090",
|
||||
},
|
||||
},
|
||||
reqMethod: "GET",
|
||||
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{
|
||||
{
|
||||
"__name__": "up",
|
||||
|
@ -486,16 +447,12 @@ func TestAPIs(t *testing.T) {
|
|||
{
|
||||
"__name__": "up",
|
||||
"job": "prometheus",
|
||||
"instance": "localhost:9090"},
|
||||
"instance": "localhost:9090",
|
||||
},
|
||||
},
|
||||
inWarnings: []string{"a"},
|
||||
reqMethod: "GET",
|
||||
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{
|
||||
{
|
||||
"__name__": "up",
|
||||
|
@ -503,7 +460,6 @@ func TestAPIs(t *testing.T) {
|
|||
"instance": "localhost:9090",
|
||||
},
|
||||
},
|
||||
warnings: []string{"a"},
|
||||
},
|
||||
|
||||
{
|
||||
|
@ -511,12 +467,7 @@ func TestAPIs(t *testing.T) {
|
|||
inErr: fmt.Errorf("some error"),
|
||||
reqMethod: "GET",
|
||||
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)},
|
||||
},
|
||||
err: fmt.Errorf("some error"),
|
||||
err: errors.New("some error"),
|
||||
},
|
||||
// Series with error and warning.
|
||||
{
|
||||
|
@ -525,13 +476,7 @@ func TestAPIs(t *testing.T) {
|
|||
inWarnings: []string{"a"},
|
||||
reqMethod: "GET",
|
||||
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)},
|
||||
},
|
||||
err: fmt.Errorf("some error"),
|
||||
warnings: []string{"a"},
|
||||
err: errors.New("some error"),
|
||||
},
|
||||
|
||||
{
|
||||
|
@ -541,9 +486,6 @@ func TestAPIs(t *testing.T) {
|
|||
},
|
||||
reqMethod: "POST",
|
||||
reqPath: "/api/v1/admin/tsdb/snapshot",
|
||||
reqParam: url.Values{
|
||||
"skip_head": []string{"true"},
|
||||
},
|
||||
res: SnapshotResult{
|
||||
Name: "20171210T211224Z-2be650b6d019eb54",
|
||||
},
|
||||
|
@ -554,7 +496,7 @@ func TestAPIs(t *testing.T) {
|
|||
inErr: fmt.Errorf("some error"),
|
||||
reqMethod: "POST",
|
||||
reqPath: "/api/v1/admin/tsdb/snapshot",
|
||||
err: fmt.Errorf("some error"),
|
||||
err: errors.New("some error"),
|
||||
},
|
||||
|
||||
{
|
||||
|
@ -568,7 +510,7 @@ func TestAPIs(t *testing.T) {
|
|||
inErr: fmt.Errorf("some error"),
|
||||
reqMethod: "POST",
|
||||
reqPath: "/api/v1/admin/tsdb/clean_tombstones",
|
||||
err: fmt.Errorf("some error"),
|
||||
err: errors.New("some error"),
|
||||
},
|
||||
|
||||
{
|
||||
|
@ -577,15 +519,11 @@ func TestAPIs(t *testing.T) {
|
|||
{
|
||||
"__name__": "up",
|
||||
"job": "prometheus",
|
||||
"instance": "localhost:9090"},
|
||||
"instance": "localhost:9090",
|
||||
},
|
||||
},
|
||||
reqMethod: "POST",
|
||||
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)},
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
|
@ -593,12 +531,7 @@ func TestAPIs(t *testing.T) {
|
|||
inErr: fmt.Errorf("some error"),
|
||||
reqMethod: "POST",
|
||||
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)},
|
||||
},
|
||||
err: fmt.Errorf("some error"),
|
||||
err: errors.New("some error"),
|
||||
},
|
||||
|
||||
{
|
||||
|
@ -696,8 +629,6 @@ func TestAPIs(t *testing.T) {
|
|||
"CWD": "/prometheus",
|
||||
"reloadConfigSuccess": true,
|
||||
"lastConfigTime": "2020-05-18T15:52:56Z",
|
||||
"chunkCount": 72692,
|
||||
"timeSeriesCount": 18476,
|
||||
"corruptionCount": 0,
|
||||
"goroutineCount": 217,
|
||||
"GOMAXPROCS": 2,
|
||||
|
@ -710,8 +641,6 @@ func TestAPIs(t *testing.T) {
|
|||
CWD: "/prometheus",
|
||||
ReloadConfigSuccess: true,
|
||||
LastConfigTime: time.Date(2020, 5, 18, 15, 52, 56, 0, time.UTC),
|
||||
ChunkCount: 72692,
|
||||
TimeSeriesCount: 18476,
|
||||
CorruptionCount: 0,
|
||||
GoroutineCount: 217,
|
||||
GOMAXPROCS: 2,
|
||||
|
@ -1061,11 +990,6 @@ func TestAPIs(t *testing.T) {
|
|||
},
|
||||
reqMethod: "GET",
|
||||
reqPath: "/api/v1/targets/metadata",
|
||||
reqParam: url.Values{
|
||||
"match_target": []string{"{job=\"prometheus\"}"},
|
||||
"metric": []string{"go_goroutines"},
|
||||
"limit": []string{"1"},
|
||||
},
|
||||
res: []MetricMetadata{
|
||||
{
|
||||
Target: map[string]string{
|
||||
|
@ -1084,12 +1008,7 @@ func TestAPIs(t *testing.T) {
|
|||
inErr: fmt.Errorf("some error"),
|
||||
reqMethod: "GET",
|
||||
reqPath: "/api/v1/targets/metadata",
|
||||
reqParam: url.Values{
|
||||
"match_target": []string{"{job=\"prometheus\"}"},
|
||||
"metric": []string{"go_goroutines"},
|
||||
"limit": []string{"1"},
|
||||
},
|
||||
err: fmt.Errorf("some error"),
|
||||
err: errors.New("some error"),
|
||||
},
|
||||
|
||||
{
|
||||
|
@ -1105,12 +1024,8 @@ func TestAPIs(t *testing.T) {
|
|||
},
|
||||
reqMethod: "GET",
|
||||
reqPath: "/api/v1/metadata",
|
||||
reqParam: url.Values{
|
||||
"metric": []string{"go_goroutines"},
|
||||
"limit": []string{"1"},
|
||||
},
|
||||
res: map[string][]Metadata{
|
||||
"go_goroutines": []Metadata{
|
||||
"go_goroutines": {
|
||||
{
|
||||
Type: "gauge",
|
||||
Help: "Number of goroutines that currently exist.",
|
||||
|
@ -1125,11 +1040,7 @@ func TestAPIs(t *testing.T) {
|
|||
inErr: fmt.Errorf("some error"),
|
||||
reqMethod: "GET",
|
||||
reqPath: "/api/v1/metadata",
|
||||
reqParam: url.Values{
|
||||
"metric": []string{""},
|
||||
"limit": []string{"1"},
|
||||
},
|
||||
err: fmt.Errorf("some error"),
|
||||
err: errors.New("some error"),
|
||||
},
|
||||
|
||||
{
|
||||
|
@ -1145,6 +1056,13 @@ func TestAPIs(t *testing.T) {
|
|||
reqMethod: "GET",
|
||||
reqPath: "/api/v1/status/tsdb",
|
||||
inRes: map[string]interface{}{
|
||||
"headStats": map[string]interface{}{
|
||||
"numSeries": 18476,
|
||||
"numLabelPairs": 4301,
|
||||
"chunkCount": 72692,
|
||||
"minTime": 1634644800304,
|
||||
"maxTime": 1634650590304,
|
||||
},
|
||||
"seriesCountByMetricName": []interface{}{
|
||||
map[string]interface{}{
|
||||
"name": "kubelet_http_requests_duration_seconds_bucket",
|
||||
|
@ -1171,6 +1089,13 @@ func TestAPIs(t *testing.T) {
|
|||
},
|
||||
},
|
||||
res: TSDBResult{
|
||||
HeadStats: TSDBHeadStats{
|
||||
NumSeries: 18476,
|
||||
NumLabelPairs: 4301,
|
||||
ChunkCount: 72692,
|
||||
MinTime: 1634644800304,
|
||||
MaxTime: 1634650590304,
|
||||
},
|
||||
SeriesCountByMetricName: []Stat{
|
||||
{
|
||||
Name: "kubelet_http_requests_duration_seconds_bucket",
|
||||
|
@ -1198,12 +1123,36 @@ func TestAPIs(t *testing.T) {
|
|||
},
|
||||
},
|
||||
|
||||
{
|
||||
do: doWalReply(),
|
||||
reqMethod: "GET",
|
||||
reqPath: "/api/v1/status/walreplay",
|
||||
inErr: fmt.Errorf("some error"),
|
||||
err: fmt.Errorf("some error"),
|
||||
},
|
||||
|
||||
{
|
||||
do: doWalReply(),
|
||||
reqMethod: "GET",
|
||||
reqPath: "/api/v1/status/walreplay",
|
||||
inRes: map[string]interface{}{
|
||||
"min": 2,
|
||||
"max": 5,
|
||||
"current": 40,
|
||||
},
|
||||
res: WalReplayStatus{
|
||||
Min: 2,
|
||||
Max: 5,
|
||||
Current: 40,
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
do: doQueryExemplars("tns_request_duration_seconds_bucket", testTime.Add(-1*time.Minute), testTime),
|
||||
reqMethod: "GET",
|
||||
reqPath: "/api/v1/query_exemplars",
|
||||
inErr: fmt.Errorf("some error"),
|
||||
err: fmt.Errorf("some error"),
|
||||
inErr: errors.New("some error"),
|
||||
err: errors.New("some error"),
|
||||
},
|
||||
|
||||
{
|
||||
|
@ -1279,7 +1228,9 @@ func TestAPIs(t *testing.T) {
|
|||
if err.Error() != test.err.Error() {
|
||||
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 {
|
||||
t.Errorf("%q should be %q", apiErr.Detail, test.inRes)
|
||||
}
|
||||
|
@ -1480,7 +1431,6 @@ func TestAPIClientDo(t *testing.T) {
|
|||
|
||||
for i, test := range tests {
|
||||
t.Run(fmt.Sprintf("%d", i), func(t *testing.T) {
|
||||
|
||||
tc.ch <- test
|
||||
|
||||
_, body, warnings, err := client.Do(context.Background(), tc.req)
|
||||
|
@ -1505,10 +1455,14 @@ func TestAPIClientDo(t *testing.T) {
|
|||
}
|
||||
|
||||
if test.expectedErr.Detail != "" {
|
||||
apiErr := err.(*Error)
|
||||
apiErr := &Error{}
|
||||
if errors.As(err, &apiErr) {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
|
@ -1521,7 +1475,6 @@ func TestAPIClientDo(t *testing.T) {
|
|||
t.Fatalf("expected body :%v, but got:%v", test.expectedBody, string(body))
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1638,7 +1591,7 @@ func (c *httpTestClient) Do(ctx context.Context, req *http.Request) (*http.Respo
|
|||
var body []byte
|
||||
done := make(chan struct{})
|
||||
go func() {
|
||||
body, err = ioutil.ReadAll(resp.Body)
|
||||
body, err = io.ReadAll(resp.Body)
|
||||
close(done)
|
||||
}()
|
||||
|
||||
|
|
|
@ -22,9 +22,10 @@ import (
|
|||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/prometheus/client_golang/api"
|
||||
v1 "github.com/prometheus/client_golang/api/prometheus/v1"
|
||||
"github.com/prometheus/common/config"
|
||||
|
||||
"git.internal/re/client_golang/api"
|
||||
v1 "git.internal/re/client_golang/api/prometheus/v1"
|
||||
)
|
||||
|
||||
func ExampleAPI_query() {
|
||||
|
@ -39,7 +40,7 @@ func ExampleAPI_query() {
|
|||
v1api := v1.NewAPI(client)
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
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 {
|
||||
fmt.Printf("Error querying Prometheus: %v\n", err)
|
||||
os.Exit(1)
|
||||
|
@ -67,7 +68,7 @@ func ExampleAPI_queryRange() {
|
|||
End: time.Now(),
|
||||
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 {
|
||||
fmt.Printf("Error querying Prometheus: %v\n", err)
|
||||
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))
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
// 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
|
||||
// +build go1.17
|
||||
|
||||
// A minimal example of how to include Prometheus instrumentation.
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"regexp"
|
||||
|
||||
"git.internal/re/client_golang/prometheus"
|
||||
"git.internal/re/client_golang/prometheus/collectors"
|
||||
"git.internal/re/client_golang/prometheus/promhttp"
|
||||
)
|
||||
|
||||
var addr = flag.String("listen-address", ":8080", "The address to listen on for HTTP requests.")
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
|
||||
// Create a new registry.
|
||||
reg := prometheus.NewRegistry()
|
||||
|
||||
// Add Go module build info.
|
||||
reg.MustRegister(collectors.NewBuildInfoCollector())
|
||||
reg.MustRegister(collectors.NewGoCollector(
|
||||
collectors.WithGoCollectorRuntimeMetrics(collectors.GoRuntimeMetricsRule{Matcher: regexp.MustCompile("/.*")}),
|
||||
))
|
||||
|
||||
// Expose the registered metrics via HTTP.
|
||||
http.Handle("/metrics", promhttp.HandlerFor(
|
||||
reg,
|
||||
promhttp.HandlerOpts{
|
||||
// Opt into OpenMetrics to support exemplars.
|
||||
EnableOpenMetrics: true,
|
||||
},
|
||||
))
|
||||
fmt.Println("Hello world from new Go Collector!")
|
||||
log.Fatal(http.ListenAndServe(*addr, nil))
|
||||
}
|
|
@ -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,10 +25,53 @@ import (
|
|||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||
"git.internal/re/client_golang/prometheus"
|
||||
"git.internal/re/client_golang/prometheus/collectors"
|
||||
"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() {
|
||||
var (
|
||||
addr = flag.String("listen-address", ":8080", "The address to listen on for HTTP requests.")
|
||||
uniformDomain = flag.Float64("uniform.domain", 0.0002, "The domain for the uniform distribution.")
|
||||
|
@ -37,40 +80,16 @@ var (
|
|||
oscillationPeriod = flag.Duration("oscillation-period", 10*time.Minute, "The duration of the rate oscillation period.")
|
||||
)
|
||||
|
||||
var (
|
||||
// Create a summary to track fictional interservice 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 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),
|
||||
})
|
||||
)
|
||||
|
||||
func init() {
|
||||
// Register the summary and the histogram with Prometheus's default registry.
|
||||
prometheus.MustRegister(rpcDurations)
|
||||
prometheus.MustRegister(rpcDurationsHistogram)
|
||||
// Add Go module build info.
|
||||
prometheus.MustRegister(prometheus.NewBuildInfoCollector())
|
||||
}
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
|
||||
// Create a non-global registry.
|
||||
reg := prometheus.NewRegistry()
|
||||
|
||||
// Create new metrics and register them using the custom registry.
|
||||
m := NewMetrics(reg, *normMean, *normDomain)
|
||||
// Add Go module build info.
|
||||
reg.MustRegister(collectors.NewBuildInfoCollector())
|
||||
|
||||
start := time.Now()
|
||||
|
||||
oscillationFactor := func() float64 {
|
||||
|
@ -81,7 +100,7 @@ func main() {
|
|||
go func() {
|
||||
for {
|
||||
v := rand.Float64() * *uniformDomain
|
||||
rpcDurations.WithLabelValues("uniform").Observe(v)
|
||||
m.rpcDurations.WithLabelValues("uniform").Observe(v)
|
||||
time.Sleep(time.Duration(100*oscillationFactor()) * time.Millisecond)
|
||||
}
|
||||
}()
|
||||
|
@ -89,14 +108,14 @@ func main() {
|
|||
go func() {
|
||||
for {
|
||||
v := (rand.NormFloat64() * *normDomain) + *normMean
|
||||
rpcDurations.WithLabelValues("normal").Observe(v)
|
||||
m.rpcDurations.WithLabelValues("normal").Observe(v)
|
||||
// Demonstrate exemplar support with a dummy ID. This
|
||||
// would be something like a trace ID in a real
|
||||
// application. Note the necessary type assertion. We
|
||||
// already know that rpcDurationsHistogram implements
|
||||
// the ExemplarObserver interface and thus don't need to
|
||||
// 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))},
|
||||
)
|
||||
time.Sleep(time.Duration(75*oscillationFactor()) * time.Millisecond)
|
||||
|
@ -106,17 +125,19 @@ func main() {
|
|||
go func() {
|
||||
for {
|
||||
v := rand.ExpFloat64() / 1e6
|
||||
rpcDurations.WithLabelValues("exponential").Observe(v)
|
||||
m.rpcDurations.WithLabelValues("exponential").Observe(v)
|
||||
time.Sleep(time.Duration(50*oscillationFactor()) * time.Millisecond)
|
||||
}
|
||||
}()
|
||||
|
||||
// Expose the registered metrics via HTTP.
|
||||
http.Handle("/metrics", promhttp.HandlerFor(
|
||||
prometheus.DefaultGatherer,
|
||||
reg,
|
||||
promhttp.HandlerOpts{
|
||||
// Opt into OpenMetrics to support exemplars.
|
||||
EnableOpenMetrics: true,
|
||||
// Pass custom registry
|
||||
Registry: reg,
|
||||
},
|
||||
))
|
||||
log.Fatal(http.ListenAndServe(*addr, nil))
|
||||
|
|
|
@ -19,13 +19,27 @@ import (
|
|||
"log"
|
||||
"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.")
|
||||
|
||||
func main() {
|
||||
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))
|
||||
}
|
||||
|
|
38
go.mod
38
go.mod
|
@ -1,16 +1,32 @@
|
|||
module github.com/prometheus/client_golang
|
||||
module git.internal/re/client_golang
|
||||
|
||||
go 1.17
|
||||
|
||||
require (
|
||||
github.com/beorn7/perks v1.0.1
|
||||
github.com/cespare/xxhash/v2 v2.1.1
|
||||
github.com/golang/protobuf v1.4.3
|
||||
github.com/json-iterator/go v1.1.11
|
||||
github.com/prometheus/client_model v0.2.0
|
||||
github.com/prometheus/common v0.26.0
|
||||
github.com/prometheus/procfs v0.6.0
|
||||
golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40
|
||||
golang.org/x/text v0.3.2 // indirect
|
||||
google.golang.org/protobuf v1.26.0-rc.1 // indirect
|
||||
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/json-iterator/go v1.1.12
|
||||
github.com/prometheus/client_model v0.3.0
|
||||
github.com/prometheus/common v0.37.0
|
||||
github.com/prometheus/procfs v0.8.0
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a
|
||||
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
|
||||
|
|
374
go.sum
374
go.sum
|
@ -1,4 +1,38 @@
|
|||
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=
|
||||
|
@ -8,45 +42,104 @@ github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24
|
|||
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/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY=
|
||||
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 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM=
|
||||
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 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.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/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 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA=
|
||||
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
|
||||
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/json-iterator/go v1.1.11 h1:uVUAXhF2To8cbw/3xN3pxj6kk7TYKs98NIrTqPlMWAQ=
|
||||
github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||
github.com/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=
|
||||
|
@ -61,8 +154,9 @@ github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJ
|
|||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI=
|
||||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f h1:KUppIJq7/+SVif2QVs3tOP0zanoHgBEVAwHxUSIzRqU=
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||
|
@ -74,19 +168,29 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN
|
|||
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
|
||||
github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
|
||||
github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=
|
||||
github.com/prometheus/client_golang v1.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-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/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.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
|
||||
github.com/prometheus/common v0.26.0 h1:iMAkS2TDoNWnKM+Kopnx/8tnEStIfpYA0ur0xQzzhMQ=
|
||||
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 h1:mxy4L2jP6qMonqmq+aTtOx1ifVWUgG/TAmntgbh3xv4=
|
||||
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=
|
||||
|
@ -96,57 +200,295 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf
|
|||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
|
||||
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-20200625001655-4c5254603344 h1:vGXIOMxbNfDTk/aXCmfdLgkrSV+Z2tcbze+pEc3v5W4=
|
||||
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/oauth2 v0.0.0-20190226205417-e64efc72b421 h1:Wo7BWFiOk0QRFMLYMqJGFMd9CgUAcGx7V+qEg/h5IBI=
|
||||
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 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-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 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-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/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-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-20210603081109-ebe580a85c40 h1:JWgyZ1qgdTaF3N3oxC+MdTV7qvEEgHo3otj+HB5CM7Q=
|
||||
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.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
|
||||
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 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-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/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
|
||||
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=
|
||||
google.golang.org/appengine v1.4.0 h1:/wp5JvzpHIxhs/dumFmF7BXTf3Z+dd4uXta4kVyO508=
|
||||
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 h1:lMO5rYAqUxkmaj76jAkRUvt5JZgFymx/+Q5Mzfivuhc=
|
||||
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.26.0-rc.1 h1:7QnIQpGRHE5RnLKnESfDoxm2dTapTZua5a0kS0A+VXQ=
|
||||
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 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
|
||||
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 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
|
||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
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=
|
||||
|
|
|
@ -1 +1 @@
|
|||
See [![go-doc](https://godoc.org/github.com/prometheus/client_golang/prometheus?status.svg)](https://godoc.org/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).
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
// 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 prometheus
|
||||
|
||||
import "runtime/debug"
|
||||
|
||||
// NewBuildInfoCollector is the obsolete version of collectors.NewBuildInfoCollector.
|
||||
// See there for documentation.
|
||||
//
|
||||
// Deprecated: Use collectors.NewBuildInfoCollector instead.
|
||||
func NewBuildInfoCollector() Collector {
|
||||
path, version, sum := "unknown", "unknown", "unknown"
|
||||
if bi, ok := debug.ReadBuildInfo(); ok {
|
||||
path = bi.Main.Path
|
||||
version = bi.Main.Version
|
||||
sum = bi.Main.Sum
|
||||
}
|
||||
c := &selfCollector{MustNewConstMetric(
|
||||
NewDesc(
|
||||
"go_build_info",
|
||||
"Build information about the main Go module.",
|
||||
nil, Labels{"path": path, "version": version, "checksum": sum},
|
||||
),
|
||||
GaugeValue, 1)}
|
||||
c.init(c.self)
|
||||
return c
|
||||
}
|
|
@ -118,3 +118,11 @@ func (c *selfCollector) Describe(ch chan<- *Desc) {
|
|||
func (c *selfCollector) Collect(ch chan<- Metric) {
|
||||
ch <- c.self
|
||||
}
|
||||
|
||||
// collectorMetric is a metric that is also a collector.
|
||||
// Because of selfCollector, most (if not all) Metrics in
|
||||
// this package are also collectors.
|
||||
type collectorMetric interface {
|
||||
Metric
|
||||
Collector
|
||||
}
|
||||
|
|
|
@ -30,7 +30,6 @@ func (c collectorDescribedByCollect) Describe(ch chan<- *Desc) {
|
|||
}
|
||||
|
||||
func TestDescribeByCollect(t *testing.T) {
|
||||
|
||||
goodCollector := collectorDescribedByCollect{
|
||||
cnt: NewCounter(CounterOpts{Name: "c1", Help: "help c1"}),
|
||||
gge: NewGauge(GaugeOpts{Name: "g1", Help: "help g1"}),
|
||||
|
|
|
@ -14,3 +14,27 @@
|
|||
// Package collectors provides implementations of prometheus.Collector to
|
||||
// conveniently collect process and Go-related metrics.
|
||||
package collectors
|
||||
|
||||
import "git.internal/re/client_golang/prometheus"
|
||||
|
||||
// NewBuildInfoCollector returns a collector collecting a single metric
|
||||
// "go_build_info" with the constant value 1 and three labels "path", "version",
|
||||
// and "checksum". Their label values contain the main module path, version, and
|
||||
// checksum, respectively. The labels will only have meaningful values if the
|
||||
// binary is built with Go module support and from source code retrieved from
|
||||
// the source repository (rather than the local file system). This is usually
|
||||
// accomplished by building from outside of GOPATH, specifying the full address
|
||||
// of the main package, e.g. "GO111MODULE=on go run
|
||||
// git.internal/re/client_golang/examples/random". If built without Go
|
||||
// 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
|
||||
// be set appropriately, but "checksum" will be empty and "version" will be
|
||||
// "(devel)".
|
||||
//
|
||||
// This collector uses only the build information for the main module. See
|
||||
// https://github.com/povilasv/prommod for an example of a collector for the
|
||||
// module dependencies.
|
||||
func NewBuildInfoCollector() prometheus.Collector {
|
||||
//nolint:staticcheck // Ignore SA1019 until v2.
|
||||
return prometheus.NewBuildInfoCollector()
|
||||
}
|
||||
|
|
|
@ -16,7 +16,7 @@ package collectors
|
|||
import (
|
||||
"database/sql"
|
||||
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"git.internal/re/client_golang/prometheus"
|
||||
)
|
||||
|
||||
type dbStatsCollector struct {
|
||||
|
@ -101,7 +101,7 @@ func (c *dbStatsCollector) Describe(ch chan<- *prometheus.Desc) {
|
|||
ch <- c.waitDuration
|
||||
ch <- c.maxIdleClosed
|
||||
ch <- c.maxLifetimeClosed
|
||||
c.describeNewInGo115(ch)
|
||||
ch <- c.maxIdleTimeClosed
|
||||
}
|
||||
|
||||
// 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.maxIdleClosed, prometheus.CounterValue, float64(stats.MaxIdleClosed))
|
||||
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 (
|
||||
"database/sql"
|
||||
"runtime"
|
||||
"testing"
|
||||
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"git.internal/re/client_golang/prometheus"
|
||||
)
|
||||
|
||||
func TestDBStatsCollector(t *testing.T) {
|
||||
|
@ -50,9 +49,7 @@ func TestDBStatsCollector(t *testing.T) {
|
|||
"go_sql_wait_duration_seconds_total",
|
||||
"go_sql_max_idle_closed_total",
|
||||
"go_sql_max_lifetime_closed_total",
|
||||
}
|
||||
if runtime.Version() >= "go1.15" {
|
||||
names = append(names, "go_sql_max_idle_time_closed_total")
|
||||
"go_sql_max_idle_time_closed_total",
|
||||
}
|
||||
type result struct {
|
||||
found bool
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
|
||||
package collectors
|
||||
|
||||
import "github.com/prometheus/client_golang/prometheus"
|
||||
import "git.internal/re/client_golang/prometheus"
|
||||
|
||||
// NewExpvarCollector returns a newly allocated expvar Collector.
|
||||
//
|
||||
|
|
|
@ -11,9 +11,12 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//go:build !go1.17
|
||||
// +build !go1.17
|
||||
|
||||
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
|
||||
// process. This includes memory stats. To collect those, runtime.ReadMemStats
|
||||
|
@ -42,28 +45,5 @@ import "github.com/prometheus/client_golang/prometheus"
|
|||
// NOTE: The problem is solved in Go 1.15, see
|
||||
// https://github.com/golang/go/issues/19812 for the related Go issue.
|
||||
func NewGoCollector() prometheus.Collector {
|
||||
//nolint:staticcheck // Ignore SA1019 until v2.
|
||||
return prometheus.NewGoCollector()
|
||||
}
|
||||
|
||||
// NewBuildInfoCollector returns a collector collecting a single metric
|
||||
// "go_build_info" with the constant value 1 and three labels "path", "version",
|
||||
// and "checksum". Their label values contain the main module path, version, and
|
||||
// checksum, respectively. The labels will only have meaningful values if the
|
||||
// binary is built with Go module support and from source code retrieved from
|
||||
// the source repository (rather than the local file system). This is usually
|
||||
// accomplished by building from outside of GOPATH, specifying the full address
|
||||
// of the main package, e.g. "GO111MODULE=on go run
|
||||
// github.com/prometheus/client_golang/examples/random". If built without Go
|
||||
// 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
|
||||
// be set appropriately, but "checksum" will be empty and "version" will be
|
||||
// "(devel)".
|
||||
//
|
||||
// This collector uses only the build information for the main module. See
|
||||
// https://github.com/povilasv/prommod for an example of a collector for the
|
||||
// module dependencies.
|
||||
func NewBuildInfoCollector() prometheus.Collector {
|
||||
//nolint:staticcheck // Ignore SA1019 until v2.
|
||||
return prometheus.NewBuildInfoCollector()
|
||||
}
|
|
@ -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",
|
||||
}
|
||||
}
|
|
@ -0,0 +1,160 @@
|
|||
// Copyright 2021 The Prometheus Authors
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//go:build go1.17
|
||||
// +build go1.17
|
||||
|
||||
package collectors
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
|
||||
"git.internal/re/client_golang/prometheus"
|
||||
"git.internal/re/client_golang/prometheus/internal"
|
||||
)
|
||||
|
||||
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
|
||||
|
||||
const (
|
||||
// GoRuntimeMemStatsCollection represents the metrics represented by runtime.MemStats structure.
|
||||
// Deprecated. Use WithGoCollectorMemStatsMetricsDisabled() function to disable those metrics in the collector.
|
||||
GoRuntimeMemStatsCollection GoCollectionOption = 1 << iota
|
||||
// GoRuntimeMetricsCollection is the new set of metrics represented by runtime/metrics package.
|
||||
// Deprecated. Use WithGoCollectorRuntimeMetrics(GoRuntimeMetricsRule{Matcher: regexp.MustCompile("/.*")})
|
||||
// function to enable those metrics in the collector.
|
||||
GoRuntimeMetricsCollection
|
||||
)
|
||||
|
||||
// WithGoCollections allows enabling different collections for Go collector on top of base metrics.
|
||||
// Deprecated. Use WithGoCollectorRuntimeMetrics() and WithGoCollectorMemStatsMetricsDisabled() instead to control metrics.
|
||||
func WithGoCollections(flags GoCollectionOption) func(options *internal.GoCollectorOptions) {
|
||||
return func(options *internal.GoCollectorOptions) {
|
||||
if flags&GoRuntimeMemStatsCollection == 0 {
|
||||
WithGoCollectorMemStatsMetricsDisabled()(options)
|
||||
}
|
||||
|
||||
if flags&GoRuntimeMetricsCollection != 0 {
|
||||
WithGoCollectorRuntimeMetrics(GoRuntimeMetricsRule{Matcher: regexp.MustCompile("/.*")})(options)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// NewGoCollector returns a collector that exports metrics about the current Go
|
||||
// process using debug.GCStats (base metrics) and runtime/metrics (both in MemStats style and new ones).
|
||||
func NewGoCollector(opts ...func(o *internal.GoCollectorOptions)) prometheus.Collector {
|
||||
//nolint:staticcheck // Ignore SA1019 until v2.
|
||||
return prometheus.NewGoCollector(opts...)
|
||||
}
|
|
@ -0,0 +1,233 @@
|
|||
// Copyright 2021 The Prometheus Authors
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//go:build go1.17
|
||||
// +build go1.17
|
||||
|
||||
package collectors
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"log"
|
||||
"net/http"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"sort"
|
||||
"testing"
|
||||
|
||||
"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) {
|
||||
reg := prometheus.NewRegistry()
|
||||
reg.MustRegister(NewGoCollector(
|
||||
WithGoCollectorRuntimeMetrics(GoRuntimeMetricsRule{
|
||||
Matcher: regexp.MustCompile("/.*"),
|
||||
}),
|
||||
))
|
||||
result, err := reg.Gather()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if _, err := json.Marshal(result); err != nil {
|
||||
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
|
||||
|
||||
import "github.com/prometheus/client_golang/prometheus"
|
||||
import "git.internal/re/client_golang/prometheus"
|
||||
|
||||
// ProcessCollectorOpts defines the behavior of a process metrics collector
|
||||
// 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
|
||||
// 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
|
||||
// than 64 runes in total.
|
||||
// than 128 runes in total.
|
||||
type ExemplarAdder interface {
|
||||
AddWithExemplar(value float64, exemplar Labels)
|
||||
}
|
||||
|
@ -133,15 +133,20 @@ func (c *counter) Inc() {
|
|||
atomic.AddUint64(&c.valInt, 1)
|
||||
}
|
||||
|
||||
func (c *counter) Write(out *dto.Metric) error {
|
||||
func (c *counter) get() float64 {
|
||||
fval := math.Float64frombits(atomic.LoadUint64(&c.valBits))
|
||||
ival := atomic.LoadUint64(&c.valInt)
|
||||
val := fval + float64(ival)
|
||||
return fval + float64(ival)
|
||||
}
|
||||
|
||||
func (c *counter) Write(out *dto.Metric) error {
|
||||
// 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
|
||||
if e := c.exemplar.Load(); e != nil {
|
||||
exemplar = e.(*dto.Exemplar)
|
||||
}
|
||||
val := c.get()
|
||||
|
||||
return populateMetric(CounterValue, val, c.labelPairs, exemplar, out)
|
||||
}
|
||||
|
@ -241,6 +246,7 @@ func (v *CounterVec) GetMetricWith(labels Labels) (Counter, error) {
|
|||
// WithLabelValues works as GetMetricWithLabelValues, but panics where
|
||||
// GetMetricWithLabelValues would have returned an error. Not returning an
|
||||
// error allows shortcuts like
|
||||
//
|
||||
// myVec.WithLabelValues("404", "GET").Add(42)
|
||||
func (v *CounterVec) WithLabelValues(lvs ...string) Counter {
|
||||
c, err := v.GetMetricWithLabelValues(lvs...)
|
||||
|
@ -252,6 +258,7 @@ func (v *CounterVec) WithLabelValues(lvs ...string) Counter {
|
|||
|
||||
// With works as GetMetricWith, but panics where GetMetricWithLabels would have
|
||||
// returned an error. Not returning an error allows shortcuts like
|
||||
//
|
||||
// myVec.With(prometheus.Labels{"code": "404", "method": "GET"}).Add(42)
|
||||
func (v *CounterVec) With(labels Labels) Counter {
|
||||
c, err := v.GetMetricWith(labels)
|
||||
|
|
|
@ -16,12 +16,13 @@ package prometheus
|
|||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
//nolint:staticcheck // Ignore SA1019. Need to keep deprecated package for compatibility.
|
||||
"github.com/golang/protobuf/proto"
|
||||
"github.com/golang/protobuf/ptypes"
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
|
||||
dto "github.com/prometheus/client_model/go"
|
||||
)
|
||||
|
@ -225,13 +226,13 @@ func TestCounterExemplar(t *testing.T) {
|
|||
}).(*counter)
|
||||
counter.now = func() time.Time { return now }
|
||||
|
||||
ts, err := ptypes.TimestampProto(now)
|
||||
if err != nil {
|
||||
ts := timestamppb.New(now)
|
||||
if err := ts.CheckValid(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
expectedExemplar := &dto.Exemplar{
|
||||
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),
|
||||
Timestamp: ts,
|
||||
|
@ -262,10 +263,11 @@ func TestCounterExemplar(t *testing.T) {
|
|||
err = e.(error)
|
||||
}
|
||||
}()
|
||||
// Should panic because of 65 runes.
|
||||
// Should panic because of 129 runes.
|
||||
counter.AddWithExemplar(42, Labels{
|
||||
"abcdefghijklmnopqrstuvwxyz": "26+16 characters",
|
||||
"x1234567": "8+15 characters",
|
||||
"z": strings.Repeat("x", 63),
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -20,6 +20,9 @@ import (
|
|||
"strings"
|
||||
|
||||
"github.com/cespare/xxhash/v2"
|
||||
|
||||
"git.internal/re/client_golang/prometheus/internal"
|
||||
|
||||
//nolint:staticcheck // Ignore SA1019. Need to keep deprecated package for compatibility.
|
||||
"github.com/golang/protobuf/proto"
|
||||
"github.com/prometheus/common/model"
|
||||
|
@ -154,7 +157,7 @@ func NewDesc(fqName, help string, variableLabels []string, constLabels Labels) *
|
|||
Value: proto.String(v),
|
||||
})
|
||||
}
|
||||
sort.Sort(labelPairSorter(d.constLabelPairs))
|
||||
sort.Sort(internal.LabelPairSorter(d.constLabelPairs))
|
||||
return d
|
||||
}
|
||||
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
// All exported functions and methods are safe to be used concurrently unless
|
||||
// specified otherwise.
|
||||
//
|
||||
// A Basic Example
|
||||
// # A Basic Example
|
||||
//
|
||||
// As a starting point, a very basic usage example:
|
||||
//
|
||||
|
@ -31,45 +31,56 @@
|
|||
// "log"
|
||||
// "net/http"
|
||||
//
|
||||
// "github.com/prometheus/client_golang/prometheus"
|
||||
// "github.com/prometheus/client_golang/prometheus/promhttp"
|
||||
// "git.internal/re/client_golang/prometheus"
|
||||
// "git.internal/re/client_golang/prometheus/promhttp"
|
||||
// )
|
||||
//
|
||||
// var (
|
||||
// cpuTemp = prometheus.NewGauge(prometheus.GaugeOpts{
|
||||
// type metrics struct {
|
||||
// cpuTemp prometheus.Gauge
|
||||
// hdFailures *prometheus.CounterVec
|
||||
// }
|
||||
//
|
||||
// func NewMetrics(reg prometheus.Registerer) *metrics {
|
||||
// m := &metrics{
|
||||
// cpuTemp: prometheus.NewGauge(prometheus.GaugeOpts{
|
||||
// Name: "cpu_temperature_celsius",
|
||||
// Help: "Current temperature of the CPU.",
|
||||
// })
|
||||
// hdFailures = prometheus.NewCounterVec(
|
||||
// }),
|
||||
// hdFailures: prometheus.NewCounterVec(
|
||||
// prometheus.CounterOpts{
|
||||
// Name: "hd_errors_total",
|
||||
// Help: "Number of hard-disk errors.",
|
||||
// },
|
||||
// []string{"device"},
|
||||
// )
|
||||
// )
|
||||
//
|
||||
// func init() {
|
||||
// // Metrics have to be registered to be exposed:
|
||||
// prometheus.MustRegister(cpuTemp)
|
||||
// prometheus.MustRegister(hdFailures)
|
||||
// ),
|
||||
// }
|
||||
// reg.MustRegister(m.cpuTemp)
|
||||
// reg.MustRegister(m.hdFailures)
|
||||
// return m
|
||||
// }
|
||||
//
|
||||
// func main() {
|
||||
// cpuTemp.Set(65.3)
|
||||
// hdFailures.With(prometheus.Labels{"device":"/dev/sda"}).Inc()
|
||||
// // Create a non-global registry.
|
||||
// reg := prometheus.NewRegistry()
|
||||
//
|
||||
// // The Handler function provides a default handler to expose metrics
|
||||
// // via an HTTP server. "/metrics" is the usual endpoint for that.
|
||||
// http.Handle("/metrics", promhttp.Handler())
|
||||
// // Create new metrics and register them using the custom registry.
|
||||
// m := NewMetrics(reg)
|
||||
// // Set values for the new created metrics.
|
||||
// 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,
|
||||
// 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
|
||||
// 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
|
||||
// …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
|
||||
// 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
|
||||
// shortcuts.
|
||||
//
|
||||
// Advanced Uses of the Registry
|
||||
// # Advanced Uses of the Registry
|
||||
//
|
||||
// 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.
|
||||
|
@ -176,23 +187,23 @@
|
|||
// NewProcessCollector). With a custom registry, you are in control and decide
|
||||
// yourself about the Collectors to register.
|
||||
//
|
||||
// HTTP Exposition
|
||||
// # HTTP Exposition
|
||||
//
|
||||
// The Registry implements the Gatherer interface. The caller of the Gather
|
||||
// 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
|
||||
// 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.
|
||||
//
|
||||
// Graphite Bridge
|
||||
// # Graphite Bridge
|
||||
//
|
||||
// Functions and examples to push metrics from a Gatherer to Graphite can be
|
||||
// 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
|
||||
// of the existing implementations.
|
||||
|
|
|
@ -17,8 +17,8 @@ import (
|
|||
"log"
|
||||
"net/http"
|
||||
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||
"git.internal/re/client_golang/prometheus"
|
||||
"git.internal/re/client_golang/prometheus/promhttp"
|
||||
)
|
||||
|
||||
// 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"
|
||||
|
||||
"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
|
||||
|
@ -108,7 +108,6 @@ func (v *InfoVec) MustCurryWith(labels prometheus.Labels) *InfoVec {
|
|||
}
|
||||
|
||||
func ExampleMetricVec() {
|
||||
|
||||
infoVec := NewInfoVec(
|
||||
"library_version_info",
|
||||
"Versions of the libraries used in this binary.",
|
||||
|
|
|
@ -16,10 +16,9 @@ package prometheus_test
|
|||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"git.internal/re/client_golang/prometheus"
|
||||
)
|
||||
|
||||
var (
|
||||
// apiRequestDuration tracks the duration separate for each HTTP status
|
||||
// class (1xx, 2xx, ...). This creates a fair amount of time series on
|
||||
// the Prometheus server. Usually, you would track the duration of
|
||||
|
@ -32,7 +31,7 @@ var (
|
|||
// only where you really need separate latency tracking. Partitioning by
|
||||
// status class is only an example. In concrete cases, other partitions
|
||||
// might make more sense.
|
||||
apiRequestDuration = prometheus.NewHistogramVec(
|
||||
var apiRequestDuration = prometheus.NewHistogramVec(
|
||||
prometheus.HistogramOpts{
|
||||
Name: "api_request_duration_seconds",
|
||||
Help: "Histogram for the request duration of the public API, partitioned by status class.",
|
||||
|
@ -40,7 +39,6 @@ var (
|
|||
},
|
||||
[]string{"status_class"},
|
||||
)
|
||||
)
|
||||
|
||||
func handler(w http.ResponseWriter, r *http.Request) {
|
||||
status := http.StatusOK
|
||||
|
|
|
@ -16,20 +16,18 @@ package prometheus_test
|
|||
import (
|
||||
"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
|
||||
// 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
|
||||
// and pushing the result to a Pushgateway, see also the comprehensive
|
||||
// example in the push package.
|
||||
funcDuration = prometheus.NewGauge(prometheus.GaugeOpts{
|
||||
var funcDuration = prometheus.NewGauge(prometheus.GaugeOpts{
|
||||
Name: "example_function_duration_seconds",
|
||||
Help: "Duration of the last call of an example function.",
|
||||
})
|
||||
)
|
||||
|
||||
func run() error {
|
||||
// The Set method of the Gauge is used to observe the duration.
|
||||
|
|
|
@ -17,16 +17,14 @@ import (
|
|||
"math/rand"
|
||||
"time"
|
||||
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"git.internal/re/client_golang/prometheus"
|
||||
)
|
||||
|
||||
var (
|
||||
requestDuration = prometheus.NewHistogram(prometheus.HistogramOpts{
|
||||
var requestDuration = prometheus.NewHistogram(prometheus.HistogramOpts{
|
||||
Name: "example_request_duration_seconds",
|
||||
Help: "Histogram for the runtime of a simple example function.",
|
||||
Buckets: prometheus.LinearBuckets(0.01, 0.01, 10),
|
||||
})
|
||||
)
|
||||
|
||||
func ExampleTimer() {
|
||||
// timer times this example function. It uses a Histogram, but a Summary
|
||||
|
|
|
@ -15,6 +15,7 @@ package prometheus_test
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"net/http"
|
||||
|
@ -24,12 +25,11 @@ import (
|
|||
|
||||
//nolint:staticcheck // Ignore SA1019. Need to keep deprecated package for compatibility.
|
||||
"github.com/golang/protobuf/proto"
|
||||
dto "github.com/prometheus/client_model/go"
|
||||
"github.com/prometheus/common/expfmt"
|
||||
|
||||
dto "github.com/prometheus/client_model/go"
|
||||
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||
"git.internal/re/client_golang/prometheus"
|
||||
"git.internal/re/client_golang/prometheus/promhttp"
|
||||
)
|
||||
|
||||
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() {
|
||||
reqCounter := prometheus.NewCounter(prometheus.CounterOpts{
|
||||
Name: "requests_total",
|
||||
Help: "The total number of requests served.",
|
||||
})
|
||||
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.
|
||||
// Use the old counter from now on.
|
||||
reqCounter = are.ExistingCollector.(prometheus.Counter)
|
||||
|
|
|
@ -21,7 +21,7 @@ import (
|
|||
|
||||
dto "github.com/prometheus/client_model/go"
|
||||
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"git.internal/re/client_golang/prometheus"
|
||||
)
|
||||
|
||||
func ExampleNewExpvarCollector() {
|
||||
|
|
|
@ -210,6 +210,7 @@ func (v *GaugeVec) GetMetricWith(labels Labels) (Gauge, error) {
|
|||
// WithLabelValues works as GetMetricWithLabelValues, but panics where
|
||||
// GetMetricWithLabelValues would have returned an error. Not returning an
|
||||
// error allows shortcuts like
|
||||
//
|
||||
// myVec.WithLabelValues("404", "GET").Add(42)
|
||||
func (v *GaugeVec) WithLabelValues(lvs ...string) Gauge {
|
||||
g, err := v.GetMetricWithLabelValues(lvs...)
|
||||
|
@ -221,6 +222,7 @@ func (v *GaugeVec) WithLabelValues(lvs ...string) Gauge {
|
|||
|
||||
// With works as GetMetricWith, but panics where GetMetricWithLabels would have
|
||||
// returned an error. Not returning an error allows shortcuts like
|
||||
//
|
||||
// myVec.With(prometheus.Labels{"code": "404", "method": "GET"}).Add(42)
|
||||
func (v *GaugeVec) With(labels Labels) Gauge {
|
||||
g, err := v.GetMetricWith(labels)
|
||||
|
|
|
@ -0,0 +1,178 @@
|
|||
// Copyright 2021 The Prometheus Authors
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//go:build ignore
|
||||
// +build ignore
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"go/format"
|
||||
"log"
|
||||
"math"
|
||||
"os"
|
||||
"runtime"
|
||||
"runtime/metrics"
|
||||
"strings"
|
||||
"text/template"
|
||||
|
||||
"git.internal/re/client_golang/prometheus"
|
||||
"git.internal/re/client_golang/prometheus/internal"
|
||||
|
||||
"github.com/hashicorp/go-version"
|
||||
)
|
||||
|
||||
func main() {
|
||||
var givenVersion string
|
||||
toolVersion := runtime.Version()
|
||||
if len(os.Args) != 2 {
|
||||
log.Printf("requires Go version (e.g. go1.17) as an argument. Since it is not specified, assuming %s.", toolVersion)
|
||||
givenVersion = toolVersion
|
||||
} else {
|
||||
givenVersion = os.Args[1]
|
||||
}
|
||||
log.Printf("given version for Go: %s", givenVersion)
|
||||
log.Printf("tool version for Go: %s", toolVersion)
|
||||
|
||||
tv, err := version.NewVersion(strings.TrimPrefix(givenVersion, "go"))
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
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.
|
||||
var buf bytes.Buffer
|
||||
err = testFile.Execute(&buf, struct {
|
||||
Descriptions []metrics.Description
|
||||
GoVersion goVersion
|
||||
Cardinality int
|
||||
}{
|
||||
Descriptions: metrics.All(),
|
||||
GoVersion: v,
|
||||
Cardinality: rmCardinality(),
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatalf("executing template: %v", err)
|
||||
}
|
||||
|
||||
// Format it.
|
||||
result, err := format.Source(buf.Bytes())
|
||||
if err != nil {
|
||||
log.Fatalf("formatting code: %v", err)
|
||||
}
|
||||
|
||||
// Write it to a file.
|
||||
fname := fmt.Sprintf("go_collector_metrics_%s_test.go", v.Abbr())
|
||||
if err := os.WriteFile(fname, result, 0o644); err != nil {
|
||||
log.Fatalf("writing file: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
type goVersion int
|
||||
|
||||
func (g goVersion) String() string {
|
||||
return fmt.Sprintf("go1.%d", g)
|
||||
}
|
||||
|
||||
func (g goVersion) Abbr() string {
|
||||
return fmt.Sprintf("go1%d", g)
|
||||
}
|
||||
|
||||
func rmCardinality() int {
|
||||
cardinality := 0
|
||||
|
||||
// Collect all histogram samples so that we can get their buckets.
|
||||
// The API guarantees that the buckets are always fixed for the lifetime
|
||||
// of the process.
|
||||
var histograms []metrics.Sample
|
||||
for _, d := range metrics.All() {
|
||||
if d.Kind == metrics.KindFloat64Histogram {
|
||||
histograms = append(histograms, metrics.Sample{Name: d.Name})
|
||||
} else {
|
||||
cardinality++
|
||||
}
|
||||
}
|
||||
|
||||
// Handle histograms.
|
||||
metrics.Read(histograms)
|
||||
for i := range histograms {
|
||||
name := histograms[i].Name
|
||||
buckets := internal.RuntimeMetricsBucketsForUnit(
|
||||
histograms[i].Value.Float64Histogram().Buckets,
|
||||
name[strings.IndexRune(name, ':')+1:],
|
||||
)
|
||||
cardinality += len(buckets) + 3 // Plus total count, sum, and the implicit infinity bucket.
|
||||
|
||||
// runtime/metrics bucket boundaries are lower-bound-inclusive, but
|
||||
// always represents each actual *boundary* so Buckets is always
|
||||
// 1 longer than Counts, while in Prometheus the mapping is one-to-one,
|
||||
// as the bottom bucket extends to -Inf, and the top infinity bucket is
|
||||
// implicit. Therefore, we should have one fewer bucket than is listed
|
||||
// above.
|
||||
cardinality--
|
||||
if buckets[len(buckets)-1] == math.Inf(1) {
|
||||
// We already counted the infinity bucket separately.
|
||||
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
|
||||
}
|
||||
|
||||
var testFile = template.Must(template.New("testFile").Funcs(map[string]interface{}{
|
||||
"rm2prom": func(d metrics.Description) string {
|
||||
ns, ss, n, ok := internal.RuntimeMetricsToProm(&d)
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
return prometheus.BuildFQName(ns, ss, n)
|
||||
},
|
||||
"nextVersion": func(version goVersion) string {
|
||||
return (version + goVersion(1)).String()
|
||||
},
|
||||
}).Parse(`// Code generated by gen_go_collector_metrics_set.go; DO NOT EDIT.
|
||||
//go:generate go run gen_go_collector_metrics_set.go {{.GoVersion}}
|
||||
|
||||
//go:build {{.GoVersion}} && !{{nextVersion .GoVersion}}
|
||||
// +build {{.GoVersion}},!{{nextVersion .GoVersion}}
|
||||
|
||||
package prometheus
|
||||
|
||||
var expectedRuntimeMetrics = map[string]string{
|
||||
{{- range .Descriptions -}}
|
||||
{{- $trans := rm2prom . -}}
|
||||
{{- if ne $trans "" }}
|
||||
{{.Name | printf "%q"}}: {{$trans | printf "%q"}},
|
||||
{{- end -}}
|
||||
{{end}}
|
||||
}
|
||||
|
||||
const expectedRuntimeMetricsCardinality = {{.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");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
|
@ -11,16 +11,16 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// +build !go1.15
|
||||
//go:build !js || wasm
|
||||
// +build !js wasm
|
||||
|
||||
package collectors
|
||||
package prometheus
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
import "os"
|
||||
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
)
|
||||
|
||||
func (c *dbStatsCollector) describeNewInGo115(ch chan<- *prometheus.Desc) {}
|
||||
|
||||
func (c *dbStatsCollector) collectNewInGo115(ch chan<- prometheus.Metric, stats sql.DBStats) {}
|
||||
func getPIDFn() func() (int, error) {
|
||||
pid := os.Getpid()
|
||||
return func() (int, error) {
|
||||
return pid, nil
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -16,53 +16,15 @@ package prometheus
|
|||
import (
|
||||
"runtime"
|
||||
"runtime/debug"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
type goCollector struct {
|
||||
goroutinesDesc *Desc
|
||||
threadsDesc *Desc
|
||||
gcDesc *Desc
|
||||
goInfoDesc *Desc
|
||||
|
||||
// ms... are memstats related.
|
||||
msLast *runtime.MemStats // Previously collected memstats.
|
||||
msLastTimestamp time.Time
|
||||
msMtx sync.Mutex // Protects msLast and msLastTimestamp.
|
||||
msMetrics memStatsMetrics
|
||||
msRead func(*runtime.MemStats) // For mocking in tests.
|
||||
msMaxWait time.Duration // Wait time for fresh memstats.
|
||||
msMaxAge time.Duration // Maximum allowed age of old memstats.
|
||||
}
|
||||
|
||||
// NewGoCollector is the obsolete version of collectors.NewGoCollector.
|
||||
// See there for documentation.
|
||||
//
|
||||
// Deprecated: Use collectors.NewGoCollector instead.
|
||||
func NewGoCollector() Collector {
|
||||
return &goCollector{
|
||||
goroutinesDesc: NewDesc(
|
||||
"go_goroutines",
|
||||
"Number of goroutines that currently exist.",
|
||||
nil, nil),
|
||||
threadsDesc: NewDesc(
|
||||
"go_threads",
|
||||
"Number of OS threads created.",
|
||||
nil, nil),
|
||||
gcDesc: NewDesc(
|
||||
"go_gc_duration_seconds",
|
||||
"A summary of the pause duration of garbage collection cycles.",
|
||||
nil, nil),
|
||||
goInfoDesc: NewDesc(
|
||||
"go_info",
|
||||
"Information about the Go environment.",
|
||||
nil, Labels{"version": runtime.Version()}),
|
||||
msLast: &runtime.MemStats{},
|
||||
msRead: runtime.ReadMemStats,
|
||||
msMaxWait: time.Second,
|
||||
msMaxAge: 5 * time.Minute,
|
||||
msMetrics: memStatsMetrics{
|
||||
// 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 {
|
||||
return memStatsMetrics{
|
||||
{
|
||||
desc: NewDesc(
|
||||
memstatNamespace("alloc_bytes"),
|
||||
|
@ -239,61 +201,58 @@ func NewGoCollector() Collector {
|
|||
),
|
||||
eval: func(ms *runtime.MemStats) float64 { return float64(ms.NextGC) },
|
||||
valType: GaugeValue,
|
||||
}, {
|
||||
desc: NewDesc(
|
||||
memstatNamespace("last_gc_time_seconds"),
|
||||
"Number of seconds since 1970 of last garbage collection.",
|
||||
nil, nil,
|
||||
),
|
||||
eval: func(ms *runtime.MemStats) float64 { return float64(ms.LastGC) / 1e9 },
|
||||
valType: GaugeValue,
|
||||
}, {
|
||||
desc: NewDesc(
|
||||
memstatNamespace("gc_cpu_fraction"),
|
||||
"The fraction of this program's available CPU time used by the GC since the program started.",
|
||||
nil, nil,
|
||||
),
|
||||
eval: func(ms *runtime.MemStats) float64 { return ms.GCCPUFraction },
|
||||
valType: GaugeValue,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func memstatNamespace(s string) string {
|
||||
return "go_memstats_" + s
|
||||
type baseGoCollector struct {
|
||||
goroutinesDesc *Desc
|
||||
threadsDesc *Desc
|
||||
gcDesc *Desc
|
||||
gcLastTimeDesc *Desc
|
||||
goInfoDesc *Desc
|
||||
}
|
||||
|
||||
func newBaseGoCollector() baseGoCollector {
|
||||
return baseGoCollector{
|
||||
goroutinesDesc: NewDesc(
|
||||
"go_goroutines",
|
||||
"Number of goroutines that currently exist.",
|
||||
nil, nil),
|
||||
threadsDesc: NewDesc(
|
||||
"go_threads",
|
||||
"Number of OS threads created.",
|
||||
nil, nil),
|
||||
gcDesc: NewDesc(
|
||||
"go_gc_duration_seconds",
|
||||
"A summary of the pause duration of garbage collection cycles.",
|
||||
nil, nil),
|
||||
gcLastTimeDesc: NewDesc(
|
||||
"go_memstats_last_gc_time_seconds",
|
||||
"Number of seconds since 1970 of last garbage collection.",
|
||||
nil, nil),
|
||||
goInfoDesc: NewDesc(
|
||||
"go_info",
|
||||
"Information about the Go environment.",
|
||||
nil, Labels{"version": runtime.Version()}),
|
||||
}
|
||||
}
|
||||
|
||||
// Describe returns all descriptions of the collector.
|
||||
func (c *goCollector) Describe(ch chan<- *Desc) {
|
||||
func (c *baseGoCollector) Describe(ch chan<- *Desc) {
|
||||
ch <- c.goroutinesDesc
|
||||
ch <- c.threadsDesc
|
||||
ch <- c.gcDesc
|
||||
ch <- c.gcLastTimeDesc
|
||||
ch <- c.goInfoDesc
|
||||
for _, i := range c.msMetrics {
|
||||
ch <- i.desc
|
||||
}
|
||||
}
|
||||
|
||||
// Collect returns the current state of all metrics of the collector.
|
||||
func (c *goCollector) Collect(ch chan<- Metric) {
|
||||
var (
|
||||
ms = &runtime.MemStats{}
|
||||
done = make(chan struct{})
|
||||
)
|
||||
// Start reading memstats first as it might take a while.
|
||||
go func() {
|
||||
c.msRead(ms)
|
||||
c.msMtx.Lock()
|
||||
c.msLast = ms
|
||||
c.msLastTimestamp = time.Now()
|
||||
c.msMtx.Unlock()
|
||||
close(done)
|
||||
}()
|
||||
|
||||
func (c *baseGoCollector) Collect(ch chan<- Metric) {
|
||||
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
|
||||
stats.PauseQuantiles = make([]time.Duration, 5)
|
||||
|
@ -305,63 +264,18 @@ func (c *goCollector) Collect(ch chan<- Metric) {
|
|||
}
|
||||
quantiles[0.0] = stats.PauseQuantiles[0].Seconds()
|
||||
ch <- MustNewConstSummary(c.gcDesc, uint64(stats.NumGC), stats.PauseTotal.Seconds(), quantiles)
|
||||
|
||||
ch <- MustNewConstMetric(c.gcLastTimeDesc, GaugeValue, float64(stats.LastGC.UnixNano())/1e9)
|
||||
ch <- MustNewConstMetric(c.goInfoDesc, GaugeValue, 1)
|
||||
|
||||
timer := time.NewTimer(c.msMaxWait)
|
||||
select {
|
||||
case <-done: // Our own ReadMemStats succeeded in time. Use it.
|
||||
timer.Stop() // Important for high collection frequencies to not pile up timers.
|
||||
c.msCollect(ch, ms)
|
||||
return
|
||||
case <-timer.C: // Time out, use last memstats if possible. Continue below.
|
||||
}
|
||||
c.msMtx.Lock()
|
||||
if time.Since(c.msLastTimestamp) < c.msMaxAge {
|
||||
// Last memstats are recent enough. Collect from them under the lock.
|
||||
c.msCollect(ch, c.msLast)
|
||||
c.msMtx.Unlock()
|
||||
return
|
||||
}
|
||||
// If we are here, the last memstats are too old or don't exist. We have
|
||||
// to wait until our own ReadMemStats finally completes. For that to
|
||||
// happen, we have to release the lock.
|
||||
c.msMtx.Unlock()
|
||||
<-done
|
||||
c.msCollect(ch, ms)
|
||||
}
|
||||
|
||||
func (c *goCollector) msCollect(ch chan<- Metric, ms *runtime.MemStats) {
|
||||
for _, i := range c.msMetrics {
|
||||
ch <- MustNewConstMetric(i.desc, i.valType, i.eval(ms))
|
||||
}
|
||||
func memstatNamespace(s string) string {
|
||||
return "go_memstats_" + s
|
||||
}
|
||||
|
||||
// memStatsMetrics provide description, value, and value type for memstat metrics.
|
||||
// memStatsMetrics provide description, evaluator, runtime/metrics name, and
|
||||
// value type for memstat metrics.
|
||||
type memStatsMetrics []struct {
|
||||
desc *Desc
|
||||
eval func(*runtime.MemStats) float64
|
||||
valType ValueType
|
||||
}
|
||||
|
||||
// NewBuildInfoCollector is the obsolete version of collectors.NewBuildInfoCollector.
|
||||
// See there for documentation.
|
||||
//
|
||||
// Deprecated: Use collectors.NewBuildInfoCollector instead.
|
||||
func NewBuildInfoCollector() Collector {
|
||||
path, version, sum := "unknown", "unknown", "unknown"
|
||||
if bi, ok := debug.ReadBuildInfo(); ok {
|
||||
path = bi.Main.Path
|
||||
version = bi.Main.Version
|
||||
sum = bi.Main.Sum
|
||||
}
|
||||
c := &selfCollector{MustNewConstMetric(
|
||||
NewDesc(
|
||||
"go_build_info",
|
||||
"Build information about the main Go module.",
|
||||
nil, Labels{"path": path, "version": version, "checksum": sum},
|
||||
),
|
||||
GaugeValue, 1)}
|
||||
c.init(c.self)
|
||||
return c
|
||||
}
|
||||
|
|
|
@ -0,0 +1,122 @@
|
|||
// Copyright 2021 The Prometheus Authors
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//go:build !go1.17
|
||||
// +build !go1.17
|
||||
|
||||
package prometheus
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
type goCollector struct {
|
||||
base baseGoCollector
|
||||
|
||||
// ms... are memstats related.
|
||||
msLast *runtime.MemStats // Previously collected memstats.
|
||||
msLastTimestamp time.Time
|
||||
msMtx sync.Mutex // Protects msLast and msLastTimestamp.
|
||||
msMetrics memStatsMetrics
|
||||
msRead func(*runtime.MemStats) // For mocking in tests.
|
||||
msMaxWait time.Duration // Wait time for fresh memstats.
|
||||
msMaxAge time.Duration // Maximum allowed age of old memstats.
|
||||
}
|
||||
|
||||
// NewGoCollector is the obsolete version of collectors.NewGoCollector.
|
||||
// See there for documentation.
|
||||
//
|
||||
// Deprecated: Use collectors.NewGoCollector instead.
|
||||
func NewGoCollector() Collector {
|
||||
msMetrics := goRuntimeMemStats()
|
||||
msMetrics = append(msMetrics, struct {
|
||||
desc *Desc
|
||||
eval func(*runtime.MemStats) float64
|
||||
valType ValueType
|
||||
}{
|
||||
// This metric is omitted in Go1.17+, see https://git.internal/re/client_golang/issues/842#issuecomment-861812034
|
||||
desc: NewDesc(
|
||||
memstatNamespace("gc_cpu_fraction"),
|
||||
"The fraction of this program's available CPU time used by the GC since the program started.",
|
||||
nil, nil,
|
||||
),
|
||||
eval: func(ms *runtime.MemStats) float64 { return ms.GCCPUFraction },
|
||||
valType: GaugeValue,
|
||||
})
|
||||
return &goCollector{
|
||||
base: newBaseGoCollector(),
|
||||
msLast: &runtime.MemStats{},
|
||||
msRead: runtime.ReadMemStats,
|
||||
msMaxWait: time.Second,
|
||||
msMaxAge: 5 * time.Minute,
|
||||
msMetrics: msMetrics,
|
||||
}
|
||||
}
|
||||
|
||||
// Describe returns all descriptions of the collector.
|
||||
func (c *goCollector) Describe(ch chan<- *Desc) {
|
||||
c.base.Describe(ch)
|
||||
for _, i := range c.msMetrics {
|
||||
ch <- i.desc
|
||||
}
|
||||
}
|
||||
|
||||
// Collect returns the current state of all metrics of the collector.
|
||||
func (c *goCollector) Collect(ch chan<- Metric) {
|
||||
var (
|
||||
ms = &runtime.MemStats{}
|
||||
done = make(chan struct{})
|
||||
)
|
||||
// Start reading memstats first as it might take a while.
|
||||
go func() {
|
||||
c.msRead(ms)
|
||||
c.msMtx.Lock()
|
||||
c.msLast = ms
|
||||
c.msLastTimestamp = time.Now()
|
||||
c.msMtx.Unlock()
|
||||
close(done)
|
||||
}()
|
||||
|
||||
// Collect base non-memory metrics.
|
||||
c.base.Collect(ch)
|
||||
|
||||
timer := time.NewTimer(c.msMaxWait)
|
||||
select {
|
||||
case <-done: // Our own ReadMemStats succeeded in time. Use it.
|
||||
timer.Stop() // Important for high collection frequencies to not pile up timers.
|
||||
c.msCollect(ch, ms)
|
||||
return
|
||||
case <-timer.C: // Time out, use last memstats if possible. Continue below.
|
||||
}
|
||||
c.msMtx.Lock()
|
||||
if time.Since(c.msLastTimestamp) < c.msMaxAge {
|
||||
// Last memstats are recent enough. Collect from them under the lock.
|
||||
c.msCollect(ch, c.msLast)
|
||||
c.msMtx.Unlock()
|
||||
return
|
||||
}
|
||||
// If we are here, the last memstats are too old or don't exist. We have
|
||||
// to wait until our own ReadMemStats finally completes. For that to
|
||||
// happen, we have to release the lock.
|
||||
c.msMtx.Unlock()
|
||||
<-done
|
||||
c.msCollect(ch, ms)
|
||||
}
|
||||
|
||||
func (c *goCollector) msCollect(ch chan<- Metric, ms *runtime.MemStats) {
|
||||
for _, i := range c.msMetrics {
|
||||
ch <- MustNewConstMetric(i.desc, i.valType, i.eval(ms))
|
||||
}
|
||||
}
|
|
@ -0,0 +1,118 @@
|
|||
// Copyright 2021 The Prometheus Authors
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//go:build !go1.17
|
||||
// +build !go1.17
|
||||
|
||||
package prometheus
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
dto "github.com/prometheus/client_model/go"
|
||||
)
|
||||
|
||||
func TestGoCollectorMemStats(t *testing.T) {
|
||||
var (
|
||||
c = NewGoCollector().(*goCollector)
|
||||
got uint64
|
||||
)
|
||||
|
||||
checkCollect := func(want uint64) {
|
||||
metricCh := make(chan Metric)
|
||||
endCh := make(chan struct{})
|
||||
|
||||
go func() {
|
||||
c.Collect(metricCh)
|
||||
close(endCh)
|
||||
}()
|
||||
Collect:
|
||||
for {
|
||||
select {
|
||||
case metric := <-metricCh:
|
||||
if metric.Desc().fqName != "go_memstats_alloc_bytes" {
|
||||
continue Collect
|
||||
}
|
||||
pb := &dto.Metric{}
|
||||
metric.Write(pb)
|
||||
got = uint64(pb.GetGauge().GetValue())
|
||||
case <-endCh:
|
||||
break Collect
|
||||
}
|
||||
}
|
||||
if want != got {
|
||||
t.Errorf("unexpected value of go_memstats_alloc_bytes, want %d, got %d", want, got)
|
||||
}
|
||||
}
|
||||
|
||||
// Speed up the timing to make the test faster.
|
||||
c.msMaxWait = 5 * time.Millisecond
|
||||
c.msMaxAge = 50 * time.Millisecond
|
||||
|
||||
// Scenario 1: msRead responds slowly, no previous memstats available,
|
||||
// msRead is executed anyway.
|
||||
c.msRead = func(ms *runtime.MemStats) {
|
||||
time.Sleep(20 * time.Millisecond)
|
||||
ms.Alloc = 1
|
||||
}
|
||||
checkCollect(1)
|
||||
// Now msLast is set.
|
||||
c.msMtx.Lock()
|
||||
if want, got := uint64(1), c.msLast.Alloc; want != got {
|
||||
t.Errorf("unexpected of msLast.Alloc, want %d, got %d", want, got)
|
||||
}
|
||||
c.msMtx.Unlock()
|
||||
|
||||
// Scenario 2: msRead responds fast, previous memstats available, new
|
||||
// value collected.
|
||||
c.msRead = func(ms *runtime.MemStats) {
|
||||
ms.Alloc = 2
|
||||
}
|
||||
checkCollect(2)
|
||||
// msLast is set, too.
|
||||
c.msMtx.Lock()
|
||||
if want, got := uint64(2), c.msLast.Alloc; want != got {
|
||||
t.Errorf("unexpected of msLast.Alloc, want %d, got %d", want, got)
|
||||
}
|
||||
c.msMtx.Unlock()
|
||||
|
||||
// Scenario 3: msRead responds slowly, previous memstats available, old
|
||||
// value collected.
|
||||
c.msRead = func(ms *runtime.MemStats) {
|
||||
time.Sleep(20 * time.Millisecond)
|
||||
ms.Alloc = 3
|
||||
}
|
||||
checkCollect(2)
|
||||
// After waiting, new value is still set in msLast.
|
||||
time.Sleep(80 * time.Millisecond)
|
||||
c.msMtx.Lock()
|
||||
if want, got := uint64(3), c.msLast.Alloc; want != got {
|
||||
t.Errorf("unexpected of msLast.Alloc, want %d, got %d", want, got)
|
||||
}
|
||||
c.msMtx.Unlock()
|
||||
|
||||
// Scenario 4: msRead responds slowly, previous memstats is too old, new
|
||||
// value collected.
|
||||
c.msRead = func(ms *runtime.MemStats) {
|
||||
time.Sleep(20 * time.Millisecond)
|
||||
ms.Alloc = 4
|
||||
}
|
||||
checkCollect(4)
|
||||
c.msMtx.Lock()
|
||||
if want, got := uint64(4), c.msLast.Alloc; want != got {
|
||||
t.Errorf("unexpected of msLast.Alloc, want %d, got %d", want, got)
|
||||
}
|
||||
c.msMtx.Unlock()
|
||||
}
|
|
@ -0,0 +1,568 @@
|
|||
// Copyright 2021 The Prometheus Authors
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//go:build go1.17
|
||||
// +build go1.17
|
||||
|
||||
package prometheus
|
||||
|
||||
import (
|
||||
"math"
|
||||
"runtime"
|
||||
"runtime/metrics"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
//nolint:staticcheck // Ignore SA1019. Need to keep deprecated package for compatibility.
|
||||
"github.com/golang/protobuf/proto"
|
||||
dto "github.com/prometheus/client_model/go"
|
||||
|
||||
"git.internal/re/client_golang/prometheus/internal"
|
||||
)
|
||||
|
||||
const (
|
||||
// constants for strings referenced more than once.
|
||||
goGCHeapTinyAllocsObjects = "/gc/heap/tiny/allocs:objects"
|
||||
goGCHeapAllocsObjects = "/gc/heap/allocs:objects"
|
||||
goGCHeapFreesObjects = "/gc/heap/frees:objects"
|
||||
goGCHeapFreesBytes = "/gc/heap/frees:bytes"
|
||||
goGCHeapAllocsBytes = "/gc/heap/allocs:bytes"
|
||||
goGCHeapObjects = "/gc/heap/objects:objects"
|
||||
goGCHeapGoalBytes = "/gc/heap/goal:bytes"
|
||||
goMemoryClassesTotalBytes = "/memory/classes/total:bytes"
|
||||
goMemoryClassesHeapObjectsBytes = "/memory/classes/heap/objects:bytes"
|
||||
goMemoryClassesHeapUnusedBytes = "/memory/classes/heap/unused:bytes"
|
||||
goMemoryClassesHeapReleasedBytes = "/memory/classes/heap/released:bytes"
|
||||
goMemoryClassesHeapFreeBytes = "/memory/classes/heap/free:bytes"
|
||||
goMemoryClassesHeapStacksBytes = "/memory/classes/heap/stacks:bytes"
|
||||
goMemoryClassesOSStacksBytes = "/memory/classes/os-stacks:bytes"
|
||||
goMemoryClassesMetadataMSpanInuseBytes = "/memory/classes/metadata/mspan/inuse:bytes"
|
||||
goMemoryClassesMetadataMSPanFreeBytes = "/memory/classes/metadata/mspan/free:bytes"
|
||||
goMemoryClassesMetadataMCacheInuseBytes = "/memory/classes/metadata/mcache/inuse:bytes"
|
||||
goMemoryClassesMetadataMCacheFreeBytes = "/memory/classes/metadata/mcache/free:bytes"
|
||||
goMemoryClassesProfilingBucketsBytes = "/memory/classes/profiling/buckets:bytes"
|
||||
goMemoryClassesMetadataOtherBytes = "/memory/classes/metadata/other:bytes"
|
||||
goMemoryClassesOtherBytes = "/memory/classes/other:bytes"
|
||||
)
|
||||
|
||||
// rmNamesForMemStatsMetrics represents runtime/metrics names required to populate goRuntimeMemStats from like logic.
|
||||
var rmNamesForMemStatsMetrics = []string{
|
||||
goGCHeapTinyAllocsObjects,
|
||||
goGCHeapAllocsObjects,
|
||||
goGCHeapFreesObjects,
|
||||
goGCHeapAllocsBytes,
|
||||
goGCHeapObjects,
|
||||
goGCHeapGoalBytes,
|
||||
goMemoryClassesTotalBytes,
|
||||
goMemoryClassesHeapObjectsBytes,
|
||||
goMemoryClassesHeapUnusedBytes,
|
||||
goMemoryClassesHeapReleasedBytes,
|
||||
goMemoryClassesHeapFreeBytes,
|
||||
goMemoryClassesHeapStacksBytes,
|
||||
goMemoryClassesOSStacksBytes,
|
||||
goMemoryClassesMetadataMSpanInuseBytes,
|
||||
goMemoryClassesMetadataMSPanFreeBytes,
|
||||
goMemoryClassesMetadataMCacheInuseBytes,
|
||||
goMemoryClassesMetadataMCacheFreeBytes,
|
||||
goMemoryClassesProfilingBucketsBytes,
|
||||
goMemoryClassesMetadataOtherBytes,
|
||||
goMemoryClassesOtherBytes,
|
||||
}
|
||||
|
||||
func bestEffortLookupRM(lookup []string) []metrics.Description {
|
||||
ret := make([]metrics.Description, 0, len(lookup))
|
||||
for _, rm := range metrics.All() {
|
||||
for _, m := range lookup {
|
||||
if m == rm.Name {
|
||||
ret = append(ret, rm)
|
||||
}
|
||||
}
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
type goCollector struct {
|
||||
base baseGoCollector
|
||||
|
||||
// mu protects updates to all fields ensuring a consistent
|
||||
// snapshot is always produced by Collect.
|
||||
mu sync.Mutex
|
||||
|
||||
// Contains all samples that has to retrieved from runtime/metrics (not all of them will be exposed).
|
||||
sampleBuf []metrics.Sample
|
||||
// sampleMap allows lookup for MemStats metrics and runtime/metrics histograms for exact sums.
|
||||
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.
|
||||
// From that point on, metric names produced by the runtime/metrics
|
||||
// package could be generated from runtime/metrics names. However,
|
||||
// these differ from the old names for the same values.
|
||||
//
|
||||
// This field exists to export the same values under the old names
|
||||
// as well.
|
||||
msMetrics memStatsMetrics
|
||||
msMetricsEnabled bool
|
||||
}
|
||||
|
||||
type rmMetricDesc struct {
|
||||
metrics.Description
|
||||
}
|
||||
|
||||
func matchRuntimeMetricsRules(rules []internal.GoCollectorRule) []rmMetricDesc {
|
||||
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
|
||||
}
|
||||
|
||||
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.
|
||||
// See there for documentation.
|
||||
//
|
||||
// Deprecated: Use collectors.NewGoCollector instead.
|
||||
func NewGoCollector(opts ...func(o *internal.GoCollectorOptions)) Collector {
|
||||
opt := defaultGoCollectorOptions()
|
||||
for _, o := range opts {
|
||||
o(&opt)
|
||||
}
|
||||
|
||||
exposedDescriptions := matchRuntimeMetricsRules(opt.RuntimeMetricRules)
|
||||
|
||||
// Collect all histogram samples so that we can get their buckets.
|
||||
// The API guarantees that the buckets are always fixed for the lifetime
|
||||
// of the process.
|
||||
var histograms []metrics.Sample
|
||||
for _, d := range exposedDescriptions {
|
||||
if d.Kind == metrics.KindFloat64Histogram {
|
||||
histograms = append(histograms, metrics.Sample{Name: d.Name})
|
||||
}
|
||||
}
|
||||
|
||||
if len(histograms) > 0 {
|
||||
metrics.Read(histograms)
|
||||
}
|
||||
|
||||
bucketsMap := make(map[string][]float64)
|
||||
for i := range histograms {
|
||||
bucketsMap[histograms[i].Name] = histograms[i].Value.Float64Histogram().Buckets
|
||||
}
|
||||
|
||||
// Generate a collector for each exposed runtime/metrics metric.
|
||||
metricSet := make([]collectorMetric, 0, len(exposedDescriptions))
|
||||
// SampleBuf is used for reading from runtime/metrics.
|
||||
// We are assuming the largest case to have stable pointers for sampleMap purposes.
|
||||
sampleBuf := make([]metrics.Sample, 0, len(exposedDescriptions)+len(opt.RuntimeMetricSumForHist)+len(rmNamesForMemStatsMetrics))
|
||||
sampleMap := make(map[string]*metrics.Sample, len(exposedDescriptions))
|
||||
for _, d := range exposedDescriptions {
|
||||
namespace, subsystem, name, ok := internal.RuntimeMetricsToProm(&d.Description)
|
||||
if !ok {
|
||||
// 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
|
||||
// to fail here. This condition is tested in TestExpectedRuntimeMetrics.
|
||||
continue
|
||||
}
|
||||
|
||||
sampleBuf = append(sampleBuf, metrics.Sample{Name: d.Name})
|
||||
sampleMap[d.Name] = &sampleBuf[len(sampleBuf)-1]
|
||||
|
||||
var m collectorMetric
|
||||
if d.Kind == metrics.KindFloat64Histogram {
|
||||
_, hasSum := opt.RuntimeMetricSumForHist[d.Name]
|
||||
unit := d.Name[strings.IndexRune(d.Name, ':')+1:]
|
||||
m = newBatchHistogram(
|
||||
NewDesc(
|
||||
BuildFQName(namespace, subsystem, name),
|
||||
d.Description.Description,
|
||||
nil,
|
||||
nil,
|
||||
),
|
||||
internal.RuntimeMetricsBucketsForUnit(bucketsMap[d.Name], unit),
|
||||
hasSum,
|
||||
)
|
||||
} else if d.Cumulative {
|
||||
m = NewCounter(CounterOpts{
|
||||
Namespace: namespace,
|
||||
Subsystem: subsystem,
|
||||
Name: name,
|
||||
Help: d.Description.Description,
|
||||
},
|
||||
)
|
||||
} else {
|
||||
m = NewGauge(GaugeOpts{
|
||||
Namespace: namespace,
|
||||
Subsystem: subsystem,
|
||||
Name: name,
|
||||
Help: d.Description.Description,
|
||||
})
|
||||
}
|
||||
metricSet = append(metricSet, m)
|
||||
}
|
||||
|
||||
// Add exact sum metrics to sampleBuf if not added before.
|
||||
for _, h := range histograms {
|
||||
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{
|
||||
base: newBaseGoCollector(),
|
||||
sampleBuf: sampleBuf,
|
||||
sampleMap: sampleMap,
|
||||
rmExposedMetrics: metricSet,
|
||||
rmExactSumMapForHist: opt.RuntimeMetricSumForHist,
|
||||
msMetrics: msMetrics,
|
||||
msMetricsEnabled: !opt.DisableMemStatsLikeMetrics,
|
||||
}
|
||||
}
|
||||
|
||||
// Describe returns all descriptions of the collector.
|
||||
func (c *goCollector) Describe(ch chan<- *Desc) {
|
||||
c.base.Describe(ch)
|
||||
for _, i := range c.msMetrics {
|
||||
ch <- i.desc
|
||||
}
|
||||
for _, m := range c.rmExposedMetrics {
|
||||
ch <- m.Desc()
|
||||
}
|
||||
}
|
||||
|
||||
// Collect returns the current state of all metrics of the collector.
|
||||
func (c *goCollector) Collect(ch chan<- Metric) {
|
||||
// Collect base non-memory metrics.
|
||||
c.base.Collect(ch)
|
||||
|
||||
if len(c.sampleBuf) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
// Collect must be thread-safe, so prevent concurrent use of
|
||||
// sampleBuf elements. Just read into sampleBuf but write all the data
|
||||
// we get into our Metrics or MemStats.
|
||||
//
|
||||
// This lock also ensures that the Metrics we send out are all from
|
||||
// the same updates, ensuring their mutual consistency insofar as
|
||||
// is guaranteed by the runtime/metrics package.
|
||||
//
|
||||
// N.B. This locking is heavy-handed, but Collect is expected to be called
|
||||
// relatively infrequently. Also the core operation here, metrics.Read,
|
||||
// is fast (O(tens of microseconds)) so contention should certainly be
|
||||
// low, though channel operations and any allocations may add to that.
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
|
||||
// Populate runtime/metrics sample buffer.
|
||||
metrics.Read(c.sampleBuf)
|
||||
|
||||
// Collect all our runtime/metrics user chose to expose from sampleBuf (if any).
|
||||
for i, metric := range c.rmExposedMetrics {
|
||||
// We created samples for exposed metrics first in order, so indexes match.
|
||||
sample := c.sampleBuf[i]
|
||||
|
||||
// N.B. switch on concrete type because it's significantly more efficient
|
||||
// than checking for the Counter and Gauge interface implementations. In
|
||||
// this case, we control all the types here.
|
||||
switch m := metric.(type) {
|
||||
case *counter:
|
||||
// Guard against decreases. This should never happen, but a failure
|
||||
// to do so will result in a panic, which is a harsh consequence for
|
||||
// a metrics collection bug.
|
||||
v0, v1 := m.get(), unwrapScalarRMValue(sample.Value)
|
||||
if v1 > v0 {
|
||||
m.Add(unwrapScalarRMValue(sample.Value) - m.get())
|
||||
}
|
||||
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")
|
||||
}
|
||||
}
|
||||
|
||||
if c.msMetricsEnabled {
|
||||
// ms is a dummy MemStats that we populate ourselves so that we can
|
||||
// populate the old metrics from it if goMemStatsCollection is enabled.
|
||||
var ms runtime.MemStats
|
||||
memStatsFromRM(&ms, c.sampleMap)
|
||||
for _, i := range c.msMetrics {
|
||||
ch <- MustNewConstMetric(i.desc, i.valType, i.eval(&ms))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// unwrapScalarRMValue unwraps a runtime/metrics value that is assumed
|
||||
// to be scalar and returns the equivalent float64 value. Panics if the
|
||||
// value is not scalar.
|
||||
func unwrapScalarRMValue(v metrics.Value) float64 {
|
||||
switch v.Kind() {
|
||||
case metrics.KindUint64:
|
||||
return float64(v.Uint64())
|
||||
case metrics.KindFloat64:
|
||||
return v.Float64()
|
||||
case metrics.KindBad:
|
||||
// Unsupported metric.
|
||||
//
|
||||
// This should never happen because we always populate our metric
|
||||
// set from the runtime/metrics package.
|
||||
panic("unexpected unsupported metric")
|
||||
default:
|
||||
// Unsupported metric kind.
|
||||
//
|
||||
// This should never happen because we check for this during initialization
|
||||
// and flag and filter metrics whose kinds we don't understand.
|
||||
panic("unexpected unsupported metric kind")
|
||||
}
|
||||
}
|
||||
|
||||
// exactSumFor takes a runtime/metrics metric name (that is assumed to
|
||||
// be of kind KindFloat64Histogram) and returns its exact sum and whether
|
||||
// its exact sum exists.
|
||||
//
|
||||
// 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.
|
||||
func (c *goCollector) exactSumFor(rmName string) float64 {
|
||||
sumName, ok := c.rmExactSumMapForHist[rmName]
|
||||
if !ok {
|
||||
return 0
|
||||
}
|
||||
s, ok := c.sampleMap[sumName]
|
||||
if !ok {
|
||||
return 0
|
||||
}
|
||||
return unwrapScalarRMValue(s.Value)
|
||||
}
|
||||
|
||||
func memStatsFromRM(ms *runtime.MemStats, rm map[string]*metrics.Sample) {
|
||||
lookupOrZero := func(name string) uint64 {
|
||||
if s, ok := rm[name]; ok {
|
||||
return s.Value.Uint64()
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// Currently, MemStats adds tiny alloc count to both Mallocs AND Frees.
|
||||
// The reason for this is because MemStats couldn't be extended at the time
|
||||
// but there was a desire to have Mallocs at least be a little more representative,
|
||||
// while having Mallocs - Frees still represent a live object count.
|
||||
// Unfortunately, MemStats doesn't actually export a large allocation count,
|
||||
// so it's impossible to pull this number out directly.
|
||||
tinyAllocs := lookupOrZero(goGCHeapTinyAllocsObjects)
|
||||
ms.Mallocs = lookupOrZero(goGCHeapAllocsObjects) + tinyAllocs
|
||||
ms.Frees = lookupOrZero(goGCHeapFreesObjects) + tinyAllocs
|
||||
|
||||
ms.TotalAlloc = lookupOrZero(goGCHeapAllocsBytes)
|
||||
ms.Sys = lookupOrZero(goMemoryClassesTotalBytes)
|
||||
ms.Lookups = 0 // Already always zero.
|
||||
ms.HeapAlloc = lookupOrZero(goMemoryClassesHeapObjectsBytes)
|
||||
ms.Alloc = ms.HeapAlloc
|
||||
ms.HeapInuse = ms.HeapAlloc + lookupOrZero(goMemoryClassesHeapUnusedBytes)
|
||||
ms.HeapReleased = lookupOrZero(goMemoryClassesHeapReleasedBytes)
|
||||
ms.HeapIdle = ms.HeapReleased + lookupOrZero(goMemoryClassesHeapFreeBytes)
|
||||
ms.HeapSys = ms.HeapInuse + ms.HeapIdle
|
||||
ms.HeapObjects = lookupOrZero(goGCHeapObjects)
|
||||
ms.StackInuse = lookupOrZero(goMemoryClassesHeapStacksBytes)
|
||||
ms.StackSys = ms.StackInuse + lookupOrZero(goMemoryClassesOSStacksBytes)
|
||||
ms.MSpanInuse = lookupOrZero(goMemoryClassesMetadataMSpanInuseBytes)
|
||||
ms.MSpanSys = ms.MSpanInuse + lookupOrZero(goMemoryClassesMetadataMSPanFreeBytes)
|
||||
ms.MCacheInuse = lookupOrZero(goMemoryClassesMetadataMCacheInuseBytes)
|
||||
ms.MCacheSys = ms.MCacheInuse + lookupOrZero(goMemoryClassesMetadataMCacheFreeBytes)
|
||||
ms.BuckHashSys = lookupOrZero(goMemoryClassesProfilingBucketsBytes)
|
||||
ms.GCSys = lookupOrZero(goMemoryClassesMetadataOtherBytes)
|
||||
ms.OtherSys = lookupOrZero(goMemoryClassesOtherBytes)
|
||||
ms.NextGC = lookupOrZero(goGCHeapGoalBytes)
|
||||
|
||||
// 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
|
||||
// of the process.
|
||||
// See https://git.internal/re/client_golang/issues/842#issuecomment-861812034
|
||||
// for more details.
|
||||
ms.GCCPUFraction = 0
|
||||
}
|
||||
|
||||
// batchHistogram is a mutable histogram that is updated
|
||||
// in batches.
|
||||
type batchHistogram struct {
|
||||
selfCollector
|
||||
|
||||
// Static fields updated only once.
|
||||
desc *Desc
|
||||
hasSum bool
|
||||
|
||||
// Because this histogram operates in batches, it just uses a
|
||||
// single mutex for everything. updates are always serialized
|
||||
// but Write calls may operate concurrently with updates.
|
||||
// Contention between these two sources should be rare.
|
||||
mu sync.Mutex
|
||||
buckets []float64 // Inclusive lower bounds, like runtime/metrics.
|
||||
counts []uint64
|
||||
sum float64 // Used if hasSum is true.
|
||||
}
|
||||
|
||||
// newBatchHistogram creates a new batch histogram value with the given
|
||||
// Desc, buckets, and whether or not it has an exact sum available.
|
||||
//
|
||||
// buckets must always be from the runtime/metrics package, following
|
||||
// the same conventions.
|
||||
func newBatchHistogram(desc *Desc, buckets []float64, hasSum bool) *batchHistogram {
|
||||
// We need to remove -Inf values. runtime/metrics keeps them around.
|
||||
// But -Inf bucket should not be allowed for prometheus histograms.
|
||||
if buckets[0] == math.Inf(-1) {
|
||||
buckets = buckets[1:]
|
||||
}
|
||||
h := &batchHistogram{
|
||||
desc: desc,
|
||||
buckets: buckets,
|
||||
// Because buckets follows runtime/metrics conventions, there's
|
||||
// 1 more value in the buckets list than there are buckets represented,
|
||||
// because in runtime/metrics, the bucket values represent *boundaries*,
|
||||
// and non-Inf boundaries are inclusive lower bounds for that bucket.
|
||||
counts: make([]uint64, len(buckets)-1),
|
||||
hasSum: hasSum,
|
||||
}
|
||||
h.init(h)
|
||||
return h
|
||||
}
|
||||
|
||||
// update updates the batchHistogram from a runtime/metrics histogram.
|
||||
//
|
||||
// sum must be provided if the batchHistogram was created to have an exact sum.
|
||||
// h.buckets must be a strict subset of his.Buckets.
|
||||
func (h *batchHistogram) update(his *metrics.Float64Histogram, sum float64) {
|
||||
counts, buckets := his.Counts, his.Buckets
|
||||
|
||||
h.mu.Lock()
|
||||
defer h.mu.Unlock()
|
||||
|
||||
// Clear buckets.
|
||||
for i := range h.counts {
|
||||
h.counts[i] = 0
|
||||
}
|
||||
// Copy and reduce buckets.
|
||||
var j int
|
||||
for i, count := range counts {
|
||||
h.counts[j] += count
|
||||
if buckets[i+1] == h.buckets[j+1] {
|
||||
j++
|
||||
}
|
||||
}
|
||||
if h.hasSum {
|
||||
h.sum = sum
|
||||
}
|
||||
}
|
||||
|
||||
func (h *batchHistogram) Desc() *Desc {
|
||||
return h.desc
|
||||
}
|
||||
|
||||
func (h *batchHistogram) Write(out *dto.Metric) error {
|
||||
h.mu.Lock()
|
||||
defer h.mu.Unlock()
|
||||
|
||||
sum := float64(0)
|
||||
if h.hasSum {
|
||||
sum = h.sum
|
||||
}
|
||||
dtoBuckets := make([]*dto.Bucket, 0, len(h.counts))
|
||||
totalCount := uint64(0)
|
||||
for i, count := range h.counts {
|
||||
totalCount += count
|
||||
if !h.hasSum {
|
||||
if count != 0 {
|
||||
// N.B. This computed sum is an underestimate.
|
||||
sum += h.buckets[i] * float64(count)
|
||||
}
|
||||
}
|
||||
|
||||
// Skip the +Inf bucket, but only for the bucket list.
|
||||
// It must still count for sum and totalCount.
|
||||
if math.IsInf(h.buckets[i+1], 1) {
|
||||
break
|
||||
}
|
||||
// Float64Histogram's upper bound is exclusive, so make it inclusive
|
||||
// by obtaining the next float64 value down, in order.
|
||||
upperBound := math.Nextafter(h.buckets[i+1], h.buckets[i])
|
||||
dtoBuckets = append(dtoBuckets, &dto.Bucket{
|
||||
CumulativeCount: proto.Uint64(totalCount),
|
||||
UpperBound: proto.Float64(upperBound),
|
||||
})
|
||||
}
|
||||
out.Histogram = &dto.Histogram{
|
||||
Bucket: dtoBuckets,
|
||||
SampleCount: proto.Uint64(totalCount),
|
||||
SampleSum: proto.Float64(sum),
|
||||
}
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,406 @@
|
|||
// Copyright 2021 The Prometheus Authors
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//go:build go1.17
|
||||
// +build go1.17
|
||||
|
||||
package prometheus
|
||||
|
||||
import (
|
||||
"math"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"runtime/metrics"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
dto "github.com/prometheus/client_model/go"
|
||||
|
||||
"git.internal/re/client_golang/prometheus/internal"
|
||||
)
|
||||
|
||||
func TestRmForMemStats(t *testing.T) {
|
||||
descs := bestEffortLookupRM(rmNamesForMemStatsMetrics)
|
||||
|
||||
if got, want := len(descs), len(rmNamesForMemStatsMetrics); 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{} {
|
||||
metrics := map[string]struct{}{}
|
||||
b := newBaseGoCollector()
|
||||
for _, m := range []string{
|
||||
b.gcDesc.fqName,
|
||||
b.goInfoDesc.fqName,
|
||||
b.goroutinesDesc.fqName,
|
||||
b.gcLastTimeDesc.fqName,
|
||||
b.threadsDesc.fqName,
|
||||
} {
|
||||
metrics[m] = struct{}{}
|
||||
}
|
||||
return metrics
|
||||
}
|
||||
|
||||
func addExpectedRuntimeMemStats(metrics map[string]struct{}) map[string]struct{} {
|
||||
for _, m := range goRuntimeMemStats() {
|
||||
metrics[m.desc.fqName] = struct{}{}
|
||||
}
|
||||
return metrics
|
||||
}
|
||||
|
||||
func addExpectedRuntimeMetrics(metrics map[string]struct{}) map[string]struct{} {
|
||||
for _, m := range expectedRuntimeMetrics {
|
||||
metrics[m] = struct{}{}
|
||||
}
|
||||
return metrics
|
||||
}
|
||||
|
||||
func TestGoCollector_ExposedMetrics(t *testing.T) {
|
||||
for _, tcase := range []struct {
|
||||
opts internal.GoCollectorOptions
|
||||
expectedFQNameSet map[string]struct{}
|
||||
}{
|
||||
{
|
||||
opts: internal.GoCollectorOptions{
|
||||
DisableMemStatsLikeMetrics: true,
|
||||
},
|
||||
expectedFQNameSet: expectedBaseMetrics(),
|
||||
},
|
||||
{
|
||||
// Default, only MemStats.
|
||||
expectedFQNameSet: addExpectedRuntimeMemStats(expectedBaseMetrics()),
|
||||
},
|
||||
{
|
||||
// Get all runtime/metrics without MemStats.
|
||||
opts: internal.GoCollectorOptions{
|
||||
DisableMemStatsLikeMetrics: true,
|
||||
RuntimeMetricRules: []internal.GoCollectorRule{
|
||||
{Matcher: regexp.MustCompile("/.*")},
|
||||
},
|
||||
},
|
||||
expectedFQNameSet: addExpectedRuntimeMetrics(expectedBaseMetrics()),
|
||||
},
|
||||
{
|
||||
// Get all runtime/metrics and MemStats.
|
||||
opts: internal.GoCollectorOptions{
|
||||
RuntimeMetricRules: []internal.GoCollectorRule{
|
||||
{Matcher: regexp.MustCompile("/.*")},
|
||||
},
|
||||
},
|
||||
expectedFQNameSet: addExpectedRuntimeMemStats(addExpectedRuntimeMetrics(expectedBaseMetrics())),
|
||||
},
|
||||
} {
|
||||
if ok := t.Run("", func(t *testing.T) {
|
||||
goMetrics := collectGoMetrics(t, tcase.opts)
|
||||
goMetricSet := make(map[string]Metric)
|
||||
for _, m := range goMetrics {
|
||||
goMetricSet[m.Desc().fqName] = m
|
||||
}
|
||||
|
||||
for i := range goMetrics {
|
||||
name := goMetrics[i].Desc().fqName
|
||||
|
||||
if _, ok := tcase.expectedFQNameSet[name]; !ok {
|
||||
t.Errorf("found unpexpected metric %s", name)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// Now iterate over the expected metrics and look for removals.
|
||||
for expectedName := range tcase.expectedFQNameSet {
|
||||
if _, ok := goMetricSet[expectedName]; !ok {
|
||||
t.Errorf("missing expected metric %s in collection", expectedName)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}); !ok {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var sink interface{}
|
||||
|
||||
func TestBatchHistogram(t *testing.T) {
|
||||
goMetrics := collectGoMetrics(t, internal.GoCollectorOptions{
|
||||
RuntimeMetricRules: []internal.GoCollectorRule{
|
||||
{Matcher: regexp.MustCompile("/.*")},
|
||||
},
|
||||
})
|
||||
|
||||
var mhist Metric
|
||||
for _, m := range goMetrics {
|
||||
if m.Desc().fqName == "go_gc_heap_allocs_by_size_bytes" {
|
||||
mhist = m
|
||||
break
|
||||
}
|
||||
}
|
||||
if mhist == nil {
|
||||
t.Fatal("failed to find metric to test")
|
||||
}
|
||||
hist, ok := mhist.(*batchHistogram)
|
||||
if !ok {
|
||||
t.Fatal("found metric is not a runtime/metrics histogram")
|
||||
}
|
||||
|
||||
// Make a bunch of allocations then do another collection.
|
||||
//
|
||||
// The runtime/metrics API tries to reuse memory where possible,
|
||||
// so make sure that we didn't hang on to any of that memory in
|
||||
// hist.
|
||||
countsCopy := make([]uint64, len(hist.counts))
|
||||
copy(countsCopy, hist.counts)
|
||||
for i := 0; i < 100; i++ {
|
||||
sink = make([]byte, 128)
|
||||
}
|
||||
|
||||
collectGoMetrics(t, defaultGoCollectorOptions())
|
||||
for i, v := range hist.counts {
|
||||
if v != countsCopy[i] {
|
||||
t.Error("counts changed during new collection")
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Get the runtime/metrics copy.
|
||||
s := []metrics.Sample{
|
||||
{Name: "/gc/heap/allocs-by-size:bytes"},
|
||||
}
|
||||
metrics.Read(s)
|
||||
rmHist := s[0].Value.Float64Histogram()
|
||||
wantBuckets := internal.RuntimeMetricsBucketsForUnit(rmHist.Buckets, "bytes")
|
||||
// runtime/metrics histograms always have a +Inf bucket and are lower
|
||||
// bound inclusive. In contrast, we have an implicit +Inf bucket and
|
||||
// are upper bound inclusive, so we can chop off the first bucket
|
||||
// (since the conversion to upper bound inclusive will shift all buckets
|
||||
// down one index) and the +Inf for the last bucket.
|
||||
wantBuckets = wantBuckets[1 : len(wantBuckets)-1]
|
||||
|
||||
// Check to make sure the output proto makes sense.
|
||||
pb := &dto.Metric{}
|
||||
hist.Write(pb)
|
||||
|
||||
if math.IsInf(pb.Histogram.Bucket[len(pb.Histogram.Bucket)-1].GetUpperBound(), +1) {
|
||||
t.Errorf("found +Inf bucket")
|
||||
}
|
||||
if got := len(pb.Histogram.Bucket); got != len(wantBuckets) {
|
||||
t.Errorf("got %d buckets in protobuf, want %d", got, len(wantBuckets))
|
||||
}
|
||||
for i, bucket := range pb.Histogram.Bucket {
|
||||
// runtime/metrics histograms are lower-bound inclusive, but we're
|
||||
// upper-bound inclusive. So just make sure the new inclusive upper
|
||||
// bound is somewhere close by (in some cases it's equal).
|
||||
wantBound := wantBuckets[i]
|
||||
if gotBound := *bucket.UpperBound; (wantBound-gotBound)/wantBound > 0.001 {
|
||||
t.Errorf("got bound %f, want within 0.1%% of %f", gotBound, wantBound)
|
||||
}
|
||||
// Make sure counts are cumulative. Because of the consistency guarantees
|
||||
// made by the runtime/metrics package, we're really not guaranteed to get
|
||||
// anything even remotely the same here.
|
||||
if i > 0 && *bucket.CumulativeCount < *pb.Histogram.Bucket[i-1].CumulativeCount {
|
||||
t.Error("cumulative counts are non-monotonic")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func collectGoMetrics(t *testing.T, opts internal.GoCollectorOptions) []Metric {
|
||||
t.Helper()
|
||||
|
||||
c := NewGoCollector(func(o *internal.GoCollectorOptions) {
|
||||
o.DisableMemStatsLikeMetrics = opts.DisableMemStatsLikeMetrics
|
||||
o.RuntimeMetricSumForHist = opts.RuntimeMetricSumForHist
|
||||
o.RuntimeMetricRules = opts.RuntimeMetricRules
|
||||
}).(*goCollector)
|
||||
|
||||
// Collect all metrics.
|
||||
ch := make(chan Metric)
|
||||
var wg sync.WaitGroup
|
||||
var metrics []Metric
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for metric := range ch {
|
||||
metrics = append(metrics, metric)
|
||||
}
|
||||
}()
|
||||
c.Collect(ch)
|
||||
close(ch)
|
||||
|
||||
wg.Wait()
|
||||
|
||||
return metrics
|
||||
}
|
||||
|
||||
func TestMemStatsEquivalence(t *testing.T) {
|
||||
var msReal, msFake runtime.MemStats
|
||||
descs := bestEffortLookupRM(rmNamesForMemStatsMetrics)
|
||||
|
||||
samples := make([]metrics.Sample, len(descs))
|
||||
samplesMap := make(map[string]*metrics.Sample)
|
||||
for i := range descs {
|
||||
samples[i].Name = descs[i].Name
|
||||
samplesMap[descs[i].Name] = &samples[i]
|
||||
}
|
||||
|
||||
// Force a GC cycle to try to reach a clean slate.
|
||||
runtime.GC()
|
||||
|
||||
// Populate msReal.
|
||||
runtime.ReadMemStats(&msReal)
|
||||
// Populate msFake and hope that no GC happened in between (:
|
||||
metrics.Read(samples)
|
||||
|
||||
memStatsFromRM(&msFake, samplesMap)
|
||||
|
||||
// Iterate over them and make sure they're somewhat close.
|
||||
msRealValue := reflect.ValueOf(msReal)
|
||||
msFakeValue := reflect.ValueOf(msFake)
|
||||
|
||||
typ := msRealValue.Type()
|
||||
for i := 0; i < msRealValue.NumField(); i++ {
|
||||
fr := msRealValue.Field(i)
|
||||
ff := msFakeValue.Field(i)
|
||||
|
||||
if typ.Field(i).Name == "PauseTotalNs" || typ.Field(i).Name == "LastGC" {
|
||||
// We don't use those fields for metrics,
|
||||
// thus we are not interested in having this filled.
|
||||
continue
|
||||
}
|
||||
switch fr.Kind() {
|
||||
// Fields which we are interested in are all uint64s.
|
||||
// The only float64 field GCCPUFraction is by design omitted.
|
||||
case reflect.Uint64:
|
||||
vr := fr.Interface().(uint64)
|
||||
vf := ff.Interface().(uint64)
|
||||
if float64(vr-vf)/float64(vf) > 0.05 {
|
||||
t.Errorf("wrong value for %s: got %d, want %d", typ.Field(i).Name, vf, vr)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestExpectedRuntimeMetrics(t *testing.T) {
|
||||
goMetrics := collectGoMetrics(t, internal.GoCollectorOptions{
|
||||
DisableMemStatsLikeMetrics: true,
|
||||
RuntimeMetricRules: []internal.GoCollectorRule{
|
||||
{Matcher: regexp.MustCompile("/.*")},
|
||||
},
|
||||
})
|
||||
goMetricSet := make(map[string]Metric)
|
||||
for _, m := range goMetrics {
|
||||
goMetricSet[m.Desc().fqName] = m
|
||||
}
|
||||
|
||||
descs := metrics.All()
|
||||
rmSet := make(map[string]struct{})
|
||||
// Iterate over runtime-reported descriptions to find new metrics.
|
||||
for i := range descs {
|
||||
rmName := descs[i].Name
|
||||
rmSet[rmName] = struct{}{}
|
||||
|
||||
// expectedRuntimeMetrics depends on Go version.
|
||||
expFQName, ok := expectedRuntimeMetrics[rmName]
|
||||
if !ok {
|
||||
t.Errorf("found new runtime/metrics metric %s", rmName)
|
||||
_, _, _, ok := internal.RuntimeMetricsToProm(&descs[i])
|
||||
if !ok {
|
||||
t.Errorf("new metric has name that can't be converted, or has an unsupported Kind")
|
||||
}
|
||||
continue
|
||||
}
|
||||
_, ok = goMetricSet[expFQName]
|
||||
if !ok {
|
||||
t.Errorf("existing runtime/metrics metric %s (expected fq name %s) not collected", rmName, expFQName)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// Now iterate over the expected metrics and look for removals.
|
||||
cardinality := 0
|
||||
for rmName, fqName := range expectedRuntimeMetrics {
|
||||
if _, ok := rmSet[rmName]; !ok {
|
||||
t.Errorf("runtime/metrics metric %s removed", rmName)
|
||||
continue
|
||||
}
|
||||
if _, ok := goMetricSet[fqName]; !ok {
|
||||
t.Errorf("runtime/metrics metric %s not appearing under expected name %s", rmName, fqName)
|
||||
continue
|
||||
}
|
||||
|
||||
// While we're at it, check to make sure expected cardinality lines
|
||||
// up, but at the point of the protobuf write to get as close to the
|
||||
// real deal as possible.
|
||||
//
|
||||
// Note that we filter out non-runtime/metrics metrics here, because
|
||||
// those are manually managed.
|
||||
var m dto.Metric
|
||||
if err := goMetricSet[fqName].Write(&m); err != nil {
|
||||
t.Errorf("writing metric %s: %v", fqName, err)
|
||||
continue
|
||||
}
|
||||
// N.B. These are the only fields populated by runtime/metrics metrics specifically.
|
||||
// Other fields are populated by e.g. GCStats metrics.
|
||||
switch {
|
||||
case m.Counter != nil:
|
||||
fallthrough
|
||||
case m.Gauge != nil:
|
||||
cardinality++
|
||||
case m.Histogram != nil:
|
||||
cardinality += len(m.Histogram.Bucket) + 3 // + sum, count, and +inf
|
||||
default:
|
||||
t.Errorf("unexpected protobuf structure for metric %s", fqName)
|
||||
}
|
||||
}
|
||||
|
||||
if t.Failed() {
|
||||
t.Log("a new Go version may have been detected, please run")
|
||||
t.Log("\tgo run gen_go_collector_metrics_set.go go1.X")
|
||||
t.Log("where X is the Go version you are currently using")
|
||||
}
|
||||
|
||||
expectCardinality := expectedRuntimeMetricsCardinality
|
||||
if cardinality != expectCardinality {
|
||||
t.Errorf("unexpected cardinality for runtime/metrics metrics: got %d, want %d", cardinality, expectCardinality)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGoCollectorConcurrency(t *testing.T) {
|
||||
c := NewGoCollector().(*goCollector)
|
||||
|
||||
// Set up multiple goroutines to Collect from the
|
||||
// same GoCollector. In race mode with GOMAXPROCS > 1,
|
||||
// this test should fail often if Collect is not
|
||||
// concurrent-safe.
|
||||
for i := 0; i < 4; i++ {
|
||||
go func() {
|
||||
ch := make(chan Metric)
|
||||
go func() {
|
||||
// Drain all metrics received until the
|
||||
// channel is closed.
|
||||
for range ch {
|
||||
}
|
||||
}()
|
||||
c.Collect(ch)
|
||||
close(ch)
|
||||
}()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
// Code generated by gen_go_collector_metrics_set.go; DO NOT EDIT.
|
||||
//go:generate go run gen_go_collector_metrics_set.go go1.17
|
||||
|
||||
//go:build go1.17 && !go1.18
|
||||
// +build go1.17,!go1.18
|
||||
|
||||
package prometheus
|
||||
|
||||
var expectedRuntimeMetrics = map[string]string{
|
||||
"/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/pauses:seconds": "go_gc_pauses_seconds",
|
||||
"/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/goroutines:goroutines": "go_sched_goroutines_goroutines",
|
||||
"/sched/latencies:seconds": "go_sched_latencies_seconds",
|
||||
}
|
||||
|
||||
const expectedRuntimeMetricsCardinality = 77
|
|
@ -0,0 +1,41 @@
|
|||
// Code generated by gen_go_collector_metrics_set.go; DO NOT EDIT.
|
||||
//go:generate go run gen_go_collector_metrics_set.go go1.18
|
||||
|
||||
//go:build go1.18 && !go1.19
|
||||
// +build go1.18,!go1.19
|
||||
|
||||
package prometheus
|
||||
|
||||
var expectedRuntimeMetrics = map[string]string{
|
||||
"/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/pauses:seconds": "go_gc_pauses_seconds",
|
||||
"/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/goroutines:goroutines": "go_sched_goroutines_goroutines",
|
||||
"/sched/latencies:seconds": "go_sched_latencies_seconds",
|
||||
}
|
||||
|
||||
const expectedRuntimeMetricsCardinality = 77
|
|
@ -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
|
|
@ -155,95 +155,19 @@ func TestGoCollectorGC(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestGoCollectorMemStats(t *testing.T) {
|
||||
var (
|
||||
c = NewGoCollector().(*goCollector)
|
||||
got uint64
|
||||
)
|
||||
|
||||
checkCollect := func(want uint64) {
|
||||
metricCh := make(chan Metric)
|
||||
endCh := make(chan struct{})
|
||||
func BenchmarkGoCollector(b *testing.B) {
|
||||
c := NewGoCollector().(*goCollector)
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
ch := make(chan Metric, 8)
|
||||
go func() {
|
||||
c.Collect(metricCh)
|
||||
close(endCh)
|
||||
// Drain all metrics received until the
|
||||
// channel is closed.
|
||||
for range ch {
|
||||
}
|
||||
}()
|
||||
Collect:
|
||||
for {
|
||||
select {
|
||||
case metric := <-metricCh:
|
||||
if metric.Desc().fqName != "go_memstats_alloc_bytes" {
|
||||
continue Collect
|
||||
}
|
||||
pb := &dto.Metric{}
|
||||
metric.Write(pb)
|
||||
got = uint64(pb.GetGauge().GetValue())
|
||||
case <-endCh:
|
||||
break Collect
|
||||
c.Collect(ch)
|
||||
close(ch)
|
||||
}
|
||||
}
|
||||
if want != got {
|
||||
t.Errorf("unexpected value of go_memstats_alloc_bytes, want %d, got %d", want, got)
|
||||
}
|
||||
}
|
||||
|
||||
// Speed up the timing to make the test faster.
|
||||
c.msMaxWait = 5 * time.Millisecond
|
||||
c.msMaxAge = 50 * time.Millisecond
|
||||
|
||||
// Scenario 1: msRead responds slowly, no previous memstats available,
|
||||
// msRead is executed anyway.
|
||||
c.msRead = func(ms *runtime.MemStats) {
|
||||
time.Sleep(20 * time.Millisecond)
|
||||
ms.Alloc = 1
|
||||
}
|
||||
checkCollect(1)
|
||||
// Now msLast is set.
|
||||
c.msMtx.Lock()
|
||||
if want, got := uint64(1), c.msLast.Alloc; want != got {
|
||||
t.Errorf("unexpected of msLast.Alloc, want %d, got %d", want, got)
|
||||
}
|
||||
c.msMtx.Unlock()
|
||||
|
||||
// Scenario 2: msRead responds fast, previous memstats available, new
|
||||
// value collected.
|
||||
c.msRead = func(ms *runtime.MemStats) {
|
||||
ms.Alloc = 2
|
||||
}
|
||||
checkCollect(2)
|
||||
// msLast is set, too.
|
||||
c.msMtx.Lock()
|
||||
if want, got := uint64(2), c.msLast.Alloc; want != got {
|
||||
t.Errorf("unexpected of msLast.Alloc, want %d, got %d", want, got)
|
||||
}
|
||||
c.msMtx.Unlock()
|
||||
|
||||
// Scenario 3: msRead responds slowly, previous memstats available, old
|
||||
// value collected.
|
||||
c.msRead = func(ms *runtime.MemStats) {
|
||||
time.Sleep(20 * time.Millisecond)
|
||||
ms.Alloc = 3
|
||||
}
|
||||
checkCollect(2)
|
||||
// After waiting, new value is still set in msLast.
|
||||
time.Sleep(80 * time.Millisecond)
|
||||
c.msMtx.Lock()
|
||||
if want, got := uint64(3), c.msLast.Alloc; want != got {
|
||||
t.Errorf("unexpected of msLast.Alloc, want %d, got %d", want, got)
|
||||
}
|
||||
c.msMtx.Unlock()
|
||||
|
||||
// Scenario 4: msRead responds slowly, previous memstats is too old, new
|
||||
// value collected.
|
||||
c.msRead = func(ms *runtime.MemStats) {
|
||||
time.Sleep(20 * time.Millisecond)
|
||||
ms.Alloc = 4
|
||||
}
|
||||
checkCollect(4)
|
||||
c.msMtx.Lock()
|
||||
if want, got := uint64(4), c.msLast.Alloc; want != got {
|
||||
t.Errorf("unexpected of msLast.Alloc, want %d, got %d", want, got)
|
||||
}
|
||||
c.msMtx.Unlock()
|
||||
}
|
||||
|
|
|
@ -30,7 +30,7 @@ import (
|
|||
|
||||
dto "github.com/prometheus/client_model/go"
|
||||
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"git.internal/re/client_golang/prometheus"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -197,6 +197,7 @@ func writeMetrics(w io.Writer, mfs []*dto.MetricFamily, useTags bool, prefix str
|
|||
|
||||
buf := bufio.NewWriter(w)
|
||||
for _, s := range vec {
|
||||
if prefix != "" {
|
||||
for _, c := range prefix {
|
||||
if _, err := buf.WriteRune(c); err != nil {
|
||||
return err
|
||||
|
@ -205,6 +206,7 @@ func writeMetrics(w io.Writer, mfs []*dto.MetricFamily, useTags bool, prefix str
|
|||
if err := buf.WriteByte('.'); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err := writeMetric(buf, s.Metric, useTags); err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -238,9 +240,8 @@ func writeMetric(buf *bufio.Writer, m model.Metric, useTags bool) error {
|
|||
}
|
||||
if useTags {
|
||||
return writeTags(buf, m)
|
||||
} else {
|
||||
return writeLabels(buf, m, numLabels)
|
||||
}
|
||||
return writeLabels(buf, m, numLabels)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -31,7 +31,7 @@ import (
|
|||
|
||||
"github.com/prometheus/common/model"
|
||||
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"git.internal/re/client_golang/prometheus"
|
||||
)
|
||||
|
||||
func TestSanitize(t *testing.T) {
|
||||
|
@ -101,6 +101,7 @@ func testWriteSummary(t *testing.T, useTags bool) {
|
|||
{prefix: "prefix"},
|
||||
{prefix: "pre/fix"},
|
||||
{prefix: "pre.fix"},
|
||||
{prefix: ""},
|
||||
}
|
||||
|
||||
var (
|
||||
|
@ -141,10 +142,15 @@ func testWriteSummary(t *testing.T, useTags bool) {
|
|||
t.Fatalf("error: %v", err)
|
||||
}
|
||||
|
||||
wantWithPrefix := fmt.Sprintf(want,
|
||||
var wantWithPrefix string
|
||||
if 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()
|
||||
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -20,13 +20,16 @@ import (
|
|||
"runtime"
|
||||
"sort"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
"testing/quick"
|
||||
"time"
|
||||
|
||||
//nolint:staticcheck // Ignore SA1019. Need to keep deprecated package for compatibility.
|
||||
"github.com/golang/protobuf/proto"
|
||||
"github.com/golang/protobuf/ptypes"
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
|
||||
"git.internal/re/client_golang/prometheus/internal"
|
||||
|
||||
dto "github.com/prometheus/client_model/go"
|
||||
)
|
||||
|
@ -167,7 +170,7 @@ func TestHistogramConcurrency(t *testing.T) {
|
|||
start.Add(1)
|
||||
end.Add(concLevel)
|
||||
|
||||
sum := NewHistogram(HistogramOpts{
|
||||
his := NewHistogram(HistogramOpts{
|
||||
Name: "test_histogram",
|
||||
Help: "helpless",
|
||||
Buckets: testBuckets,
|
||||
|
@ -188,9 +191,9 @@ func TestHistogramConcurrency(t *testing.T) {
|
|||
start.Wait()
|
||||
for _, v := range vals {
|
||||
if n%2 == 0 {
|
||||
sum.Observe(v)
|
||||
his.Observe(v)
|
||||
} else {
|
||||
sum.(ExemplarObserver).ObserveWithExemplar(v, Labels{"foo": "bar"})
|
||||
his.(ExemplarObserver).ObserveWithExemplar(v, Labels{"foo": "bar"})
|
||||
}
|
||||
}
|
||||
end.Done()
|
||||
|
@ -201,7 +204,7 @@ func TestHistogramConcurrency(t *testing.T) {
|
|||
end.Wait()
|
||||
|
||||
m := &dto.Metric{}
|
||||
sum.Write(m)
|
||||
his.Write(m)
|
||||
if got, want := int(*m.Histogram.SampleCount), total; got != want {
|
||||
t.Errorf("got sample count %d, want %d", got, want)
|
||||
}
|
||||
|
@ -352,6 +355,16 @@ func TestBuckets(t *testing.T) {
|
|||
if !reflect.DeepEqual(got, want) {
|
||||
t.Errorf("exponential buckets: got %v, want %v", got, want)
|
||||
}
|
||||
|
||||
got = ExponentialBucketsRange(1, 100, 10)
|
||||
want = []float64{
|
||||
1.0, 1.6681, 2.7825, 4.6415, 7.7426, 12.9154, 21.5443,
|
||||
35.9381, 59.9484, 100.0000,
|
||||
}
|
||||
const epsilon = 0.0001
|
||||
if !internal.AlmostEqualFloat64s(got, want, epsilon) {
|
||||
t.Errorf("exponential buckets range: got %v, want %v (epsilon %f)", got, want, epsilon)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHistogramAtomicObserve(t *testing.T) {
|
||||
|
@ -408,30 +421,30 @@ func TestHistogramExemplar(t *testing.T) {
|
|||
}).(*histogram)
|
||||
histogram.now = func() time.Time { return now }
|
||||
|
||||
ts, err := ptypes.TimestampProto(now)
|
||||
if err != nil {
|
||||
ts := timestamppb.New(now)
|
||||
if err := ts.CheckValid(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
expectedExemplars := []*dto.Exemplar{
|
||||
nil,
|
||||
&dto.Exemplar{
|
||||
{
|
||||
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),
|
||||
Timestamp: ts,
|
||||
},
|
||||
nil,
|
||||
&dto.Exemplar{
|
||||
{
|
||||
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),
|
||||
Timestamp: ts,
|
||||
},
|
||||
&dto.Exemplar{
|
||||
{
|
||||
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),
|
||||
Timestamp: ts,
|
||||
|
@ -456,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
|
||||
}
|
|
@ -0,0 +1,142 @@
|
|||
// Copyright 2021 The Prometheus Authors
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//go:build go1.17
|
||||
// +build go1.17
|
||||
|
||||
package internal
|
||||
|
||||
import (
|
||||
"math"
|
||||
"path"
|
||||
"runtime/metrics"
|
||||
"strings"
|
||||
|
||||
"github.com/prometheus/common/model"
|
||||
)
|
||||
|
||||
// RuntimeMetricsToProm produces a Prometheus metric name from a runtime/metrics
|
||||
// metric description and validates whether the metric is suitable for integration
|
||||
// with Prometheus.
|
||||
//
|
||||
// Returns false if a name could not be produced, or if Prometheus does not understand
|
||||
// the runtime/metrics Kind.
|
||||
//
|
||||
// Note that the main reason a name couldn't be produced is if the runtime/metrics
|
||||
// package exports a name with characters outside the valid Prometheus metric name
|
||||
// character set. This is theoretically possible, but should never happen in practice.
|
||||
// Still, don't rely on it.
|
||||
func RuntimeMetricsToProm(d *metrics.Description) (string, string, string, bool) {
|
||||
namespace := "go"
|
||||
|
||||
comp := strings.SplitN(d.Name, ":", 2)
|
||||
key := comp[0]
|
||||
unit := comp[1]
|
||||
|
||||
// The last path element in the key is the name,
|
||||
// the rest is the subsystem.
|
||||
subsystem := path.Dir(key[1:] /* remove leading / */)
|
||||
name := path.Base(key)
|
||||
|
||||
// subsystem is translated by replacing all / and - with _.
|
||||
subsystem = strings.ReplaceAll(subsystem, "/", "_")
|
||||
subsystem = strings.ReplaceAll(subsystem, "-", "_")
|
||||
|
||||
// unit is translated assuming that the unit contains no
|
||||
// non-ASCII characters.
|
||||
unit = strings.ReplaceAll(unit, "-", "_")
|
||||
unit = strings.ReplaceAll(unit, "*", "_")
|
||||
unit = strings.ReplaceAll(unit, "/", "_per_")
|
||||
|
||||
// name has - replaced with _ and is concatenated with the unit and
|
||||
// other data.
|
||||
name = strings.ReplaceAll(name, "-", "_")
|
||||
name += "_" + unit
|
||||
if d.Cumulative && d.Kind != metrics.KindFloat64Histogram {
|
||||
name += "_total"
|
||||
}
|
||||
|
||||
valid := model.IsValidMetricName(model.LabelValue(namespace + "_" + subsystem + "_" + name))
|
||||
switch d.Kind {
|
||||
case metrics.KindUint64:
|
||||
case metrics.KindFloat64:
|
||||
case metrics.KindFloat64Histogram:
|
||||
default:
|
||||
valid = false
|
||||
}
|
||||
return namespace, subsystem, name, valid
|
||||
}
|
||||
|
||||
// RuntimeMetricsBucketsForUnit takes a set of buckets obtained for a runtime/metrics histogram
|
||||
// type (so, lower-bound inclusive) and a unit from a runtime/metrics name, and produces
|
||||
// a reduced set of buckets. This function always removes any -Inf bucket as it's represented
|
||||
// as the bottom-most upper-bound inclusive bucket in Prometheus.
|
||||
func RuntimeMetricsBucketsForUnit(buckets []float64, unit string) []float64 {
|
||||
switch unit {
|
||||
case "bytes":
|
||||
// Re-bucket as powers of 2.
|
||||
return reBucketExp(buckets, 2)
|
||||
case "seconds":
|
||||
// Re-bucket as powers of 10 and then merge all buckets greater
|
||||
// than 1 second into the +Inf bucket.
|
||||
b := reBucketExp(buckets, 10)
|
||||
for i := range b {
|
||||
if b[i] <= 1 {
|
||||
continue
|
||||
}
|
||||
b[i] = math.Inf(1)
|
||||
b = b[:i+1]
|
||||
break
|
||||
}
|
||||
return b
|
||||
}
|
||||
return buckets
|
||||
}
|
||||
|
||||
// reBucketExp takes a list of bucket boundaries (lower bound inclusive) and
|
||||
// downsamples the buckets to those a multiple of base apart. The end result
|
||||
// is a roughly exponential (in many cases, perfectly exponential) bucketing
|
||||
// scheme.
|
||||
func reBucketExp(buckets []float64, base float64) []float64 {
|
||||
bucket := buckets[0]
|
||||
var newBuckets []float64
|
||||
// We may see a -Inf here, in which case, add it and skip it
|
||||
// since we risk producing NaNs otherwise.
|
||||
//
|
||||
// We need to preserve -Inf values to maintain runtime/metrics
|
||||
// conventions. We'll strip it out later.
|
||||
if bucket == math.Inf(-1) {
|
||||
newBuckets = append(newBuckets, bucket)
|
||||
buckets = buckets[1:]
|
||||
bucket = buckets[0]
|
||||
}
|
||||
// From now on, bucket should always have a non-Inf value because
|
||||
// Infs are only ever at the ends of the bucket lists, so
|
||||
// arithmetic operations on it are non-NaN.
|
||||
for i := 1; i < len(buckets); i++ {
|
||||
if bucket >= 0 && buckets[i] < bucket*base {
|
||||
// The next bucket we want to include is at least bucket*base.
|
||||
continue
|
||||
} else if bucket < 0 && buckets[i] < bucket/base {
|
||||
// In this case the bucket we're targeting is negative, and since
|
||||
// we're ascending through buckets here, we need to divide to get
|
||||
// closer to zero exponentially.
|
||||
continue
|
||||
}
|
||||
// The +Inf bucket will always be the last one, and we'll always
|
||||
// end up including it here because bucket
|
||||
newBuckets = append(newBuckets, bucket)
|
||||
bucket = buckets[i]
|
||||
}
|
||||
return append(newBuckets, bucket)
|
||||
}
|
|
@ -0,0 +1,97 @@
|
|||
// Copyright 2021 The Prometheus Authors
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//go:build go1.17
|
||||
// +build go1.17
|
||||
|
||||
package internal
|
||||
|
||||
import (
|
||||
"runtime/metrics"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestRuntimeMetricsToProm(t *testing.T) {
|
||||
tests := []struct {
|
||||
got metrics.Description
|
||||
expect string
|
||||
}{
|
||||
{
|
||||
metrics.Description{
|
||||
Name: "/memory/live:bytes",
|
||||
Kind: metrics.KindUint64,
|
||||
},
|
||||
"go_memory_live_bytes",
|
||||
},
|
||||
{
|
||||
metrics.Description{
|
||||
Name: "/memory/allocs:bytes",
|
||||
Kind: metrics.KindUint64,
|
||||
Cumulative: true,
|
||||
},
|
||||
"go_memory_allocs_bytes_total",
|
||||
},
|
||||
{
|
||||
metrics.Description{
|
||||
Name: "/memory/alloc-rate:bytes/second",
|
||||
Kind: metrics.KindFloat64,
|
||||
},
|
||||
"go_memory_alloc_rate_bytes_per_second",
|
||||
},
|
||||
{
|
||||
metrics.Description{
|
||||
Name: "/gc/time:cpu*seconds",
|
||||
Kind: metrics.KindFloat64,
|
||||
Cumulative: true,
|
||||
},
|
||||
"go_gc_time_cpu_seconds_total",
|
||||
},
|
||||
{
|
||||
metrics.Description{
|
||||
Name: "/this/is/a/very/deep/metric:metrics",
|
||||
Kind: metrics.KindFloat64,
|
||||
},
|
||||
"go_this_is_a_very_deep_metric_metrics",
|
||||
},
|
||||
{
|
||||
metrics.Description{
|
||||
Name: "/this*is*an*invalid...:µname",
|
||||
Kind: metrics.KindUint64,
|
||||
},
|
||||
"",
|
||||
},
|
||||
{
|
||||
metrics.Description{
|
||||
Name: "/this/is/a/valid/name:objects",
|
||||
Kind: metrics.KindBad,
|
||||
},
|
||||
"",
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
ns, ss, n, ok := RuntimeMetricsToProm(&test.got)
|
||||
name := ns + "_" + ss + "_" + n
|
||||
if test.expect == "" && ok {
|
||||
t.Errorf("bad input expected a bad output: input %s, got %s", test.got.Name, name)
|
||||
continue
|
||||
}
|
||||
if test.expect != "" && !ok {
|
||||
t.Errorf("unexpected bad output on good input: input %s", test.got.Name)
|
||||
continue
|
||||
}
|
||||
if test.expect != "" && name != test.expect {
|
||||
t.Errorf("expected %s, got %s", test.expect, name)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
|
@ -19,18 +19,34 @@ import (
|
|||
dto "github.com/prometheus/client_model/go"
|
||||
)
|
||||
|
||||
// metricSorter is a sortable slice of *dto.Metric.
|
||||
type metricSorter []*dto.Metric
|
||||
// LabelPairSorter implements sort.Interface. It is used to sort a slice of
|
||||
// dto.LabelPair pointers.
|
||||
type LabelPairSorter []*dto.LabelPair
|
||||
|
||||
func (s metricSorter) Len() int {
|
||||
func (s LabelPairSorter) Len() int {
|
||||
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]
|
||||
}
|
||||
|
||||
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) {
|
||||
// This should not happen. The metrics are
|
||||
// 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.
|
||||
func NormalizeMetricFamilies(metricFamiliesByName map[string]*dto.MetricFamily) []*dto.MetricFamily {
|
||||
for _, mf := range metricFamiliesByName {
|
||||
sort.Sort(metricSorter(mf.Metric))
|
||||
sort.Sort(MetricSorter(mf.Metric))
|
||||
}
|
||||
names := make([]string, 0, len(metricFamiliesByName))
|
||||
for name, mf := range metricFamiliesByName {
|
||||
|
|
|
@ -25,6 +25,7 @@ import (
|
|||
// Labels represents a collection of label name -> value mappings. This type is
|
||||
// commonly used with the With(Labels) and GetMetricWith(Labels) methods of
|
||||
// metric vector Collectors, e.g.:
|
||||
//
|
||||
// myVec.With(Labels{"code": "404", "method": "GET"}).Add(42)
|
||||
//
|
||||
// The other use-case is the specification of constant label pairs in Opts or to
|
||||
|
@ -39,7 +40,7 @@ var errInconsistentCardinality = errors.New("inconsistent label cardinality")
|
|||
|
||||
func makeInconsistentCardinalityError(fqName string, labels, labelValues []string) error {
|
||||
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,
|
||||
len(labels), labels,
|
||||
len(labelValues), labelValues,
|
||||
|
@ -49,7 +50,7 @@ func makeInconsistentCardinalityError(fqName string, labels, labelValues []strin
|
|||
func validateValuesInLabels(labels Labels, expectedNumberOfValues int) error {
|
||||
if len(labels) != expectedNumberOfValues {
|
||||
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,
|
||||
len(labels), labels,
|
||||
)
|
||||
|
@ -67,7 +68,7 @@ func validateValuesInLabels(labels Labels, expectedNumberOfValues int) error {
|
|||
func validateLabelValues(vals []string, expectedNumberOfValues int) error {
|
||||
if len(vals) != expectedNumberOfValues {
|
||||
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,
|
||||
len(vals), vals,
|
||||
)
|
||||
|
|
|
@ -14,6 +14,9 @@
|
|||
package prometheus
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"math"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
|
@ -115,22 +118,6 @@ func BuildFQName(namespace, subsystem, name string) string {
|
|||
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 {
|
||||
desc *Desc
|
||||
err error
|
||||
|
@ -174,3 +161,96 @@ func (m timestampedMetric) Write(pb *dto.Metric) error {
|
|||
func NewMetricWithTimestamp(t time.Time, m Metric) Metric {
|
||||
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
|
||||
|
||||
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) {
|
||||
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
|
||||
// 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
|
||||
// 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 {
|
||||
ObserveWithExemplar(value float64, exemplar Labels)
|
||||
}
|
||||
|
|
|
@ -16,7 +16,6 @@ package prometheus
|
|||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
@ -104,8 +103,7 @@ func NewProcessCollector(opts ProcessCollectorOpts) Collector {
|
|||
}
|
||||
|
||||
if opts.PidFn == nil {
|
||||
pid := os.Getpid()
|
||||
c.pidFn = func() (int, error) { return pid, nil }
|
||||
c.pidFn = getPIDFn()
|
||||
} else {
|
||||
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.
|
||||
func NewPidFileFn(pidFilePath string) func() (int, error) {
|
||||
return func() (int, error) {
|
||||
content, err := ioutil.ReadFile(pidFilePath)
|
||||
content, err := os.ReadFile(pidFilePath)
|
||||
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)))
|
||||
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
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright 2021 The Prometheus Authors
|
||||
// Copyright 2019 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
|
||||
|
@ -11,20 +11,16 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// +build go1.15
|
||||
//go:build js
|
||||
// +build js
|
||||
|
||||
package collectors
|
||||
package prometheus
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
)
|
||||
|
||||
func (c *dbStatsCollector) describeNewInGo115(ch chan<- *prometheus.Desc) {
|
||||
ch <- c.maxIdleTimeClosed
|
||||
func canCollectProcess() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (c *dbStatsCollector) collectNewInGo115(ch chan<- prometheus.Metric, stats sql.DBStats) {
|
||||
ch <- prometheus.MustNewConstMetric(c.maxIdleTimeClosed, prometheus.CounterValue, float64(stats.MaxIdleTimeClosed))
|
||||
func (c *processCollector) processCollect(ch chan<- Metric) {
|
||||
// noop on this platform
|
||||
return
|
||||
}
|
|
@ -11,7 +11,8 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// +build !windows
|
||||
//go:build !windows && !js
|
||||
// +build !windows,!js
|
||||
|
||||
package prometheus
|
||||
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//go:build linux
|
||||
// +build linux
|
||||
|
||||
package prometheus
|
||||
|
|
|
@ -14,13 +14,13 @@
|
|||
// Package promauto provides alternative constructors for the fundamental
|
||||
// Prometheus metric types and their …Vec and …Func variants. The difference to
|
||||
// their counterparts in the prometheus package is that the promauto
|
||||
// constructors return Collectors that are already registered with a
|
||||
// registry. There are two sets of constructors. The constructors in the first
|
||||
// set are top-level functions, while the constructors in the other set are
|
||||
// methods of the Factory type. The top-level function return Collectors
|
||||
// registered with the global registry (prometheus.DefaultRegisterer), while the
|
||||
// methods return Collectors registered with the registry the Factory was
|
||||
// constructed with. All constructors panic if the registration fails.
|
||||
// constructors register the Collectors with a registry before returning them.
|
||||
// There are two sets of constructors. The constructors in the first set are
|
||||
// top-level functions, while the constructors in the other set are methods of
|
||||
// the Factory type. The top-level function return Collectors registered with
|
||||
// the global registry (prometheus.DefaultRegisterer), while the methods return
|
||||
// Collectors registered with the registry the Factory was constructed with. All
|
||||
// constructors panic if the registration fails.
|
||||
//
|
||||
// The following example is a complete program to create a histogram of normally
|
||||
// distributed random numbers from the math/rand package:
|
||||
|
@ -31,9 +31,9 @@
|
|||
// "math/rand"
|
||||
// "net/http"
|
||||
//
|
||||
// "github.com/prometheus/client_golang/prometheus"
|
||||
// "github.com/prometheus/client_golang/prometheus/promauto"
|
||||
// "github.com/prometheus/client_golang/prometheus/promhttp"
|
||||
// "git.internal/re/client_golang/prometheus"
|
||||
// "git.internal/re/client_golang/prometheus/promauto"
|
||||
// "git.internal/re/client_golang/prometheus/promhttp"
|
||||
// )
|
||||
//
|
||||
// var histogram = promauto.NewHistogram(prometheus.HistogramOpts{
|
||||
|
@ -62,9 +62,9 @@
|
|||
// "fmt"
|
||||
// "net/http"
|
||||
//
|
||||
// "github.com/prometheus/client_golang/prometheus"
|
||||
// "github.com/prometheus/client_golang/prometheus/promauto"
|
||||
// "github.com/prometheus/client_golang/prometheus/promhttp"
|
||||
// "git.internal/re/client_golang/prometheus"
|
||||
// "git.internal/re/client_golang/prometheus/promauto"
|
||||
// "git.internal/re/client_golang/prometheus/promhttp"
|
||||
// )
|
||||
//
|
||||
// func main() {
|
||||
|
@ -159,7 +159,7 @@
|
|||
// Enjoy promauto responsibly!
|
||||
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
|
||||
// but it automatically registers the Counter with the
|
||||
|
|
|
@ -16,7 +16,7 @@ package promauto
|
|||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"git.internal/re/client_golang/prometheus"
|
||||
)
|
||||
|
||||
func TestNil(t *testing.T) {
|
||||
|
|
|
@ -76,16 +76,19 @@ func (r *responseWriterDelegator) Write(b []byte) (int, error) {
|
|||
return n, err
|
||||
}
|
||||
|
||||
type closeNotifierDelegator struct{ *responseWriterDelegator }
|
||||
type flusherDelegator struct{ *responseWriterDelegator }
|
||||
type hijackerDelegator struct{ *responseWriterDelegator }
|
||||
type readerFromDelegator struct{ *responseWriterDelegator }
|
||||
type pusherDelegator struct{ *responseWriterDelegator }
|
||||
type (
|
||||
closeNotifierDelegator struct{ *responseWriterDelegator }
|
||||
flusherDelegator struct{ *responseWriterDelegator }
|
||||
hijackerDelegator struct{ *responseWriterDelegator }
|
||||
readerFromDelegator struct{ *responseWriterDelegator }
|
||||
pusherDelegator struct{ *responseWriterDelegator }
|
||||
)
|
||||
|
||||
func (d closeNotifierDelegator) CloseNotify() <-chan bool {
|
||||
//nolint:staticcheck // Ignore SA1019. http.CloseNotifier is deprecated but we keep it here to not break existing users.
|
||||
return d.ResponseWriter.(http.CloseNotifier).CloseNotify()
|
||||
}
|
||||
|
||||
func (d flusherDelegator) Flush() {
|
||||
// If applicable, call WriteHeader here so that observeWriteHeader is
|
||||
// handled appropriately.
|
||||
|
@ -94,9 +97,11 @@ func (d flusherDelegator) Flush() {
|
|||
}
|
||||
d.ResponseWriter.(http.Flusher).Flush()
|
||||
}
|
||||
|
||||
func (d hijackerDelegator) Hijack() (net.Conn, *bufio.ReadWriter, error) {
|
||||
return d.ResponseWriter.(http.Hijacker).Hijack()
|
||||
}
|
||||
|
||||
func (d readerFromDelegator) ReadFrom(re io.Reader) (int64, error) {
|
||||
// If applicable, call WriteHeader here so that observeWriteHeader is
|
||||
// handled appropriately.
|
||||
|
@ -107,6 +112,7 @@ func (d readerFromDelegator) ReadFrom(re io.Reader) (int64, error) {
|
|||
d.written += n
|
||||
return n, err
|
||||
}
|
||||
|
||||
func (d pusherDelegator) Push(target string, opts *http.PushOptions) error {
|
||||
return d.ResponseWriter.(http.Pusher).Push(target, opts)
|
||||
}
|
||||
|
|
|
@ -33,6 +33,7 @@ package promhttp
|
|||
|
||||
import (
|
||||
"compress/gzip"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
|
@ -42,7 +43,7 @@ import (
|
|||
|
||||
"github.com/prometheus/common/expfmt"
|
||||
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"git.internal/re/client_golang/prometheus"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -84,6 +85,13 @@ func Handler() http.Handler {
|
|||
// instrumentation. Use the InstrumentMetricHandler function to apply the same
|
||||
// kind of instrumentation as it is used by the Handler function.
|
||||
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 (
|
||||
inFlightSem chan struct{}
|
||||
errCnt = prometheus.NewCounterVec(
|
||||
|
@ -103,7 +111,8 @@ func HandlerFor(reg prometheus.Gatherer, opts HandlerOpts) http.Handler {
|
|||
errCnt.WithLabelValues("gathering")
|
||||
errCnt.WithLabelValues("encoding")
|
||||
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)
|
||||
} else {
|
||||
panic(err)
|
||||
|
@ -123,7 +132,8 @@ func HandlerFor(reg prometheus.Gatherer, opts HandlerOpts) http.Handler {
|
|||
return
|
||||
}
|
||||
}
|
||||
mfs, err := reg.Gather()
|
||||
mfs, done, err := reg.Gather()
|
||||
defer done()
|
||||
if err != nil {
|
||||
if opts.ErrorLog != nil {
|
||||
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("503")
|
||||
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)
|
||||
} else {
|
||||
panic(err)
|
||||
|
@ -254,7 +265,8 @@ func InstrumentMetricHandler(reg prometheus.Registerer, handler http.Handler) ht
|
|||
Help: "Current number of scrapes being served.",
|
||||
})
|
||||
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)
|
||||
} else {
|
||||
panic(err)
|
||||
|
|
|
@ -16,6 +16,7 @@ package promhttp
|
|||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
|
@ -23,7 +24,9 @@ import (
|
|||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
dto "github.com/prometheus/client_model/go"
|
||||
|
||||
"git.internal/re/client_golang/prometheus"
|
||||
)
|
||||
|
||||
type errorCollector struct{}
|
||||
|
@ -56,8 +59,19 @@ func (b blockingCollector) Collect(ch chan<- prometheus.Metric) {
|
|||
<-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,
|
||||
// another with one, and reports an error. Further down, we'll use the
|
||||
// same registry in the HandlerOpts.
|
||||
|
@ -90,21 +104,30 @@ func TestHandlerErrorHandling(t *testing.T) {
|
|||
request, _ := http.NewRequest("GET", "/", nil)
|
||||
request.Header.Add("Accept", "test/plain")
|
||||
|
||||
errorHandler := HandlerFor(reg, HandlerOpts{
|
||||
mReg := &mockTransactionGatherer{g: reg}
|
||||
errorHandler := HandlerForTransactional(mReg, HandlerOpts{
|
||||
ErrorLog: logger,
|
||||
ErrorHandling: HTTPErrorOnError,
|
||||
Registry: reg,
|
||||
})
|
||||
continueHandler := HandlerFor(reg, HandlerOpts{
|
||||
continueHandler := HandlerForTransactional(mReg, HandlerOpts{
|
||||
ErrorLog: logger,
|
||||
ErrorHandling: ContinueOnError,
|
||||
Registry: reg,
|
||||
})
|
||||
panicHandler := HandlerFor(reg, HandlerOpts{
|
||||
panicHandler := HandlerForTransactional(mReg, HandlerOpts{
|
||||
ErrorLog: logger,
|
||||
ErrorHandling: PanicOnError,
|
||||
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
|
||||
`
|
||||
wantErrorBody := `An error has occurred while serving metrics:
|
||||
|
@ -140,25 +163,39 @@ the_count 0
|
|||
`
|
||||
|
||||
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 {
|
||||
t.Errorf("got HTTP status code %d, want %d", got, want)
|
||||
}
|
||||
if got := logBuf.String(); got != wantMsg {
|
||||
t.Errorf("got log message:\n%s\nwant log message:\n%s\n", got, wantMsg)
|
||||
if got, want := logBuf.String(), wantMsg; got != want {
|
||||
t.Errorf("got log buf %q, want %q", got, want)
|
||||
}
|
||||
if got := writer.Body.String(); got != wantErrorBody {
|
||||
t.Errorf("got body:\n%s\nwant body:\n%s\n", got, wantErrorBody)
|
||||
if got, want := writer.Body.String(), wantErrorBody; got != want {
|
||||
t.Errorf("got body %q, want %q", got, want)
|
||||
}
|
||||
|
||||
logBuf.Reset()
|
||||
writer.Body.Reset()
|
||||
writer.Code = http.StatusOK
|
||||
|
||||
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 {
|
||||
t.Errorf("got HTTP status code %d, want %d", got, want)
|
||||
}
|
||||
if got := logBuf.String(); got != wantMsg {
|
||||
t.Errorf("got log message %q, want %q", got, wantMsg)
|
||||
if got, want := logBuf.String(), wantMsg; got != want {
|
||||
t.Errorf("got log buf %q, want %q", got, want)
|
||||
}
|
||||
if got := writer.Body.String(); got != wantOKBody1 && got != 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 {
|
||||
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)
|
||||
}
|
||||
|
||||
func TestInstrumentMetricHandler(t *testing.T) {
|
||||
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.
|
||||
InstrumentMetricHandler(reg, HandlerFor(reg, HandlerOpts{}))
|
||||
InstrumentMetricHandler(reg, HandlerForTransactional(mReg, HandlerOpts{}))
|
||||
writer := httptest.NewRecorder()
|
||||
request, _ := http.NewRequest("GET", "/", nil)
|
||||
request.Header.Add("Accept", "test/plain")
|
||||
|
||||
handler.ServeHTTP(writer, request)
|
||||
if got := mReg.gatherInvoked; got != 1 {
|
||||
t.Fatalf("unexpected number of gather invokes, want 1, got %d", got)
|
||||
}
|
||||
if got := mReg.doneInvoked; got != 1 {
|
||||
t.Fatalf("unexpected number of done invokes, want 1, got %d", got)
|
||||
}
|
||||
|
||||
if got, want := writer.Code, http.StatusOK; got != want {
|
||||
t.Errorf("got HTTP status code %d, want %d", got, want)
|
||||
}
|
||||
|
@ -195,21 +246,30 @@ func TestInstrumentMetricHandler(t *testing.T) {
|
|||
t.Errorf("got body %q, does not contain %q", got, want)
|
||||
}
|
||||
|
||||
for i := 0; i < 100; i++ {
|
||||
writer.Body.Reset()
|
||||
handler.ServeHTTP(writer, request)
|
||||
|
||||
if got, want := mReg.gatherInvoked, i+2; got != want {
|
||||
t.Fatalf("unexpected number of gather invokes, want %d, got %d", want, got)
|
||||
}
|
||||
if got, want := mReg.doneInvoked, i+2; got != want {
|
||||
t.Fatalf("unexpected number of done invokes, want %d, got %d", want, got)
|
||||
}
|
||||
if got, want := writer.Code, http.StatusOK; got != want {
|
||||
t.Errorf("got HTTP status code %d, want %d", got, want)
|
||||
}
|
||||
|
||||
want = "promhttp_metric_handler_requests_in_flight 1\n"
|
||||
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 = "promhttp_metric_handler_requests_total{code=\"200\"} 1\n"
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandlerMaxRequestsInFlight(t *testing.T) {
|
||||
reg := prometheus.NewRegistry()
|
||||
|
|
|
@ -19,7 +19,7 @@ import (
|
|||
"net/http/httptrace"
|
||||
"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
|
||||
|
@ -38,42 +38,59 @@ func (rt RoundTripperFunc) RoundTrip(r *http.Request) (*http.Response, error) {
|
|||
//
|
||||
// See the example for ExampleInstrumentRoundTripperDuration for example usage.
|
||||
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()
|
||||
defer gauge.Dec()
|
||||
return next.RoundTrip(r)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// InstrumentRoundTripperCounter is a middleware that wraps the provided
|
||||
// http.RoundTripper to observe the request result with the provided CounterVec.
|
||||
// The CounterVec must have zero, one, or two non-const non-curried labels. For
|
||||
// those, the only allowed label names are "code" and "method". The function
|
||||
// panics otherwise. Partitioning of the CounterVec happens by HTTP status code
|
||||
// panics otherwise. For the "method" label a predefined default label value set
|
||||
// is used to filter given values. Values besides predefined values will count
|
||||
// as `unknown` method.`WithExtraMethods` can be used to add more
|
||||
// methods to the set. Partitioning of the CounterVec happens by HTTP status code
|
||||
// and/or HTTP method if the respective instance label names are present in the
|
||||
// CounterVec. For unpartitioned counting, use a CounterVec with zero labels.
|
||||
//
|
||||
// If the wrapped RoundTripper panics or returns a non-nil error, the Counter
|
||||
// is not incremented.
|
||||
//
|
||||
// Use with WithExemplarFromContext to instrument the exemplars on the counter of requests.
|
||||
//
|
||||
// See the example for ExampleInstrumentRoundTripperDuration for example usage.
|
||||
func InstrumentRoundTripperCounter(counter *prometheus.CounterVec, next http.RoundTripper) RoundTripperFunc {
|
||||
func InstrumentRoundTripperCounter(counter *prometheus.CounterVec, next http.RoundTripper, opts ...Option) RoundTripperFunc {
|
||||
rtOpts := defaultOptions()
|
||||
for _, o := range opts {
|
||||
o.apply(rtOpts)
|
||||
}
|
||||
|
||||
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)
|
||||
if err == nil {
|
||||
counter.With(labels(code, method, r.Method, resp.StatusCode)).Inc()
|
||||
addWithExemplar(
|
||||
counter.With(labels(code, method, r.Method, resp.StatusCode, rtOpts.extraMethods...)),
|
||||
1,
|
||||
rtOpts.getExemplarFn(r.Context()),
|
||||
)
|
||||
}
|
||||
return resp, err
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// InstrumentRoundTripperDuration is a middleware that wraps the provided
|
||||
// http.RoundTripper to observe the request duration with the provided
|
||||
// ObserverVec. The ObserverVec must have zero, one, or two non-const
|
||||
// non-curried labels. For those, the only allowed label names are "code" and
|
||||
// "method". The function panics otherwise. The Observe method of the Observer
|
||||
// "method". The function panics otherwise. For the "method" label a predefined
|
||||
// default label value set is used to filter given values. Values besides
|
||||
// predefined values will count as `unknown` method. `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 in
|
||||
// seconds. Partitioning happens by HTTP status code and/or HTTP method if the
|
||||
// respective instance label names are present in the ObserverVec. For
|
||||
|
@ -83,19 +100,30 @@ func InstrumentRoundTripperCounter(counter *prometheus.CounterVec, next http.Rou
|
|||
// If the wrapped RoundTripper panics or returns a non-nil error, no values are
|
||||
// reported.
|
||||
//
|
||||
// Use with WithExemplarFromContext to instrument the exemplars on the duration histograms.
|
||||
//
|
||||
// Note that this method is only guaranteed to never observe negative durations
|
||||
// if used with Go1.9+.
|
||||
func InstrumentRoundTripperDuration(obs prometheus.ObserverVec, next http.RoundTripper) RoundTripperFunc {
|
||||
func InstrumentRoundTripperDuration(obs prometheus.ObserverVec, next http.RoundTripper, opts ...Option) RoundTripperFunc {
|
||||
rtOpts := defaultOptions()
|
||||
for _, o := range opts {
|
||||
o.apply(rtOpts)
|
||||
}
|
||||
|
||||
code, method := checkLabels(obs)
|
||||
|
||||
return RoundTripperFunc(func(r *http.Request) (*http.Response, error) {
|
||||
return func(r *http.Request) (*http.Response, error) {
|
||||
start := time.Now()
|
||||
resp, err := next.RoundTrip(r)
|
||||
if err == nil {
|
||||
obs.With(labels(code, method, r.Method, resp.StatusCode)).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
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// InstrumentTrace is used to offer flexibility in instrumenting the available
|
||||
|
@ -133,7 +161,7 @@ type InstrumentTrace struct {
|
|||
//
|
||||
// See the example for ExampleInstrumentRoundTripperDuration for example usage.
|
||||
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()
|
||||
|
||||
trace := &httptrace.ClientTrace{
|
||||
|
@ -215,5 +243,5 @@ func InstrumentRoundTripperTrace(it *InstrumentTrace, next http.RoundTripper) Ro
|
|||
r = r.WithContext(httptrace.WithClientTrace(r.Context(), trace))
|
||||
|
||||
return next.RoundTrip(r)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,14 +18,20 @@ import (
|
|||
"log"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strings"
|
||||
"testing"
|
||||
"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.Timeout = 1 * time.Second
|
||||
|
||||
|
@ -91,13 +97,91 @@ func makeInstrumentedClient() (*http.Client, *prometheus.Registry) {
|
|||
client.Transport = InstrumentRoundTripperInFlight(inFlightGauge,
|
||||
InstrumentRoundTripperCounter(counter,
|
||||
InstrumentRoundTripperTrace(trace,
|
||||
InstrumentRoundTripperDuration(histVec, http.DefaultTransport),
|
||||
),
|
||||
InstrumentRoundTripperDuration(histVec, http.DefaultTransport, opts...),
|
||||
),
|
||||
opts...),
|
||||
)
|
||||
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) {
|
||||
client, reg := makeInstrumentedClient()
|
||||
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()
|
||||
|
||||
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 {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if want, got := 3, 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())
|
||||
}
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
assetMetricAndExemplars(t, reg, 3, labelsToLabelPair(exemplar))
|
||||
}
|
||||
|
||||
func TestClientMiddlewareAPIWithRequestContext(t *testing.T) {
|
||||
func TestClientMiddlewareAPI_WithRequestContext(t *testing.T) {
|
||||
client, reg := makeInstrumentedClient()
|
||||
backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
|
@ -160,6 +251,19 @@ func TestClientMiddlewareAPIWithRequestContext(t *testing.T) {
|
|||
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) {
|
||||
|
|
|
@ -22,12 +22,32 @@ import (
|
|||
|
||||
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.
|
||||
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
|
||||
// http.Handler. It sets the provided prometheus.Gauge to the number of
|
||||
// requests currently handled by the wrapped http.Handler.
|
||||
|
@ -45,7 +65,10 @@ func InstrumentHandlerInFlight(g prometheus.Gauge, next http.Handler) http.Handl
|
|||
// http.Handler to observe the request duration with the provided ObserverVec.
|
||||
// The ObserverVec must have valid metric and label names and must have zero,
|
||||
// one, or two non-const non-curried labels. For those, the only allowed label
|
||||
// names are "code" and "method". The function panics otherwise. The Observe
|
||||
// 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.
|
||||
// Values besides predefined values will count as `unknown` method.
|
||||
// `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
|
||||
// in seconds. Partitioning happens by HTTP status code and/or HTTP method if
|
||||
// the respective instance label names are present in the ObserverVec. For
|
||||
|
@ -58,31 +81,48 @@ func InstrumentHandlerInFlight(g prometheus.Gauge, next http.Handler) http.Handl
|
|||
//
|
||||
// Note that this method is only guaranteed to never observe negative durations
|
||||
// if used with Go1.9+.
|
||||
func InstrumentHandlerDuration(obs prometheus.ObserverVec, next http.Handler) http.HandlerFunc {
|
||||
func InstrumentHandlerDuration(obs prometheus.ObserverVec, next http.Handler, opts ...Option) http.HandlerFunc {
|
||||
hOpts := defaultOptions()
|
||||
for _, o := range opts {
|
||||
o.apply(hOpts)
|
||||
}
|
||||
|
||||
code, method := checkLabels(obs)
|
||||
|
||||
if code {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
now := time.Now()
|
||||
d := newDelegator(w, nil)
|
||||
next.ServeHTTP(d, r)
|
||||
|
||||
obs.With(labels(code, method, r.Method, d.Status())).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()
|
||||
next.ServeHTTP(w, r)
|
||||
obs.With(labels(code, method, r.Method, 0)).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
|
||||
// to observe the request result with the provided CounterVec. The CounterVec
|
||||
// must have valid metric and label names and must have zero, one, or two
|
||||
// non-const non-curried labels. For those, the only allowed label names are
|
||||
// "code" and "method". The function panics otherwise. Partitioning of the
|
||||
// "code" and "method". The function panics otherwise. For the "method"
|
||||
// label a predefined default label value set is used to filter given values.
|
||||
// Values besides predefined values will count as `unknown` method.
|
||||
// `WithExtraMethods` can be used to add more methods to the set. Partitioning of the
|
||||
// CounterVec happens by HTTP status code and/or HTTP method if the respective
|
||||
// instance label names are present in the CounterVec. For unpartitioned
|
||||
// counting, use a CounterVec with zero labels.
|
||||
|
@ -92,21 +132,35 @@ func InstrumentHandlerDuration(obs prometheus.ObserverVec, next http.Handler) ht
|
|||
// If the wrapped Handler panics, the Counter is not incremented.
|
||||
//
|
||||
// See the example for InstrumentHandlerDuration for example usage.
|
||||
func InstrumentHandlerCounter(counter *prometheus.CounterVec, next http.Handler) http.HandlerFunc {
|
||||
func InstrumentHandlerCounter(counter *prometheus.CounterVec, next http.Handler, opts ...Option) http.HandlerFunc {
|
||||
hOpts := defaultOptions()
|
||||
for _, o := range opts {
|
||||
o.apply(hOpts)
|
||||
}
|
||||
|
||||
code, method := checkLabels(counter)
|
||||
|
||||
if code {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
d := newDelegator(w, nil)
|
||||
next.ServeHTTP(d, r)
|
||||
counter.With(labels(code, method, r.Method, d.Status())).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)
|
||||
counter.With(labels(code, method, r.Method, 0)).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
|
||||
|
@ -114,7 +168,10 @@ func InstrumentHandlerCounter(counter *prometheus.CounterVec, next http.Handler)
|
|||
// until the response headers are written. The ObserverVec must have valid
|
||||
// metric and label names and must have zero, one, or two non-const non-curried
|
||||
// labels. For those, the only allowed label names are "code" and "method". The
|
||||
// function panics otherwise. The Observe method of the Observer in the
|
||||
// function panics otherwise. For the "method" label a predefined default label
|
||||
// value set is used to filter given values. Values besides predefined values
|
||||
// will count as `unknown` method.`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 in seconds. Partitioning
|
||||
// happens by HTTP status code and/or HTTP method if the respective instance
|
||||
// label names are present in the ObserverVec. For unpartitioned observations,
|
||||
|
@ -128,24 +185,36 @@ func InstrumentHandlerCounter(counter *prometheus.CounterVec, next http.Handler)
|
|||
// if used with Go1.9+.
|
||||
//
|
||||
// See the example for InstrumentHandlerDuration for example usage.
|
||||
func InstrumentHandlerTimeToWriteHeader(obs prometheus.ObserverVec, next http.Handler) http.HandlerFunc {
|
||||
func InstrumentHandlerTimeToWriteHeader(obs prometheus.ObserverVec, next http.Handler, opts ...Option) http.HandlerFunc {
|
||||
hOpts := defaultOptions()
|
||||
for _, o := range opts {
|
||||
o.apply(hOpts)
|
||||
}
|
||||
|
||||
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()
|
||||
d := newDelegator(w, func(status int) {
|
||||
obs.With(labels(code, method, r.Method, status)).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)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// InstrumentHandlerRequestSize is a middleware that wraps the provided
|
||||
// http.Handler to observe the request size with the provided ObserverVec. The
|
||||
// ObserverVec must have valid metric and label names and must have zero, one,
|
||||
// or two non-const non-curried labels. For those, the only allowed label names
|
||||
// are "code" and "method". The function panics otherwise. The Observe method of
|
||||
// the Observer in the ObserverVec is called with the request size in
|
||||
// are "code" and "method". The function panics otherwise. For the "method"
|
||||
// label a predefined default label value set is used to filter given values.
|
||||
// Values besides predefined values will count as `unknown` method.
|
||||
// `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 size in
|
||||
// bytes. Partitioning happens by HTTP status code and/or HTTP method if the
|
||||
// respective instance label names are present in the ObserverVec. For
|
||||
// unpartitioned observations, use an ObserverVec with zero labels. Note that
|
||||
|
@ -156,31 +225,46 @@ func InstrumentHandlerTimeToWriteHeader(obs prometheus.ObserverVec, next http.Ha
|
|||
// If the wrapped Handler panics, no values are reported.
|
||||
//
|
||||
// See the example for InstrumentHandlerDuration for example usage.
|
||||
func InstrumentHandlerRequestSize(obs prometheus.ObserverVec, next http.Handler) http.HandlerFunc {
|
||||
code, method := checkLabels(obs)
|
||||
func InstrumentHandlerRequestSize(obs prometheus.ObserverVec, next http.Handler, opts ...Option) http.HandlerFunc {
|
||||
hOpts := defaultOptions()
|
||||
for _, o := range opts {
|
||||
o.apply(hOpts)
|
||||
}
|
||||
|
||||
code, method := checkLabels(obs)
|
||||
if code {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
d := newDelegator(w, nil)
|
||||
next.ServeHTTP(d, r)
|
||||
size := computeApproximateRequestSize(r)
|
||||
obs.With(labels(code, method, r.Method, d.Status())).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)
|
||||
size := computeApproximateRequestSize(r)
|
||||
obs.With(labels(code, method, r.Method, 0)).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
|
||||
// http.Handler to observe the response size with the provided ObserverVec. The
|
||||
// ObserverVec must have valid metric and label names and must have zero, one,
|
||||
// or two non-const non-curried labels. For those, the only allowed label names
|
||||
// are "code" and "method". The function panics otherwise. The Observe method of
|
||||
// the Observer in the ObserverVec is called with the response size in
|
||||
// are "code" and "method". The function panics otherwise. For the "method"
|
||||
// label a predefined default label value set is used to filter given values.
|
||||
// Values besides predefined values will count as `unknown` method.
|
||||
// `WithExtraMethods` can be used to add more methods to the set. The Observe
|
||||
// method of the Observer in the ObserverVec is called with the response size in
|
||||
// bytes. Partitioning happens by HTTP status code and/or HTTP method if the
|
||||
// respective instance label names are present in the ObserverVec. For
|
||||
// unpartitioned observations, use an ObserverVec with zero labels. Note that
|
||||
|
@ -191,12 +275,22 @@ func InstrumentHandlerRequestSize(obs prometheus.ObserverVec, next http.Handler)
|
|||
// If the wrapped Handler panics, no values are reported.
|
||||
//
|
||||
// See the example for InstrumentHandlerDuration for example usage.
|
||||
func InstrumentHandlerResponseSize(obs prometheus.ObserverVec, next http.Handler) http.Handler {
|
||||
func InstrumentHandlerResponseSize(obs prometheus.ObserverVec, next http.Handler, opts ...Option) http.Handler {
|
||||
hOpts := defaultOptions()
|
||||
for _, o := range opts {
|
||||
o.apply(hOpts)
|
||||
}
|
||||
|
||||
code, method := checkLabels(obs)
|
||||
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
d := newDelegator(w, nil)
|
||||
next.ServeHTTP(d, r)
|
||||
obs.With(labels(code, method, r.Method, d.Status())).Observe(float64(d.Written()))
|
||||
observeWithExemplar(
|
||||
obs.With(labels(code, method, r.Method, d.Status(), hOpts.extraMethods...)),
|
||||
float64(d.Written()),
|
||||
hOpts.getExemplarFn(r.Context()),
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -205,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
|
||||
// invalid. It also panics if the Collector has any non-const, non-curried
|
||||
// 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
|
||||
// once Descriptors can have their dimensionality queried.
|
||||
var (
|
||||
|
@ -290,7 +384,7 @@ func isLabelCurried(c prometheus.Collector, label string) bool {
|
|||
// unnecessary allocations on each request.
|
||||
var emptyLabels = prometheus.Labels{}
|
||||
|
||||
func labels(code, method bool, reqMethod string, status int) prometheus.Labels {
|
||||
func labels(code, method bool, reqMethod string, status int, extraMethods ...string) prometheus.Labels {
|
||||
if !(code || method) {
|
||||
return emptyLabels
|
||||
}
|
||||
|
@ -300,7 +394,7 @@ func labels(code, method bool, reqMethod string, status int) prometheus.Labels {
|
|||
labels["code"] = sanitizeCode(status)
|
||||
}
|
||||
if method {
|
||||
labels["method"] = sanitizeMethod(reqMethod)
|
||||
labels["method"] = sanitizeMethod(reqMethod, extraMethods...)
|
||||
}
|
||||
|
||||
return labels
|
||||
|
@ -330,7 +424,12 @@ func computeApproximateRequestSize(r *http.Request) int {
|
|||
return s
|
||||
}
|
||||
|
||||
func sanitizeMethod(m string) string {
|
||||
// If the wrapped http.Handler has a known method, it will be sanitized and returned.
|
||||
// Otherwise, "unknown" will be returned. The known method list can be extended
|
||||
// as needed by using extraMethods parameter.
|
||||
func sanitizeMethod(m string, extraMethods ...string) string {
|
||||
// See https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods for
|
||||
// the methods chosen as default.
|
||||
switch m {
|
||||
case "GET", "get":
|
||||
return "get"
|
||||
|
@ -348,15 +447,25 @@ func sanitizeMethod(m string) string {
|
|||
return "options"
|
||||
case "NOTIFY", "notify":
|
||||
return "notify"
|
||||
case "TRACE", "trace":
|
||||
return "trace"
|
||||
case "PATCH", "patch":
|
||||
return "patch"
|
||||
default:
|
||||
for _, method := range extraMethods {
|
||||
if strings.EqualFold(m, method) {
|
||||
return strings.ToLower(m)
|
||||
}
|
||||
}
|
||||
return "unknown"
|
||||
}
|
||||
}
|
||||
|
||||
// If the wrapped http.Handler has not set a status code, i.e. the value is
|
||||
// currently 0, santizeCode will return 200, for consistency with behavior in
|
||||
// currently 0, sanitizeCode will return 200, for consistency with behavior in
|
||||
// the stdlib.
|
||||
func sanitizeCode(s int) string {
|
||||
// See for accepted codes https://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml
|
||||
switch s {
|
||||
case 100:
|
||||
return "100"
|
||||
|
@ -453,6 +562,9 @@ func sanitizeCode(s int) string {
|
|||
return "511"
|
||||
|
||||
default:
|
||||
if s >= 100 && s <= 599 {
|
||||
return strconv.Itoa(s)
|
||||
}
|
||||
return "unknown"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,13 +14,14 @@
|
|||
package promhttp
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"git.internal/re/client_golang/prometheus"
|
||||
)
|
||||
|
||||
func TestLabelCheck(t *testing.T) {
|
||||
|
@ -204,7 +205,123 @@ func TestLabelCheck(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestMiddlewareAPI(t *testing.T) {
|
||||
func TestLabels(t *testing.T) {
|
||||
scenarios := map[string]struct {
|
||||
varLabels []string
|
||||
reqMethod string
|
||||
respStatus int
|
||||
extraMethods []string
|
||||
wantLabels prometheus.Labels
|
||||
ok bool
|
||||
}{
|
||||
"empty": {
|
||||
varLabels: []string{},
|
||||
wantLabels: emptyLabels,
|
||||
reqMethod: "GET",
|
||||
respStatus: 200,
|
||||
ok: true,
|
||||
},
|
||||
"code as single var label": {
|
||||
varLabels: []string{"code"},
|
||||
reqMethod: "GET",
|
||||
respStatus: 200,
|
||||
wantLabels: prometheus.Labels{"code": "200"},
|
||||
ok: true,
|
||||
},
|
||||
"code as single var label and out-of-range code": {
|
||||
varLabels: []string{"code"},
|
||||
reqMethod: "GET",
|
||||
respStatus: 99,
|
||||
wantLabels: prometheus.Labels{"code": "unknown"},
|
||||
ok: true,
|
||||
},
|
||||
"code as single var label and in-range but unrecognized code": {
|
||||
varLabels: []string{"code"},
|
||||
reqMethod: "GET",
|
||||
respStatus: 308,
|
||||
wantLabels: prometheus.Labels{"code": "308"},
|
||||
ok: true,
|
||||
},
|
||||
"method as single var label": {
|
||||
varLabels: []string{"method"},
|
||||
reqMethod: "GET",
|
||||
respStatus: 200,
|
||||
wantLabels: prometheus.Labels{"method": "get"},
|
||||
ok: true,
|
||||
},
|
||||
"method as single var label and unknown method": {
|
||||
varLabels: []string{"method"},
|
||||
reqMethod: "CUSTOM_METHOD",
|
||||
respStatus: 200,
|
||||
wantLabels: prometheus.Labels{"method": "unknown"},
|
||||
ok: true,
|
||||
},
|
||||
"code and method as var labels": {
|
||||
varLabels: []string{"method", "code"},
|
||||
reqMethod: "GET",
|
||||
respStatus: 200,
|
||||
wantLabels: prometheus.Labels{"method": "get", "code": "200"},
|
||||
ok: true,
|
||||
},
|
||||
"method as single var label with extra methods specified": {
|
||||
varLabels: []string{"method"},
|
||||
reqMethod: "CUSTOM_METHOD",
|
||||
respStatus: 200,
|
||||
extraMethods: []string{"CUSTOM_METHOD", "CUSTOM_METHOD_1"},
|
||||
wantLabels: prometheus.Labels{"method": "custom_method"},
|
||||
ok: true,
|
||||
},
|
||||
"all labels used with an unknown method and out-of-range code": {
|
||||
varLabels: []string{"code", "method"},
|
||||
reqMethod: "CUSTOM_METHOD",
|
||||
respStatus: 99,
|
||||
wantLabels: prometheus.Labels{"method": "unknown", "code": "unknown"},
|
||||
ok: false,
|
||||
},
|
||||
}
|
||||
checkLabels := func(labels []string) (gotCode, gotMethod bool) {
|
||||
for _, label := range labels {
|
||||
switch label {
|
||||
case "code":
|
||||
gotCode = true
|
||||
case "method":
|
||||
gotMethod = true
|
||||
default:
|
||||
panic("metric partitioned with non-supported labels for this test")
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
equalLabels := func(gotLabels, wantLabels prometheus.Labels) bool {
|
||||
if len(gotLabels) != len(wantLabels) {
|
||||
return false
|
||||
}
|
||||
for ln, lv := range gotLabels {
|
||||
olv, ok := wantLabels[ln]
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
if olv != lv {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
for name, sc := range scenarios {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
if sc.ok {
|
||||
gotCode, gotMethod := checkLabels(sc.varLabels)
|
||||
gotLabels := labels(gotCode, gotMethod, sc.reqMethod, sc.respStatus, sc.extraMethods...)
|
||||
if !equalLabels(gotLabels, sc.wantLabels) {
|
||||
t.Errorf("wanted labels=%v for counter, got code=%v", sc.wantLabels, gotLabels)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func makeInstrumentedHandler(handler http.HandlerFunc, opts ...Option) (http.Handler, *prometheus.Registry) {
|
||||
reg := prometheus.NewRegistry()
|
||||
|
||||
inFlightGauge := prometheus.NewGauge(prometheus.GaugeOpts{
|
||||
|
@ -249,25 +366,43 @@ func TestMiddlewareAPI(t *testing.T) {
|
|||
[]string{},
|
||||
)
|
||||
|
||||
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write([]byte("OK"))
|
||||
})
|
||||
|
||||
reg.MustRegister(inFlightGauge, counter, histVec, responseSize, writeHeaderVec)
|
||||
|
||||
chain := InstrumentHandlerInFlight(inFlightGauge,
|
||||
return InstrumentHandlerInFlight(inFlightGauge,
|
||||
InstrumentHandlerCounter(counter,
|
||||
InstrumentHandlerDuration(histVec,
|
||||
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)
|
||||
w := httptest.NewRecorder()
|
||||
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) {
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue