Merge branch 'master' into beorn7/histogram

This commit is contained in:
beorn7 2021-04-03 17:28:55 +02:00
commit d6983369d2
52 changed files with 2980 additions and 430 deletions

View File

@ -1,15 +1,13 @@
---
version: 2.1 version: 2.1
orbs: orbs:
go: circleci/go@0.2.0 go: circleci/go@0.2.0
prometheus: prometheus/prometheus@0.11.0
jobs: jobs:
test: test:
parameters: parameters:
go_version: go_version:
type: string type: string
run_style: run_style_and_unused:
type: boolean type: boolean
default: false default: false
run_lint: run_lint:
@ -19,56 +17,52 @@ jobs:
type: boolean type: boolean
default: true default: true
docker: docker:
- image: circleci/golang:<< parameters.go_version >> - image: circleci/golang:<< parameters.go_version >>
working_directory: /go/src/github.com/prometheus/client_golang working_directory: /go/src/github.com/prometheus/client_golang
steps: steps:
- checkout - checkout
- when: - when:
condition: << parameters.use_gomod_cache >> condition: << parameters.use_gomod_cache >>
steps: steps:
- go/load-cache: - go/load-cache:
key: v1-go<< parameters.go_version >> key: v1-go<< parameters.go_version >>
- run: make check_license unused test - run: make check_license test
- when: - when:
condition: << parameters.run_lint >> condition: << parameters.run_lint >>
steps: steps:
- run: make lint - run: make lint
- when: - when:
condition: << parameters.run_style >> condition: << parameters.run_style_and_unused >>
steps: steps:
- run: make style - run: make style unused
- when: - when:
condition: << parameters.use_gomod_cache >> condition: << parameters.use_gomod_cache >>
steps: steps:
- go/save-cache: - go/save-cache:
key: v1-go<< parameters.go_version >> key: v1-go<< parameters.go_version >>
- store_test_results: - store_test_results:
path: test-results path: test-results
workflows: workflows:
version: 2 version: 2
client_golang: client_golang:
jobs: jobs:
# Refer to README.md for the currently supported versions. # Refer to README.md for the currently supported versions.
- test: - test:
name: go-1-9 name: go-1-13
go_version: "1.9" go_version: "1.13"
use_gomod_cache: false run_lint: true
- test: - test:
name: go-1-10 name: go-1-14
go_version: "1.10" go_version: "1.14"
use_gomod_cache: false run_lint: true
- test: - test:
name: go-1-11 name: go-1-15
go_version: "1.11" go_version: "1.15"
run_lint: true run_lint: true
- test: - test:
name: go-1-12 name: go-1-16
go_version: "1.12" go_version: "1.16"
run_lint: true run_lint: true
- test: # Style and unused/missing packages are only checked against
name: go-1-13 # the latest supported Go version.
go_version: "1.13" run_style_and_unused: true
run_lint: true
# Style is only checked against the latest supported Go version.
run_style: true

View File

@ -1,14 +0,0 @@
language: go
go:
- 1.9.x # See README.md for current minimum version.
- 1.10.x
- 1.11.x
- 1.12.x
- 1.13.x
script:
- make check_license unused test-short
- if [[ ! $TRAVIS_GO_VERSION =~ ^1\.(9|10)\. ]]; then make lint; fi
# Style is only checked against the latest supported Go version.
- if [[ $TRAVIS_GO_VERSION =~ ^1\.(13)\. ]]; then make style; fi

View File

@ -1,3 +1,46 @@
## 1.10.0 / 2021-03-18
* [CHANGE] Minimum required Go version is now 1.13.
* [CHANGE] API client: Add matchers to `LabelNames` and `LabesValues`. #828
* [FEATURE] API client: Add buildinfo call. #841
* [BUGFIX] Fix build on riscv64. #833
## 1.9.0 / 2020-12-17
* [FEATURE] `NewPidFileFn` helper to create process collectors for processes whose PID is read from a file. #804
* [BUGFIX] promhttp: Prevent endless loop in `InstrumentHandler...` middlewares with invalid metric or label names. #823
## 1.8.0 / 2020-10-15
* [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
* [ENHANCEMENT] API client: Enable GET fallback on status code 501. #802
* [ENHANCEMENT] Remove `Metric` references after reslicing to free up more memory. #784
## 1.7.1 / 2020-06-23
* [BUGFIX] API client: Actually propagate start/end parameters of `LabelNames` and `LabelValues`. #771
## 1.7.0 / 2020-06-17
* [CHANGE] API client: Add start/end parameters to `LabelNames` and `LabelValues`. #767
* [FEATURE] testutil: Add `GatherAndCount` and enable filtering in `CollectAndCount` #753
* [FEATURE] API client: Add support for `status` and `runtimeinfo` endpoints. #755
* [ENHANCEMENT] Wrapping `nil` with a `WrapRegistererWith...` function creates a no-op `Registerer`. #764
* [ENHANCEMENT] promlint: Allow Kelvin as a base unit for cases like color temperature. #761
* [BUGFIX] push: Properly handle empty job and label values. #752
## 1.6.0 / 2020-04-28
* [FEATURE] testutil: Add lint checks for metrics, including a sub-package `promlint` to expose the linter engine for external usage. #739 #743
* [ENHANCEMENT] API client: Improve error messages. #731
* [BUGFIX] process collector: Fix `process_resident_memory_bytes` on 32bit MS Windows. #734
## 1.5.1 / 2020-03-14
* [BUGFIX] promhttp: Remove another superfluous `WriteHeader` call. #726
## 1.5.0 / 2020-03-03 ## 1.5.0 / 2020-03-03
* [FEATURE] promauto: Add a factory to allow automatic registration with a local registry. #713 * [FEATURE] promauto: Add a factory to allow automatic registration with a local registry. #713

3
CODE_OF_CONDUCT.md Normal file
View File

@ -0,0 +1,3 @@
## Prometheus Community Code of Conduct
Prometheus follows the [CNCF Code of Conduct](https://github.com/cncf/foundation/blob/master/code-of-conduct.md).

View File

@ -13,13 +13,6 @@
include Makefile.common include Makefile.common
# http.CloseNotifier is deprecated but we don't want to remove support
# from client_golang to not break anybody still using it.
STATICCHECK_IGNORE = \
github.com/prometheus/client_golang/prometheus/promhttp/delegator*.go:SA1019 \
github.com/prometheus/client_golang/prometheus/promhttp/instrument_server_test.go:SA1019 \
github.com/prometheus/client_golang/prometheus/http.go:SA1019
.PHONY: test .PHONY: test
test: deps common-test test: deps common-test

View File

@ -78,12 +78,12 @@ ifneq ($(shell which gotestsum),)
endif endif
endif endif
PROMU_VERSION ?= 0.5.0 PROMU_VERSION ?= 0.11.1
PROMU_URL := https://github.com/prometheus/promu/releases/download/v$(PROMU_VERSION)/promu-$(PROMU_VERSION).$(GO_BUILD_PLATFORM).tar.gz PROMU_URL := https://github.com/prometheus/promu/releases/download/v$(PROMU_VERSION)/promu-$(PROMU_VERSION).$(GO_BUILD_PLATFORM).tar.gz
GOLANGCI_LINT := GOLANGCI_LINT :=
GOLANGCI_LINT_OPTS ?= GOLANGCI_LINT_OPTS ?=
GOLANGCI_LINT_VERSION ?= v1.18.0 GOLANGCI_LINT_VERSION ?= v1.36.0
# golangci-lint only supports linux, darwin and windows platforms on i386/amd64. # golangci-lint only supports linux, darwin and windows platforms on i386/amd64.
# windows isn't included here because of the path separator being different. # windows isn't included here because of the path separator being different.
ifeq ($(GOHOSTOS),$(filter $(GOHOSTOS),linux darwin)) ifeq ($(GOHOSTOS),$(filter $(GOHOSTOS),linux darwin))
@ -150,6 +150,17 @@ else
$(GO) get $(GOOPTS) -t ./... $(GO) get $(GOOPTS) -t ./...
endif endif
.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; \
done
GO111MODULE=$(GO111MODULE) $(GO) mod tidy
ifneq (,$(wildcard vendor))
GO111MODULE=$(GO111MODULE) $(GO) mod vendor
endif
.PHONY: common-test-short .PHONY: common-test-short
common-test-short: $(GOTEST_DIR) common-test-short: $(GOTEST_DIR)
@echo ">> running short tests" @echo ">> running short tests"
@ -234,10 +245,12 @@ common-docker-publish: $(PUBLISH_DOCKER_ARCHS)
$(PUBLISH_DOCKER_ARCHS): common-docker-publish-%: $(PUBLISH_DOCKER_ARCHS): common-docker-publish-%:
docker push "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$*:$(DOCKER_IMAGE_TAG)" docker push "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$*:$(DOCKER_IMAGE_TAG)"
DOCKER_MAJOR_VERSION_TAG = $(firstword $(subst ., ,$(shell cat VERSION)))
.PHONY: common-docker-tag-latest $(TAG_DOCKER_ARCHS) .PHONY: common-docker-tag-latest $(TAG_DOCKER_ARCHS)
common-docker-tag-latest: $(TAG_DOCKER_ARCHS) common-docker-tag-latest: $(TAG_DOCKER_ARCHS)
$(TAG_DOCKER_ARCHS): common-docker-tag-latest-%: $(TAG_DOCKER_ARCHS): common-docker-tag-latest-%:
docker tag "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$*:$(DOCKER_IMAGE_TAG)" "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$*:latest" docker tag "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$*:$(DOCKER_IMAGE_TAG)" "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$*:latest"
docker tag "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$*:$(DOCKER_IMAGE_TAG)" "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$*:v$(DOCKER_MAJOR_VERSION_TAG)"
.PHONY: common-docker-manifest .PHONY: common-docker-manifest
common-docker-manifest: common-docker-manifest:

View File

@ -9,7 +9,7 @@ This is the [Go](http://golang.org) client library for
instrumenting application code, and one for creating clients that talk to the instrumenting application code, and one for creating clients that talk to the
Prometheus HTTP API. Prometheus HTTP API.
__This library requires Go1.9 or later.__ The minimum required patch releases for older Go versions are Go1.9.7 and Go1.10.3. __This library requires Go1.13 or later.__
## Important note about releases and stability ## Important note about releases and stability

6
SECURITY.md Normal file
View File

@ -0,0 +1,6 @@
# Reporting a security issue
The Prometheus security policy, including how to report vulnerabilities, can be
found here:
https://prometheus.io/docs/operating/security/

View File

@ -1 +1 @@
1.5.0 1.10.0

View File

@ -117,8 +117,6 @@ func marshalPointJSONIsEmpty(ptr unsafe.Pointer) bool {
} }
const ( const (
statusAPIError = 422
apiPrefix = "/api/v1" apiPrefix = "/api/v1"
epAlerts = apiPrefix + "/alerts" epAlerts = apiPrefix + "/alerts"
@ -137,6 +135,9 @@ const (
epCleanTombstones = apiPrefix + "/admin/tsdb/clean_tombstones" epCleanTombstones = apiPrefix + "/admin/tsdb/clean_tombstones"
epConfig = apiPrefix + "/status/config" epConfig = apiPrefix + "/status/config"
epFlags = apiPrefix + "/status/flags" epFlags = apiPrefix + "/status/flags"
epBuildinfo = apiPrefix + "/status/buildinfo"
epRuntimeinfo = apiPrefix + "/status/runtimeinfo"
epTSDB = apiPrefix + "/status/tsdb"
) )
// AlertState models the state of an alert. // AlertState models the state of an alert.
@ -230,14 +231,18 @@ type API interface {
DeleteSeries(ctx context.Context, matches []string, startTime time.Time, endTime time.Time) error DeleteSeries(ctx context.Context, matches []string, startTime time.Time, endTime time.Time) error
// Flags returns the flag values that Prometheus was launched with. // Flags returns the flag values that Prometheus was launched with.
Flags(ctx context.Context) (FlagsResult, error) Flags(ctx context.Context) (FlagsResult, error)
// LabelNames returns all the unique label names present in the block in sorted order. // LabelNames returns the unique label names present in the block in sorted order by given time range and matchers.
LabelNames(ctx context.Context) ([]string, Warnings, error) LabelNames(ctx context.Context, matches []string, startTime time.Time, endTime time.Time) ([]string, Warnings, error)
// LabelValues performs a query for the values of the given label. // LabelValues performs a query for the values of the given label, time range and matchers.
LabelValues(ctx context.Context, label string) (model.LabelValues, Warnings, error) LabelValues(ctx context.Context, label string, matches []string, startTime time.Time, endTime time.Time) (model.LabelValues, Warnings, error)
// Query performs a query for the given time. // Query performs a query for the given time.
Query(ctx context.Context, query string, ts time.Time) (model.Value, Warnings, error) Query(ctx context.Context, query string, ts time.Time) (model.Value, Warnings, error)
// QueryRange performs a query for the given range. // QueryRange performs a query for the given range.
QueryRange(ctx context.Context, query string, r Range) (model.Value, Warnings, error) QueryRange(ctx context.Context, query string, r Range) (model.Value, Warnings, 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 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 time.Time, endTime time.Time) ([]model.LabelSet, Warnings, error)
// Snapshot creates a snapshot of all current data into snapshots/<datetime>-<rand> // Snapshot creates a snapshot of all current data into snapshots/<datetime>-<rand>
@ -251,6 +256,8 @@ type API interface {
TargetsMetadata(ctx context.Context, matchTarget string, metric string, limit string) ([]MetricMetadata, error) TargetsMetadata(ctx context.Context, matchTarget string, metric string, limit string) ([]MetricMetadata, error)
// Metadata returns metadata about metrics currently scraped by the metric name. // Metadata returns metadata about metrics currently scraped by the metric name.
Metadata(ctx context.Context, metric string, limit string) (map[string][]Metadata, error) Metadata(ctx context.Context, metric string, limit string) (map[string][]Metadata, error)
// TSDB returns the cardinality statistics.
TSDB(ctx context.Context) (TSDBResult, error)
} }
// AlertsResult contains the result from querying the alerts endpoint. // AlertsResult contains the result from querying the alerts endpoint.
@ -277,6 +284,32 @@ type ConfigResult struct {
// FlagsResult contains the result from querying the flag endpoint. // FlagsResult contains the result from querying the flag endpoint.
type FlagsResult map[string]string type FlagsResult map[string]string
// BuildinfoResult contains the results from querying the buildinfo endpoint.
type BuildinfoResult struct {
Version string `json:"version"`
Revision string `json:"revision"`
Branch string `json:"branch"`
BuildUser string `json:"buildUser"`
BuildDate string `json:"buildDate"`
GoVersion string `json:"goVersion"`
}
// RuntimeinfoResult contains the result from querying the runtimeinfo endpoint.
type RuntimeinfoResult struct {
StartTime time.Time `json:"startTime"`
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"`
GOGC string `json:"GOGC"`
GODEBUG string `json:"GODEBUG"`
StorageRetention string `json:"storageRetention"`
}
// SnapshotResult contains the result from querying the snapshot endpoint. // SnapshotResult contains the result from querying the snapshot endpoint.
type SnapshotResult struct { type SnapshotResult struct {
Name string `json:"name"` Name string `json:"name"`
@ -385,6 +418,20 @@ type queryResult struct {
v model.Value v model.Value
} }
// TSDBResult contains the result from querying the tsdb endpoint.
type TSDBResult struct {
SeriesCountByMetricName []Stat `json:"seriesCountByMetricName"`
LabelValueCountByLabelName []Stat `json:"labelValueCountByLabelName"`
MemoryInBytesByLabelName []Stat `json:"memoryInBytesByLabelName"`
SeriesCountByLabelValuePair []Stat `json:"seriesCountByLabelValuePair"`
}
// Stat models information about statistic value.
type Stat struct {
Name string `json:"name"`
Value uint64 `json:"value"`
}
func (rg *RuleGroup) UnmarshalJSON(b []byte) error { func (rg *RuleGroup) UnmarshalJSON(b []byte) error {
v := struct { v := struct {
Name string `json:"name"` Name string `json:"name"`
@ -640,8 +687,51 @@ func (h *httpAPI) Flags(ctx context.Context) (FlagsResult, error) {
return res, json.Unmarshal(body, &res) return res, json.Unmarshal(body, &res)
} }
func (h *httpAPI) LabelNames(ctx context.Context) ([]string, Warnings, error) { func (h *httpAPI) Buildinfo(ctx context.Context) (BuildinfoResult, error) {
u := h.client.URL(epBuildinfo, nil)
req, err := http.NewRequest(http.MethodGet, u.String(), nil)
if err != nil {
return BuildinfoResult{}, err
}
_, body, _, err := h.client.Do(ctx, req)
if err != nil {
return BuildinfoResult{}, err
}
var res BuildinfoResult
return res, json.Unmarshal(body, &res)
}
func (h *httpAPI) Runtimeinfo(ctx context.Context) (RuntimeinfoResult, error) {
u := h.client.URL(epRuntimeinfo, nil)
req, err := http.NewRequest(http.MethodGet, u.String(), nil)
if err != nil {
return RuntimeinfoResult{}, err
}
_, body, _, err := h.client.Do(ctx, req)
if err != nil {
return RuntimeinfoResult{}, err
}
var res RuntimeinfoResult
return res, json.Unmarshal(body, &res)
}
func (h *httpAPI) LabelNames(ctx context.Context, matches []string, startTime time.Time, endTime time.Time) ([]string, Warnings, error) {
u := h.client.URL(epLabels, nil) u := h.client.URL(epLabels, nil)
q := u.Query()
q.Set("start", formatTime(startTime))
q.Set("end", formatTime(endTime))
for _, m := range matches {
q.Add("match[]", m)
}
u.RawQuery = q.Encode()
req, err := http.NewRequest(http.MethodGet, u.String(), nil) req, err := http.NewRequest(http.MethodGet, u.String(), nil)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
@ -654,8 +744,17 @@ func (h *httpAPI) LabelNames(ctx context.Context) ([]string, Warnings, error) {
return labelNames, w, json.Unmarshal(body, &labelNames) return labelNames, w, json.Unmarshal(body, &labelNames)
} }
func (h *httpAPI) LabelValues(ctx context.Context, label string) (model.LabelValues, Warnings, error) { func (h *httpAPI) LabelValues(ctx context.Context, label string, matches []string, startTime time.Time, endTime time.Time) (model.LabelValues, Warnings, error) {
u := h.client.URL(epLabelValues, map[string]string{"name": label}) u := h.client.URL(epLabelValues, map[string]string{"name": label})
q := u.Query()
q.Set("start", formatTime(startTime))
q.Set("end", formatTime(endTime))
for _, m := range matches {
q.Add("match[]", m)
}
u.RawQuery = q.Encode()
req, err := http.NewRequest(http.MethodGet, u.String(), nil) req, err := http.NewRequest(http.MethodGet, u.String(), nil)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
@ -835,6 +934,24 @@ func (h *httpAPI) Metadata(ctx context.Context, metric string, limit string) (ma
return res, json.Unmarshal(body, &res) return res, json.Unmarshal(body, &res)
} }
func (h *httpAPI) TSDB(ctx context.Context) (TSDBResult, error) {
u := h.client.URL(epTSDB, nil)
req, err := http.NewRequest(http.MethodGet, u.String(), nil)
if err != nil {
return TSDBResult{}, err
}
_, body, _, err := h.client.Do(ctx, req)
if err != nil {
return TSDBResult{}, err
}
var res TSDBResult
return res, json.Unmarshal(body, &res)
}
// Warnings is an array of non critical errors // Warnings is an array of non critical errors
type Warnings []string type Warnings []string
@ -860,7 +977,7 @@ type apiResponse struct {
func apiError(code int) bool { func apiError(code int) bool {
// These are the codes that Prometheus sends when it returns an error. // These are the codes that Prometheus sends when it returns an error.
return code == statusAPIError || code == http.StatusBadRequest return code == http.StatusUnprocessableEntity || code == http.StatusBadRequest
} }
func errorTypeAndMsgFor(resp *http.Response) (ErrorType, string) { func errorTypeAndMsgFor(resp *http.Response) (ErrorType, string) {
@ -905,14 +1022,14 @@ func (h *apiClientImpl) Do(ctx context.Context, req *http.Request) (*http.Respon
} }
} }
if apiError(code) != (result.Status == "error") { if apiError(code) && result.Status == "success" {
err = &Error{ err = &Error{
Type: ErrBadResponse, Type: ErrBadResponse,
Msg: "inconsistent body for response code", Msg: "inconsistent body for response code",
} }
} }
if apiError(code) && result.Status == "error" { if result.Status == "error" {
err = &Error{ err = &Error{
Type: result.ErrorType, Type: result.ErrorType,
Msg: result.Error, Msg: result.Error,
@ -923,7 +1040,8 @@ func (h *apiClientImpl) Do(ctx context.Context, req *http.Request) (*http.Respon
} }
// DoGetFallback will attempt to do the request as-is, and on a 405 it will fallback to a GET request. // 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) { 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())) req, err := http.NewRequest(http.MethodPost, u.String(), strings.NewReader(args.Encode()))
if err != nil { if err != nil {
@ -932,7 +1050,7 @@ func (h *apiClientImpl) DoGetFallback(ctx context.Context, u *url.URL, args url.
req.Header.Set("Content-Type", "application/x-www-form-urlencoded") req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
resp, body, warnings, err := h.Do(ctx, req) resp, body, warnings, err := h.Do(ctx, req)
if resp != nil && resp.StatusCode == http.StatusMethodNotAllowed { if resp != nil && (resp.StatusCode == http.StatusMethodNotAllowed || resp.StatusCode == http.StatusNotImplemented) {
u.RawQuery = args.Encode() u.RawQuery = args.Encode()
req, err = http.NewRequest(http.MethodGet, u.String(), nil) req, err = http.NewRequest(http.MethodGet, u.String(), nil)
if err != nil { if err != nil {

View File

@ -84,7 +84,7 @@ func (c *apiTestClient) Do(ctx context.Context, req *http.Request) (*http.Respon
if test.inStatusCode != 0 { if test.inStatusCode != 0 {
resp.StatusCode = test.inStatusCode resp.StatusCode = test.inStatusCode
} else if test.inErr != nil { } else if test.inErr != nil {
resp.StatusCode = statusAPIError resp.StatusCode = http.StatusUnprocessableEntity
} else { } else {
resp.StatusCode = http.StatusOK resp.StatusCode = http.StatusOK
} }
@ -144,15 +144,29 @@ func TestAPIs(t *testing.T) {
} }
} }
doLabelNames := func(label string) func() (interface{}, Warnings, error) { doBuildinfo := func() func() (interface{}, Warnings, error) {
return func() (interface{}, Warnings, error) { return func() (interface{}, Warnings, error) {
return promAPI.LabelNames(context.Background()) v, err := promAPI.Buildinfo(context.Background())
return v, nil, err
} }
} }
doLabelValues := func(label string) func() (interface{}, Warnings, error) { doRuntimeinfo := func() func() (interface{}, Warnings, error) {
return func() (interface{}, Warnings, error) { return func() (interface{}, Warnings, error) {
return promAPI.LabelValues(context.Background(), label) v, err := promAPI.Runtimeinfo(context.Background())
return v, nil, err
}
}
doLabelNames := func(matches []string) func() (interface{}, Warnings, error) {
return func() (interface{}, Warnings, error) {
return promAPI.LabelNames(context.Background(), matches, time.Now().Add(-100*time.Hour), time.Now())
}
}
doLabelValues := func(matches []string, label string) func() (interface{}, Warnings, error) {
return func() (interface{}, Warnings, error) {
return promAPI.LabelValues(context.Background(), label, matches, time.Now().Add(-100*time.Hour), time.Now())
} }
} }
@ -209,6 +223,13 @@ func TestAPIs(t *testing.T) {
} }
} }
doTSDB := func() func() (interface{}, Warnings, error) {
return func() (interface{}, Warnings, error) {
v, err := promAPI.TSDB(context.Background())
return v, nil, err
}
}
queryTests := []apiTest{ queryTests := []apiTest{
{ {
do: doQuery("2", testTime), do: doQuery("2", testTime),
@ -345,14 +366,14 @@ func TestAPIs(t *testing.T) {
}, },
{ {
do: doLabelNames("mylabel"), do: doLabelNames(nil),
inRes: []string{"val1", "val2"}, inRes: []string{"val1", "val2"},
reqMethod: "GET", reqMethod: "GET",
reqPath: "/api/v1/labels", reqPath: "/api/v1/labels",
res: []string{"val1", "val2"}, res: []string{"val1", "val2"},
}, },
{ {
do: doLabelNames("mylabel"), do: doLabelNames(nil),
inRes: []string{"val1", "val2"}, inRes: []string{"val1", "val2"},
inWarnings: []string{"a"}, inWarnings: []string{"a"},
reqMethod: "GET", reqMethod: "GET",
@ -362,14 +383,14 @@ func TestAPIs(t *testing.T) {
}, },
{ {
do: doLabelNames("mylabel"), do: doLabelNames(nil),
inErr: fmt.Errorf("some error"), inErr: fmt.Errorf("some error"),
reqMethod: "GET", reqMethod: "GET",
reqPath: "/api/v1/labels", reqPath: "/api/v1/labels",
err: fmt.Errorf("some error"), err: fmt.Errorf("some error"),
}, },
{ {
do: doLabelNames("mylabel"), do: doLabelNames(nil),
inErr: fmt.Errorf("some error"), inErr: fmt.Errorf("some error"),
inWarnings: []string{"a"}, inWarnings: []string{"a"},
reqMethod: "GET", reqMethod: "GET",
@ -377,16 +398,24 @@ func TestAPIs(t *testing.T) {
err: fmt.Errorf("some error"), err: fmt.Errorf("some error"),
warnings: []string{"a"}, warnings: []string{"a"},
}, },
{
do: doLabelNames([]string{"up"}),
inRes: []string{"val1", "val2"},
reqMethod: "GET",
reqPath: "/api/v1/labels",
reqParam: url.Values{"match[]": {"up"}},
res: []string{"val1", "val2"},
},
{ {
do: doLabelValues("mylabel"), do: doLabelValues(nil, "mylabel"),
inRes: []string{"val1", "val2"}, inRes: []string{"val1", "val2"},
reqMethod: "GET", reqMethod: "GET",
reqPath: "/api/v1/label/mylabel/values", reqPath: "/api/v1/label/mylabel/values",
res: model.LabelValues{"val1", "val2"}, res: model.LabelValues{"val1", "val2"},
}, },
{ {
do: doLabelValues("mylabel"), do: doLabelValues(nil, "mylabel"),
inRes: []string{"val1", "val2"}, inRes: []string{"val1", "val2"},
inWarnings: []string{"a"}, inWarnings: []string{"a"},
reqMethod: "GET", reqMethod: "GET",
@ -396,14 +425,14 @@ func TestAPIs(t *testing.T) {
}, },
{ {
do: doLabelValues("mylabel"), do: doLabelValues(nil, "mylabel"),
inErr: fmt.Errorf("some error"), inErr: fmt.Errorf("some error"),
reqMethod: "GET", reqMethod: "GET",
reqPath: "/api/v1/label/mylabel/values", reqPath: "/api/v1/label/mylabel/values",
err: fmt.Errorf("some error"), err: fmt.Errorf("some error"),
}, },
{ {
do: doLabelValues("mylabel"), do: doLabelValues(nil, "mylabel"),
inErr: fmt.Errorf("some error"), inErr: fmt.Errorf("some error"),
inWarnings: []string{"a"}, inWarnings: []string{"a"},
reqMethod: "GET", reqMethod: "GET",
@ -411,6 +440,14 @@ func TestAPIs(t *testing.T) {
err: fmt.Errorf("some error"), err: fmt.Errorf("some error"),
warnings: []string{"a"}, warnings: []string{"a"},
}, },
{
do: doLabelValues([]string{"up"}, "mylabel"),
inRes: []string{"val1", "val2"},
reqMethod: "GET",
reqPath: "/api/v1/label/mylabel/values",
reqParam: url.Values{"match[]": {"up"}},
res: model.LabelValues{"val1", "val2"},
},
{ {
do: doSeries("up", testTime.Add(-time.Minute), testTime), do: doSeries("up", testTime.Add(-time.Minute), testTime),
@ -605,6 +642,78 @@ func TestAPIs(t *testing.T) {
err: fmt.Errorf("some error"), err: fmt.Errorf("some error"),
}, },
{
do: doBuildinfo(),
reqMethod: "GET",
reqPath: "/api/v1/status/buildinfo",
inErr: fmt.Errorf("some error"),
err: fmt.Errorf("some error"),
},
{
do: doBuildinfo(),
reqMethod: "GET",
reqPath: "/api/v1/status/buildinfo",
inRes: map[string]interface{}{
"version": "2.23.0",
"revision": "26d89b4b0776fe4cd5a3656dfa520f119a375273",
"branch": "HEAD",
"buildUser": "root@37609b3a0a21",
"buildDate": "20201126-10:56:17",
"goVersion": "go1.15.5",
},
res: BuildinfoResult{
Version: "2.23.0",
Revision: "26d89b4b0776fe4cd5a3656dfa520f119a375273",
Branch: "HEAD",
BuildUser: "root@37609b3a0a21",
BuildDate: "20201126-10:56:17",
GoVersion: "go1.15.5",
},
},
{
do: doRuntimeinfo(),
reqMethod: "GET",
reqPath: "/api/v1/status/runtimeinfo",
inErr: fmt.Errorf("some error"),
err: fmt.Errorf("some error"),
},
{
do: doRuntimeinfo(),
reqMethod: "GET",
reqPath: "/api/v1/status/runtimeinfo",
inRes: map[string]interface{}{
"startTime": "2020-05-18T15:52:53.4503113Z",
"CWD": "/prometheus",
"reloadConfigSuccess": true,
"lastConfigTime": "2020-05-18T15:52:56Z",
"chunkCount": 72692,
"timeSeriesCount": 18476,
"corruptionCount": 0,
"goroutineCount": 217,
"GOMAXPROCS": 2,
"GOGC": "100",
"GODEBUG": "allocfreetrace",
"storageRetention": "1d",
},
res: RuntimeinfoResult{
StartTime: time.Date(2020, 5, 18, 15, 52, 53, 450311300, time.UTC),
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,
GOGC: "100",
GODEBUG: "allocfreetrace",
StorageRetention: "1d",
},
},
{ {
do: doAlertManagers(), do: doAlertManagers(),
reqMethod: "GET", reqMethod: "GET",
@ -904,6 +1013,72 @@ func TestAPIs(t *testing.T) {
}, },
err: fmt.Errorf("some error"), err: fmt.Errorf("some error"),
}, },
{
do: doTSDB(),
reqMethod: "GET",
reqPath: "/api/v1/status/tsdb",
inErr: fmt.Errorf("some error"),
err: fmt.Errorf("some error"),
},
{
do: doTSDB(),
reqMethod: "GET",
reqPath: "/api/v1/status/tsdb",
inRes: map[string]interface{}{
"seriesCountByMetricName": []interface{}{
map[string]interface{}{
"name": "kubelet_http_requests_duration_seconds_bucket",
"value": 1000,
},
},
"labelValueCountByLabelName": []interface{}{
map[string]interface{}{
"name": "__name__",
"value": 200,
},
},
"memoryInBytesByLabelName": []interface{}{
map[string]interface{}{
"name": "id",
"value": 4096,
},
},
"seriesCountByLabelValuePair": []interface{}{
map[string]interface{}{
"name": "job=kubelet",
"value": 30000,
},
},
},
res: TSDBResult{
SeriesCountByMetricName: []Stat{
{
Name: "kubelet_http_requests_duration_seconds_bucket",
Value: 1000,
},
},
LabelValueCountByLabelName: []Stat{
{
Name: "__name__",
Value: 200,
},
},
MemoryInBytesByLabelName: []Stat{
{
Name: "id",
Value: 4096,
},
},
SeriesCountByLabelValuePair: []Stat{
{
Name: "job=kubelet",
Value: 30000,
},
},
},
},
} }
var tests []apiTest var tests []apiTest
@ -996,7 +1171,7 @@ func (c *testClient) Do(ctx context.Context, req *http.Request) (*http.Response,
func TestAPIClientDo(t *testing.T) { func TestAPIClientDo(t *testing.T) {
tests := []apiClientTest{ tests := []apiClientTest{
{ {
code: statusAPIError, code: http.StatusUnprocessableEntity,
response: &apiResponse{ response: &apiResponse{
Status: "error", Status: "error",
Data: json.RawMessage(`null`), Data: json.RawMessage(`null`),
@ -1010,7 +1185,7 @@ func TestAPIClientDo(t *testing.T) {
expectedBody: `null`, expectedBody: `null`,
}, },
{ {
code: statusAPIError, code: http.StatusUnprocessableEntity,
response: &apiResponse{ response: &apiResponse{
Status: "error", Status: "error",
Data: json.RawMessage(`"test"`), Data: json.RawMessage(`"test"`),
@ -1055,7 +1230,7 @@ func TestAPIClientDo(t *testing.T) {
}, },
}, },
{ {
code: statusAPIError, code: http.StatusUnprocessableEntity,
response: "bad json", response: "bad json",
expectedErr: &Error{ expectedErr: &Error{
Type: ErrBadResponse, Type: ErrBadResponse,
@ -1063,7 +1238,7 @@ func TestAPIClientDo(t *testing.T) {
}, },
}, },
{ {
code: statusAPIError, code: http.StatusUnprocessableEntity,
response: &apiResponse{ response: &apiResponse{
Status: "success", Status: "success",
Data: json.RawMessage(`"test"`), Data: json.RawMessage(`"test"`),
@ -1074,7 +1249,7 @@ func TestAPIClientDo(t *testing.T) {
}, },
}, },
{ {
code: statusAPIError, code: http.StatusUnprocessableEntity,
response: &apiResponse{ response: &apiResponse{
Status: "success", Status: "success",
Data: json.RawMessage(`"test"`), Data: json.RawMessage(`"test"`),
@ -1095,8 +1270,8 @@ func TestAPIClientDo(t *testing.T) {
Error: "timed out", Error: "timed out",
}, },
expectedErr: &Error{ expectedErr: &Error{
Type: ErrBadResponse, Type: ErrTimeout,
Msg: "inconsistent body for response code", Msg: "timed out",
}, },
}, },
{ {
@ -1109,8 +1284,8 @@ func TestAPIClientDo(t *testing.T) {
Warnings: []string{"a"}, Warnings: []string{"a"},
}, },
expectedErr: &Error{ expectedErr: &Error{
Type: ErrBadResponse, Type: ErrTimeout,
Msg: "inconsistent body for response code", Msg: "timed out",
}, },
expectedWarnings: []string{"a"}, expectedWarnings: []string{"a"},
}, },
@ -1325,12 +1500,19 @@ func TestDoGetFallback(t *testing.T) {
body, _ := json.Marshal(apiResp) body, _ := json.Marshal(apiResp)
if req.Method == http.MethodPost { if req.Method == http.MethodPost {
if req.URL.Path == "/blockPost" { if req.URL.Path == "/blockPost405" {
http.Error(w, string(body), http.StatusMethodNotAllowed) http.Error(w, string(body), http.StatusMethodNotAllowed)
return return
} }
} }
if req.Method == http.MethodPost {
if req.URL.Path == "/blockPost501" {
http.Error(w, string(body), http.StatusNotImplemented)
return
}
}
w.Write(body) w.Write(body)
})) }))
// Close the server when test finishes. // Close the server when test finishes.
@ -1361,8 +1543,24 @@ func TestDoGetFallback(t *testing.T) {
t.Fatalf("Mismatch in values") t.Fatalf("Mismatch in values")
} }
// Do a fallbcak to a get. // Do a fallback to a get on 405.
u.Path = "/blockPost" u.Path = "/blockPost405"
_, b, _, err = api.DoGetFallback(context.TODO(), u, v)
if err != nil {
t.Fatalf("Error doing local request: %v", err)
}
if err := json.Unmarshal(b, resp); err != nil {
t.Fatal(err)
}
if resp.Method != http.MethodGet {
t.Fatalf("Mismatch method")
}
if resp.Values != v.Encode() {
t.Fatalf("Mismatch in values")
}
// Do a fallback to a get on 501.
u.Path = "/blockPost501"
_, b, _, err = api.DoGetFallback(context.TODO(), u, v) _, b, _, err = api.DoGetFallback(context.TODO(), u, v)
if err != nil { if err != nil {
t.Fatalf("Error doing local request: %v", err) t.Fatalf("Error doing local request: %v", err)

View File

@ -18,11 +18,13 @@ package v1_test
import ( import (
"context" "context"
"fmt" "fmt"
"net/http"
"os" "os"
"time" "time"
"github.com/prometheus/client_golang/api" "github.com/prometheus/client_golang/api"
v1 "github.com/prometheus/client_golang/api/prometheus/v1" v1 "github.com/prometheus/client_golang/api/prometheus/v1"
"github.com/prometheus/common/config"
) )
func ExampleAPI_query() { func ExampleAPI_query() {
@ -76,6 +78,118 @@ func ExampleAPI_queryRange() {
fmt.Printf("Result:\n%v\n", result) fmt.Printf("Result:\n%v\n", result)
} }
type userAgentRoundTripper struct {
name string
rt http.RoundTripper
}
// RoundTrip implements the http.RoundTripper interface.
func (u userAgentRoundTripper) RoundTrip(r *http.Request) (*http.Response, error) {
if r.UserAgent() == "" {
// The specification of http.RoundTripper says that it shouldn't mutate
// the request so make a copy of req.Header since this is all that is
// modified.
r2 := new(http.Request)
*r2 = *r
r2.Header = make(http.Header)
for k, s := range r.Header {
r2.Header[k] = s
}
r2.Header.Set("User-Agent", u.name)
r = r2
}
return u.rt.RoundTrip(r)
}
func ExampleAPI_queryRangeWithUserAgent() {
client, err := api.NewClient(api.Config{
Address: "http://demo.robustperception.io:9090",
RoundTripper: userAgentRoundTripper{name: "Client-Golang", rt: api.DefaultRoundTripper},
})
if err != nil {
fmt.Printf("Error creating client: %v\n", err)
os.Exit(1)
}
v1api := v1.NewAPI(client)
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
r := v1.Range{
Start: time.Now().Add(-time.Hour),
End: time.Now(),
Step: time.Minute,
}
result, warnings, err := v1api.QueryRange(ctx, "rate(prometheus_tsdb_head_samples_appended_total[5m])", r)
if err != nil {
fmt.Printf("Error querying Prometheus: %v\n", err)
os.Exit(1)
}
if len(warnings) > 0 {
fmt.Printf("Warnings: %v\n", warnings)
}
fmt.Printf("Result:\n%v\n", result)
}
func ExampleAPI_queryRangeWithBasicAuth() {
client, err := api.NewClient(api.Config{
Address: "http://demo.robustperception.io:9090",
// We can use amazing github.com/prometheus/common/config helper!
RoundTripper: config.NewBasicAuthRoundTripper("me", "defintely_me", "", api.DefaultRoundTripper),
})
if err != nil {
fmt.Printf("Error creating client: %v\n", err)
os.Exit(1)
}
v1api := v1.NewAPI(client)
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
r := v1.Range{
Start: time.Now().Add(-time.Hour),
End: time.Now(),
Step: time.Minute,
}
result, warnings, err := v1api.QueryRange(ctx, "rate(prometheus_tsdb_head_samples_appended_total[5m])", r)
if err != nil {
fmt.Printf("Error querying Prometheus: %v\n", err)
os.Exit(1)
}
if len(warnings) > 0 {
fmt.Printf("Warnings: %v\n", warnings)
}
fmt.Printf("Result:\n%v\n", result)
}
func ExampleAPI_queryRangeWithAuthBearerToken() {
client, err := api.NewClient(api.Config{
Address: "http://demo.robustperception.io:9090",
// We can use amazing github.com/prometheus/common/config helper!
RoundTripper: config.NewAuthorizationCredentialsRoundTripper("Bearer", "secret_token", api.DefaultRoundTripper),
})
if err != nil {
fmt.Printf("Error creating client: %v\n", err)
os.Exit(1)
}
v1api := v1.NewAPI(client)
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
r := v1.Range{
Start: time.Now().Add(-time.Hour),
End: time.Now(),
Step: time.Minute,
}
result, warnings, err := v1api.QueryRange(ctx, "rate(prometheus_tsdb_head_samples_appended_total[5m])", r)
if err != nil {
fmt.Printf("Error querying Prometheus: %v\n", err)
os.Exit(1)
}
if len(warnings) > 0 {
fmt.Printf("Warnings: %v\n", warnings)
}
fmt.Printf("Result:\n%v\n", result)
}
func ExampleAPI_series() { func ExampleAPI_series() {
client, err := api.NewClient(api.Config{ client, err := api.NewClient(api.Config{
Address: "http://demo.robustperception.io:9090", Address: "http://demo.robustperception.io:9090",

17
go.mod
View File

@ -3,17 +3,12 @@ module github.com/prometheus/client_golang
require ( require (
github.com/beorn7/perks v1.0.1 github.com/beorn7/perks v1.0.1
github.com/cespare/xxhash/v2 v2.1.1 github.com/cespare/xxhash/v2 v2.1.1
github.com/golang/protobuf v1.3.2 github.com/golang/protobuf v1.4.3
github.com/google/go-cmp v0.4.0 // indirect github.com/json-iterator/go v1.1.10
github.com/json-iterator/go v1.1.9
github.com/kr/pretty v0.1.0 // indirect
github.com/prometheus/client_model v0.2.1-0.20200406191659-4b803f3550a4 github.com/prometheus/client_model v0.2.1-0.20200406191659-4b803f3550a4
github.com/prometheus/common v0.9.1 github.com/prometheus/common v0.18.0
github.com/prometheus/procfs v0.0.8 github.com/prometheus/procfs v0.6.0
github.com/stretchr/testify v1.4.0 // indirect golang.org/x/sys v0.0.0-20210309074719-68d13333faf2
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect
gopkg.in/yaml.v2 v2.2.5 // indirect
) )
go 1.11 go 1.13

368
go.sum
View File

@ -1,128 +1,416 @@
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=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=
github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g=
github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 h1:JYp7IbQjafoB+tBA3gMyHYHrpOtNuDiK/uB5uXxq5wM=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/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-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-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973 h1:xJ4a3vCFaGF/jqvzLMYoU8P317H5OQ+Via4RmuPwCS0= github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A=
github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU=
github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0 h1:HWo1m869IqiPhD389kmkxeTalrjNbbJTC8LXupb+sl0=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= 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 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ=
github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY= github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8=
github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI=
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 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 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/go-kit/kit v0.8.0 h1:Wz+5lgoB0kkuqLEc6NVmwRknTKP6dTGbSqvhZtBI/j0= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU=
github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M=
github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4=
github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/kit v0.9.0 h1:wDJmvq38kDhkVxi50ni9ykkdUr1PKgqKOoi01fa0Mdk=
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-logfmt/logfmt v0.3.0 h1:8HUsc87TaSWLKwrnumgC8/YconD2fJQsRJAsWaPg2ic= github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgOZ7o=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0 h1:MP4Eh7ZCb31lleYCFuwm0oe4/YGak+5l1vA2NOE80nA=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM= github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg=
github.com/golang/protobuf v1.3.1/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 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/google/go-cmp v0.3.1 h1:Xye71clBPdm5HgqGwUkwhbynsUJZhDbS20FvLhQ2izg= 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.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/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
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.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/gofuzz v1.0.0 h1:A8PeW59pxE9IoFRqBp37U+mSNaQoZ46F1f0f863XSXw= github.com/google/go-cmp v0.5.4 h1:L8R9j+yAqZuZjsqh/z+F1NCffTKKLShY6zXTItVIZ8M=
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/json-iterator/go v1.1.6 h1:MrUvLMLTMxbqFJ9kzlvat/rYZqZnW3u4wkLzWTaFwKs= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE=
github.com/hashicorp/consul/sdk v0.3.0/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=
github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo=
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
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.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGns= github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.10 h1:Kz6Cvnvv2wGdaG/V8yMvfkmNiXq9Ya2KUv4rouJJr68=
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= 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/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
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.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515 h1:T+h1c/A9Gawja4Y9mFVWj2vyii2bbUNDw3kt9VxK2EY= 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/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 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/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM=
github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4=
github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= 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/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=
github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/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 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 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223 h1:F9x/1yl3T2AeKLr2AMdilSD8+f9bvMnNN8VS5iDtovc=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f h1:KUppIJq7/+SVif2QVs3tOP0zanoHgBEVAwHxUSIzRqU=
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg=
github.com/nats-io/jwt v0.3.2/go.mod h1:/euKqTS1ZD+zzjYrY7pseZrTtWQSjujC7xjPc8wL6eU=
github.com/nats-io/nats-server/v2 v2.1.2/go.mod h1:Afk+wRZqkMQs/p45uXdrVLuab3gwv3Z8C4HTBu8GD/k=
github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzEE/Zbp4w=
github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs=
github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA=
github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk=
github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go.mod h1:Ngi6UdF0k5OKD5t5wlmGhe/EDKPoUM3BXZSSfIuJbis=
github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74=
github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5/go.mod h1:/wsWhb9smxSfWAKL3wpBW7V8scJMt8N8gnaMCS9E/cA=
github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw=
github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4=
github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4=
github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIwwtUjcrb0b5/5kLM=
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=
github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac=
github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc=
github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
github.com/pkg/errors v0.8.1/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/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs=
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910 h1:idejC8f05m9MGOsuEi1ATq9shN03HrxNkD/luQvxCv8= github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeDPbaTKGT+JTgUa3og=
github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90 h1:S/YWwWx/RA8rT8tKFRuGUZhuA90OyIBpPCXkcbwU8DE= github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 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.1.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.2.1-0.20200406191659-4b803f3550a4 h1:7Ws+6l4/5eJPHAxe0Axwo4XJwSAA4i0ipEjuoLXWFyo= github.com/prometheus/client_model v0.2.1-0.20200406191659-4b803f3550a4 h1:7Ws+6l4/5eJPHAxe0Axwo4XJwSAA4i0ipEjuoLXWFyo=
github.com/prometheus/client_model v0.2.1-0.20200406191659-4b803f3550a4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.2.1-0.20200406191659-4b803f3550a4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/common v0.4.1 h1:K0MGApIoQvMw27RTdJkPbr3JZ7DNbtxQNyi5STVM6Kw= github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/common v0.9.1 h1:KOMtN28tlbam3/7ZKEYKHhKoJZYYj3gMH4uc62x7X7U= github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA=
github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4= github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
github.com/prometheus/common v0.18.0 h1:WCVKW7aL6LEe1uryfI9dnEc2ZqNB1Fn0ok930v0iL1Y=
github.com/prometheus/common v0.18.0/go.mod h1:U+gB1OBLb1lF3O42bTCL+FK18tX9Oar16Clt/msog/s=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.2 h1:6LJUbpNm42llc4HRCuvApCSWB/WfhuNo9K98Q9sNGfs= github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/procfs v0.0.8 h1:+fpWZdT24pJBiqJdAwYBjPSk+5YmQzYNPYzQsdzLkt8=
github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
github.com/prometheus/procfs v0.6.0 h1:mxy4L2jP6qMonqmq+aTtOx1ifVWUgG/TAmntgbh3xv4=
github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E=
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= 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/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY=
github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 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 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg=
go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4=
go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/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-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-20190701094942-4def268fd1a4/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/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-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/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-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/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-20190125091013-d26f9f9a57f3/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-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-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f h1:Bl/8QSvNqXvPGPGXa2z5xUTmV7VDcZyvRZ+QQXkXTZQ= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/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-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
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/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4 h1:YUO/7uOKsKeq9UokNS62b8FYywz3ker1l1vDZRCRefw=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY= 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-20190911185100-cd5d95a43a6e/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-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5 h1:mzjBh+S5frKOsOBobWIMAbXavqjmgO17k/2puhcFR94= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/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-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
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-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82 h1:ywK/j/KkyTHcdyYSZNXGjMwgmDSfjglYZ3vStQ/gSCU= golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/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-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210309074719-68d13333faf2 h1:46ULzRKLh1CwgRq2dC5SlBzEqqNCi8rreOZnNrbqcIY=
golang.org/x/sys v0.0.0-20210309074719-68d13333faf2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/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-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
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-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
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 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/alecthomas/kingpin.v2 v2.2.6 h1:jMFz6MfLP0/4fUyZle81rXUoxOBFi19VUFKVDOQfozc= google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
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-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190530194941-fb225487d101/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
google.golang.org/grpc v1.22.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
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.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= 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/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o=
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 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.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I=
gopkg.in/yaml.v2 v2.2.4/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 h1:ymVxjfMaHvXD8RqPRmzHHsB3VvucivSkIAvJFDI5O3c=
gopkg.in/yaml.v2 v2.2.5/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=
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/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=
sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU=

View File

@ -1,29 +0,0 @@
// 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
//
// 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.
// +build go1.12
package prometheus
import "runtime/debug"
// readBuildInfo is a wrapper around debug.ReadBuildInfo for Go 1.12+.
func readBuildInfo() (path, version, sum string) {
path, version, sum = "unknown", "unknown", "unknown"
if bi, ok := debug.ReadBuildInfo(); ok {
path = bi.Main.Path
version = bi.Main.Version
sum = bi.Main.Sum
}
return
}

View File

@ -163,7 +163,7 @@ func (c *counter) updateExemplar(v float64, l Labels) {
// (e.g. number of HTTP requests, partitioned by response code and // (e.g. number of HTTP requests, partitioned by response code and
// method). Create instances with NewCounterVec. // method). Create instances with NewCounterVec.
type CounterVec struct { type CounterVec struct {
*metricVec *MetricVec
} }
// NewCounterVec creates a new CounterVec based on the provided CounterOpts and // NewCounterVec creates a new CounterVec based on the provided CounterOpts and
@ -176,11 +176,11 @@ func NewCounterVec(opts CounterOpts, labelNames []string) *CounterVec {
opts.ConstLabels, opts.ConstLabels,
) )
return &CounterVec{ return &CounterVec{
metricVec: newMetricVec(desc, func(lvs ...string) Metric { MetricVec: NewMetricVec(desc, func(lvs ...string) Metric {
if len(lvs) != len(desc.variableLabels) { if len(lvs) != len(desc.variableLabels) {
panic(makeInconsistentCardinalityError(desc.fqName, desc.variableLabels, lvs)) panic(makeInconsistentCardinalityError(desc.fqName, desc.variableLabels, lvs))
} }
result := &counter{desc: desc, labelPairs: makeLabelPairs(desc, lvs), now: time.Now} result := &counter{desc: desc, labelPairs: MakeLabelPairs(desc, lvs), now: time.Now}
result.init(result) // Init self-collection. result.init(result) // Init self-collection.
return result return result
}), }),
@ -188,7 +188,7 @@ func NewCounterVec(opts CounterOpts, labelNames []string) *CounterVec {
} }
// GetMetricWithLabelValues returns the Counter for the given slice of label // GetMetricWithLabelValues returns the Counter for the given slice of label
// values (same order as the VariableLabels in Desc). If that combination of // values (same order as the variable labels in Desc). If that combination of
// label values is accessed for the first time, a new Counter is created. // label values is accessed for the first time, a new Counter is created.
// //
// It is possible to call this method without using the returned Counter to only // It is possible to call this method without using the returned Counter to only
@ -202,7 +202,7 @@ func NewCounterVec(opts CounterOpts, labelNames []string) *CounterVec {
// Counter with the same label values is created later. // Counter with the same label values is created later.
// //
// An error is returned if the number of label values is not the same as the // An error is returned if the number of label values is not the same as the
// number of VariableLabels in Desc (minus any curried labels). // number of variable labels in Desc (minus any curried labels).
// //
// Note that for more than one label value, this method is prone to mistakes // Note that for more than one label value, this method is prone to mistakes
// caused by an incorrect order of arguments. Consider GetMetricWith(Labels) as // caused by an incorrect order of arguments. Consider GetMetricWith(Labels) as
@ -211,7 +211,7 @@ func NewCounterVec(opts CounterOpts, labelNames []string) *CounterVec {
// with a performance overhead (for creating and processing the Labels map). // with a performance overhead (for creating and processing the Labels map).
// See also the GaugeVec example. // See also the GaugeVec example.
func (v *CounterVec) GetMetricWithLabelValues(lvs ...string) (Counter, error) { func (v *CounterVec) GetMetricWithLabelValues(lvs ...string) (Counter, error) {
metric, err := v.metricVec.getMetricWithLabelValues(lvs...) metric, err := v.MetricVec.GetMetricWithLabelValues(lvs...)
if metric != nil { if metric != nil {
return metric.(Counter), err return metric.(Counter), err
} }
@ -219,19 +219,19 @@ func (v *CounterVec) GetMetricWithLabelValues(lvs ...string) (Counter, error) {
} }
// GetMetricWith returns the Counter for the given Labels map (the label names // GetMetricWith returns the Counter for the given Labels map (the label names
// must match those of the VariableLabels in Desc). If that label map is // must match those of the variable labels in Desc). If that label map is
// accessed for the first time, a new Counter is created. Implications of // accessed for the first time, a new Counter is created. Implications of
// creating a Counter without using it and keeping the Counter for later use are // creating a Counter without using it and keeping the Counter for later use are
// the same as for GetMetricWithLabelValues. // the same as for GetMetricWithLabelValues.
// //
// An error is returned if the number and names of the Labels are inconsistent // An error is returned if the number and names of the Labels are inconsistent
// with those of the VariableLabels in Desc (minus any curried labels). // with those of the variable labels in Desc (minus any curried labels).
// //
// This method is used for the same purpose as // This method is used for the same purpose as
// GetMetricWithLabelValues(...string). See there for pros and cons of the two // GetMetricWithLabelValues(...string). See there for pros and cons of the two
// methods. // methods.
func (v *CounterVec) GetMetricWith(labels Labels) (Counter, error) { func (v *CounterVec) GetMetricWith(labels Labels) (Counter, error) {
metric, err := v.metricVec.getMetricWith(labels) metric, err := v.MetricVec.GetMetricWith(labels)
if metric != nil { if metric != nil {
return metric.(Counter), err return metric.(Counter), err
} }
@ -275,7 +275,7 @@ func (v *CounterVec) With(labels Labels) Counter {
// registered with a given registry (usually the uncurried version). The Reset // registered with a given registry (usually the uncurried version). The Reset
// method deletes all metrics, even if called on a curried vector. // method deletes all metrics, even if called on a curried vector.
func (v *CounterVec) CurryWith(labels Labels) (*CounterVec, error) { func (v *CounterVec) CurryWith(labels Labels) (*CounterVec, error) {
vec, err := v.curryWith(labels) vec, err := v.MetricVec.CurryWith(labels)
if vec != nil { if vec != nil {
return &CounterVec{vec}, err return &CounterVec{vec}, err
} }
@ -309,6 +309,8 @@ type CounterFunc interface {
// provided function must be concurrency-safe. The function should also honor // provided function must be concurrency-safe. The function should also honor
// the contract for a Counter (values only go up, not down), but compliance will // the contract for a Counter (values only go up, not down), but compliance will
// not be checked. // not be checked.
//
// Check out the ExampleGaugeFunc examples for the similar GaugeFunc.
func NewCounterFunc(opts CounterOpts, function func() float64) CounterFunc { func NewCounterFunc(opts CounterOpts, function func() float64) CounterFunc {
return newValueFunc(NewDesc( return newValueFunc(NewDesc(
BuildFQName(opts.Namespace, opts.Subsystem, opts.Name), BuildFQName(opts.Namespace, opts.Subsystem, opts.Name),

View File

@ -19,6 +19,7 @@ import (
"testing" "testing"
"time" "time"
//nolint:staticcheck // Ignore SA1019. Need to keep deprecated package for compatibility.
"github.com/golang/protobuf/proto" "github.com/golang/protobuf/proto"
"github.com/golang/protobuf/ptypes" "github.com/golang/protobuf/ptypes"

View File

@ -20,6 +20,7 @@ import (
"strings" "strings"
"github.com/cespare/xxhash/v2" "github.com/cespare/xxhash/v2"
//nolint:staticcheck // Ignore SA1019. Need to keep deprecated package for compatibility.
"github.com/golang/protobuf/proto" "github.com/golang/protobuf/proto"
"github.com/prometheus/common/model" "github.com/prometheus/common/model"
@ -50,7 +51,7 @@ type Desc struct {
// constLabelPairs contains precalculated DTO label pairs based on // constLabelPairs contains precalculated DTO label pairs based on
// the constant labels. // the constant labels.
constLabelPairs []*dto.LabelPair constLabelPairs []*dto.LabelPair
// VariableLabels contains names of labels for which the metric // variableLabels contains names of labels for which the metric
// maintains variable values. // maintains variable values.
variableLabels []string variableLabels []string
// id is a hash of the values of the ConstLabels and fqName. This // id is a hash of the values of the ConstLabels and fqName. This

View File

@ -0,0 +1,163 @@
// Copyright 2020 The Prometheus Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package prometheus_test
import (
"fmt"
//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/client_golang/prometheus"
)
// Info implements an info pseudo-metric, which is modeled as a Gauge that
// always has a value of 1. In practice, you would just use a Gauge directly,
// but for this example, we pretend it would be useful to have a “native”
// implementation.
type Info struct {
desc *prometheus.Desc
labelPairs []*dto.LabelPair
}
func (i Info) Desc() *prometheus.Desc {
return i.desc
}
func (i Info) Write(out *dto.Metric) error {
out.Label = i.labelPairs
out.Gauge = &dto.Gauge{Value: proto.Float64(1)}
return nil
}
// InfoVec is the vector version for Info. As an info metric never changes, we
// wouldn't really need to wrap GetMetricWithLabelValues and GetMetricWith
// because Info has no additional methods compared to the vanilla Metric that
// the unwrapped MetricVec methods return. However, to demonstrate all there is
// to do to fully implement a vector for a custom Metric implementation, we do
// it in this example anyway.
type InfoVec struct {
*prometheus.MetricVec
}
func NewInfoVec(name, help string, labelNames []string) *InfoVec {
desc := prometheus.NewDesc(name, help, labelNames, nil)
return &InfoVec{
MetricVec: prometheus.NewMetricVec(desc, func(lvs ...string) prometheus.Metric {
if len(lvs) != len(labelNames) {
panic("inconsistent label cardinality")
}
return Info{desc: desc, labelPairs: prometheus.MakeLabelPairs(desc, lvs)}
}),
}
}
func (v *InfoVec) GetMetricWithLabelValues(lvs ...string) (Info, error) {
metric, err := v.MetricVec.GetMetricWithLabelValues(lvs...)
return metric.(Info), err
}
func (v *InfoVec) GetMetricWith(labels prometheus.Labels) (Info, error) {
metric, err := v.MetricVec.GetMetricWith(labels)
return metric.(Info), err
}
func (v *InfoVec) WithLabelValues(lvs ...string) Info {
i, err := v.GetMetricWithLabelValues(lvs...)
if err != nil {
panic(err)
}
return i
}
func (v *InfoVec) With(labels prometheus.Labels) Info {
i, err := v.GetMetricWith(labels)
if err != nil {
panic(err)
}
return i
}
func (v *InfoVec) CurryWith(labels prometheus.Labels) (*InfoVec, error) {
vec, err := v.MetricVec.CurryWith(labels)
if vec != nil {
return &InfoVec{vec}, err
}
return nil, err
}
func (v *InfoVec) MustCurryWith(labels prometheus.Labels) *InfoVec {
vec, err := v.CurryWith(labels)
if err != nil {
panic(err)
}
return vec
}
func ExampleMetricVec() {
infoVec := NewInfoVec(
"library_version_info",
"Versions of the libraries used in this binary.",
[]string{"library", "version"},
)
infoVec.WithLabelValues("prometheus/client_golang", "1.7.1")
infoVec.WithLabelValues("k8s.io/client-go", "0.18.8")
// Just for demonstration, let's check the state of the InfoVec by
// registering it with a custom registry and then let it collect the
// metrics.
reg := prometheus.NewRegistry()
reg.MustRegister(infoVec)
metricFamilies, err := reg.Gather()
if err != nil || len(metricFamilies) != 1 {
panic("unexpected behavior of custom test registry")
}
fmt.Println(proto.MarshalTextString(metricFamilies[0]))
// Output:
// name: "library_version_info"
// help: "Versions of the libraries used in this binary."
// type: GAUGE
// metric: <
// label: <
// name: "library"
// value: "k8s.io/client-go"
// >
// label: <
// name: "version"
// value: "0.18.8"
// >
// gauge: <
// value: 1
// >
// >
// metric: <
// label: <
// name: "library"
// value: "prometheus/client_golang"
// >
// label: <
// name: "version"
// value: "1.7.1"
// >
// gauge: <
// value: 1
// >
// >
}

View File

@ -22,6 +22,7 @@ import (
"strings" "strings"
"time" "time"
//nolint:staticcheck // Ignore SA1019. Need to keep deprecated package for compatibility.
"github.com/golang/protobuf/proto" "github.com/golang/protobuf/proto"
"github.com/prometheus/common/expfmt" "github.com/prometheus/common/expfmt"
@ -72,7 +73,7 @@ func ExampleGaugeVec() {
opsQueued.With(prometheus.Labels{"type": "delete", "user": "alice"}).Inc() opsQueued.With(prometheus.Labels{"type": "delete", "user": "alice"}).Inc()
} }
func ExampleGaugeFunc() { func ExampleGaugeFunc_simple() {
if err := prometheus.Register(prometheus.NewGaugeFunc( if err := prometheus.Register(prometheus.NewGaugeFunc(
prometheus.GaugeOpts{ prometheus.GaugeOpts{
Subsystem: "runtime", Subsystem: "runtime",
@ -90,6 +91,44 @@ func ExampleGaugeFunc() {
// GaugeFunc 'goroutines_count' registered. // GaugeFunc 'goroutines_count' registered.
} }
func ExampleGaugeFunc_constLabels() {
// primaryDB and secondaryDB represent two example *sql.DB connections we want to instrument.
var primaryDB, secondaryDB interface {
Stats() struct{ OpenConnections int }
}
if err := prometheus.Register(prometheus.NewGaugeFunc(
prometheus.GaugeOpts{
Namespace: "mysql",
Name: "connections_open",
Help: "Number of mysql connections open.",
ConstLabels: prometheus.Labels{"destination": "primary"},
},
func() float64 { return float64(primaryDB.Stats().OpenConnections) },
)); err == nil {
fmt.Println(`GaugeFunc 'connections_open' for primary DB connection registered with labels {destination="primary"}`)
}
if err := prometheus.Register(prometheus.NewGaugeFunc(
prometheus.GaugeOpts{
Namespace: "mysql",
Name: "connections_open",
Help: "Number of mysql connections open.",
ConstLabels: prometheus.Labels{"destination": "secondary"},
},
func() float64 { return float64(secondaryDB.Stats().OpenConnections) },
)); err == nil {
fmt.Println(`GaugeFunc 'connections_open' for secondary DB connection registered with labels {destination="secondary"}`)
}
// Note that we can register more than once GaugeFunc with same metric name
// as long as their const labels are consistent.
// Output:
// GaugeFunc 'connections_open' for primary DB connection registered with labels {destination="primary"}
// GaugeFunc 'connections_open' for secondary DB connection registered with labels {destination="secondary"}
}
func ExampleCounterVec() { func ExampleCounterVec() {
httpReqs := prometheus.NewCounterVec( httpReqs := prometheus.NewCounterVec(
prometheus.CounterOpts{ prometheus.CounterOpts{

View File

@ -132,7 +132,7 @@ func (g *gauge) Write(out *dto.Metric) error {
// (e.g. number of operations queued, partitioned by user and operation // (e.g. number of operations queued, partitioned by user and operation
// type). Create instances with NewGaugeVec. // type). Create instances with NewGaugeVec.
type GaugeVec struct { type GaugeVec struct {
*metricVec *MetricVec
} }
// NewGaugeVec creates a new GaugeVec based on the provided GaugeOpts and // NewGaugeVec creates a new GaugeVec based on the provided GaugeOpts and
@ -145,11 +145,11 @@ func NewGaugeVec(opts GaugeOpts, labelNames []string) *GaugeVec {
opts.ConstLabels, opts.ConstLabels,
) )
return &GaugeVec{ return &GaugeVec{
metricVec: newMetricVec(desc, func(lvs ...string) Metric { MetricVec: NewMetricVec(desc, func(lvs ...string) Metric {
if len(lvs) != len(desc.variableLabels) { if len(lvs) != len(desc.variableLabels) {
panic(makeInconsistentCardinalityError(desc.fqName, desc.variableLabels, lvs)) panic(makeInconsistentCardinalityError(desc.fqName, desc.variableLabels, lvs))
} }
result := &gauge{desc: desc, labelPairs: makeLabelPairs(desc, lvs)} result := &gauge{desc: desc, labelPairs: MakeLabelPairs(desc, lvs)}
result.init(result) // Init self-collection. result.init(result) // Init self-collection.
return result return result
}), }),
@ -157,7 +157,7 @@ func NewGaugeVec(opts GaugeOpts, labelNames []string) *GaugeVec {
} }
// GetMetricWithLabelValues returns the Gauge for the given slice of label // GetMetricWithLabelValues returns the Gauge for the given slice of label
// values (same order as the VariableLabels in Desc). If that combination of // values (same order as the variable labels in Desc). If that combination of
// label values is accessed for the first time, a new Gauge is created. // label values is accessed for the first time, a new Gauge is created.
// //
// It is possible to call this method without using the returned Gauge to only // It is possible to call this method without using the returned Gauge to only
@ -172,7 +172,7 @@ func NewGaugeVec(opts GaugeOpts, labelNames []string) *GaugeVec {
// example. // example.
// //
// An error is returned if the number of label values is not the same as the // An error is returned if the number of label values is not the same as the
// number of VariableLabels in Desc (minus any curried labels). // number of variable labels in Desc (minus any curried labels).
// //
// Note that for more than one label value, this method is prone to mistakes // Note that for more than one label value, this method is prone to mistakes
// caused by an incorrect order of arguments. Consider GetMetricWith(Labels) as // caused by an incorrect order of arguments. Consider GetMetricWith(Labels) as
@ -180,7 +180,7 @@ func NewGaugeVec(opts GaugeOpts, labelNames []string) *GaugeVec {
// latter has a much more readable (albeit more verbose) syntax, but it comes // latter has a much more readable (albeit more verbose) syntax, but it comes
// with a performance overhead (for creating and processing the Labels map). // with a performance overhead (for creating and processing the Labels map).
func (v *GaugeVec) GetMetricWithLabelValues(lvs ...string) (Gauge, error) { func (v *GaugeVec) GetMetricWithLabelValues(lvs ...string) (Gauge, error) {
metric, err := v.metricVec.getMetricWithLabelValues(lvs...) metric, err := v.MetricVec.GetMetricWithLabelValues(lvs...)
if metric != nil { if metric != nil {
return metric.(Gauge), err return metric.(Gauge), err
} }
@ -188,19 +188,19 @@ func (v *GaugeVec) GetMetricWithLabelValues(lvs ...string) (Gauge, error) {
} }
// GetMetricWith returns the Gauge for the given Labels map (the label names // GetMetricWith returns the Gauge for the given Labels map (the label names
// must match those of the VariableLabels in Desc). If that label map is // must match those of the variable labels in Desc). If that label map is
// accessed for the first time, a new Gauge is created. Implications of // accessed for the first time, a new Gauge is created. Implications of
// creating a Gauge without using it and keeping the Gauge for later use are // creating a Gauge without using it and keeping the Gauge for later use are
// the same as for GetMetricWithLabelValues. // the same as for GetMetricWithLabelValues.
// //
// An error is returned if the number and names of the Labels are inconsistent // An error is returned if the number and names of the Labels are inconsistent
// with those of the VariableLabels in Desc (minus any curried labels). // with those of the variable labels in Desc (minus any curried labels).
// //
// This method is used for the same purpose as // This method is used for the same purpose as
// GetMetricWithLabelValues(...string). See there for pros and cons of the two // GetMetricWithLabelValues(...string). See there for pros and cons of the two
// methods. // methods.
func (v *GaugeVec) GetMetricWith(labels Labels) (Gauge, error) { func (v *GaugeVec) GetMetricWith(labels Labels) (Gauge, error) {
metric, err := v.metricVec.getMetricWith(labels) metric, err := v.MetricVec.GetMetricWith(labels)
if metric != nil { if metric != nil {
return metric.(Gauge), err return metric.(Gauge), err
} }
@ -244,7 +244,7 @@ func (v *GaugeVec) With(labels Labels) Gauge {
// registered with a given registry (usually the uncurried version). The Reset // registered with a given registry (usually the uncurried version). The Reset
// method deletes all metrics, even if called on a curried vector. // method deletes all metrics, even if called on a curried vector.
func (v *GaugeVec) CurryWith(labels Labels) (*GaugeVec, error) { func (v *GaugeVec) CurryWith(labels Labels) (*GaugeVec, error) {
vec, err := v.curryWith(labels) vec, err := v.MetricVec.CurryWith(labels)
if vec != nil { if vec != nil {
return &GaugeVec{vec}, err return &GaugeVec{vec}, err
} }

View File

@ -139,7 +139,7 @@ func TestGaugeVecConcurrency(t *testing.T) {
start.Wait() start.Wait()
for i, v := range vals { for i, v := range vals {
sStreams[pick[i]] <- v sStreams[pick[i]] <- v
gge.WithLabelValues(string('A' + pick[i])).Add(v) gge.WithLabelValues(string('A' + rune(pick[i]))).Add(v)
} }
end.Done() end.Done()
}(vals) }(vals)
@ -147,7 +147,7 @@ func TestGaugeVecConcurrency(t *testing.T) {
start.Done() start.Done()
for i := range sStreams { for i := range sStreams {
if expected, got := <-results[i], math.Float64frombits(gge.WithLabelValues(string('A'+i)).(*gauge).valBits); math.Abs(expected-got) > 0.000001 { if expected, got := <-results[i], math.Float64frombits(gge.WithLabelValues(string('A'+rune(i))).(*gauge).valBits); math.Abs(expected-got) > 0.000001 {
t.Fatalf("expected approx. %f, got %f", expected, got) t.Fatalf("expected approx. %f, got %f", expected, got)
return false return false
} }

View File

@ -58,9 +58,10 @@ type goCollector struct {
// collector will use the memstats from a previous collection if // collector will use the memstats from a previous collection if
// runtime.ReadMemStats takes more than 1s. However, if there are no previously // runtime.ReadMemStats takes more than 1s. However, if there are no previously
// collected memstats, or their collection is more than 5m ago, the collection // collected memstats, or their collection is more than 5m ago, the collection
// will block until runtime.ReadMemStats succeeds. (The problem might be solved // will block until runtime.ReadMemStats succeeds.
// in Go1.13, see https://github.com/golang/go/issues/19812 for the related Go //
// issue.) // NOTE: The problem is solved in Go 1.15, see
// https://github.com/golang/go/issues/19812 for the related Go issue.
func NewGoCollector() Collector { func NewGoCollector() Collector {
return &goCollector{ return &goCollector{
goroutinesDesc: NewDesc( goroutinesDesc: NewDesc(
@ -383,7 +384,12 @@ type memStatsMetrics []struct {
// https://github.com/povilasv/prommod for an example of a collector for the // https://github.com/povilasv/prommod for an example of a collector for the
// module dependencies. // module dependencies.
func NewBuildInfoCollector() Collector { func NewBuildInfoCollector() Collector {
path, version, sum := readBuildInfo() 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( c := &selfCollector{MustNewConstMetric(
NewDesc( NewDesc(
"go_build_info", "go_build_info",

View File

@ -22,6 +22,7 @@ import (
"sync/atomic" "sync/atomic"
"time" "time"
//nolint:staticcheck // Ignore SA1019. Need to keep deprecated package for compatibility.
"github.com/golang/protobuf/proto" "github.com/golang/protobuf/proto"
dto "github.com/prometheus/client_model/go" dto "github.com/prometheus/client_model/go"
@ -215,7 +216,7 @@ func newHistogram(desc *Desc, opts HistogramOpts, labelValues ...string) Histogr
upperBounds: opts.Buckets, upperBounds: opts.Buckets,
sparseResolution: uint32(opts.SparseBucketsResolution), sparseResolution: uint32(opts.SparseBucketsResolution),
sparseThreshold: opts.SparseBucketsZeroThreshold, sparseThreshold: opts.SparseBucketsZeroThreshold,
labelPairs: makeLabelPairs(desc, labelValues), labelPairs: MakeLabelPairs(desc, labelValues),
counts: [2]*histogramCounts{{}, {}}, counts: [2]*histogramCounts{{}, {}},
now: time.Now, now: time.Now,
} }
@ -565,7 +566,7 @@ func (h *histogram) updateExemplar(v float64, bucket int, l Labels) {
// (e.g. HTTP request latencies, partitioned by status code and method). Create // (e.g. HTTP request latencies, partitioned by status code and method). Create
// instances with NewHistogramVec. // instances with NewHistogramVec.
type HistogramVec struct { type HistogramVec struct {
*metricVec *MetricVec
} }
// NewHistogramVec creates a new HistogramVec based on the provided HistogramOpts and // NewHistogramVec creates a new HistogramVec based on the provided HistogramOpts and
@ -578,14 +579,14 @@ func NewHistogramVec(opts HistogramOpts, labelNames []string) *HistogramVec {
opts.ConstLabels, opts.ConstLabels,
) )
return &HistogramVec{ return &HistogramVec{
metricVec: newMetricVec(desc, func(lvs ...string) Metric { MetricVec: NewMetricVec(desc, func(lvs ...string) Metric {
return newHistogram(desc, opts, lvs...) return newHistogram(desc, opts, lvs...)
}), }),
} }
} }
// GetMetricWithLabelValues returns the Histogram for the given slice of label // GetMetricWithLabelValues returns the Histogram for the given slice of label
// values (same order as the VariableLabels in Desc). If that combination of // values (same order as the variable labels in Desc). If that combination of
// label values is accessed for the first time, a new Histogram is created. // label values is accessed for the first time, a new Histogram is created.
// //
// It is possible to call this method without using the returned Histogram to only // It is possible to call this method without using the returned Histogram to only
@ -600,7 +601,7 @@ func NewHistogramVec(opts HistogramOpts, labelNames []string) *HistogramVec {
// example. // example.
// //
// An error is returned if the number of label values is not the same as the // An error is returned if the number of label values is not the same as the
// number of VariableLabels in Desc (minus any curried labels). // number of variable labels in Desc (minus any curried labels).
// //
// Note that for more than one label value, this method is prone to mistakes // Note that for more than one label value, this method is prone to mistakes
// caused by an incorrect order of arguments. Consider GetMetricWith(Labels) as // caused by an incorrect order of arguments. Consider GetMetricWith(Labels) as
@ -609,7 +610,7 @@ func NewHistogramVec(opts HistogramOpts, labelNames []string) *HistogramVec {
// with a performance overhead (for creating and processing the Labels map). // with a performance overhead (for creating and processing the Labels map).
// See also the GaugeVec example. // See also the GaugeVec example.
func (v *HistogramVec) GetMetricWithLabelValues(lvs ...string) (Observer, error) { func (v *HistogramVec) GetMetricWithLabelValues(lvs ...string) (Observer, error) {
metric, err := v.metricVec.getMetricWithLabelValues(lvs...) metric, err := v.MetricVec.GetMetricWithLabelValues(lvs...)
if metric != nil { if metric != nil {
return metric.(Observer), err return metric.(Observer), err
} }
@ -617,19 +618,19 @@ func (v *HistogramVec) GetMetricWithLabelValues(lvs ...string) (Observer, error)
} }
// GetMetricWith returns the Histogram for the given Labels map (the label names // GetMetricWith returns the Histogram for the given Labels map (the label names
// must match those of the VariableLabels in Desc). If that label map is // must match those of the variable labels in Desc). If that label map is
// accessed for the first time, a new Histogram is created. Implications of // accessed for the first time, a new Histogram is created. Implications of
// creating a Histogram without using it and keeping the Histogram for later use // creating a Histogram without using it and keeping the Histogram for later use
// are the same as for GetMetricWithLabelValues. // are the same as for GetMetricWithLabelValues.
// //
// An error is returned if the number and names of the Labels are inconsistent // An error is returned if the number and names of the Labels are inconsistent
// with those of the VariableLabels in Desc (minus any curried labels). // with those of the variable labels in Desc (minus any curried labels).
// //
// This method is used for the same purpose as // This method is used for the same purpose as
// GetMetricWithLabelValues(...string). See there for pros and cons of the two // GetMetricWithLabelValues(...string). See there for pros and cons of the two
// methods. // methods.
func (v *HistogramVec) GetMetricWith(labels Labels) (Observer, error) { func (v *HistogramVec) GetMetricWith(labels Labels) (Observer, error) {
metric, err := v.metricVec.getMetricWith(labels) metric, err := v.MetricVec.GetMetricWith(labels)
if metric != nil { if metric != nil {
return metric.(Observer), err return metric.(Observer), err
} }
@ -673,7 +674,7 @@ func (v *HistogramVec) With(labels Labels) Observer {
// registered with a given registry (usually the uncurried version). The Reset // registered with a given registry (usually the uncurried version). The Reset
// method deletes all metrics, even if called on a curried vector. // method deletes all metrics, even if called on a curried vector.
func (v *HistogramVec) CurryWith(labels Labels) (ObserverVec, error) { func (v *HistogramVec) CurryWith(labels Labels) (ObserverVec, error) {
vec, err := v.curryWith(labels) vec, err := v.MetricVec.CurryWith(labels)
if vec != nil { if vec != nil {
return &HistogramVec{vec}, err return &HistogramVec{vec}, err
} }
@ -758,12 +759,12 @@ func NewConstHistogram(
count: count, count: count,
sum: sum, sum: sum,
buckets: buckets, buckets: buckets,
labelPairs: makeLabelPairs(desc, labelValues), labelPairs: MakeLabelPairs(desc, labelValues),
}, nil }, nil
} }
// MustNewConstHistogram is a version of NewConstHistogram that panics where // MustNewConstHistogram is a version of NewConstHistogram that panics where
// NewConstMetric would have returned an error. // NewConstHistogram would have returned an error.
func MustNewConstHistogram( func MustNewConstHistogram(
desc *Desc, desc *Desc,
count uint64, count uint64,

View File

@ -24,6 +24,7 @@ import (
"testing/quick" "testing/quick"
"time" "time"
//nolint:staticcheck // Ignore SA1019. Need to keep deprecated package for compatibility.
"github.com/golang/protobuf/proto" "github.com/golang/protobuf/proto"
"github.com/golang/protobuf/ptypes" "github.com/golang/protobuf/ptypes"
@ -278,7 +279,7 @@ func TestHistogramVecConcurrency(t *testing.T) {
go func(vals []float64) { go func(vals []float64) {
start.Wait() start.Wait()
for i, v := range vals { for i, v := range vals {
his.WithLabelValues(string('A' + picks[i])).Observe(v) his.WithLabelValues(string('A' + rune(picks[i]))).Observe(v)
} }
end.Done() end.Done()
}(vals) }(vals)
@ -291,7 +292,7 @@ func TestHistogramVecConcurrency(t *testing.T) {
for i := 0; i < vecLength; i++ { for i := 0; i < vecLength; i++ {
m := &dto.Metric{} m := &dto.Metric{}
s := his.WithLabelValues(string('A' + i)) s := his.WithLabelValues(string('A' + rune(i)))
s.(Histogram).Write(m) s.(Histogram).Write(m)
if got, want := len(m.Histogram.Bucket), len(testBuckets)-1; got != want { if got, want := len(m.Histogram.Bucket), len(testBuckets)-1; got != want {

View File

@ -17,6 +17,7 @@ import (
"strings" "strings"
"time" "time"
//nolint:staticcheck // Ignore SA1019. Need to keep deprecated package for compatibility.
"github.com/golang/protobuf/proto" "github.com/golang/protobuf/proto"
"github.com/prometheus/common/model" "github.com/prometheus/common/model"
@ -57,7 +58,7 @@ type Metric interface {
} }
// Opts bundles the options for creating most Metric types. Each metric // Opts bundles the options for creating most Metric types. Each metric
// implementation XXX has its own XXXOpts type, but in most cases, it is just be // implementation XXX has its own XXXOpts type, but in most cases, it is just
// an alias of this type (which might change when the requirement arises.) // an alias of this type (which might change when the requirement arises.)
// //
// It is mandatory to set Name to a non-empty string. All other fields are // It is mandatory to set Name to a non-empty string. All other fields are
@ -88,7 +89,7 @@ type Opts struct {
// better covered by target labels set by the scraping Prometheus // better covered by target labels set by the scraping Prometheus
// server, or by one specific metric (e.g. a build_info or a // server, or by one specific metric (e.g. a build_info or a
// machine_role metric). See also // machine_role metric). See also
// https://prometheus.io/docs/instrumenting/writing_exporters/#target-labels,-not-static-scraped-labels // https://prometheus.io/docs/instrumenting/writing_exporters/#target-labels-not-static-scraped-labels
ConstLabels Labels ConstLabels Labels
} }

View File

@ -15,7 +15,11 @@ package prometheus
import ( import (
"errors" "errors"
"fmt"
"io/ioutil"
"os" "os"
"strconv"
"strings"
) )
type processCollector struct { type processCollector struct {
@ -149,3 +153,20 @@ func (c *processCollector) reportError(ch chan<- Metric, desc *Desc, err error)
} }
ch <- NewInvalidMetric(desc, err) ch <- NewInvalidMetric(desc, err)
} }
// NewPidFileFn returns a function that retrieves a pid from the specified file.
// 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)
if err != nil {
return 0, fmt.Errorf("can't read pid file %q: %+v", 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 pid, nil
}
}

View File

@ -18,8 +18,11 @@ package prometheus
import ( import (
"bytes" "bytes"
"errors" "errors"
"fmt"
"os" "os"
"path/filepath"
"regexp" "regexp"
"strings"
"testing" "testing"
"github.com/prometheus/common/expfmt" "github.com/prometheus/common/expfmt"
@ -101,3 +104,64 @@ func TestProcessCollector(t *testing.T) {
t.Errorf("%d metrics collected, want 1", n) t.Errorf("%d metrics collected, want 1", n)
} }
} }
func TestNewPidFileFn(t *testing.T) {
folderPath, err := os.Getwd()
if err != nil {
t.Error("failed to get current path")
}
mockPidFilePath := filepath.Join(folderPath, "mockPidFile")
defer os.Remove(mockPidFilePath)
testCases := []struct {
mockPidFile func()
expectedErrPrefix string
expectedPid int
desc string
}{
{
mockPidFile: func() {
os.Remove(mockPidFilePath)
},
expectedErrPrefix: "can't read pid file",
expectedPid: 0,
desc: "no existed pid file",
},
{
mockPidFile: func() {
os.Remove(mockPidFilePath)
f, _ := os.Create(mockPidFilePath)
f.Write([]byte("abc"))
f.Close()
},
expectedErrPrefix: "can't parse pid file",
expectedPid: 0,
desc: "existed pid file, error pid number",
},
{
mockPidFile: func() {
os.Remove(mockPidFilePath)
f, _ := os.Create(mockPidFilePath)
f.Write([]byte("123"))
f.Close()
},
expectedErrPrefix: "",
expectedPid: 123,
desc: "existed pid file, correct pid number",
},
}
for _, tc := range testCases {
fn := NewPidFileFn(mockPidFilePath)
if fn == nil {
t.Error("Should not get nil PidFileFn")
}
tc.mockPidFile()
if pid, err := fn(); pid != tc.expectedPid || (err != nil && !strings.HasPrefix(err.Error(), tc.expectedErrPrefix)) {
fmt.Println(err.Error())
t.Error(tc.desc)
}
}
}

View File

@ -33,18 +33,22 @@ var (
) )
type processMemoryCounters struct { type processMemoryCounters struct {
// https://docs.microsoft.com/en-us/windows/desktop/api/psapi/ns-psapi-_process_memory_counters_ex // System interface description
// https://docs.microsoft.com/en-us/windows/desktop/api/psapi/ns-psapi-process_memory_counters_ex
// Refer to the Golang internal implementation
// https://golang.org/src/internal/syscall/windows/psapi_windows.go
_ uint32 _ uint32
PageFaultCount uint32 PageFaultCount uint32
PeakWorkingSetSize uint64 PeakWorkingSetSize uintptr
WorkingSetSize uint64 WorkingSetSize uintptr
QuotaPeakPagedPoolUsage uint64 QuotaPeakPagedPoolUsage uintptr
QuotaPagedPoolUsage uint64 QuotaPagedPoolUsage uintptr
QuotaPeakNonPagedPoolUsage uint64 QuotaPeakNonPagedPoolUsage uintptr
QuotaNonPagedPoolUsage uint64 QuotaNonPagedPoolUsage uintptr
PagefileUsage uint64 PagefileUsage uintptr
PeakPagefileUsage uint64 PeakPagefileUsage uintptr
PrivateUsage uint64 PrivateUsage uintptr
} }
func getProcessMemoryInfo(handle windows.Handle) (processMemoryCounters, error) { func getProcessMemoryInfo(handle windows.Handle) (processMemoryCounters, error) {

View File

@ -98,7 +98,7 @@
// requestCount = promauto.With(reg).NewCounterVec( // requestCount = promauto.With(reg).NewCounterVec(
// prometheus.CounterOpts{ // prometheus.CounterOpts{
// Name: "http_requests_total", // Name: "http_requests_total",
// Help: "Total number of HTTP requests by status code end method.", // Help: "Total number of HTTP requests by status code and method.",
// }, // },
// []string{"code", "method"}, // []string{"code", "method"},
// ) // )
@ -117,7 +117,7 @@
// requestCount = factory.NewCounterVec( // requestCount = factory.NewCounterVec(
// prometheus.CounterOpts{ // prometheus.CounterOpts{
// Name: "http_requests_total", // Name: "http_requests_total",
// Help: "Total number of HTTP requests by status code end method.", // Help: "Total number of HTTP requests by status code and method.",
// }, // },
// []string{"code", "method"}, // []string{"code", "method"},
// ) // )
@ -127,29 +127,30 @@
// separate package? // separate package?
// //
// The main problem is that registration may fail, e.g. if a metric inconsistent // The main problem is that registration may fail, e.g. if a metric inconsistent
// with the newly to be registered one is already registered. Therefore, the // with or equal to the newly to be registered one is already registered.
// Register method in the prometheus.Registerer interface returns an error, and // Therefore, the Register method in the prometheus.Registerer interface returns
// the same is the case for the top-level prometheus.Register function that // an error, and the same is the case for the top-level prometheus.Register
// registers with the global registry. The prometheus package also provides // function that registers with the global registry. The prometheus package also
// MustRegister versions for both. They panic if the registration fails, and // provides MustRegister versions for both. They panic if the registration
// they clearly call this out by using the Must… idiom. Panicking is a bit // fails, and they clearly call this out by using the Must… idiom. Panicking is
// problematic here because it doesn't just happen on input provided by the // problematic in this case because it doesn't just happen on input provided by
// caller that is invalid on its own. Things are a bit more subtle here: Metric // the caller that is invalid on its own. Things are a bit more subtle here:
// creation and registration tend to be spread widely over the codebase. It can // Metric creation and registration tend to be spread widely over the
// easily happen that an incompatible metric is added to an unrelated part of // codebase. It can easily happen that an incompatible metric is added to an
// the code, and suddenly code that used to work perfectly fine starts to panic // unrelated part of the code, and suddenly code that used to work perfectly
// (provided that the registration of the newly added metric happens before the // fine starts to panic (provided that the registration of the newly added
// registration of the previously existing metric). This may come as an even // metric happens before the registration of the previously existing
// bigger surprise with the global registry, where simply importing another // metric). This may come as an even bigger surprise with the global registry,
// package can trigger a panic (if the newly imported package registers metrics // where simply importing another package can trigger a panic (if the newly
// in its init function). At least, in the prometheus package, creation of // imported package registers metrics in its init function). At least, in the
// metrics and other collectors is separate from registration. You first create // prometheus package, creation of metrics and other collectors is separate from
// the metric, and then you decide explicitly if you want to register it with a // registration. You first create the metric, and then you decide explicitly if
// local or the global registry, and if you want to handle the error or risk a // you want to register it with a local or the global registry, and if you want
// panic. With the constructors in the promauto package, registration is // to handle the error or risk a panic. With the constructors in the promauto
// automatic, and if it fails, it will always panic. Furthermore, the // package, registration is automatic, and if it fails, it will always
// constructors will often be called in the var section of a file, which means // panic. Furthermore, the constructors will often be called in the var section
// that panicking will happen as a side effect of merely importing a package. // of a file, which means that panicking will happen as a side effect of merely
// importing a package.
// //
// A separate package allows conservative users to entirely ignore it. And // A separate package allows conservative users to entirely ignore it. And
// whoever wants to use it, will do so explicitly, with an opportunity to read // whoever wants to use it, will do so explicitly, with an opportunity to read
@ -252,7 +253,8 @@ type Factory struct {
} }
// With creates a Factory using the provided Registerer for registration of the // With creates a Factory using the provided Registerer for registration of the
// created Collectors. // created Collectors. If the provided Registerer is nil, the returned Factory
// creates Collectors that are not registered with any Registerer.
func With(r prometheus.Registerer) Factory { return Factory{r} } func With(r prometheus.Registerer) Factory { return Factory{r} }
// NewCounter works like the function of the same name in the prometheus package // NewCounter works like the function of the same name in the prometheus package

View File

@ -1,4 +1,4 @@
// Copyright 2019 The Prometheus Authors // Copyright 2020 The Prometheus Authors
// Licensed under the Apache License, Version 2.0 (the "License"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. // you may not use this file except in compliance with the License.
// You may obtain a copy of the License at // You may obtain a copy of the License at
@ -11,12 +11,15 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
// +build !go1.12 package promauto
package prometheus import (
"testing"
// readBuildInfo is a wrapper around debug.ReadBuildInfo for Go versions before "github.com/prometheus/client_golang/prometheus"
// 1.12. Remove this whole file once the minimum supported Go version is 1.12. )
func readBuildInfo() (path, version, sum string) {
return "unknown", "unknown", "unknown" func TestNil(t *testing.T) {
// A nil registerer should be treated as a no-op by promauto.
With(nil).NewCounter(prometheus.CounterOpts{Name: "test"}).Inc()
} }

View File

@ -53,12 +53,16 @@ func (r *responseWriterDelegator) Written() int64 {
} }
func (r *responseWriterDelegator) WriteHeader(code int) { func (r *responseWriterDelegator) WriteHeader(code int) {
if r.observeWriteHeader != nil && !r.wroteHeader {
// Only call observeWriteHeader for the 1st time. It's a bug if
// WriteHeader is called more than once, but we want to protect
// against it here. Note that we still delegate the WriteHeader
// to the original ResponseWriter to not mask the bug from it.
r.observeWriteHeader(code)
}
r.status = code r.status = code
r.wroteHeader = true r.wroteHeader = true
r.ResponseWriter.WriteHeader(code) r.ResponseWriter.WriteHeader(code)
if r.observeWriteHeader != nil {
r.observeWriteHeader(code)
}
} }
func (r *responseWriterDelegator) Write(b []byte) (int, error) { func (r *responseWriterDelegator) Write(b []byte) (int, error) {
@ -79,8 +83,7 @@ type readerFromDelegator struct{ *responseWriterDelegator }
type pusherDelegator struct{ *responseWriterDelegator } type pusherDelegator struct{ *responseWriterDelegator }
func (d closeNotifierDelegator) CloseNotify() <-chan bool { func (d closeNotifierDelegator) CloseNotify() <-chan bool {
//lint:ignore SA1019 http.CloseNotifier is deprecated but we don't want to //nolint:staticcheck // Ignore SA1019. http.CloseNotifier is deprecated but we keep it here to not break existing users.
//remove support from client_golang yet.
return d.ResponseWriter.(http.CloseNotifier).CloseNotify() return d.ResponseWriter.(http.CloseNotifier).CloseNotify()
} }
func (d flusherDelegator) Flush() { func (d flusherDelegator) Flush() {
@ -344,8 +347,7 @@ func newDelegator(w http.ResponseWriter, observeWriteHeaderFunc func(int)) deleg
} }
id := 0 id := 0
//lint:ignore SA1019 http.CloseNotifier is deprecated but we don't want to //nolint:staticcheck // Ignore SA1019. http.CloseNotifier is deprecated but we keep it here to not break existing users.
//remove support from client_golang yet.
if _, ok := w.(http.CloseNotifier); ok { if _, ok := w.(http.CloseNotifier); ok {
id += closeNotifier id += closeNotifier
} }

View File

@ -99,7 +99,7 @@ func HandlerFor(reg prometheus.Gatherer, opts HandlerOpts) http.Handler {
inFlightSem = make(chan struct{}, opts.MaxRequestsInFlight) inFlightSem = make(chan struct{}, opts.MaxRequestsInFlight)
} }
if opts.Registry != nil { if opts.Registry != nil {
// Initialize all possibilites that can occur below. // Initialize all possibilities that can occur below.
errCnt.WithLabelValues("gathering") errCnt.WithLabelValues("gathering")
errCnt.WithLabelValues("encoding") errCnt.WithLabelValues("encoding")
if err := opts.Registry.Register(errCnt); err != nil { if err := opts.Registry.Register(errCnt); err != nil {
@ -167,15 +167,12 @@ func HandlerFor(reg prometheus.Gatherer, opts HandlerOpts) http.Handler {
enc := expfmt.NewEncoder(w, contentType) enc := expfmt.NewEncoder(w, contentType)
var lastErr error
// handleError handles the error according to opts.ErrorHandling // handleError handles the error according to opts.ErrorHandling
// and returns true if we have to abort after the handling. // and returns true if we have to abort after the handling.
handleError := func(err error) bool { handleError := func(err error) bool {
if err == nil { if err == nil {
return false return false
} }
lastErr = err
if opts.ErrorLog != nil { if opts.ErrorLog != nil {
opts.ErrorLog.Println("error encoding and sending metric family:", err) opts.ErrorLog.Println("error encoding and sending metric family:", err)
} }
@ -184,7 +181,10 @@ func HandlerFor(reg prometheus.Gatherer, opts HandlerOpts) http.Handler {
case PanicOnError: case PanicOnError:
panic(err) panic(err)
case HTTPErrorOnError: case HTTPErrorOnError:
httpError(rsp, err) // We cannot really send an HTTP error at this
// point because we most likely have written
// something to rsp already. But at least we can
// stop sending.
return true return true
} }
// Do nothing in all other cases, including ContinueOnError. // Do nothing in all other cases, including ContinueOnError.
@ -202,10 +202,6 @@ func HandlerFor(reg prometheus.Gatherer, opts HandlerOpts) http.Handler {
return return
} }
} }
if lastErr != nil {
httpError(rsp, lastErr)
}
}) })
if opts.Timeout <= 0 { if opts.Timeout <= 0 {
@ -276,7 +272,12 @@ type HandlerErrorHandling int
// errors are encountered. // errors are encountered.
const ( const (
// Serve an HTTP status code 500 upon the first error // Serve an HTTP status code 500 upon the first error
// encountered. Report the error message in the body. // encountered. Report the error message in the body. Note that HTTP
// errors cannot be served anymore once the beginning of a regular
// payload has been sent. Thus, in the (unlikely) case that encoding the
// payload into the negotiated wire format fails, serving the response
// will simply be aborted. Set an ErrorLog in HandlerOpts to detect
// those errors.
HTTPErrorOnError HandlerErrorHandling = iota HTTPErrorOnError HandlerErrorHandling = iota
// Ignore errors and try to serve as many metrics as possible. However, // Ignore errors and try to serve as many metrics as possible. However,
// if no metrics can be served, serve an HTTP status code 500 and the // if no metrics can be served, serve an HTTP status code 500 and the
@ -302,8 +303,12 @@ type Logger interface {
// HandlerOpts specifies options how to serve metrics via an http.Handler. The // HandlerOpts specifies options how to serve metrics via an http.Handler. The
// zero value of HandlerOpts is a reasonable default. // zero value of HandlerOpts is a reasonable default.
type HandlerOpts struct { type HandlerOpts struct {
// ErrorLog specifies an optional logger for errors collecting and // ErrorLog specifies an optional Logger for errors collecting and
// serving metrics. If nil, errors are not logged at all. // serving metrics. If nil, errors are not logged at all. Note that the
// type of a reported error is often prometheus.MultiError, which
// formats into a multi-line error string. If you want to avoid the
// latter, create a Logger implementation that detects a
// prometheus.MultiError and formats the contained errors into one line.
ErrorLog Logger ErrorLog Logger
// ErrorHandling defines how errors are handled. Note that errors are // ErrorHandling defines how errors are handled. Note that errors are
// logged regardless of the configured ErrorHandling provided ErrorLog // logged regardless of the configured ErrorHandling provided ErrorLog
@ -365,11 +370,9 @@ func gzipAccepted(header http.Header) bool {
} }
// httpError removes any content-encoding header and then calls http.Error with // httpError removes any content-encoding header and then calls http.Error with
// the provided error and http.StatusInternalServerErrer. Error contents is // the provided error and http.StatusInternalServerError. Error contents is
// supposed to be uncompressed plain text. However, same as with a plain // supposed to be uncompressed plain text. Same as with a plain http.Error, this
// http.Error, any header settings will be void if the header has already been // must not be called if the header or any payload has already been sent.
// sent. The error message will still be written to the writer, but it will
// probably be of limited use.
func httpError(rsp http.ResponseWriter, err error) { func httpError(rsp http.ResponseWriter, err error) {
rsp.Header().Del(contentEncodingHeader) rsp.Header().Del(contentEncodingHeader)
http.Error( http.Error(

View File

@ -15,10 +15,10 @@ package promhttp
import ( import (
"context" "context"
"fmt"
"log" "log"
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
"strings"
"testing" "testing"
"time" "time"
@ -186,8 +186,9 @@ func TestClientMiddlewareAPIWithRequestContextTimeout(t *testing.T) {
if err == nil { if err == nil {
t.Fatal("did not get timeout error") t.Fatal("did not get timeout error")
} }
if want, got := fmt.Sprintf("Get %s: context deadline exceeded", backend.URL), err.Error(); want != got { expectedMsg := "context deadline exceeded"
t.Fatalf("want error %q, got %q", want, got) if !strings.Contains(err.Error(), expectedMsg) {
t.Fatalf("unexpected error: %q, expect error: %q", err.Error(), expectedMsg)
} }
} }

View File

@ -43,14 +43,14 @@ func InstrumentHandlerInFlight(g prometheus.Gauge, next http.Handler) http.Handl
// InstrumentHandlerDuration is a middleware that wraps the provided // InstrumentHandlerDuration is a middleware that wraps the provided
// http.Handler to observe the request duration with the provided ObserverVec. // http.Handler to observe the request duration with the provided ObserverVec.
// The ObserverVec must have zero, one, or two non-const non-curried labels. For // The ObserverVec must have valid metric and label names and must have zero,
// those, the only allowed label names are "code" and "method". The function // one, or two non-const non-curried labels. For those, the only allowed label
// panics otherwise. The Observe method of the Observer in the ObserverVec is // names are "code" and "method". The function panics otherwise. The Observe
// called with the request duration in seconds. Partitioning happens by HTTP // method of the Observer in the ObserverVec is called with the request duration
// status code and/or HTTP method if the respective instance label names are // in seconds. Partitioning happens by HTTP status code and/or HTTP method if
// present in the ObserverVec. For unpartitioned observations, use an // the respective instance label names are present in the ObserverVec. For
// ObserverVec with zero labels. Note that partitioning of Histograms is // unpartitioned observations, use an ObserverVec with zero labels. Note that
// expensive and should be used judiciously. // partitioning of Histograms is expensive and should be used judiciously.
// //
// If the wrapped Handler does not set a status code, a status code of 200 is assumed. // If the wrapped Handler does not set a status code, a status code of 200 is assumed.
// //
@ -79,12 +79,13 @@ func InstrumentHandlerDuration(obs prometheus.ObserverVec, next http.Handler) ht
} }
// InstrumentHandlerCounter is a middleware that wraps the provided http.Handler // InstrumentHandlerCounter is a middleware that wraps the provided http.Handler
// to observe the request result with the provided CounterVec. The CounterVec // 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 // must have valid metric and label names and must have zero, one, or two
// allowed label names are "code" and "method". The function panics // non-const non-curried labels. For those, the only allowed label names are
// otherwise. Partitioning of the CounterVec happens by HTTP status code and/or // "code" and "method". The function panics otherwise. Partitioning of the
// HTTP method if the respective instance label names are present in the // CounterVec happens by HTTP status code and/or HTTP method if the respective
// CounterVec. For unpartitioned counting, use a CounterVec with zero labels. // instance label names are present in the CounterVec. For unpartitioned
// counting, use a CounterVec with zero labels.
// //
// If the wrapped Handler does not set a status code, a status code of 200 is assumed. // If the wrapped Handler does not set a status code, a status code of 200 is assumed.
// //
@ -110,14 +111,15 @@ func InstrumentHandlerCounter(counter *prometheus.CounterVec, next http.Handler)
// InstrumentHandlerTimeToWriteHeader is a middleware that wraps the provided // InstrumentHandlerTimeToWriteHeader is a middleware that wraps the provided
// http.Handler to observe with the provided ObserverVec the request duration // http.Handler to observe with the provided ObserverVec the request duration
// until the response headers are written. The ObserverVec must have zero, one, // until the response headers are written. The ObserverVec must have valid
// or two non-const non-curried labels. For those, the only allowed label names // metric and label names and must have zero, one, or two non-const non-curried
// are "code" and "method". The function panics otherwise. The Observe method of // labels. For those, the only allowed label names are "code" and "method". The
// the Observer in the ObserverVec is called with the request duration in // function panics otherwise. The Observe method of the Observer in the
// seconds. Partitioning happens by HTTP status code and/or HTTP method if the // ObserverVec is called with the request duration in seconds. Partitioning
// respective instance label names are present in the ObserverVec. For // happens by HTTP status code and/or HTTP method if the respective instance
// unpartitioned observations, use an ObserverVec with zero labels. Note that // label names are present in the ObserverVec. For unpartitioned observations,
// partitioning of Histograms is expensive and should be used judiciously. // use an ObserverVec with zero labels. Note that partitioning of Histograms is
// expensive and should be used judiciously.
// //
// If the wrapped Handler panics before calling WriteHeader, no value is // If the wrapped Handler panics before calling WriteHeader, no value is
// reported. // reported.
@ -139,15 +141,15 @@ func InstrumentHandlerTimeToWriteHeader(obs prometheus.ObserverVec, next http.Ha
} }
// InstrumentHandlerRequestSize is a middleware that wraps the provided // InstrumentHandlerRequestSize is a middleware that wraps the provided
// http.Handler to observe the request size with the provided ObserverVec. The // http.Handler to observe the request size with the provided ObserverVec. The
// ObserverVec must have zero, one, or two non-const non-curried labels. For // ObserverVec must have valid metric and label names and must have zero, one,
// those, the only allowed label names are "code" and "method". The function // or two non-const non-curried labels. For those, the only allowed label names
// panics otherwise. The Observe method of the Observer in the ObserverVec is // are "code" and "method". The function panics otherwise. The Observe method of
// called with the request size in bytes. Partitioning happens by HTTP status // the Observer in the ObserverVec is called with the request size in
// code and/or HTTP method if the respective instance label names are present in // bytes. Partitioning happens by HTTP status code and/or HTTP method if the
// the ObserverVec. For unpartitioned observations, use an ObserverVec with zero // respective instance label names are present in the ObserverVec. For
// labels. Note that partitioning of Histograms is expensive and should be used // unpartitioned observations, use an ObserverVec with zero labels. Note that
// judiciously. // partitioning of Histograms is expensive and should be used judiciously.
// //
// If the wrapped Handler does not set a status code, a status code of 200 is assumed. // If the wrapped Handler does not set a status code, a status code of 200 is assumed.
// //
@ -174,15 +176,15 @@ func InstrumentHandlerRequestSize(obs prometheus.ObserverVec, next http.Handler)
} }
// InstrumentHandlerResponseSize is a middleware that wraps the provided // InstrumentHandlerResponseSize is a middleware that wraps the provided
// http.Handler to observe the response size with the provided ObserverVec. The // http.Handler to observe the response size with the provided ObserverVec. The
// ObserverVec must have zero, one, or two non-const non-curried labels. For // ObserverVec must have valid metric and label names and must have zero, one,
// those, the only allowed label names are "code" and "method". The function // or two non-const non-curried labels. For those, the only allowed label names
// panics otherwise. The Observe method of the Observer in the ObserverVec is // are "code" and "method". The function panics otherwise. The Observe method of
// called with the response size in bytes. Partitioning happens by HTTP status // the Observer in the ObserverVec is called with the response size in
// code and/or HTTP method if the respective instance label names are present in // bytes. Partitioning happens by HTTP status code and/or HTTP method if the
// the ObserverVec. For unpartitioned observations, use an ObserverVec with zero // respective instance label names are present in the ObserverVec. For
// labels. Note that partitioning of Histograms is expensive and should be used // unpartitioned observations, use an ObserverVec with zero labels. Note that
// judiciously. // partitioning of Histograms is expensive and should be used judiciously.
// //
// If the wrapped Handler does not set a status code, a status code of 200 is assumed. // If the wrapped Handler does not set a status code, a status code of 200 is assumed.
// //
@ -198,6 +200,11 @@ func InstrumentHandlerResponseSize(obs prometheus.ObserverVec, next http.Handler
}) })
} }
// checkLabels returns whether the provided Collector has a non-const,
// non-curried label named "code" and/or "method". It panics if the provided
// 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 bool, method bool) {
// TODO(beorn7): Remove this hacky way to check for instance labels // TODO(beorn7): Remove this hacky way to check for instance labels
// once Descriptors can have their dimensionality queried. // once Descriptors can have their dimensionality queried.
@ -225,6 +232,10 @@ func checkLabels(c prometheus.Collector) (code bool, method bool) {
close(descc) close(descc)
// Make sure the Collector has a valid Desc by registering it with a
// temporary registry.
prometheus.NewRegistry().MustRegister(c)
// Create a ConstMetric with the Desc. Since we don't know how many // Create a ConstMetric with the Desc. Since we don't know how many
// variable labels there are, try for as long as it needs. // variable labels there are, try for as long as it needs.
for err := errors.New("dummy"); err != nil; lvs = append(lvs, magicString) { for err := errors.New("dummy"); err != nil; lvs = append(lvs, magicString) {

View File

@ -25,6 +25,7 @@ import (
func TestLabelCheck(t *testing.T) { func TestLabelCheck(t *testing.T) {
scenarios := map[string]struct { scenarios := map[string]struct {
metricName string // Defaults to "c".
varLabels []string varLabels []string
constLabels []string constLabels []string
curriedLabels []string curriedLabels []string
@ -48,7 +49,7 @@ func TestLabelCheck(t *testing.T) {
curriedLabels: []string{}, curriedLabels: []string{},
ok: true, ok: true,
}, },
"cade and method as var labels": { "code and method as var labels": {
varLabels: []string{"method", "code"}, varLabels: []string{"method", "code"},
constLabels: []string{}, constLabels: []string{},
curriedLabels: []string{}, curriedLabels: []string{},
@ -60,6 +61,12 @@ func TestLabelCheck(t *testing.T) {
curriedLabels: []string{"dings", "bums"}, curriedLabels: []string{"dings", "bums"},
ok: true, ok: true,
}, },
"all labels used with an invalid const label name": {
varLabels: []string{"code", "method"},
constLabels: []string{"in-valid", "bar"},
curriedLabels: []string{"dings", "bums"},
ok: false,
},
"unsupported var label": { "unsupported var label": {
varLabels: []string{"foo"}, varLabels: []string{"foo"},
constLabels: []string{}, constLabels: []string{},
@ -96,17 +103,35 @@ func TestLabelCheck(t *testing.T) {
curriedLabels: []string{"method"}, curriedLabels: []string{"method"},
ok: false, ok: false,
}, },
"invalid name and otherwise empty": {
metricName: "in-valid",
varLabels: []string{},
constLabels: []string{},
curriedLabels: []string{},
ok: false,
},
"invalid name with all the otherwise valid labels": {
metricName: "in-valid",
varLabels: []string{"code", "method"},
constLabels: []string{"foo", "bar"},
curriedLabels: []string{"dings", "bums"},
ok: false,
},
} }
for name, sc := range scenarios { for name, sc := range scenarios {
t.Run(name, func(t *testing.T) { t.Run(name, func(t *testing.T) {
metricName := sc.metricName
if metricName == "" {
metricName = "c"
}
constLabels := prometheus.Labels{} constLabels := prometheus.Labels{}
for _, l := range sc.constLabels { for _, l := range sc.constLabels {
constLabels[l] = "dummy" constLabels[l] = "dummy"
} }
c := prometheus.NewCounterVec( c := prometheus.NewCounterVec(
prometheus.CounterOpts{ prometheus.CounterOpts{
Name: "c", Name: metricName,
Help: "c help", Help: "c help",
ConstLabels: constLabels, ConstLabels: constLabels,
}, },
@ -114,7 +139,7 @@ func TestLabelCheck(t *testing.T) {
) )
o := prometheus.ObserverVec(prometheus.NewHistogramVec( o := prometheus.ObserverVec(prometheus.NewHistogramVec(
prometheus.HistogramOpts{ prometheus.HistogramOpts{
Name: "c", Name: metricName,
Help: "c help", Help: "c help",
ConstLabels: constLabels, ConstLabels: constLabels,
}, },
@ -294,8 +319,7 @@ func (t *testFlusher) Flush() { t.flushCalled = true }
func TestInterfaceUpgrade(t *testing.T) { func TestInterfaceUpgrade(t *testing.T) {
w := &testResponseWriter{} w := &testResponseWriter{}
d := newDelegator(w, nil) d := newDelegator(w, nil)
//lint:ignore SA1019 http.CloseNotifier is deprecated but we don't want to //nolint:staticcheck // Ignore SA1019. http.CloseNotifier is deprecated but we keep it here to not break existing users.
//remove support from client_golang yet.
d.(http.CloseNotifier).CloseNotify() d.(http.CloseNotifier).CloseNotify()
if !w.closeNotifyCalled { if !w.closeNotifyCalled {
t.Error("CloseNotify not called") t.Error("CloseNotify not called")
@ -314,8 +338,7 @@ func TestInterfaceUpgrade(t *testing.T) {
f := &testFlusher{} f := &testFlusher{}
d = newDelegator(f, nil) d = newDelegator(f, nil)
//lint:ignore SA1019 http.CloseNotifier is deprecated but we don't want to //nolint:staticcheck // Ignore SA1019. http.CloseNotifier is deprecated but we keep it here to not break existing users.
//remove support from client_golang yet.
if _, ok := d.(http.CloseNotifier); ok { if _, ok := d.(http.CloseNotifier); ok {
t.Error("delegator unexpectedly implements http.CloseNotifier") t.Error("delegator unexpectedly implements http.CloseNotifier")
} }

View File

@ -37,6 +37,7 @@ package push
import ( import (
"bytes" "bytes"
"encoding/base64" "encoding/base64"
"errors"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"net/http" "net/http"
@ -56,6 +57,8 @@ const (
base64Suffix = "@base64" base64Suffix = "@base64"
) )
var errJobEmpty = errors.New("job name is empty")
// HTTPDoer is an interface for the one method of http.Client that is used by Pusher // HTTPDoer is an interface for the one method of http.Client that is used by Pusher
type HTTPDoer interface { type HTTPDoer interface {
Do(*http.Request) (*http.Response, error) Do(*http.Request) (*http.Response, error)
@ -80,14 +83,17 @@ type Pusher struct {
} }
// New creates a new Pusher to push to the provided URL with the provided job // New creates a new Pusher to push to the provided URL with the provided job
// name. You can use just host:port or ip:port as url, in which case “http://” // name (which must not be empty). You can use just host:port or ip:port as url,
// is added automatically. Alternatively, include the schema in the // in which case “http://” is added automatically. Alternatively, include the
// URL. However, do not include the “/metrics/jobs/…” part. // schema in the URL. However, do not include the “/metrics/jobs/…” part.
func New(url, job string) *Pusher { func New(url, job string) *Pusher {
var ( var (
reg = prometheus.NewRegistry() reg = prometheus.NewRegistry()
err error err error
) )
if job == "" {
err = errJobEmpty
}
if !strings.Contains(url, "://") { if !strings.Contains(url, "://") {
url = "http://" + url url = "http://" + url
} }
@ -267,7 +273,7 @@ func (p *Pusher) push(method string) error {
return err return err
} }
defer resp.Body.Close() defer resp.Body.Close()
// Pushgateway 0.10+ responds with StatusOK, earlier versions with StatusAccepted. // Depending on version and configuration of the PGW, StatusOK or StatusAccepted may be returned.
if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusAccepted { if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusAccepted {
body, _ := ioutil.ReadAll(resp.Body) // Ignore any further error as this is for an error message only. body, _ := ioutil.ReadAll(resp.Body) // Ignore any further error as this is for an error message only.
return fmt.Errorf("unexpected status code %d while pushing to %s: %s", resp.StatusCode, p.fullURL(), body) return fmt.Errorf("unexpected status code %d while pushing to %s: %s", resp.StatusCode, p.fullURL(), body)
@ -278,9 +284,11 @@ func (p *Pusher) push(method string) error {
// fullURL assembles the URL used to push/delete metrics and returns it as a // fullURL assembles the URL used to push/delete metrics and returns it as a
// string. The job name and any grouping label values containing a '/' will // string. The job name and any grouping label values containing a '/' will
// trigger a base64 encoding of the affected component and proper suffixing of // trigger a base64 encoding of the affected component and proper suffixing of
// the preceding component. If the component does not contain a '/' but other // the preceding component. Similarly, an empty grouping label value will be
// special character, the usual url.QueryEscape is used for compatibility with // encoded as base64 just with a single `=` padding character (to avoid an empty
// older versions of the Pushgateway and for better readability. // path component). If the component does not contain a '/' but other special
// characters, the usual url.QueryEscape is used for compatibility with older
// versions of the Pushgateway and for better readability.
func (p *Pusher) fullURL() string { func (p *Pusher) fullURL() string {
urlComponents := []string{} urlComponents := []string{}
if encodedJob, base64 := encodeComponent(p.job); base64 { if encodedJob, base64 := encodeComponent(p.job); base64 {
@ -299,9 +307,12 @@ func (p *Pusher) fullURL() string {
} }
// encodeComponent encodes the provided string with base64.RawURLEncoding in // encodeComponent encodes the provided string with base64.RawURLEncoding in
// case it contains '/'. If not, it uses url.QueryEscape instead. It returns // case it contains '/' and as "=" in case it is empty. If neither is the case,
// true in the former case. // it uses url.QueryEscape instead. It returns true in the former two cases.
func encodeComponent(s string) (string, bool) { func encodeComponent(s string) (string, bool) {
if s == "" {
return "=", true
}
if strings.Contains(s, "/") { if strings.Contains(s, "/") {
return base64.RawURLEncoding.EncodeToString([]byte(s)), true return base64.RawURLEncoding.EncodeToString([]byte(s)), true
} }

View File

@ -176,6 +176,36 @@ func TestPush(t *testing.T) {
t.Error("unexpected path:", lastPath) t.Error("unexpected path:", lastPath)
} }
// Empty label value triggers special base64 encoding.
if err := New(pgwOK.URL, "testjob").
Grouping("empty", "").
Collector(metric1).
Collector(metric2).
Push(); err != nil {
t.Fatal(err)
}
if lastMethod != http.MethodPut {
t.Errorf("got method %q for Push, want %q", lastMethod, http.MethodPut)
}
if !bytes.Equal(lastBody, wantBody) {
t.Errorf("got body %v, want %v", lastBody, wantBody)
}
if lastPath != "/metrics/job/testjob/empty@base64/=" {
t.Error("unexpected path:", lastPath)
}
// Empty job name results in error.
if err := New(pgwErr.URL, "").
Collector(metric1).
Collector(metric2).
Push(); err == nil {
t.Error("push with empty job succeded")
} else {
if got, want := err, errJobEmpty; got != want {
t.Errorf("got error %q, want %q", got, want)
}
}
// Push some Collectors with a broken PGW. // Push some Collectors with a broken PGW.
if err := New(pgwErr.URL, "testjob"). if err := New(pgwErr.URL, "testjob").
Collector(metric1). Collector(metric1).
@ -251,5 +281,4 @@ func TestPush(t *testing.T) {
if lastPath != "/metrics/job/testjob/a/x/b/y" && lastPath != "/metrics/job/testjob/b/y/a/x" { if lastPath != "/metrics/job/testjob/a/x/b/y" && lastPath != "/metrics/job/testjob/b/y/a/x" {
t.Error("unexpected path:", lastPath) t.Error("unexpected path:", lastPath)
} }
} }

View File

@ -26,6 +26,7 @@ import (
"unicode/utf8" "unicode/utf8"
"github.com/cespare/xxhash/v2" "github.com/cespare/xxhash/v2"
//nolint:staticcheck // Ignore SA1019. Need to keep deprecated package for compatibility.
"github.com/golang/protobuf/proto" "github.com/golang/protobuf/proto"
"github.com/prometheus/common/expfmt" "github.com/prometheus/common/expfmt"
@ -214,6 +215,8 @@ func (err AlreadyRegisteredError) Error() string {
// by a Gatherer to report multiple errors during MetricFamily gathering. // by a Gatherer to report multiple errors during MetricFamily gathering.
type MultiError []error type MultiError []error
// Error formats the contained errors as a bullet point list, preceded by the
// total number of errors. Note that this results in a multi-line string.
func (errs MultiError) Error() string { func (errs MultiError) Error() string {
if len(errs) == 0 { if len(errs) == 0 {
return "" return ""

View File

@ -33,6 +33,7 @@ import (
dto "github.com/prometheus/client_model/go" dto "github.com/prometheus/client_model/go"
//nolint:staticcheck // Ignore SA1019. Need to keep deprecated package for compatibility.
"github.com/golang/protobuf/proto" "github.com/golang/protobuf/proto"
"github.com/prometheus/common/expfmt" "github.com/prometheus/common/expfmt"

View File

@ -23,6 +23,7 @@ import (
"time" "time"
"github.com/beorn7/perks/quantile" "github.com/beorn7/perks/quantile"
//nolint:staticcheck // Ignore SA1019. Need to keep deprecated package for compatibility.
"github.com/golang/protobuf/proto" "github.com/golang/protobuf/proto"
dto "github.com/prometheus/client_model/go" dto "github.com/prometheus/client_model/go"
@ -109,7 +110,7 @@ type SummaryOpts struct {
// better covered by target labels set by the scraping Prometheus // better covered by target labels set by the scraping Prometheus
// server, or by one specific metric (e.g. a build_info or a // server, or by one specific metric (e.g. a build_info or a
// machine_role metric). See also // machine_role metric). See also
// https://prometheus.io/docs/instrumenting/writing_exporters/#target-labels,-not-static-scraped-labels // https://prometheus.io/docs/instrumenting/writing_exporters/#target-labels-not-static-scraped-labels
ConstLabels Labels ConstLabels Labels
// Objectives defines the quantile rank estimates with their respective // Objectives defines the quantile rank estimates with their respective
@ -207,7 +208,7 @@ func newSummary(desc *Desc, opts SummaryOpts, labelValues ...string) Summary {
// Use the lock-free implementation of a Summary without objectives. // Use the lock-free implementation of a Summary without objectives.
s := &noObjectivesSummary{ s := &noObjectivesSummary{
desc: desc, desc: desc,
labelPairs: makeLabelPairs(desc, labelValues), labelPairs: MakeLabelPairs(desc, labelValues),
counts: [2]*summaryCounts{{}, {}}, counts: [2]*summaryCounts{{}, {}},
} }
s.init(s) // Init self-collection. s.init(s) // Init self-collection.
@ -220,7 +221,7 @@ func newSummary(desc *Desc, opts SummaryOpts, labelValues ...string) Summary {
objectives: opts.Objectives, objectives: opts.Objectives,
sortedObjectives: make([]float64, 0, len(opts.Objectives)), sortedObjectives: make([]float64, 0, len(opts.Objectives)),
labelPairs: makeLabelPairs(desc, labelValues), labelPairs: MakeLabelPairs(desc, labelValues),
hotBuf: make([]float64, 0, opts.BufCap), hotBuf: make([]float64, 0, opts.BufCap),
coldBuf: make([]float64, 0, opts.BufCap), coldBuf: make([]float64, 0, opts.BufCap),
@ -512,7 +513,7 @@ func (s quantSort) Less(i, j int) bool {
// (e.g. HTTP request latencies, partitioned by status code and method). Create // (e.g. HTTP request latencies, partitioned by status code and method). Create
// instances with NewSummaryVec. // instances with NewSummaryVec.
type SummaryVec struct { type SummaryVec struct {
*metricVec *MetricVec
} }
// NewSummaryVec creates a new SummaryVec based on the provided SummaryOpts and // NewSummaryVec creates a new SummaryVec based on the provided SummaryOpts and
@ -534,14 +535,14 @@ func NewSummaryVec(opts SummaryOpts, labelNames []string) *SummaryVec {
opts.ConstLabels, opts.ConstLabels,
) )
return &SummaryVec{ return &SummaryVec{
metricVec: newMetricVec(desc, func(lvs ...string) Metric { MetricVec: NewMetricVec(desc, func(lvs ...string) Metric {
return newSummary(desc, opts, lvs...) return newSummary(desc, opts, lvs...)
}), }),
} }
} }
// GetMetricWithLabelValues returns the Summary for the given slice of label // GetMetricWithLabelValues returns the Summary for the given slice of label
// values (same order as the VariableLabels in Desc). If that combination of // values (same order as the variable labels in Desc). If that combination of
// label values is accessed for the first time, a new Summary is created. // label values is accessed for the first time, a new Summary is created.
// //
// It is possible to call this method without using the returned Summary to only // It is possible to call this method without using the returned Summary to only
@ -556,7 +557,7 @@ func NewSummaryVec(opts SummaryOpts, labelNames []string) *SummaryVec {
// example. // example.
// //
// An error is returned if the number of label values is not the same as the // An error is returned if the number of label values is not the same as the
// number of VariableLabels in Desc (minus any curried labels). // number of variable labels in Desc (minus any curried labels).
// //
// Note that for more than one label value, this method is prone to mistakes // Note that for more than one label value, this method is prone to mistakes
// caused by an incorrect order of arguments. Consider GetMetricWith(Labels) as // caused by an incorrect order of arguments. Consider GetMetricWith(Labels) as
@ -565,7 +566,7 @@ func NewSummaryVec(opts SummaryOpts, labelNames []string) *SummaryVec {
// with a performance overhead (for creating and processing the Labels map). // with a performance overhead (for creating and processing the Labels map).
// See also the GaugeVec example. // See also the GaugeVec example.
func (v *SummaryVec) GetMetricWithLabelValues(lvs ...string) (Observer, error) { func (v *SummaryVec) GetMetricWithLabelValues(lvs ...string) (Observer, error) {
metric, err := v.metricVec.getMetricWithLabelValues(lvs...) metric, err := v.MetricVec.GetMetricWithLabelValues(lvs...)
if metric != nil { if metric != nil {
return metric.(Observer), err return metric.(Observer), err
} }
@ -573,19 +574,19 @@ func (v *SummaryVec) GetMetricWithLabelValues(lvs ...string) (Observer, error) {
} }
// GetMetricWith returns the Summary for the given Labels map (the label names // GetMetricWith returns the Summary for the given Labels map (the label names
// must match those of the VariableLabels in Desc). If that label map is // must match those of the variable labels in Desc). If that label map is
// accessed for the first time, a new Summary is created. Implications of // accessed for the first time, a new Summary is created. Implications of
// creating a Summary without using it and keeping the Summary for later use are // creating a Summary without using it and keeping the Summary for later use are
// the same as for GetMetricWithLabelValues. // the same as for GetMetricWithLabelValues.
// //
// An error is returned if the number and names of the Labels are inconsistent // An error is returned if the number and names of the Labels are inconsistent
// with those of the VariableLabels in Desc (minus any curried labels). // with those of the variable labels in Desc (minus any curried labels).
// //
// This method is used for the same purpose as // This method is used for the same purpose as
// GetMetricWithLabelValues(...string). See there for pros and cons of the two // GetMetricWithLabelValues(...string). See there for pros and cons of the two
// methods. // methods.
func (v *SummaryVec) GetMetricWith(labels Labels) (Observer, error) { func (v *SummaryVec) GetMetricWith(labels Labels) (Observer, error) {
metric, err := v.metricVec.getMetricWith(labels) metric, err := v.MetricVec.GetMetricWith(labels)
if metric != nil { if metric != nil {
return metric.(Observer), err return metric.(Observer), err
} }
@ -629,7 +630,7 @@ func (v *SummaryVec) With(labels Labels) Observer {
// registered with a given registry (usually the uncurried version). The Reset // registered with a given registry (usually the uncurried version). The Reset
// method deletes all metrics, even if called on a curried vector. // method deletes all metrics, even if called on a curried vector.
func (v *SummaryVec) CurryWith(labels Labels) (ObserverVec, error) { func (v *SummaryVec) CurryWith(labels Labels) (ObserverVec, error) {
vec, err := v.curryWith(labels) vec, err := v.MetricVec.CurryWith(labels)
if vec != nil { if vec != nil {
return &SummaryVec{vec}, err return &SummaryVec{vec}, err
} }
@ -715,7 +716,7 @@ func NewConstSummary(
count: count, count: count,
sum: sum, sum: sum,
quantiles: quantiles, quantiles: quantiles,
labelPairs: makeLabelPairs(desc, labelValues), labelPairs: MakeLabelPairs(desc, labelValues),
}, nil }, nil
} }

View File

@ -321,7 +321,7 @@ func TestSummaryVecConcurrency(t *testing.T) {
go func(vals []float64) { go func(vals []float64) {
start.Wait() start.Wait()
for i, v := range vals { for i, v := range vals {
sum.WithLabelValues(string('A' + picks[i])).Observe(v) sum.WithLabelValues(string('A' + rune(picks[i]))).Observe(v)
} }
end.Done() end.Done()
}(vals) }(vals)
@ -334,7 +334,7 @@ func TestSummaryVecConcurrency(t *testing.T) {
for i := 0; i < vecLength; i++ { for i := 0; i < vecLength; i++ {
m := &dto.Metric{} m := &dto.Metric{}
s := sum.WithLabelValues(string('A' + i)) s := sum.WithLabelValues(string('A' + rune(i)))
s.(Summary).Write(m) s.(Summary).Write(m)
if got, want := int(*m.Summary.SampleCount), len(allVars[i]); got != want { if got, want := int(*m.Summary.SampleCount), len(allVars[i]); got != want {
t.Errorf("got sample count %d for label %c, want %d", got, 'A'+i, want) t.Errorf("got sample count %d for label %c, want %d", got, 'A'+i, want)

View File

@ -0,0 +1,46 @@
// Copyright 2020 The Prometheus Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package testutil
import (
"fmt"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/testutil/promlint"
)
// CollectAndLint registers the provided Collector with a newly created pedantic
// Registry. It then calls GatherAndLint with that Registry and with the
// provided metricNames.
func CollectAndLint(c prometheus.Collector, metricNames ...string) ([]promlint.Problem, error) {
reg := prometheus.NewPedanticRegistry()
if err := reg.Register(c); err != nil {
return nil, fmt.Errorf("registering collector failed: %s", err)
}
return GatherAndLint(reg, metricNames...)
}
// GatherAndLint gathers all metrics from the provided Gatherer and checks them
// with the linter in the promlint package. If any metricNames are provided,
// only metrics with those names are checked.
func GatherAndLint(g prometheus.Gatherer, metricNames ...string) ([]promlint.Problem, error) {
got, err := g.Gather()
if err != nil {
return nil, fmt.Errorf("gathering metrics failed: %s", err)
}
if metricNames != nil {
got = filterMetrics(got, metricNames)
}
return promlint.NewWithMetricFamilies(got).Lint()
}

View File

@ -0,0 +1,71 @@
// Copyright 2020 The Prometheus Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package testutil
import (
"testing"
"github.com/prometheus/client_golang/prometheus"
)
func TestCollectAndLintGood(t *testing.T) {
cnt := prometheus.NewCounterVec(
prometheus.CounterOpts{
Name: "some_total",
Help: "A value that represents a counter.",
ConstLabels: prometheus.Labels{
"label1": "value1",
},
},
[]string{"foo"},
)
cnt.WithLabelValues("bar")
cnt.WithLabelValues("baz")
problems, err := CollectAndLint(cnt)
if err != nil {
t.Error("Unexpected error:", err)
}
if len(problems) > 0 {
t.Error("Unexpected lint problems:", problems)
}
}
func TestCollectAndLintBad(t *testing.T) {
cnt := prometheus.NewCounterVec(
prometheus.CounterOpts{
Name: "someThing_ms",
Help: "A value that represents a counter.",
ConstLabels: prometheus.Labels{
"label1": "value1",
},
},
[]string{"fooBar"},
)
cnt.WithLabelValues("bar")
cnt.WithLabelValues("baz")
problems, err := CollectAndLint(cnt)
if err != nil {
t.Error("Unexpected error:", err)
}
if len(problems) < 5 {
// The exact nature of the lint problems found is tested within
// the promlint package itself. Here we only want to make sure
// that the collector successfully hits the linter and that at
// least the five problems that the linter could recognize at
// the time of writing this test are flagged.
t.Error("Not enough lint problems found.")
}
}

View File

@ -0,0 +1,386 @@
// Copyright 2020 The Prometheus Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Package promlint provides a linter for Prometheus metrics.
package promlint
import (
"fmt"
"io"
"regexp"
"sort"
"strings"
"github.com/prometheus/common/expfmt"
dto "github.com/prometheus/client_model/go"
)
// A Linter is a Prometheus metrics linter. It identifies issues with metric
// names, types, and metadata, and reports them to the caller.
type Linter struct {
// The linter will read metrics in the Prometheus text format from r and
// then lint it, _and_ it will lint the metrics provided directly as
// MetricFamily proto messages in mfs. Note, however, that the current
// constructor functions New and NewWithMetricFamilies only ever set one
// of them.
r io.Reader
mfs []*dto.MetricFamily
}
// A Problem is an issue detected by a Linter.
type Problem struct {
// The name of the metric indicated by this Problem.
Metric string
// A description of the issue for this Problem.
Text string
}
// newProblem is helper function to create a Problem.
func newProblem(mf *dto.MetricFamily, text string) Problem {
return Problem{
Metric: mf.GetName(),
Text: text,
}
}
// New creates a new Linter that reads an input stream of Prometheus metrics in
// the Prometheus text exposition format.
func New(r io.Reader) *Linter {
return &Linter{
r: r,
}
}
// NewWithMetricFamilies creates a new Linter that reads from a slice of
// MetricFamily protobuf messages.
func NewWithMetricFamilies(mfs []*dto.MetricFamily) *Linter {
return &Linter{
mfs: mfs,
}
}
// Lint performs a linting pass, returning a slice of Problems indicating any
// issues found in the metrics stream. The slice is sorted by metric name
// and issue description.
func (l *Linter) Lint() ([]Problem, error) {
var problems []Problem
if l.r != nil {
d := expfmt.NewDecoder(l.r, expfmt.FmtText)
mf := &dto.MetricFamily{}
for {
if err := d.Decode(mf); err != nil {
if err == io.EOF {
break
}
return nil, err
}
problems = append(problems, lint(mf)...)
}
}
for _, mf := range l.mfs {
problems = append(problems, lint(mf)...)
}
// Ensure deterministic output.
sort.SliceStable(problems, func(i, j int) bool {
if problems[i].Metric == problems[j].Metric {
return problems[i].Text < problems[j].Text
}
return problems[i].Metric < problems[j].Metric
})
return problems, nil
}
// lint is the entry point for linting a single metric.
func lint(mf *dto.MetricFamily) []Problem {
fns := []func(mf *dto.MetricFamily) []Problem{
lintHelp,
lintMetricUnits,
lintCounter,
lintHistogramSummaryReserved,
lintMetricTypeInName,
lintReservedChars,
lintCamelCase,
lintUnitAbbreviations,
}
var problems []Problem
for _, fn := range fns {
problems = append(problems, fn(mf)...)
}
// TODO(mdlayher): lint rules for specific metrics types.
return problems
}
// lintHelp detects issues related to the help text for a metric.
func lintHelp(mf *dto.MetricFamily) []Problem {
var problems []Problem
// Expect all metrics to have help text available.
if mf.Help == nil {
problems = append(problems, newProblem(mf, "no help text"))
}
return problems
}
// lintMetricUnits detects issues with metric unit names.
func lintMetricUnits(mf *dto.MetricFamily) []Problem {
var problems []Problem
unit, base, ok := metricUnits(*mf.Name)
if !ok {
// No known units detected.
return nil
}
// Unit is already a base unit.
if unit == base {
return nil
}
problems = append(problems, newProblem(mf, fmt.Sprintf("use base unit %q instead of %q", base, unit)))
return problems
}
// lintCounter detects issues specific to counters, as well as patterns that should
// only be used with counters.
func lintCounter(mf *dto.MetricFamily) []Problem {
var problems []Problem
isCounter := mf.GetType() == dto.MetricType_COUNTER
isUntyped := mf.GetType() == dto.MetricType_UNTYPED
hasTotalSuffix := strings.HasSuffix(mf.GetName(), "_total")
switch {
case isCounter && !hasTotalSuffix:
problems = append(problems, newProblem(mf, `counter metrics should have "_total" suffix`))
case !isUntyped && !isCounter && hasTotalSuffix:
problems = append(problems, newProblem(mf, `non-counter metrics should not have "_total" suffix`))
}
return problems
}
// lintHistogramSummaryReserved detects when other types of metrics use names or labels
// reserved for use by histograms and/or summaries.
func lintHistogramSummaryReserved(mf *dto.MetricFamily) []Problem {
// These rules do not apply to untyped metrics.
t := mf.GetType()
if t == dto.MetricType_UNTYPED {
return nil
}
var problems []Problem
isHistogram := t == dto.MetricType_HISTOGRAM
isSummary := t == dto.MetricType_SUMMARY
n := mf.GetName()
if !isHistogram && strings.HasSuffix(n, "_bucket") {
problems = append(problems, newProblem(mf, `non-histogram metrics should not have "_bucket" suffix`))
}
if !isHistogram && !isSummary && strings.HasSuffix(n, "_count") {
problems = append(problems, newProblem(mf, `non-histogram and non-summary metrics should not have "_count" suffix`))
}
if !isHistogram && !isSummary && strings.HasSuffix(n, "_sum") {
problems = append(problems, newProblem(mf, `non-histogram and non-summary metrics should not have "_sum" suffix`))
}
for _, m := range mf.GetMetric() {
for _, l := range m.GetLabel() {
ln := l.GetName()
if !isHistogram && ln == "le" {
problems = append(problems, newProblem(mf, `non-histogram metrics should not have "le" label`))
}
if !isSummary && ln == "quantile" {
problems = append(problems, newProblem(mf, `non-summary metrics should not have "quantile" label`))
}
}
}
return problems
}
// lintMetricTypeInName detects when metric types are included in the metric name.
func lintMetricTypeInName(mf *dto.MetricFamily) []Problem {
var problems []Problem
n := strings.ToLower(mf.GetName())
for i, t := range dto.MetricType_name {
if i == int32(dto.MetricType_UNTYPED) {
continue
}
typename := strings.ToLower(t)
if strings.Contains(n, "_"+typename+"_") || strings.HasSuffix(n, "_"+typename) {
problems = append(problems, newProblem(mf, fmt.Sprintf(`metric name should not include type '%s'`, typename)))
}
}
return problems
}
// lintReservedChars detects colons in metric names.
func lintReservedChars(mf *dto.MetricFamily) []Problem {
var problems []Problem
if strings.Contains(mf.GetName(), ":") {
problems = append(problems, newProblem(mf, "metric names should not contain ':'"))
}
return problems
}
var camelCase = regexp.MustCompile(`[a-z][A-Z]`)
// lintCamelCase detects metric names and label names written in camelCase.
func lintCamelCase(mf *dto.MetricFamily) []Problem {
var problems []Problem
if camelCase.FindString(mf.GetName()) != "" {
problems = append(problems, newProblem(mf, "metric names should be written in 'snake_case' not 'camelCase'"))
}
for _, m := range mf.GetMetric() {
for _, l := range m.GetLabel() {
if camelCase.FindString(l.GetName()) != "" {
problems = append(problems, newProblem(mf, "label names should be written in 'snake_case' not 'camelCase'"))
}
}
}
return problems
}
// lintUnitAbbreviations detects abbreviated units in the metric name.
func lintUnitAbbreviations(mf *dto.MetricFamily) []Problem {
var problems []Problem
n := strings.ToLower(mf.GetName())
for _, s := range unitAbbreviations {
if strings.Contains(n, "_"+s+"_") || strings.HasSuffix(n, "_"+s) {
problems = append(problems, newProblem(mf, "metric names should not contain abbreviated units"))
}
}
return problems
}
// metricUnits attempts to detect known unit types used as part of a metric name,
// e.g. "foo_bytes_total" or "bar_baz_milligrams".
func metricUnits(m string) (unit string, base string, ok bool) {
ss := strings.Split(m, "_")
for unit, base := range units {
// Also check for "no prefix".
for _, p := range append(unitPrefixes, "") {
for _, s := range ss {
// Attempt to explicitly match a known unit with a known prefix,
// as some words may look like "units" when matching suffix.
//
// As an example, "thermometers" should not match "meters", but
// "kilometers" should.
if s == p+unit {
return p + unit, base, true
}
}
}
}
return "", "", false
}
// Units and their possible prefixes recognized by this library. More can be
// added over time as needed.
var (
// map a unit to the appropriate base unit.
units = map[string]string{
// Base units.
"amperes": "amperes",
"bytes": "bytes",
"celsius": "celsius", // Also allow Celsius because it is common in typical Prometheus use cases.
"grams": "grams",
"joules": "joules",
"kelvin": "kelvin", // SI base unit, used in special cases (e.g. color temperature, scientific measurements).
"meters": "meters", // Both American and international spelling permitted.
"metres": "metres",
"seconds": "seconds",
"volts": "volts",
// Non base units.
// Time.
"minutes": "seconds",
"hours": "seconds",
"days": "seconds",
"weeks": "seconds",
// Temperature.
"kelvins": "kelvin",
"fahrenheit": "celsius",
"rankine": "celsius",
// Length.
"inches": "meters",
"yards": "meters",
"miles": "meters",
// Bytes.
"bits": "bytes",
// Energy.
"calories": "joules",
// Mass.
"pounds": "grams",
"ounces": "grams",
}
unitPrefixes = []string{
"pico",
"nano",
"micro",
"milli",
"centi",
"deci",
"deca",
"hecto",
"kilo",
"kibi",
"mega",
"mibi",
"giga",
"gibi",
"tera",
"tebi",
"peta",
"pebi",
}
// Common abbreviations that we'd like to discourage.
unitAbbreviations = []string{
"s",
"ms",
"us",
"ns",
"sec",
"b",
"kb",
"mb",
"gb",
"tb",
"pb",
"m",
"h",
"d",
}
)

View File

@ -0,0 +1,784 @@
// Copyright 2020 The Prometheus Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package promlint_test
import (
"fmt"
"reflect"
"strings"
"testing"
"github.com/prometheus/client_golang/prometheus/testutil/promlint"
)
type test struct {
name string
in string
problems []promlint.Problem
}
func TestLintNoHelpText(t *testing.T) {
const msg = "no help text"
tests := []test{
{
name: "no help",
in: `
# TYPE go_goroutines gauge
go_goroutines 24
`,
problems: []promlint.Problem{{
Metric: "go_goroutines",
Text: msg,
}},
},
{
name: "empty help",
in: `
# HELP go_goroutines
# TYPE go_goroutines gauge
go_goroutines 24
`,
problems: []promlint.Problem{{
Metric: "go_goroutines",
Text: msg,
}},
},
{
name: "no help and empty help",
in: `
# HELP go_goroutines
# TYPE go_goroutines gauge
go_goroutines 24
# TYPE go_threads gauge
go_threads 10
`,
problems: []promlint.Problem{
{
Metric: "go_goroutines",
Text: msg,
},
{
Metric: "go_threads",
Text: msg,
},
},
},
{
name: "OK",
in: `
# HELP go_goroutines Number of goroutines that currently exist.
# TYPE go_goroutines gauge
go_goroutines 24
`,
},
}
runTests(t, tests)
}
func TestLintMetricUnits(t *testing.T) {
tests := []struct {
name string
in string
problems []promlint.Problem
}{
// good cases.
{
name: "amperes",
in: `
# HELP x_amperes Test metric.
# TYPE x_amperes untyped
x_amperes 10
`,
},
{
name: "bytes",
in: `
# HELP x_bytes Test metric.
# TYPE x_bytes untyped
x_bytes 10
`,
},
{
name: "grams",
in: `
# HELP x_grams Test metric.
# TYPE x_grams untyped
x_grams 10
`,
},
{
name: "celsius",
in: `
# HELP x_celsius Test metric.
# TYPE x_celsius untyped
x_celsius 10
`,
},
{
name: "meters",
in: `
# HELP x_meters Test metric.
# TYPE x_meters untyped
x_meters 10
`,
},
{
name: "metres",
in: `
# HELP x_metres Test metric.
# TYPE x_metres untyped
x_metres 10
`,
},
{
name: "moles",
in: `
# HELP x_moles Test metric.
# TYPE x_moles untyped
x_moles 10
`,
},
{
name: "seconds",
in: `
# HELP x_seconds Test metric.
# TYPE x_seconds untyped
x_seconds 10
`,
},
{
name: "joules",
in: `
# HELP x_joules Test metric.
# TYPE x_joules untyped
x_joules 10
`,
},
{
name: "kelvin",
in: `
# HELP x_kelvin Test metric.
# TYPE x_kelvin untyped
x_kelvin 10
`,
},
// bad cases.
{
name: "milliamperes",
in: `
# HELP x_milliamperes Test metric.
# TYPE x_milliamperes untyped
x_milliamperes 10
`,
problems: []promlint.Problem{{
Metric: "x_milliamperes",
Text: `use base unit "amperes" instead of "milliamperes"`,
}},
},
{
name: "gigabytes",
in: `
# HELP x_gigabytes Test metric.
# TYPE x_gigabytes untyped
x_gigabytes 10
`,
problems: []promlint.Problem{{
Metric: "x_gigabytes",
Text: `use base unit "bytes" instead of "gigabytes"`,
}},
},
{
name: "kilograms",
in: `
# HELP x_kilograms Test metric.
# TYPE x_kilograms untyped
x_kilograms 10
`,
problems: []promlint.Problem{{
Metric: "x_kilograms",
Text: `use base unit "grams" instead of "kilograms"`,
}},
},
{
name: "nanocelsius",
in: `
# HELP x_nanocelsius Test metric.
# TYPE x_nanocelsius untyped
x_nanocelsius 10
`,
problems: []promlint.Problem{{
Metric: "x_nanocelsius",
Text: `use base unit "celsius" instead of "nanocelsius"`,
}},
},
{
name: "kilometers",
in: `
# HELP x_kilometers Test metric.
# TYPE x_kilometers untyped
x_kilometers 10
`,
problems: []promlint.Problem{{
Metric: "x_kilometers",
Text: `use base unit "meters" instead of "kilometers"`,
}},
},
{
name: "picometers",
in: `
# HELP x_picometers Test metric.
# TYPE x_picometers untyped
x_picometers 10
`,
problems: []promlint.Problem{{
Metric: "x_picometers",
Text: `use base unit "meters" instead of "picometers"`,
}},
},
{
name: "microseconds",
in: `
# HELP x_microseconds Test metric.
# TYPE x_microseconds untyped
x_microseconds 10
`,
problems: []promlint.Problem{{
Metric: "x_microseconds",
Text: `use base unit "seconds" instead of "microseconds"`,
}},
},
{
name: "minutes",
in: `
# HELP x_minutes Test metric.
# TYPE x_minutes untyped
x_minutes 10
`,
problems: []promlint.Problem{{
Metric: "x_minutes",
Text: `use base unit "seconds" instead of "minutes"`,
}},
},
{
name: "hours",
in: `
# HELP x_hours Test metric.
# TYPE x_hours untyped
x_hours 10
`,
problems: []promlint.Problem{{
Metric: "x_hours",
Text: `use base unit "seconds" instead of "hours"`,
}},
},
{
name: "days",
in: `
# HELP x_days Test metric.
# TYPE x_days untyped
x_days 10
`,
problems: []promlint.Problem{{
Metric: "x_days",
Text: `use base unit "seconds" instead of "days"`,
}},
},
{
name: "kelvins",
in: `
# HELP x_kelvins Test metric.
# TYPE x_kelvins untyped
x_kelvins 10
`,
problems: []promlint.Problem{{
Metric: "x_kelvins",
Text: `use base unit "kelvin" instead of "kelvins"`,
}},
},
{
name: "fahrenheit",
in: `
# HELP thermometers_fahrenheit Test metric.
# TYPE thermometers_fahrenheit untyped
thermometers_fahrenheit 10
`,
problems: []promlint.Problem{{
Metric: "thermometers_fahrenheit",
Text: `use base unit "celsius" instead of "fahrenheit"`,
}},
},
{
name: "rankine",
in: `
# HELP thermometers_rankine Test metric.
# TYPE thermometers_rankine untyped
thermometers_rankine 10
`,
problems: []promlint.Problem{{
Metric: "thermometers_rankine",
Text: `use base unit "celsius" instead of "rankine"`,
}},
}, {
name: "inches",
in: `
# HELP x_inches Test metric.
# TYPE x_inches untyped
x_inches 10
`,
problems: []promlint.Problem{{
Metric: "x_inches",
Text: `use base unit "meters" instead of "inches"`,
}},
}, {
name: "yards",
in: `
# HELP x_yards Test metric.
# TYPE x_yards untyped
x_yards 10
`,
problems: []promlint.Problem{{
Metric: "x_yards",
Text: `use base unit "meters" instead of "yards"`,
}},
}, {
name: "miles",
in: `
# HELP x_miles Test metric.
# TYPE x_miles untyped
x_miles 10
`,
problems: []promlint.Problem{{
Metric: "x_miles",
Text: `use base unit "meters" instead of "miles"`,
}},
}, {
name: "bits",
in: `
# HELP x_bits Test metric.
# TYPE x_bits untyped
x_bits 10
`,
problems: []promlint.Problem{{
Metric: "x_bits",
Text: `use base unit "bytes" instead of "bits"`,
}},
},
{
name: "calories",
in: `
# HELP x_calories Test metric.
# TYPE x_calories untyped
x_calories 10
`,
problems: []promlint.Problem{{
Metric: "x_calories",
Text: `use base unit "joules" instead of "calories"`,
}},
},
{
name: "pounds",
in: `
# HELP x_pounds Test metric.
# TYPE x_pounds untyped
x_pounds 10
`,
problems: []promlint.Problem{{
Metric: "x_pounds",
Text: `use base unit "grams" instead of "pounds"`,
}},
},
{
name: "ounces",
in: `
# HELP x_ounces Test metric.
# TYPE x_ounces untyped
x_ounces 10
`,
problems: []promlint.Problem{{
Metric: "x_ounces",
Text: `use base unit "grams" instead of "ounces"`,
}},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
l := promlint.New(strings.NewReader(tt.in))
problems, err := l.Lint()
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if want, got := tt.problems, problems; !reflect.DeepEqual(want, got) {
t.Fatalf("unexpected problems:\n- want: %v\n- got: %v",
want, got)
}
})
}
}
func TestLintCounter(t *testing.T) {
tests := []test{
{
name: "counter without _total suffix",
in: `
# HELP x_bytes Test metric.
# TYPE x_bytes counter
x_bytes 10
`,
problems: []promlint.Problem{{
Metric: "x_bytes",
Text: `counter metrics should have "_total" suffix`,
}},
},
{
name: "gauge with _total suffix",
in: `
# HELP x_bytes_total Test metric.
# TYPE x_bytes_total gauge
x_bytes_total 10
`,
problems: []promlint.Problem{{
Metric: "x_bytes_total",
Text: `non-counter metrics should not have "_total" suffix`,
}},
},
{
name: "counter with _total suffix",
in: `
# HELP x_bytes_total Test metric.
# TYPE x_bytes_total counter
x_bytes_total 10
`,
},
{
name: "gauge without _total suffix",
in: `
# HELP x_bytes Test metric.
# TYPE x_bytes gauge
x_bytes 10
`,
},
{
name: "untyped with _total suffix",
in: `
# HELP x_bytes_total Test metric.
# TYPE x_bytes_total untyped
x_bytes_total 10
`,
},
{
name: "untyped without _total suffix",
in: `
# HELP x_bytes Test metric.
# TYPE x_bytes untyped
x_bytes 10
`,
},
}
runTests(t, tests)
}
func TestLintHistogramSummaryReserved(t *testing.T) {
tests := []test{
{
name: "gauge with _bucket suffix",
in: `
# HELP x_bytes_bucket Test metric.
# TYPE x_bytes_bucket gauge
x_bytes_bucket 10
`,
problems: []promlint.Problem{{
Metric: "x_bytes_bucket",
Text: `non-histogram metrics should not have "_bucket" suffix`,
}},
},
{
name: "gauge with _count suffix",
in: `
# HELP x_bytes_count Test metric.
# TYPE x_bytes_count gauge
x_bytes_count 10
`,
problems: []promlint.Problem{{
Metric: "x_bytes_count",
Text: `non-histogram and non-summary metrics should not have "_count" suffix`,
}},
},
{
name: "gauge with _sum suffix",
in: `
# HELP x_bytes_sum Test metric.
# TYPE x_bytes_sum gauge
x_bytes_sum 10
`,
problems: []promlint.Problem{{
Metric: "x_bytes_sum",
Text: `non-histogram and non-summary metrics should not have "_sum" suffix`,
}},
},
{
name: "gauge with le label",
in: `
# HELP x_bytes Test metric.
# TYPE x_bytes gauge
x_bytes{le="1"} 10
`,
problems: []promlint.Problem{{
Metric: "x_bytes",
Text: `non-histogram metrics should not have "le" label`,
}},
},
{
name: "gauge with quantile label",
in: `
# HELP x_bytes Test metric.
# TYPE x_bytes gauge
x_bytes{quantile="1"} 10
`,
problems: []promlint.Problem{{
Metric: "x_bytes",
Text: `non-summary metrics should not have "quantile" label`,
}},
},
{
name: "histogram with quantile label",
in: `
# HELP tsdb_compaction_duration Duration of compaction runs.
# TYPE tsdb_compaction_duration histogram
tsdb_compaction_duration_bucket{le="0.005",quantile="0.01"} 0
tsdb_compaction_duration_bucket{le="0.01",quantile="0.01"} 0
tsdb_compaction_duration_bucket{le="0.025",quantile="0.01"} 0
tsdb_compaction_duration_bucket{le="0.05",quantile="0.01"} 0
tsdb_compaction_duration_bucket{le="0.1",quantile="0.01"} 0
tsdb_compaction_duration_bucket{le="0.25",quantile="0.01"} 0
tsdb_compaction_duration_bucket{le="0.5",quantile="0.01"} 57
tsdb_compaction_duration_bucket{le="1",quantile="0.01"} 68
tsdb_compaction_duration_bucket{le="2.5",quantile="0.01"} 69
tsdb_compaction_duration_bucket{le="5",quantile="0.01"} 69
tsdb_compaction_duration_bucket{le="10",quantile="0.01"} 69
tsdb_compaction_duration_bucket{le="+Inf",quantile="0.01"} 69
tsdb_compaction_duration_sum 28.740810936000006
tsdb_compaction_duration_count 69
`,
problems: []promlint.Problem{{
Metric: "tsdb_compaction_duration",
Text: `non-summary metrics should not have "quantile" label`,
}},
},
{
name: "summary with le label",
in: `
# HELP go_gc_duration_seconds A summary of the GC invocation durations.
# TYPE go_gc_duration_seconds summary
go_gc_duration_seconds{quantile="0",le="0.01"} 4.2365e-05
go_gc_duration_seconds{quantile="0.25",le="0.01"} 8.1492e-05
go_gc_duration_seconds{quantile="0.5",le="0.01"} 0.000100656
go_gc_duration_seconds{quantile="0.75",le="0.01"} 0.000113913
go_gc_duration_seconds{quantile="1",le="0.01"} 0.021754305
go_gc_duration_seconds_sum 1.769429004
go_gc_duration_seconds_count 5962
`,
problems: []promlint.Problem{{
Metric: "go_gc_duration_seconds",
Text: `non-histogram metrics should not have "le" label`,
}},
},
{
name: "histogram OK",
in: `
# HELP tsdb_compaction_duration Duration of compaction runs.
# TYPE tsdb_compaction_duration histogram
tsdb_compaction_duration_bucket{le="0.005"} 0
tsdb_compaction_duration_bucket{le="0.01"} 0
tsdb_compaction_duration_bucket{le="0.025"} 0
tsdb_compaction_duration_bucket{le="0.05"} 0
tsdb_compaction_duration_bucket{le="0.1"} 0
tsdb_compaction_duration_bucket{le="0.25"} 0
tsdb_compaction_duration_bucket{le="0.5"} 57
tsdb_compaction_duration_bucket{le="1"} 68
tsdb_compaction_duration_bucket{le="2.5"} 69
tsdb_compaction_duration_bucket{le="5"} 69
tsdb_compaction_duration_bucket{le="10"} 69
tsdb_compaction_duration_bucket{le="+Inf"} 69
tsdb_compaction_duration_sum 28.740810936000006
tsdb_compaction_duration_count 69
`,
},
{
name: "summary OK",
in: `
# HELP go_gc_duration_seconds A summary of the GC invocation durations.
# TYPE go_gc_duration_seconds summary
go_gc_duration_seconds{quantile="0"} 4.2365e-05
go_gc_duration_seconds{quantile="0.25"} 8.1492e-05
go_gc_duration_seconds{quantile="0.5"} 0.000100656
go_gc_duration_seconds{quantile="0.75"} 0.000113913
go_gc_duration_seconds{quantile="1"} 0.021754305
go_gc_duration_seconds_sum 1.769429004
go_gc_duration_seconds_count 5962
`,
},
}
runTests(t, tests)
}
func TestLintMetricTypeInName(t *testing.T) {
genTest := func(n, t, err string, problems ...promlint.Problem) test {
return test{
name: fmt.Sprintf("%s with _%s suffix", t, t),
in: fmt.Sprintf(`
# HELP %s Test metric.
# TYPE %s %s
%s 10
`, n, n, t, n),
problems: append(problems, promlint.Problem{
Metric: n,
Text: fmt.Sprintf(`metric name should not include type '%s'`, err),
}),
}
}
twoProbTest := genTest("http_requests_counter", "counter", "counter", promlint.Problem{
Metric: "http_requests_counter",
Text: `counter metrics should have "_total" suffix`,
})
tests := []test{
twoProbTest,
genTest("instance_memory_limit_bytes_gauge", "gauge", "gauge"),
genTest("request_duration_seconds_summary", "summary", "summary"),
genTest("request_duration_seconds_summary", "histogram", "summary"),
genTest("request_duration_seconds_histogram", "histogram", "histogram"),
genTest("request_duration_seconds_HISTOGRAM", "histogram", "histogram"),
genTest("instance_memory_limit_gauge_bytes", "gauge", "gauge"),
}
runTests(t, tests)
}
func TestLintReservedChars(t *testing.T) {
tests := []test{
{
name: "request_duration::_seconds",
in: `
# HELP request_duration::_seconds Test metric.
# TYPE request_duration::_seconds histogram
request_duration::_seconds 10
`,
problems: []promlint.Problem{
{
Metric: "request_duration::_seconds",
Text: "metric names should not contain ':'",
},
},
},
}
runTests(t, tests)
}
func TestLintCamelCase(t *testing.T) {
tests := []test{
{
name: "requestDuration_seconds",
in: `
# HELP requestDuration_seconds Test metric.
# TYPE requestDuration_seconds histogram
requestDuration_seconds 10
`,
problems: []promlint.Problem{
{
Metric: "requestDuration_seconds",
Text: "metric names should be written in 'snake_case' not 'camelCase'",
},
},
},
{
name: "request_duration_seconds",
in: `
# HELP request_duration_seconds Test metric.
# TYPE request_duration_seconds histogram
request_duration_seconds{httpService="foo"} 10
`,
problems: []promlint.Problem{
{
Metric: "request_duration_seconds",
Text: "label names should be written in 'snake_case' not 'camelCase'",
},
},
},
}
runTests(t, tests)
}
func TestLintUnitAbbreviations(t *testing.T) {
genTest := func(n string) test {
return test{
name: fmt.Sprintf("%s with abbreviated unit", n),
in: fmt.Sprintf(`
# HELP %s Test metric.
# TYPE %s gauge
%s 10
`, n, n, n),
problems: []promlint.Problem{
{
Metric: n,
Text: "metric names should not contain abbreviated units",
},
},
}
}
tests := []test{
genTest("instance_memory_limit_b"),
genTest("instance_memory_limit_kb"),
genTest("instance_memory_limit_mb"),
genTest("instance_memory_limit_MB"),
genTest("instance_memory_limit_gb"),
genTest("instance_memory_limit_tb"),
genTest("instance_memory_limit_pb"),
genTest("request_duration_s"),
genTest("request_duration_ms"),
genTest("request_duration_us"),
genTest("request_duration_ns"),
genTest("request_duration_sec"),
genTest("request_sec_duration"),
genTest("request_duration_m"),
genTest("request_duration_h"),
genTest("request_duration_d"),
}
runTests(t, tests)
}
func runTests(t *testing.T, tests []test) {
t.Helper()
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
l := promlint.New(strings.NewReader(tt.in))
problems, err := l.Lint()
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if want, got := tt.problems, problems; !reflect.DeepEqual(want, got) {
t.Fatalf("unexpected problems:\n- want: %v\n- got: %v",
want, got)
}
})
}
}

View File

@ -31,6 +31,10 @@
// testing custom prometheus.Collector implementations and in particular whole // testing custom prometheus.Collector implementations and in particular whole
// exporters, i.e. programs that retrieve telemetry data from a 3rd party source // exporters, i.e. programs that retrieve telemetry data from a 3rd party source
// and convert it into Prometheus metrics. // and convert it into Prometheus metrics.
//
// In a similar pattern, CollectAndLint and GatherAndLint can be used to detect
// metrics that have issues with their name, type, or metadata without being
// necessarily invalid, e.g. a counter with a name missing the “_total” suffix.
package testutil package testutil
import ( import (
@ -108,36 +112,48 @@ func ToFloat64(c prometheus.Collector) float64 {
panic(fmt.Errorf("collected a non-gauge/counter/untyped metric: %s", pb)) panic(fmt.Errorf("collected a non-gauge/counter/untyped metric: %s", pb))
} }
// CollectAndCount collects all Metrics from the provided Collector and returns their number. // CollectAndCount registers the provided Collector with a newly created
// // pedantic Registry. It then calls GatherAndCount with that Registry and with
// This can be used to assert the number of metrics collected by a given collector after certain operations. // the provided metricNames. In the unlikely case that the registration or the
// // gathering fails, this function panics. (This is inconsistent with the other
// This function is only for testing purposes, and even for testing, other approaches // CollectAnd… functions in this package and has historical reasons. Changing
// are often more appropriate (see this package's documentation). // the function signature would be a breaking change and will therefore only
func CollectAndCount(c prometheus.Collector) int { // happen with the next major version bump.)
var ( func CollectAndCount(c prometheus.Collector, metricNames ...string) int {
mCount int reg := prometheus.NewPedanticRegistry()
mChan = make(chan prometheus.Metric) if err := reg.Register(c); err != nil {
done = make(chan struct{}) panic(fmt.Errorf("registering collector failed: %s", err))
) }
result, err := GatherAndCount(reg, metricNames...)
if err != nil {
panic(err)
}
return result
}
go func() { // GatherAndCount gathers all metrics from the provided Gatherer and counts
for range mChan { // them. It returns the number of metric children in all gathered metric
mCount++ // families together. If any metricNames are provided, only metrics with those
} // names are counted.
close(done) func GatherAndCount(g prometheus.Gatherer, metricNames ...string) (int, error) {
}() got, err := g.Gather()
if err != nil {
return 0, fmt.Errorf("gathering metrics failed: %s", err)
}
if metricNames != nil {
got = filterMetrics(got, metricNames)
}
c.Collect(mChan) result := 0
close(mChan) for _, mf := range got {
<-done result += len(mf.GetMetric())
}
return mCount return result, nil
} }
// CollectAndCompare registers the provided Collector with a newly created // CollectAndCompare registers the provided Collector with a newly created
// pedantic Registry. It then does the same as GatherAndCompare, gathering the // pedantic Registry. It then calls GatherAndCompare with that Registry and with
// metrics from the pedantic Registry. // the provided metricNames.
func CollectAndCompare(c prometheus.Collector, expected io.Reader, metricNames ...string) error { func CollectAndCompare(c prometheus.Collector, expected io.Reader, metricNames ...string) error {
reg := prometheus.NewPedanticRegistry() reg := prometheus.NewPedanticRegistry()
if err := reg.Register(c); err != nil { if err := reg.Register(c); err != nil {

View File

@ -306,3 +306,30 @@ some_total{label1="value1"} 1
t.Errorf("Expected\n%#+v\nGot:\n%#+v", expectedError, err.Error()) t.Errorf("Expected\n%#+v\nGot:\n%#+v", expectedError, err.Error())
} }
} }
func TestCollectAndCount(t *testing.T) {
c := prometheus.NewCounterVec(
prometheus.CounterOpts{
Name: "some_total",
Help: "A value that represents a counter.",
},
[]string{"foo"},
)
if got, want := CollectAndCount(c), 0; got != want {
t.Errorf("unexpected metric count, got %d, want %d", got, want)
}
c.WithLabelValues("bar")
if got, want := CollectAndCount(c), 1; got != want {
t.Errorf("unexpected metric count, got %d, want %d", got, want)
}
c.WithLabelValues("baz")
if got, want := CollectAndCount(c), 2; got != want {
t.Errorf("unexpected metric count, got %d, want %d", got, want)
}
if got, want := CollectAndCount(c, "some_total"), 2; got != want {
t.Errorf("unexpected metric count, got %d, want %d", got, want)
}
if got, want := CollectAndCount(c, "some_other_total"), 0; got != want {
t.Errorf("unexpected metric count, got %d, want %d", got, want)
}
}

View File

@ -19,6 +19,7 @@ import (
"time" "time"
"unicode/utf8" "unicode/utf8"
//nolint:staticcheck // Ignore SA1019. Need to keep deprecated package for compatibility.
"github.com/golang/protobuf/proto" "github.com/golang/protobuf/proto"
"github.com/golang/protobuf/ptypes" "github.com/golang/protobuf/ptypes"
@ -62,7 +63,7 @@ func newValueFunc(desc *Desc, valueType ValueType, function func() float64) *val
desc: desc, desc: desc,
valType: valueType, valType: valueType,
function: function, function: function,
labelPairs: makeLabelPairs(desc, nil), labelPairs: MakeLabelPairs(desc, nil),
} }
result.init(result) result.init(result)
return result return result
@ -94,7 +95,7 @@ func NewConstMetric(desc *Desc, valueType ValueType, value float64, labelValues
desc: desc, desc: desc,
valType: valueType, valType: valueType,
val: value, val: value,
labelPairs: makeLabelPairs(desc, labelValues), labelPairs: MakeLabelPairs(desc, labelValues),
}, nil }, nil
} }
@ -144,7 +145,14 @@ func populateMetric(
return nil return nil
} }
func makeLabelPairs(desc *Desc, labelValues []string) []*dto.LabelPair { // MakeLabelPairs is a helper function to create protobuf LabelPairs from the
// variable and constant labels in the provided Desc. The values for the
// variable labels are defined by the labelValues slice, which must be in the
// same order as the corresponding variable labels in the Desc.
//
// This function is only needed for custom Metric implementations. See MetricVec
// example.
func MakeLabelPairs(desc *Desc, labelValues []string) []*dto.LabelPair {
totalLen := len(desc.variableLabels) + len(desc.constLabelPairs) totalLen := len(desc.variableLabels) + len(desc.constLabelPairs)
if totalLen == 0 { if totalLen == 0 {
// Super fast path. // Super fast path.

View File

@ -20,12 +20,20 @@ import (
"github.com/prometheus/common/model" "github.com/prometheus/common/model"
) )
// metricVec is a Collector to bundle metrics of the same name that differ in // MetricVec is a Collector to bundle metrics of the same name that differ in
// their label values. metricVec is not used directly (and therefore // their label values. MetricVec is not used directly but as a building block
// unexported). It is used as a building block for implementations of vectors of // for implementations of vectors of a given metric type, like GaugeVec,
// a given metric type, like GaugeVec, CounterVec, SummaryVec, and HistogramVec. // CounterVec, SummaryVec, and HistogramVec. It is exported so that it can be
// It also handles label currying. // used for custom Metric implementations.
type metricVec struct { //
// To create a FooVec for custom Metric Foo, embed a pointer to MetricVec in
// FooVec and initialize it with NewMetricVec. Implement wrappers for
// GetMetricWithLabelValues and GetMetricWith that return (Foo, error) rather
// than (Metric, error). Similarly, create a wrapper for CurryWith that returns
// (*FooVec, error) rather than (*MetricVec, error). It is recommended to also
// add the convenience methods WithLabelValues, With, and MustCurryWith, which
// panic instead of returning errors. See also the MetricVec example.
type MetricVec struct {
*metricMap *metricMap
curry []curriedLabelValue curry []curriedLabelValue
@ -35,9 +43,9 @@ type metricVec struct {
hashAddByte func(h uint64, b byte) uint64 hashAddByte func(h uint64, b byte) uint64
} }
// newMetricVec returns an initialized metricVec. // NewMetricVec returns an initialized metricVec.
func newMetricVec(desc *Desc, newMetric func(lvs ...string) Metric) *metricVec { func NewMetricVec(desc *Desc, newMetric func(lvs ...string) Metric) *MetricVec {
return &metricVec{ return &MetricVec{
metricMap: &metricMap{ metricMap: &metricMap{
metrics: map[uint64][]metricWithLabelValues{}, metrics: map[uint64][]metricWithLabelValues{},
desc: desc, desc: desc,
@ -63,7 +71,7 @@ func newMetricVec(desc *Desc, newMetric func(lvs ...string) Metric) *metricVec {
// latter has a much more readable (albeit more verbose) syntax, but it comes // latter has a much more readable (albeit more verbose) syntax, but it comes
// with a performance overhead (for creating and processing the Labels map). // with a performance overhead (for creating and processing the Labels map).
// See also the CounterVec example. // See also the CounterVec example.
func (m *metricVec) DeleteLabelValues(lvs ...string) bool { func (m *MetricVec) DeleteLabelValues(lvs ...string) bool {
h, err := m.hashLabelValues(lvs) h, err := m.hashLabelValues(lvs)
if err != nil { if err != nil {
return false return false
@ -82,7 +90,7 @@ func (m *metricVec) DeleteLabelValues(lvs ...string) bool {
// //
// This method is used for the same purpose as DeleteLabelValues(...string). See // This method is used for the same purpose as DeleteLabelValues(...string). See
// there for pros and cons of the two methods. // there for pros and cons of the two methods.
func (m *metricVec) Delete(labels Labels) bool { func (m *MetricVec) Delete(labels Labels) bool {
h, err := m.hashLabels(labels) h, err := m.hashLabels(labels)
if err != nil { if err != nil {
return false return false
@ -95,15 +103,32 @@ func (m *metricVec) Delete(labels Labels) bool {
// show up in GoDoc. // show up in GoDoc.
// Describe implements Collector. // Describe implements Collector.
func (m *metricVec) Describe(ch chan<- *Desc) { m.metricMap.Describe(ch) } func (m *MetricVec) Describe(ch chan<- *Desc) { m.metricMap.Describe(ch) }
// Collect implements Collector. // Collect implements Collector.
func (m *metricVec) Collect(ch chan<- Metric) { m.metricMap.Collect(ch) } func (m *MetricVec) Collect(ch chan<- Metric) { m.metricMap.Collect(ch) }
// Reset deletes all metrics in this vector. // Reset deletes all metrics in this vector.
func (m *metricVec) Reset() { m.metricMap.Reset() } func (m *MetricVec) Reset() { m.metricMap.Reset() }
func (m *metricVec) curryWith(labels Labels) (*metricVec, error) { // CurryWith returns a vector curried with the provided labels, i.e. the
// returned vector has those labels pre-set for all labeled operations performed
// on it. The cardinality of the curried vector is reduced accordingly. The
// order of the remaining labels stays the same (just with the curried labels
// taken out of the sequence which is relevant for the
// (GetMetric)WithLabelValues methods). It is possible to curry a curried
// vector, but only with labels not yet used for currying before.
//
// The metrics contained in the MetricVec are shared between the curried and
// uncurried vectors. They are just accessed differently. Curried and uncurried
// vectors behave identically in terms of collection. Only one must be
// registered with a given registry (usually the uncurried version). The Reset
// method deletes all metrics, even if called on a curried vector.
//
// Note that CurryWith is usually not called directly but through a wrapper
// around MetricVec, implementing a vector for a specific Metric
// implementation, for example GaugeVec.
func (m *MetricVec) CurryWith(labels Labels) (*MetricVec, error) {
var ( var (
newCurry []curriedLabelValue newCurry []curriedLabelValue
oldCurry = m.curry oldCurry = m.curry
@ -128,7 +153,7 @@ func (m *metricVec) curryWith(labels Labels) (*metricVec, error) {
return nil, fmt.Errorf("%d unknown label(s) found during currying", l) return nil, fmt.Errorf("%d unknown label(s) found during currying", l)
} }
return &metricVec{ return &MetricVec{
metricMap: m.metricMap, metricMap: m.metricMap,
curry: newCurry, curry: newCurry,
hashAdd: m.hashAdd, hashAdd: m.hashAdd,
@ -136,7 +161,34 @@ func (m *metricVec) curryWith(labels Labels) (*metricVec, error) {
}, nil }, nil
} }
func (m *metricVec) getMetricWithLabelValues(lvs ...string) (Metric, error) { // GetMetricWithLabelValues returns the Metric for the given slice of label
// values (same order as the variable labels in Desc). If that combination of
// label values is accessed for the first time, a new Metric is created (by
// calling the newMetric function provided during construction of the
// MetricVec).
//
// It is possible to call this method without using the returned Metric to only
// create the new Metric but leave it in its initial state.
//
// Keeping the Metric for later use is possible (and should be considered if
// performance is critical), but keep in mind that Reset, DeleteLabelValues and
// Delete can be used to delete the Metric from the MetricVec. In that case, the
// Metric will still exist, but it will not be exported anymore, even if a
// Metric with the same label values is created later.
//
// An error is returned if the number of label values is not the same as the
// number of variable labels in Desc (minus any curried labels).
//
// Note that for more than one label value, this method is prone to mistakes
// caused by an incorrect order of arguments. Consider GetMetricWith(Labels) as
// an alternative to avoid that type of mistake. For higher label numbers, the
// latter has a much more readable (albeit more verbose) syntax, but it comes
// with a performance overhead (for creating and processing the Labels map).
//
// Note that GetMetricWithLabelValues is usually not called directly but through
// a wrapper around MetricVec, implementing a vector for a specific Metric
// implementation, for example GaugeVec.
func (m *MetricVec) GetMetricWithLabelValues(lvs ...string) (Metric, error) {
h, err := m.hashLabelValues(lvs) h, err := m.hashLabelValues(lvs)
if err != nil { if err != nil {
return nil, err return nil, err
@ -145,7 +197,23 @@ func (m *metricVec) getMetricWithLabelValues(lvs ...string) (Metric, error) {
return m.metricMap.getOrCreateMetricWithLabelValues(h, lvs, m.curry), nil return m.metricMap.getOrCreateMetricWithLabelValues(h, lvs, m.curry), nil
} }
func (m *metricVec) getMetricWith(labels Labels) (Metric, error) { // GetMetricWith returns the Metric for the given Labels map (the label names
// must match those of the variable labels in Desc). If that label map is
// accessed for the first time, a new Metric is created. Implications of
// creating a Metric without using it and keeping the Metric for later use
// are the same as for GetMetricWithLabelValues.
//
// An error is returned if the number and names of the Labels are inconsistent
// with those of the variable labels in Desc (minus any curried labels).
//
// This method is used for the same purpose as
// GetMetricWithLabelValues(...string). See there for pros and cons of the two
// methods.
//
// Note that GetMetricWith is usually not called directly but through a wrapper
// around MetricVec, implementing a vector for a specific Metric implementation,
// for example GaugeVec.
func (m *MetricVec) GetMetricWith(labels Labels) (Metric, error) {
h, err := m.hashLabels(labels) h, err := m.hashLabels(labels)
if err != nil { if err != nil {
return nil, err return nil, err
@ -154,7 +222,7 @@ func (m *metricVec) getMetricWith(labels Labels) (Metric, error) {
return m.metricMap.getOrCreateMetricWithLabels(h, labels, m.curry), nil return m.metricMap.getOrCreateMetricWithLabels(h, labels, m.curry), nil
} }
func (m *metricVec) hashLabelValues(vals []string) (uint64, error) { func (m *MetricVec) hashLabelValues(vals []string) (uint64, error) {
if err := validateLabelValues(vals, len(m.desc.variableLabels)-len(m.curry)); err != nil { if err := validateLabelValues(vals, len(m.desc.variableLabels)-len(m.curry)); err != nil {
return 0, err return 0, err
} }
@ -177,7 +245,7 @@ func (m *metricVec) hashLabelValues(vals []string) (uint64, error) {
return h, nil return h, nil
} }
func (m *metricVec) hashLabels(labels Labels) (uint64, error) { func (m *MetricVec) hashLabels(labels Labels) (uint64, error) {
if err := validateValuesInLabels(labels, len(m.desc.variableLabels)-len(m.curry)); err != nil { if err := validateValuesInLabels(labels, len(m.desc.variableLabels)-len(m.curry)); err != nil {
return 0, err return 0, err
} }
@ -276,7 +344,9 @@ func (m *metricMap) deleteByHashWithLabelValues(
} }
if len(metrics) > 1 { if len(metrics) > 1 {
old := metrics
m.metrics[h] = append(metrics[:i], metrics[i+1:]...) m.metrics[h] = append(metrics[:i], metrics[i+1:]...)
old[len(old)-1] = metricWithLabelValues{}
} else { } else {
delete(m.metrics, h) delete(m.metrics, h)
} }
@ -302,7 +372,9 @@ func (m *metricMap) deleteByHashWithLabels(
} }
if len(metrics) > 1 { if len(metrics) > 1 {
old := metrics
m.metrics[h] = append(metrics[:i], metrics[i+1:]...) m.metrics[h] = append(metrics[:i], metrics[i+1:]...)
old[len(old)-1] = metricWithLabelValues{}
} else { } else {
delete(m.metrics, h) delete(m.metrics, h)
} }

View File

@ -17,6 +17,7 @@ import (
"fmt" "fmt"
"sort" "sort"
//nolint:staticcheck // Ignore SA1019. Need to keep deprecated package for compatibility.
"github.com/golang/protobuf/proto" "github.com/golang/protobuf/proto"
dto "github.com/prometheus/client_model/go" dto "github.com/prometheus/client_model/go"
@ -27,10 +28,13 @@ import (
// registered with the wrapped Registerer in a modified way. The modified // registered with the wrapped Registerer in a modified way. The modified
// Collector adds the provided Labels to all Metrics it collects (as // Collector adds the provided Labels to all Metrics it collects (as
// ConstLabels). The Metrics collected by the unmodified Collector must not // ConstLabels). The Metrics collected by the unmodified Collector must not
// duplicate any of those labels. // duplicate any of those labels. Wrapping a nil value is valid, resulting
// in a no-op Registerer.
// //
// WrapRegistererWith provides a way to add fixed labels to a subset of // WrapRegistererWith provides a way to add fixed labels to a subset of
// Collectors. It should not be used to add fixed labels to all metrics exposed. // Collectors. It should not be used to add fixed labels to all metrics
// exposed. See also
// https://prometheus.io/docs/instrumenting/writing_exporters/#target-labels-not-static-scraped-labels
// //
// Conflicts between Collectors registered through the original Registerer with // Conflicts between Collectors registered through the original Registerer with
// Collectors registered through the wrapping Registerer will still be // Collectors registered through the wrapping Registerer will still be
@ -50,6 +54,7 @@ func WrapRegistererWith(labels Labels, reg Registerer) Registerer {
// Registerer. Collectors registered with the returned Registerer will be // Registerer. Collectors registered with the returned Registerer will be
// registered with the wrapped Registerer in a modified way. The modified // registered with the wrapped Registerer in a modified way. The modified
// Collector adds the provided prefix to the name of all Metrics it collects. // Collector adds the provided prefix to the name of all Metrics it collects.
// Wrapping a nil value is valid, resulting in a no-op Registerer.
// //
// WrapRegistererWithPrefix is useful to have one place to prefix all metrics of // WrapRegistererWithPrefix is useful to have one place to prefix all metrics of
// a sub-system. To make this work, register metrics of the sub-system with the // a sub-system. To make this work, register metrics of the sub-system with the
@ -80,6 +85,9 @@ type wrappingRegisterer struct {
} }
func (r *wrappingRegisterer) Register(c Collector) error { func (r *wrappingRegisterer) Register(c Collector) error {
if r.wrappedRegisterer == nil {
return nil
}
return r.wrappedRegisterer.Register(&wrappingCollector{ return r.wrappedRegisterer.Register(&wrappingCollector{
wrappedCollector: c, wrappedCollector: c,
prefix: r.prefix, prefix: r.prefix,
@ -88,6 +96,9 @@ func (r *wrappingRegisterer) Register(c Collector) error {
} }
func (r *wrappingRegisterer) MustRegister(cs ...Collector) { func (r *wrappingRegisterer) MustRegister(cs ...Collector) {
if r.wrappedRegisterer == nil {
return
}
for _, c := range cs { for _, c := range cs {
if err := r.Register(c); err != nil { if err := r.Register(c); err != nil {
panic(err) panic(err)
@ -96,6 +107,9 @@ func (r *wrappingRegisterer) MustRegister(cs ...Collector) {
} }
func (r *wrappingRegisterer) Unregister(c Collector) bool { func (r *wrappingRegisterer) Unregister(c Collector) bool {
if r.wrappedRegisterer == nil {
return false
}
return r.wrappedRegisterer.Unregister(&wrappingCollector{ return r.wrappedRegisterer.Unregister(&wrappingCollector{
wrappedCollector: c, wrappedCollector: c,
prefix: r.prefix, prefix: r.prefix,

View File

@ -19,6 +19,7 @@ import (
"strings" "strings"
"testing" "testing"
//nolint:staticcheck // Ignore SA1019. Need to keep deprecated package for compatibility.
"github.com/golang/protobuf/proto" "github.com/golang/protobuf/proto"
dto "github.com/prometheus/client_model/go" dto "github.com/prometheus/client_model/go"
@ -320,3 +321,12 @@ func TestWrap(t *testing.T) {
} }
} }
func TestNil(t *testing.T) {
// A wrapped nil registerer should be treated as a no-op, and not panic.
c := NewCounter(CounterOpts{Name: "test"})
err := WrapRegistererWith(Labels{"foo": "bar"}, nil).Register(c)
if err != nil {
t.Fatal("registering failed:", err)
}
}